feat: token editing and deletion

isekai-main
Julian Lam 2 years ago
parent e4888dea17
commit ce23caf7e6

@ -16,7 +16,11 @@
"last-seen": "Last seen", "last-seen": "Last seen",
"created": "Created", "created": "Created",
"create-token": "Create Token", "create-token": "Create Token",
"update-token": "Update Token",
"master-token": "Master token", "master-token": "Master token",
"last-seen-never": "This key has never been used.", "last-seen-never": "This key has never been used.",
"no-description": "No description specified." "no-description": "No description specified.",
"actions": "Actions",
"delete-confirm": "Are you sure you wish to delete this token? It will not be recoverable."
} }

@ -13,66 +13,155 @@ define('admin/settings/api', ['settings', 'clipboard', 'bootbox', 'benchpress',
const copyEls = document.querySelectorAll('[data-component="acp/tokens"] [data-action="copy"]'); const copyEls = document.querySelectorAll('[data-component="acp/tokens"] [data-action="copy"]');
new clipboard(copyEls); new clipboard(copyEls);
handleTokenCreation(); handleActions();
}; };
async function handleTokenCreation() { function handleActions() {
const createEl = document.querySelector('[data-action="create"]'); const formEl = document.querySelector('#content form');
if (createEl) { if (!formEl) {
createEl.addEventListener('click', async () => { return;
const html = await Benchpress.render('admin/partials/edit-token-modal', {});
bootbox.dialog({
title: '[[admin/settings/api:create-token]]',
message: html,
buttons: {
submit: {
label: '[[modules:bootbox.submit]]',
className: 'btn-primary',
callback: parseCreateForm,
},
},
});
});
} }
formEl.addEventListener('click', (e) => {
const subselector = e.target.closest('[data-action]');
if (subselector) {
const action = subselector.getAttribute('data-action');
switch (action) {
case 'create':
handleTokenCreation();
break;
case 'edit':
handleTokenUpdate(subselector);
break;
case 'delete':
handleTokenDeletion(subselector);
break;
}
}
});
} }
async function parseCreateForm() { async function handleTokenCreation() {
const modal = this; const html = await Benchpress.render('admin/partials/edit-token-modal', {});
const formEl = this.get(0).querySelector('form'); const parseForm = async function () {
const tokensTableBody = document.querySelector('[data-component="acp/tokens"] tbody'); const modal = this;
const valid = formEl.reportValidity(); const formEl = this.get(0).querySelector('form');
if (formEl && valid) { const tokensTableBody = document.querySelector('[data-component="acp/tokens"] tbody');
const formData = new FormData(formEl); const valid = formEl.reportValidity();
const uid = formData.get('uid'); if (formEl && valid) {
const description = formData.get('description'); const formData = new FormData(formEl);
// const qs = new URLSearchParams(payload).toString(); const uid = formData.get('uid');
const description = formData.get('description');
const token = await api.post('/admin/tokens', { uid, description }).catch(app.alertError);
try {
const now = new Date(); const token = await api.post('/admin/tokens', { uid, description });
const rowEl = (await app.parseAndTranslate(ajaxify.data.template.name, 'tokens', {
tokens: [{ if (!tokensTableBody) {
token, modal.modal('hide');
uid, return ajaxify.refresh();
description, }
timestamp: now.getTime(),
timestampISO: now.toISOString(), const now = new Date();
lastSeen: null, const tokenObj = {
lastSeenISO: new Date(0).toISOString(), token,
}], uid,
})).get(0); description,
timestamp: now.getTime(),
if (tokensTableBody) { timestampISO: now.toISOString(),
tokensTableBody.append(rowEl); lastSeen: null,
$(rowEl).find('.timeago').timeago(); lastSeenISO: new Date(0).toISOString(),
} else { };
ajaxify.refresh(); ajaxify.data.tokens.append(tokenObj);
const rowEl = (await app.parseAndTranslate(ajaxify.data.template.name, 'tokens', {
tokens: [tokenObj],
})).get(0);
tokensTableBody.append(rowEl);
$(rowEl).find('.timeago').timeago();
modal.modal('hide');
} catch (e) {
app.alertError(e);
}
} }
modal.modal('hide'); return false;
} };
bootbox.dialog({
title: '[[admin/settings/api:create-token]]',
message: html,
buttons: {
submit: {
label: '[[modules:bootbox.submit]]',
className: 'btn-primary',
callback: parseForm,
},
},
});
}
async function handleTokenUpdate(el) {
const rowEl = el.closest('[data-token]');
const token = rowEl.getAttribute('data-token');
const { uid, description } = await api.get(`/admin/tokens/${token}`);
const parseForm = async function () {
const modal = this;
const formEl = this.get(0).querySelector('form');
const valid = formEl.reportValidity();
if (formEl && valid) {
const formData = new FormData(formEl);
const uid = formData.get('uid');
const description = formData.get('description');
try {
const tokenObj = await api.put(`/admin/tokens/${token}`, { uid, description });
const newEl = (await app.parseAndTranslate(ajaxify.data.template.name, 'tokens', {
tokens: [tokenObj],
})).get(0);
rowEl.replaceWith(newEl);
$(newEl).find('.timeago').timeago();
modal.modal('hide');
} catch (e) {
app.alertError(e);
}
}
return false;
};
return false; const html = await Benchpress.render('admin/partials/edit-token-modal', { uid, description });
bootbox.dialog({
title: '[[admin/settings/api:update-token]]',
message: html,
buttons: {
submit: {
label: '[[modules:bootbox.submit]]',
className: 'btn-primary',
callback: parseForm,
},
},
});
}
async function handleTokenDeletion(el) {
const rowEl = el.closest('[data-token]');
const token = rowEl.getAttribute('data-token');
bootbox.confirm('[[admin/settings/api:delete-confirm]]', async (ok) => {
if (ok) {
try {
await api.del(`/admin/tokens/${token}`);
} catch (e) {
app.alertError(e);
}
rowEl.remove();
}
});
} }
return ACP; return ACP;

@ -34,6 +34,10 @@ Admin.generateToken = async (req, res) => {
helpers.formatApiResponse(200, res, await api.utils.tokens.generate({ uid, description })); helpers.formatApiResponse(200, res, await api.utils.tokens.generate({ uid, description }));
}; };
Admin.getToken = async (req, res) => {
helpers.formatApiResponse(200, res, await api.utils.tokens.get(req.params.token));
};
Admin.updateToken = async (req, res) => { Admin.updateToken = async (req, res) => {
// todo: token rolling via req.body // todo: token rolling via req.body
const { uid, description } = req.body; const { uid, description } = req.body;

@ -16,6 +16,7 @@ module.exports = function () {
setupApiRoute(router, 'get', '/analytics/:set', [...middlewares], controllers.write.admin.getAnalyticsData); setupApiRoute(router, 'get', '/analytics/:set', [...middlewares], controllers.write.admin.getAnalyticsData);
setupApiRoute(router, 'post', '/tokens', [...middlewares], controllers.write.admin.generateToken); setupApiRoute(router, 'post', '/tokens', [...middlewares], controllers.write.admin.generateToken);
setupApiRoute(router, 'get', '/tokens/:token', [...middlewares], controllers.write.admin.getToken);
setupApiRoute(router, 'put', '/tokens/:token', [...middlewares], controllers.write.admin.updateToken); setupApiRoute(router, 'put', '/tokens/:token', [...middlewares], controllers.write.admin.updateToken);
setupApiRoute(router, 'delete', '/tokens/:token', [...middlewares], controllers.write.admin.deleteToken); setupApiRoute(router, 'delete', '/tokens/:token', [...middlewares], controllers.write.admin.deleteToken);

@ -21,11 +21,12 @@
<th>[[admin/settings/api:uid]]</th> <th>[[admin/settings/api:uid]]</th>
<th>[[admin/settings/api:last-seen]]</th> <th>[[admin/settings/api:last-seen]]</th>
<th>[[admin/settings/api:created]]</th> <th>[[admin/settings/api:created]]</th>
<th>[[admin/settings/api:actions]]</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{{ each tokens }}} {{{ each tokens }}}
<tr> <tr data-token="{./token}">
<td> <td>
<button type="button" class="btn btn-link" data-action="copy" data-clipboard-text="{./token}"><i class="fa fa-fw fa-clipboard" aria-hidden="true"></i></button> <button type="button" class="btn btn-link" data-action="copy" data-clipboard-text="{./token}"><i class="fa fa-fw fa-clipboard" aria-hidden="true"></i></button>
<div class="vr me-3" aria-hidden="true"></div> <div class="vr me-3" aria-hidden="true"></div>
@ -55,6 +56,14 @@
<td class="align-middle"> <td class="align-middle">
<span class="timeago" title="{./timestampISO}"></span> <span class="timeago" title="{./timestampISO}"></span>
</td> </td>
<td>
<button type="button" class="btn btn-link" data-action="edit">
<i class="fa fa-edit"></i>
</button>
<button type="button" class="btn btn-link link-danger" data-action="delete">
<i class="fa fa-trash"></i>
</button>
</td>
</tr> </tr>
{{{ end }}} {{{ end }}}
</tbody> </tbody>

Loading…
Cancel
Save