484 lines
21 KiB
HTML
484 lines
21 KiB
HTML
|
<html><head><title>{{i18n "Administration"}}</title><meta charset=utf8 /><meta name=viewport content="width=device-width,initial-scale=1"/><script src=https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js type=text/javascript></script><script src=https://unpkg.com/popper.js@1.12.6/dist/umd/popper.js type=text/javascript></script><script src=https://unpkg.com/bootstrap-material-design@4.1.1/dist/js/bootstrap-material-design.js type=text/javascript></script><script src=https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.11/handlebars.min.js type=text/javascript></script><script>$(document).ready(() => $('body').bootstrapMaterialDesign())</script><style>@import"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons";@import"https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css";.btn-primary{color:#fff !important;background-color:#1e88e5 !important}.error_card{color:#ff2f00;padding:1rem;font-size:1rem}.bg-primary{background-color:#1e88e5 !important}.spinner{-webkit-animation:rotation 1.35s linear infinite;animation:rotation 1.35s linear infinite;stroke:#1e88e5}@-webkit-keyframes rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}}@keyframes rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}}.circle{stroke-dasharray:180;stroke-dashoffset:0;-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;-webkit-animation:turn 1.35s ease-in-out infinite;animation:turn 1.35s ease-in-out infinite}@-webkit-keyframes turn{0%{stroke-dashoffset:180}50%{stroke-dashoffset:45;-webkit-transform:rotate(135deg);transform:rotate(135deg)}100%{stroke-dashoffset:180;-webkit-transform:rotate(450deg);transform:rotate(450deg)}}@keyframes turn{0%{stroke-dashoffset:180}50%{stroke-dashoffset:45;-webkit-transform:rotate(135deg);transform:rotate(135deg)}100%{stroke-dashoffset:180;-webkit-transform:rotate(450deg);transform:rotate(450deg)}}header{margin-bottom:8px;padding:8px 16px;padding-bottom:0}table{word-wrap:break-word;table-layout:fixed}table td{vertical-align:inherit !important;width:auto}.col.form-group{padding-left:0 !important;margin-left:5px !important}</style></head><body><header class=bg-primary style="display: flex; justify-content: space-between;"><h3 style="display: inline">{{appname}} {{i18n "Administration"}} <span id=sitename>LOADING</span></h3><ul class="nav nav-tabs" style="display: inline-block; margin-left: auto; margin-top: -8px;"><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" data-toggle=dropdown href=# role=button aria-haspopup=true aria-expanded=false>Model</a><div class=dropdown-menu><a class=dropdown-item href="?type=user">User</a> <a class=dropdown-item href="?type=regcode">RegCode</a> <a class=dropdown-item href="?type=client">Client</a></div></li></ul></header><div id=content><div class=container><div id=error_cont class=row style="margin-bottom: 24px;display: none;"><div class=col-sm><div class="card error_card"><div id=error_msg class=card-body></div></div></div></div><div id=custom_data_cont class=row style="margin-bottom: 24px; display: none"><div class=col-sm><div class=card><div id=custom_data class=card-body></div></div></div></div><div class=row><div class=col-sm><div class=card><div class=card-body><div id=table-body><div style="width: 65px; height: 65px; margin: 0 auto;"><svg class=spinner viewBox="0 0 66 66" xmlns=http://www.w3.org/2000/svg><circle class=circle fill=none stroke-width=6 stroke-linecap=round cx=33 cy=33 r=30></circle></svg></div></div></div></div></div></div></div></div><script id=template-spinner type=text/x-handlebars-template><div style="width: 65px; height: 65px; margin: 0 auto;">
|
||
|
<svg class="spinner" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
|
||
|
<circle class="circle" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
|
||
|
</svg>
|
||
|
</div></script><script id=template-user-list type=text/x-handlebars-template><table class="table table-bordered" style="margin-bottom: 0">
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th scope="col">Username</th>
|
||
|
<th scope="col">Name</th>
|
||
|
<th scope="col">Gender</th>
|
||
|
<th scope="col">Role</th>
|
||
|
<th scope="col" style="width: 2.5rem"></th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
\{{#users}}
|
||
|
<tr>
|
||
|
<td>\{{ username }}</td>
|
||
|
<td>\{{ name }}</td>
|
||
|
|
||
|
<!-- ToDo: Make helper to resolve number to human readaby text-->
|
||
|
<td>\{{humangender gender}}</td>
|
||
|
|
||
|
<td onclick="userOnChangeType('\{{_id}}')">
|
||
|
\{{#if admin}}
|
||
|
<span class="badge badge-danger">Admin</span>
|
||
|
\{{else}}
|
||
|
<span class="badge badge-success">User</span>
|
||
|
\{{/if}}
|
||
|
</td>
|
||
|
<td style="padding: 0.25em">
|
||
|
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="deleteUser('\{{_id}}')">
|
||
|
<i class="material-icons" style="font-size: 2rem; display: inline">
|
||
|
delete
|
||
|
</i>
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
\{{/users}}
|
||
|
</tbody>
|
||
|
</table></script><script id=template-regcode-list type=text/x-handlebars-template><button class="btn btn-raised btn-primary" onclick="createRegcode()">Create</button>
|
||
|
<table class="table table-bordered" style="margin-bottom: 0">
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th scope="col">Code</th>
|
||
|
<th scope="col">Valid</th>
|
||
|
<th scope="col">Till</th>
|
||
|
<th scope="col" style="width: 2.5rem"></th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
\{{#regcodes}}
|
||
|
<tr>
|
||
|
<td>\{{ token }}</td>
|
||
|
<td>\{{ valid }}</td>
|
||
|
|
||
|
<!-- ToDo: Make helper to resolve number to human readaby text-->
|
||
|
<td>\{{formatDate validTill }}</td>
|
||
|
<td style="padding: 0.25em">
|
||
|
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="deleteRegcode('\{{_id}}')">
|
||
|
<i class="material-icons" style="font-size: 2rem; display: inline">
|
||
|
delete
|
||
|
</i>
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
\{{/regcodes}}
|
||
|
</tbody>
|
||
|
</table></script><script id=template-client-list type=text/x-handlebars-template><button class="btn btn-raised btn-primary" onclick="createClient()">Create</button>
|
||
|
<table class="table table-bordered" style="margin-bottom: 0">
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th scope="col">ID</th>
|
||
|
<th scope="col">Secret</th>
|
||
|
<th scope="col">Maintainer</th>
|
||
|
<th scope="col">Name</th>
|
||
|
<th scope="col" style="width: 80px">Type</th>
|
||
|
<th scope="col">Website</th>
|
||
|
<th scope="col" style="width: 2.5rem">
|
||
|
<div></div>
|
||
|
</th>
|
||
|
<th scope="col" style="width: 2.5rem"></th>
|
||
|
<th scope="col" style="width: 2.5rem"></th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
\{{#clients}}
|
||
|
<tr>
|
||
|
<td>\{{ client_id }}</td>
|
||
|
<td>\{{ client_secret }}</td>
|
||
|
<td>\{{ maintainer.username }}</td>
|
||
|
<td>\{{ name }}</td>
|
||
|
<td>
|
||
|
\{{#if internal}}
|
||
|
<span class="badge badge-success">Internal</span>
|
||
|
\{{else}}
|
||
|
<span class="badge badge-danger">External</span>
|
||
|
\{{/if}}
|
||
|
</td>
|
||
|
<td>
|
||
|
<a href="\{{ website }}">\{{ website }}</a>
|
||
|
</td>
|
||
|
<td style="padding: 0.25em">
|
||
|
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="permissionsClient('\{{_id}}')">
|
||
|
perm
|
||
|
</button>
|
||
|
</td>
|
||
|
<td style="padding: 0.25em">
|
||
|
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="editClient('\{{_id}}')">
|
||
|
<i class="material-icons" style="font-size: 2rem; display: inline">
|
||
|
edit
|
||
|
</i>
|
||
|
</button>
|
||
|
</td>
|
||
|
<td style="padding: 0.25em">
|
||
|
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="deleteClient('\{{_id}}')">
|
||
|
<i class="material-icons" style="font-size: 2rem; display: inline">
|
||
|
delete
|
||
|
</i>
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
\{{/clients}}
|
||
|
</tbody>
|
||
|
</table></script><script id=template-client-form type=text/x-handlebars-template><form class="form" action="JavaScript:void(null)" onsubmit="createClientSubmit(this)" style="margin-bottom: 0">
|
||
|
<input type=hidden value="\{{_id}}" name=id />
|
||
|
<div class="form-group">
|
||
|
<label for="name_input" class="bmd-label-floating">Name</label>
|
||
|
<input type="text" class="form-control" id="name_input" name=name value="\{{name}}">
|
||
|
</div>
|
||
|
<div class="form-group">
|
||
|
<label for="redirect_input" class="bmd-label-floating">Redirect Url</label>
|
||
|
<input type="text" class="form-control" id="redirect_input" name=redirect_url value="\{{redirect_url}}">
|
||
|
</div>
|
||
|
<div class="form-group">
|
||
|
<label for="website_input" class="bmd-label-floating">Website</label>
|
||
|
<input type="text" class="form-control" id="website_input" name=website value="\{{website}}">
|
||
|
</div>
|
||
|
<div class="form-group">
|
||
|
<label for="logo_input" class="bmd-label-floating">Logo</label>
|
||
|
<input type="text" class="form-control" id="logo_input" name=logo value="\{{logo}}">
|
||
|
</div>
|
||
|
|
||
|
<div class="form-group">
|
||
|
<div class="form-check">
|
||
|
<input type="checkbox" class="form-check-input" id="internal_check" \{{#if internal}} checked="checked" \{{/if}} name=internal>
|
||
|
<label class="form-check-label" for="internal_check">Internal</label>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<span class="form-group bmd-form-group">
|
||
|
<!-- needed to match padding for floating labels -->
|
||
|
<button type="submit" class="btn btn-raised btn-primary">Save</button>
|
||
|
</span>
|
||
|
</form></script><script id=template-permission-list type=text/x-handlebars-template><h2><button class="btn btn-raised btn-primary" onclick="gotoClients()">back</button> to \{{client_name}} </h2>
|
||
|
<button class="btn btn-raised btn-primary" onclick="createPermission('\{{ client_id }}')">Create</button>
|
||
|
<table class="table table-bordered" style="margin-bottom: 0">
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th scope="col">ID</th>
|
||
|
<th scope="col">Name</th>
|
||
|
<th scope="col">Description</th>
|
||
|
<th scope="col" style="width: 10ch">Type</th>
|
||
|
<th scope="col" style="width: 2.5rem"></th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
\{{#permissions}}
|
||
|
<tr>
|
||
|
<td>\{{ _id }}</td>
|
||
|
<td>\{{ name }}</td>
|
||
|
<td>\{{ description }}</td>
|
||
|
<td>\{{ grant_type }}</td>
|
||
|
<td style="padding: 0.25em">
|
||
|
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="deletePermission('\{{_id}}')">
|
||
|
<i class="material-icons" style="font-size: 2rem; display: inline">
|
||
|
delete
|
||
|
</i>
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
\{{/permissions}}
|
||
|
</tbody>
|
||
|
</table></script><script id=template-permission-form type=text/x-handlebars-template><form class="form" action="JavaScript:void(null)" onsubmit="createPermissionSubmit(this)" style="margin-bottom: 0">
|
||
|
<input type=hidden value="\{{client_id}}" name=client />
|
||
|
<div class="form-group">
|
||
|
<label for="name_input" class="bmd-label-floating">Name</label>
|
||
|
<input type="text" class="form-control" id="name_input" name=name value="">
|
||
|
</div>
|
||
|
<div class="form-group">
|
||
|
<label for=description class="bmd-label-floating">Description</label>
|
||
|
<input type="text" class="form-control" id=description name=description value="">
|
||
|
</div>
|
||
|
<div class="form-group">
|
||
|
<label for=type class="bmd-label-floating">Type</label>
|
||
|
|
||
|
<select type="text" class="form-control" id=type name=type>
|
||
|
<option value="user">User granted</option>
|
||
|
<option value="client">Client granted</option>
|
||
|
</select>
|
||
|
</div>
|
||
|
<span class="form-group bmd-form-group">
|
||
|
<!-- needed to match padding for floating labels -->
|
||
|
<button type="submit" class="btn btn-raised btn-primary">Save</button>
|
||
|
</span>
|
||
|
</form></script><script>(function(){'use strict';function request(endpoint, method, data) {
|
||
|
var headers = new Headers();
|
||
|
headers.set("Content-Type", "application/json");
|
||
|
return fetch(endpoint, {
|
||
|
method: method,
|
||
|
body: JSON.stringify(data),
|
||
|
headers: headers,
|
||
|
credentials: "include",
|
||
|
})
|
||
|
.then(async (e) => {
|
||
|
if (e.status !== 200)
|
||
|
throw new Error((await e.text()) || e.statusText);
|
||
|
return e.json();
|
||
|
})
|
||
|
.then((e) => {
|
||
|
if (e.error)
|
||
|
return Promise.reject(
|
||
|
new Error(
|
||
|
typeof e.error === "string"
|
||
|
? e.error
|
||
|
: JSON.stringify(e.error)
|
||
|
)
|
||
|
);
|
||
|
return e;
|
||
|
});
|
||
|
}function getFormData(element) {
|
||
|
let data = {};
|
||
|
if (
|
||
|
element.name !== undefined &&
|
||
|
element.name !== null &&
|
||
|
element.name !== ""
|
||
|
) {
|
||
|
if (typeof element.name === "string") {
|
||
|
if (element.type === "checkbox") data[element.name] = element.checked;
|
||
|
else data[element.name] = element.value;
|
||
|
}
|
||
|
}
|
||
|
element.childNodes.forEach((child) => {
|
||
|
let res = getFormData(child);
|
||
|
data = Object.assign(data, res);
|
||
|
});
|
||
|
return data;
|
||
|
}Handlebars.registerHelper("humangender", function (value, options) {
|
||
|
switch (value) {
|
||
|
case 1:
|
||
|
return "male";
|
||
|
case 2:
|
||
|
return "female";
|
||
|
case 3:
|
||
|
return "other";
|
||
|
default:
|
||
|
case 0:
|
||
|
return "none";
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Deprecated since version 0.8.0
|
||
|
Handlebars.registerHelper("formatDate", function (datetime, format) {
|
||
|
return new Date(datetime).toLocaleString();
|
||
|
});
|
||
|
|
||
|
(() => {
|
||
|
const tableb = document.getElementById("table-body");
|
||
|
|
||
|
function setTitle(title) {
|
||
|
document.getElementById("sitename").innerText = title;
|
||
|
}
|
||
|
|
||
|
const cc = document.getElementById("custom_data");
|
||
|
const ccc = document.getElementById("custom_data_cont");
|
||
|
|
||
|
function setCustomCard(content) {
|
||
|
if (!content) {
|
||
|
cc.innerHTML = "";
|
||
|
ccc.style.display = "none";
|
||
|
} else {
|
||
|
cc.innerHTML = content;
|
||
|
ccc.style.display = "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const error_cont = document.getElementById("error_cont");
|
||
|
const error_msg = document.getElementById("error_msg");
|
||
|
|
||
|
function catchError(error) {
|
||
|
error_cont.style.display = "";
|
||
|
error_msg.innerText = error.message;
|
||
|
console.log(error);
|
||
|
}
|
||
|
|
||
|
async function renderUser() {
|
||
|
console.log("Rendering User");
|
||
|
setTitle("User");
|
||
|
const listt = Handlebars.compile(
|
||
|
document.getElementById("template-user-list").innerText
|
||
|
);
|
||
|
|
||
|
async function loadList() {
|
||
|
let data = await request("/api/admin/user", "GET");
|
||
|
tableb.innerHTML = listt({
|
||
|
users: data,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
window.userOnChangeType = (id) => {
|
||
|
request("/api/admin/user?id=" + id, "PUT")
|
||
|
.then(() => loadList())
|
||
|
.catch(catchError);
|
||
|
};
|
||
|
|
||
|
window.deleteUser = (id) => {
|
||
|
request("/api/admin/user?id=" + id, "DELETE")
|
||
|
.then(() => loadList())
|
||
|
.catch(catchError);
|
||
|
};
|
||
|
|
||
|
await loadList();
|
||
|
}
|
||
|
|
||
|
async function renderPermissions(client_id, client_name) {
|
||
|
const listt = Handlebars.compile(
|
||
|
document.getElementById("template-permission-list").innerText
|
||
|
);
|
||
|
const formt = Handlebars.compile(
|
||
|
document.getElementById("template-permission-form").innerText
|
||
|
);
|
||
|
setCustomCard();
|
||
|
async function loadList() {
|
||
|
try {
|
||
|
let data = await request(
|
||
|
"/api/admin/permission?client=" + client_id,
|
||
|
"GET"
|
||
|
);
|
||
|
tableb.innerHTML = listt({
|
||
|
client_id: client_id,
|
||
|
client_name: client_name,
|
||
|
permissions: data,
|
||
|
});
|
||
|
} catch (err) {
|
||
|
catchError(err);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
window.gotoClients = () => {
|
||
|
renderClient();
|
||
|
};
|
||
|
|
||
|
window.deletePermission = (id) => {
|
||
|
request("/api/admin/permission?id=" + id, "DELETE")
|
||
|
.then(() => loadList())
|
||
|
.catch(catchError);
|
||
|
};
|
||
|
|
||
|
window.createPermission = () => {
|
||
|
try {
|
||
|
setCustomCard(formt({ client_id: client_id }));
|
||
|
} catch (err) {
|
||
|
console.log("Err", err);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
window.createPermissionSubmit = (elm) => {
|
||
|
console.log(elm);
|
||
|
let data = getFormData(elm);
|
||
|
console.log(data);
|
||
|
request("/api/admin/permission", "POST", data)
|
||
|
.then(() => setCustomCard())
|
||
|
.then(() => loadList())
|
||
|
.catch(catchError);
|
||
|
};
|
||
|
await loadList();
|
||
|
}
|
||
|
|
||
|
async function renderClient() {
|
||
|
console.log("Rendering Client");
|
||
|
setTitle("Client");
|
||
|
|
||
|
const listt = Handlebars.compile(
|
||
|
document.getElementById("template-client-list").innerText
|
||
|
);
|
||
|
const formt = Handlebars.compile(
|
||
|
document.getElementById("template-client-form").innerText
|
||
|
);
|
||
|
|
||
|
let clients = [];
|
||
|
async function loadList() {
|
||
|
let data = await request("/api/admin/client", "GET");
|
||
|
clients = data;
|
||
|
tableb.innerHTML = listt({
|
||
|
clients: data,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
window.permissionsClient = (id) => {
|
||
|
renderPermissions(id, clients.find((e) => e._id === id).name);
|
||
|
};
|
||
|
|
||
|
window.deleteClient = (id) => {
|
||
|
request("/api/admin/client/id=" + id, "DELETE")
|
||
|
.then(() => loadList())
|
||
|
.catch(catchError);
|
||
|
};
|
||
|
|
||
|
window.createClientSubmit = (elm) => {
|
||
|
console.log(elm);
|
||
|
let data = getFormData(elm);
|
||
|
console.log(data);
|
||
|
let id = data.id;
|
||
|
delete data.id;
|
||
|
if (id && id !== "") {
|
||
|
request("/api/admin/client?id=" + id, "PUT", data)
|
||
|
.then(() => setCustomCard())
|
||
|
.then(() => loadList())
|
||
|
.catch(catchError);
|
||
|
} else {
|
||
|
request("/api/admin/client", "POST", data)
|
||
|
.then(() => setCustomCard())
|
||
|
.then(() => loadList())
|
||
|
.catch(catchError);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
window.createClient = () => {
|
||
|
setCustomCard(formt());
|
||
|
};
|
||
|
|
||
|
window.editClient = (id) => {
|
||
|
let client = clients.find((e) => e._id === id);
|
||
|
if (!client) return catchError(new Error("Client does not exist!!"));
|
||
|
setCustomCard(formt(client));
|
||
|
};
|
||
|
|
||
|
await loadList().catch(catchError);
|
||
|
}
|
||
|
|
||
|
async function renderRegCode() {
|
||
|
console.log("Rendering RegCode");
|
||
|
setTitle("RegCode");
|
||
|
|
||
|
const listt = Handlebars.compile(
|
||
|
document.getElementById("template-regcode-list").innerText
|
||
|
);
|
||
|
|
||
|
async function loadList() {
|
||
|
let data = await request("/api/admin/regcode", "GET");
|
||
|
tableb.innerHTML = listt({
|
||
|
regcodes: data,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
window.deleteRegcode = (id) => {
|
||
|
request("/api/admin/regcode?id=" + id, "DELETE")
|
||
|
.then(() => loadList())
|
||
|
.catch(catchError);
|
||
|
};
|
||
|
|
||
|
window.createRegcode = () => {
|
||
|
request("/api/admin/regcode", "POST")
|
||
|
.then(() => loadList())
|
||
|
.catch(catchError);
|
||
|
};
|
||
|
|
||
|
await loadList().catch(catchError);
|
||
|
}
|
||
|
|
||
|
const type = new URL(window.location.href).searchParams.get("type");
|
||
|
switch (type) {
|
||
|
case "client":
|
||
|
renderClient().catch(catchError);
|
||
|
break;
|
||
|
case "regcode":
|
||
|
renderRegCode().catch(catchError);
|
||
|
break;
|
||
|
case "user":
|
||
|
default:
|
||
|
renderUser().catch(catchError);
|
||
|
break;
|
||
|
}
|
||
|
})();})();</script></body></html>
|