slon/Slon/Static/html/admin/main.html

298 lines
No EOL
16 KiB
HTML

<!doctypehtml>
<link href=https://cdn.jsdelivr.net/npm/bulma@1.0.3/css/bulma.min.css rel=stylesheet>
<style>
body {
padding: 32px
}
.container-x {
width: 640px
}
.next {
text-align: right
}
.spacer {
height: 16px
}
.main-content {
padding-left: 32px;
width: 100%;
vertical-align: top
}
.menu {
width: 240px
}
</style>
<aside class="menu is-inline-block">
<p class="menu-label">Info</p>
<ul class="menu-list">
<li><a onclick="infoStats()" id="menuitem-stats">Statistics</a></li>
</ul>
<p class="menu-label">Manage</p>
<ul class="menu-list">
<li><a onclick="manageAccounts(0)" id="menuitem-accounts">Accounts</a></li>
<li><a onclick="manageInstance()" id="menuitem-instance">Instance</a></li>
<li><a onclick="manageSettings()" id="menuitem-settings">Settings</a></li>
</ul>
<p class="menu-label">Diagnostics</p>
<ul class="menu-list">
<li><a onclick="diagsLogs()" id="menuitem-logs">Logs</a></li>
</ul>
</aside>
<div id="content" class="container main-content is-inline-block">
</div>
<script>
let obscured_setting_names = ["catbox_userhash","whitelist_ip"];
function clearActiveLinks() {
document.querySelectorAll("a").forEach(function (a) { a.className = ""; });
}
function setActiveLink(link) {
document.getElementById("menuitem-" + link).className = "is-active";
}
function setContent(html) {
document.getElementById("content").innerHTML = html;
}
async function infoStats() {
clearActiveLinks();
const request = new Request("/info/stats");
const response = await fetch(request);
const stats = await response.json();
let html = "<h4 class=\"title is-4\">Statistics</h4><div class=spacer></div><div>Uptime: " + formatTime(stats["uptime"]) + "<br/>Free memory: " + stats["free_memory"].toString() + " bytes<br/></div>";
setContent(html);
setActiveLink("stats");
}
async function manageAccounts(page) {
clearActiveLinks();
const request = new Request("/manage/accounts?skip=" + (page * 10).toString());
const response = await fetch(request);
const result = await response.json();
const accounts = result["accounts"];
let html = "<h4 class=\"title is-4\">Accounts</h4><div class=spacer></div><div>" + result["total"].toString() + " known account(s)</div><div class=spacer></div>";
if (accounts.length) {
html += "<table class=table><thead><tr><th></th><th>id</th><th>acct</th><th>display_name</th><th>type</th><th>delete</th></tr></head><tbody>";
for (let i = 0; i < accounts.length; i++) {
html += "<tr><td><img src=\"" + accounts[i]["avatar_static"] + "\" style=\"width:48px;height:48px\"></td><td>" + accounts[i]["id"] + "</td><td>" + accounts[i]["acct"] + "</td><td>" + accounts[i]["display_name"] + "</td><td>" + (accounts[i]["remote_actor"] == undefined ? "local" : "remote") + "</td><td style=\"text-align:center\"><a href=\"javascript:confirmDeleteUser('" + accounts[i]["acct"] + "','" + accounts[i]["id"] + "');\">&#10060;</a></td><tr>";
}
html += "</tbody></table>";
html += "<nav class=\"pagination\" role=\"navigation\" aria-label=\"pagination\">";
html += "<ul class=\"pagination-list\">";
for (let i = 0; i < result["total"] / 10; i++) {
html += "<li><a href=\"javascript:manageAccounts(" + i.toString() + ")\"class=\"pagination-link";
if (i == result["skip"] / 10) {
html += " is-current";
}
html += "\">" + (i + 1).toString() + "</a></li>";
}
html += "</ul></nav>";
} else {
html += "No users";
}
html += "<br><br><input onclick=manageNewUser() class=button type=button value=\"New User\">";
setContent(html);
setActiveLink("accounts");
}
async function manageInstance() {
clearActiveLinks();
const request = new Request("/manage/instance");
const response = await fetch(request);
const instance = await response.json();
let html = "<h4 class=\"title is-4\">Instance</h4><div class=spacer></div>";
html += "<form action=\"javascript:saveInstance()\"><div>";
html += "<div class=\"section is-inline-block\" style=\"width:420px;vertical-align:top\">";
html += "<label class=label>URI</label><div class=control><input id=uri class=input placeholder=my-slon-instance.foo required autocomplete=off value=\"" + instance["uri"] + "\"></div><div class=spacer></div>";
html += "<label class=label>Title</label><div class=control><input id=title class=input placeholder=\"My Slon Instance\" required autocomplete=off value=\"" + instance["title"] + "\"></div><div class=spacer></div>";
html += "<label class=label>Description</label><div class=control><input id=description class=input placeholder=\"A fediverse instance running on TempleOS\" required autocomplete=off value=\"" + instance["description"] + "\"></div><div class=spacer></div>";
html += "<label class=label>Email</label><div class=control><input id=email class=input type=email placeholder=\"alec@checksum.fail\" required autocomplete=off value=\"" + instance["email"] + "\"></div><div class=spacer></div>";
html += "<label class=label>Enable Registrations</label><div class=control><input id=registrations type=checkbox" + (instance["registrations"] ? " checked" : "") + "></div><div class=spacer></div>";
html += "</div>";
html += "</div>";
html += "<div class=\"control next\"><input class=\"button is-link\" type=submit value=Save></div>"
html += "</div></form>";
setContent(html);
setActiveLink("instance");
}
async function manageSettings() {
clearActiveLinks();
const request = new Request("/manage/settings");
const response = await fetch(request);
const settings = await response.json();
const keys = Object.keys(settings);
let html = "<h4 class=\"title is-4\">Settings</h4><div class=spacer></div>";
html += "<form action=\"javascript:saveSettings()\"><div>";
html += "<table class=table><thead><tr><th>name</th><th>value</th><th>delete</th></tr></head><tbody>";
if (keys.length) {
for (let i = 0; i < keys.length; i++) {
let inputType = "text";
if (obscured_setting_names.indexOf(keys[i]) != -1) {
inputType = "password";
}
html += "<tr><td>" + keys[i] + "</td><td><div class=control><input autocomplete=off class=input id=settings_" + keys[i] + " type=\"" + inputType + "\" value=\"" + settings[keys[i]] + "\"></div></td><td style=\"text-align:center\"><a href=\"javascript:deleteSetting('" + keys[i] + "');\">&#10060;</a></td>"
}
}
html += "</tbody></table>";
html += "<div class=\"control is-inline\"><input autocomplete=off class=\"input is-inline\" id=new_name placeholder=name></div> ";
html += "<div class=\"control is-inline\"><input autocomplete=off class=\"input is-inline\" id=new_value placeholder=value></div> ";
html += "<div class=\"control is-inline\"><input class=\"button is-inline\" onclick=\"addSetting()\" type=button value=Add></div> ";
html += "<div class=\"control next\"><input class=\"button is-link\" type=submit value=Save></div>";
html += "</div></form>";
setContent(html);
setActiveLink("settings");
}
function addSetting() {
let name = document.getElementById("new_name").value;
let value = document.getElementById("new_value").value;
if (name.indexOf(" ") != -1) {
alert("key can not contain spaces: " + name);
return;
}
if (document.getElementById("settings_" + name)) {
alert("key already exists: " + name);
return;
}
let inputType = "text";
if (obscured_setting_names.indexOf(name) != -1) {
inputType = "password";
}
let new_setting_html = "<tr><td>" + name + "</td><td><div class=control><input autocomplete=off class=input id=settings_" + name + " type=\"" + inputType + "\" value=\"" + value + "\"></div></td><td style=\"text-align:center\"><a href=\"javascript:deleteSetting('" + name + "');\">&#10060;</a></td>";
document.getElementsByTagName("tbody")[0].innerHTML += new_setting_html;
}
function deleteSetting(name) {
document.getElementById("settings_" + name).parentElement.parentElement.parentElement.remove();
}
async function saveSettings() {
let data = {};
let fields = document.querySelectorAll("[id^=settings_]")
for (var i = 0; i < fields.length; i++) {
data[fields[i].id.split("settings_")[1]] = fields[i].value;
}
const request = new Request("/save/settings", {
headers: { "Content-Type": "application/json" },
method: "POST",
body: JSON.stringify(data)
});
const response = await fetch(request);
manageSettings();
}
async function confirmDeleteUser(user, id) {
if (confirm("Are you sure you want to delete '" + user + "' ?")) {
const request = new Request("/delete/account?id=" + id);
const response = await fetch(request);
const empty_json = await response.json();
manageAccounts(0);
}
}
function manageNewUser() {
clearActiveLinks();
let html = "<h4 class=\"title is-4\">New User</h4><div class=spacer></div>";
html += "<form action=\"javascript:saveNewUser()\"><div>";
html += "<div class=\"section is-inline-block\" style=\"width:420px;vertical-align:top\">";
html += "<label class=label>Username</label><div class=control><input id=username class=input placeholder=baoh required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Display Name</label><div class=control><input id=display_name class=input placeholder=\"Ikuro Hashizawa\" required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Email</label><div class=control><input id=email class=input type=email placeholder=\"cooldude42069@checksum.fail\" required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Bio</label><div class=control><input id=bio class=input placeholder=\"ima firin mah lazer cannon\" required autocomplete=off></div><div class=spacer></div>";
html += "</div>";
html += "<div class=\"section is-inline-block\" style=\"width:420px;vertical-align:top\">";
html += "<label class=label>Avatar</label><div class=control><input id=avatar class=input placeholder=\"https://full.path.to/my/avatar.png\" required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Header</label><div class=control><input id=header class=input placeholder=\"https://full.path.to/my/header.png\" required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Private Key (must be in DER format)</label><div class=control><input onchange=updateBase64(this) id=privatekey type=file required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Public Key (must be in PEM format)</label><div class=control><input onchange=updateBase64(this) id=publickey type=file required autocomplete=off></div><div class=spacer></div>";
html += "</div>";
html += "</div>";
html += "<div class=\"control next\"><input class=\"button is-link\" type=submit value=Save></div>"
html += "</div></form>";
setContent(html);
setActiveLink("accounts");
}
async function saveNewUser() {
let data = {};
let fields = document.getElementsByTagName("input");
for (var i = 0; i < fields.length; i++) {
switch (fields[i].type) {
case "checkbox":
data[fields[i].id] = fields[i].checked;
break;
case "file":
data[fields[i].id] = fields[i].base64;
break;
case "submit":
break;
default:
data[fields[i].id] = fields[i].value;
break;
}
}
const request = new Request("/new/account", {
headers: { "Content-Type": "application/json" },
method: "POST",
body: JSON.stringify(data)
});
const response = await fetch(request);
const json = await response.json();
if (!Object.keys(json).length) {
manageAccounts(0);
} else {
alert(JSON.stringify(json));
}
}
async function saveInstance() {
let data = {};
let fields = document.getElementsByTagName("input");
for (var i = 0; i < fields.length; i++) {
switch (fields[i].type) {
case "checkbox":
data[fields[i].id] = fields[i].checked;
break;
case "submit":
break;
default:
data[fields[i].id] = fields[i].value;
break;
}
}
const request = new Request("/save/instance", {
headers: { "Content-Type": "application/json" },
method: "POST",
body: JSON.stringify(data)
});
const response = await fetch(request);
}
function updateBase64(el) {
let reader = new FileReader();
reader.readAsDataURL(el.files[0]);
reader.addEventListener(
"load",
() => {
el.base64 = reader.result.split(";base64,")[1];
}
);
}
const formatTime = milliseconds => {
const seconds = Math.floor((milliseconds / 1000) % 60);
const minutes = Math.floor((milliseconds / 1000 / 60) % 60);
const hours = Math.floor((milliseconds / 1000 / 60 / 60) % 24);
return [
hours.toString().padStart(2, "0"),
minutes.toString().padStart(2, "0"),
seconds.toString().padStart(2, "0")
].join(":");
}
addEventListener("DOMContentLoaded", (event) => {
infoStats();
})
document.querySelectorAll("input[type=file]").forEach(function (e) {
a.className = "";
});
</script>