You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

339 lines
16 KiB

<main class="container">
<div id="users-block" class="my-3 p-3 bg-white rounded shadow">
<h6 class="border-bottom pb-2 mb-0">Registrovaní uživatelé</h6>
<div id="users-list">
{{#each users}}
<div class="media pt-3">
<img class="mr-2 rounded identicon" data-src="{{Email}}">
<div class="media-body pb-3 mb-0 small border-bottom">
<div class="row justify-content-between">
<div class="col">
<strong>{{Name}}</strong>
{{#if TwoFactorEnabled}}
<span class="badge badge-success ml-2">2FA</span>
{{/if}}
{{#case _Status 1}}
<span class="badge badge-warning ml-2">Pozvání odesláno</span>
{{/case}}
<span class="d-block">{{Email}}</span>
</div>
<div class="col">
<strong> Organizations:</strong>
<span class="d-block">
{{#each Organizations}}
<span class="badge badge-primary" data-orgtype="{{Type}}">{{Name}}</span>
{{/each}}
</span>
</div>
<div style="flex: 0 0 300px; font-size: 90%; text-align: right; padding-right: 15px">
{{#if TwoFactorEnabled}}
<a class="mr-2" href="#" onclick='remove2fa({{jsesc Id}})'>Odstranit všechny 2FA</a>
{{/if}}
<a class="mr-2" href="#" onclick='deauthUser({{jsesc Id}})'>Odhlásit ze všech zařízení</a>
<a class="mr-2" href="#" onclick='deleteUser({{jsesc Id}}, {{jsesc Email}})'>Smazat uživatele</a>
</div>
</div>
</div>
</div>
{{/each}}
</div>
<div class="mt-3">
<button type="button" class="btn btn-sm btn-link" onclick="updateRevisions();"
title="Vynutí u všech klientů, aby se synchronizovali jakmile se znovu připojí. Užitečně např. po obnovení zálohy pro smazání všech uložených starých dat.">
Vynutit synchronizaci u klientů
</button>
<button type="button" class="btn btn-sm btn-primary float-right" onclick="reload();">Obnovit seznam</button>
</div>
</div>
<div id="invite-form-block" class="align-items-center p-3 mb-3 text-white-50 bg-secondary rounded shadow">
<div>
<h6 class="mb-0 text-white">Pozvat uživatele</h6>
<small>Email:</small>
<form class="form-inline" id="invite-form" onsubmit="inviteUser(); return false;">
<input type="email" class="form-control w-50 mr-2" id="email-invite" placeholder="Zadej email">
<button type="submit" class="btn btn-primary">Odeslat pozvánku</button>
</form>
</div>
</div>
<div id="config-block" class="align-items-center p-3 mb-3 bg-secondary rounded shadow">
<div>
<h6 class="text-white mb-3">Konfigurace</h6>
<div class="small text-white mb-3">
POZNÁMKA: Tato nastavení přepíšou všechny proměnné pro toto prostředí. Jakmile nastavení uložíte, doporučujeme
proměnné nastavovat aby se zabránilo zmatkům.
To ovšem neplatí pro sekci pouze pro čtení, která může být nastavena pouze přes prostředí.
</div>
<form class="form accordion" id="config-form" onsubmit="saveConfig(); return false;">
{{#each config}}
{{#if groupdoc}}
<div class="card bg-light mb-3">
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
data-target="#g_{{group}}">{{groupdoc}}</button></div>
<div id="g_{{group}}" class="card-body collapse" data-parent="#config-form">
{{#each elements}}
{{#if editable}}
<div class="form-group row" title="[{{name}}] {{doc.description}}">
{{#case type "text" "number" "password"}}
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
<div class="col-sm-8 input-group">
<input class="form-control conf-{{type}}" id="input_{{name}}" type="{{type}}"
name="{{name}}" value="{{value}}" {{#if default}} placeholder="Výchozí: {{default}}"
{{/if}}>
{{#case type "password"}}
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button"
onclick="toggleVis('input_{{name}}');">Zobrazit/skrýt</button>
</div>
{{/case}}
</div>
{{/case}}
{{#case type "checkbox"}}
<div class="col-sm-3">{{doc.name}}</div>
<div class="col-sm-8">
<div class="form-check">
<input class="form-check-input conf-{{type}}" type="checkbox" id="input_{{name}}"
name="{{name}}" {{#if value}} checked {{/if}}>
<label class="form-check-label" for="input_{{name}}"> Výchozí: {{default}} </label>
</div>
</div>
{{/case}}
</div>
{{/if}}
{{/each}}
</div>
</div>
{{/if}}
{{/each}}
<div class="card bg-light mb-3">
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
data-target="#g_readonly">Nastavení pouze pro čtení</button></div>
<div id="g_readonly" class="card-body collapse" data-parent="#config-form">
<div class="small mb-3">
POZNÁMKA: Tato nastavení nemohou být v editoru upravována, jelikož by potřebovaly restart
serveru. Pro jejich úpravu je potřeba nastavit správné proměnné pro prostředí při
spouštění serveru. Pro názvy proměnných můžeš najét myší na každou z nastavitelných položek.
</div>
{{#each config}}
{{#each elements}}
{{#unless editable}}
<div class="form-group row" title="[{{name}}] {{doc.description}}">
{{#case type "text" "number" "password"}}
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
<div class="col-sm-8 input-group">
<input readonly class="form-control" id="input_{{name}}" type="{{type}}"
value="{{value}}" {{#if default}} placeholder="Výchozí: {{default}}" {{/if}}>
{{#case type "password"}}
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button"
onclick="toggleVis('input_{{name}}');">Zobrazit/skrýt</button>
</div>
{{/case}}
</div>
{{/case}}
{{#case type "checkbox"}}
<div class="col-sm-3">{{doc.name}}</div>
<div class="col-sm-8">
<div class="form-check">
<input disabled class="form-check-input" type="checkbox" id="input_{{name}}"
{{#if value}} checked {{/if}}>
<label class="form-check-label" for="input_{{name}}"> Výchozí: {{default}} </label>
</div>
</div>
{{/case}}
</div>
{{/unless}}
{{/each}}
{{/each}}
</div>
</div>
{{#if can_backup}}
<div class="card bg-light mb-3">
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
data-target="#g_database">Záloha databáze</button></div>
<div id="g_database" class="card-body collapse" data-parent="#config-form">
<div class="small mb-3">
POZNÁMKA: Lokální instalace sqlite3 je pro funkčnost této sekce povinná.
</div>
<button type="button" class="btn btn-primary" onclick="backupDatabase();">Zálohovat</button>
</div>
</div>
{{/if}}
<button type="submit" class="btn btn-primary">Uložit</button>
<button type="button" class="btn btn-danger float-right" onclick="deleteConf();">Nastavit výchozí</button>
</form>
</div>
</div>
</main>
<style>
#config-block ::placeholder {
/* Most modern browsers support this now. */
color: orangered;
}
</style>
<script>
function reload() { window.location.reload(); }
function msg(text) { text && alert(text); reload(); }
function identicon(email) {
const data = new Identicon(md5(email), { size: 48, format: 'svg' });
return "data:image/svg+xml;base64," + data.toString();
}
function toggleVis(input_id) {
const elem = document.getElementById(input_id);
const type = elem.getAttribute("type");
if (type === "text") {
elem.setAttribute("type", "password");
} else {
elem.setAttribute("type", "text");
}
return false;
}
function _post(url, successMsg, errMsg, body) {
fetch(url, {
method: 'POST',
body: body,
mode: "same-origin",
credentials: "same-origin",
headers: { "Content-Type": "application/json" }
}).then(e => {
if (e.ok) { return msg(successMsg); }
e.json().then(json => {
const msg = json ? json.ErrorModel.Message : "Unknown error";
msg(errMsg + ": " + msg);
});
}).catch(e => { msg(errMsg + ": Unknown error") });
}
function deleteUser(id, mail) {
var input_mail = prompt("Pro smazání uživatele '" + mail + "', opište níže jeho email.")
if (input_mail != null) {
if (input_mail == mail) {
_post("/admin/users/" + id + "/delete",
"Uživatel byl smazán",
"Při smazání uživatele se stala chyba");
} else {
alert("Špatně opsaný email, zkus to znovu")
}
}
return false;
}
function remove2fa(id) {
_post("/admin/users/" + id + "/remove-2fa",
"2FA úspěšně smazáno",
"Při mazání 2FA se vyskytla chyba");
return false;
}
function deauthUser(id) {
_post("/admin/users/" + id + "/deauth",
"Odhlášení na zařízeních proběhlo úspěšně",
"Při odhlašování se vyskytla chyba");
return false;
}
function updateRevisions() {
_post("/admin/users/update_revision",
"Úspěch, při příštím připojení dojde ke synchronizaci",
"Při vyžádání synchronizace se vyskytla chyba");
return false;
}
function inviteUser() {
inv = document.getElementById("email-invite");
data = JSON.stringify({ "email": inv.value });
inv.value = "";
_post("/admin/invite/", "Uživatel úspěšně pozván",
"Vyskytla se chyba při posílání pozvánky pro", data);
return false;
}
function getFormData() {
let data = {};
document.querySelectorAll(".conf-checkbox").forEach(function (e, i) {
data[e.name] = e.checked;
});
document.querySelectorAll(".conf-number").forEach(function (e, i) {
data[e.name] = e.value ? +e.value : null;
});
document.querySelectorAll(".conf-text, .conf-password").forEach(function (e, i) {
data[e.name] = e.value || null;
});
return data;
}
function saveConfig() {
data = JSON.stringify(getFormData());
_post("/admin/config/", "Nastavení úspěšně uloženo",
"Při ukládání se vyskytla chyba:", data);
return false;
}
function deleteConf() {
var input = prompt("Tímto smažeš všechna uživatelská nastavení a obnovíš původní " +
"hodnoty nastavené prostředím. Toto může být docela nebezpečné. Napiš 'DELETE' pro potvrzení:");
if (input === "DELETE") {
_post("/admin/config/delete",
"Konfigurace úspěšně smazána",
"Při mazání konfigurace došlo k chybě");
} else {
alert("Špatně opsáno, zkus to znovu")
}
return false;
}
function backupDatabase() {
_post("/admin/config/backup_db",
"Záloha úspěšně vytvořena",
"Při tvorbě zálohy se vyskytla chyba");
return false;
}
function masterCheck(check_id, inputs_query) {
function onChanged(checkbox, inputs_query) {
return function _fn() {
document.querySelectorAll(inputs_query).forEach(function (e, i) { e.disabled = !checkbox.checked; });
checkbox.disabled = false;
};
};
const checkbox = document.getElementById(check_id);
const onChange = onChanged(checkbox, inputs_query);
onChange(); // Trigger the event initially
checkbox.addEventListener("change", onChange);
}
let OrgTypes = {
"0": { "name": "Superadministrátor", "color": "orange" },
"1": { "name": "Administrátor", "color": "blueviolet" },
"2": { "name": "GUGer", "color": "blue" },
"3": { "name": "Leader", "color": "green" },
};
document.querySelectorAll("img.identicon").forEach(function (e, i) {
e.src = identicon(e.dataset.src);
});
document.querySelectorAll("[data-orgtype]").forEach(function (e, i) {
let orgtype = OrgTypes[e.dataset.orgtype];
e.style.backgroundColor = orgtype.color;
e.title = orgtype.name;
});
// These are formatted because otherwise the
// VSCode formatter breaks But they still work
// {{#each config}} {{#if grouptoggle}}
masterCheck("input_{{grouptoggle}}", "#g_{{group}} input");
// {{/if}} {{/each}}
</script>