diff --git a/public/language/en-GB/admin/settings/api.json b/public/language/en-GB/admin/settings/api.json index 34bb73d536..e7a3cfe375 100644 --- a/public/language/en-GB/admin/settings/api.json +++ b/public/language/en-GB/admin/settings/api.json @@ -16,7 +16,11 @@ "last-seen": "Last seen", "created": "Created", "create-token": "Create Token", + "update-token": "Update Token", "master-token": "Master token", "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." } \ No newline at end of file diff --git a/public/src/admin/settings/api.js b/public/src/admin/settings/api.js index 55023a0d64..a3687ee87f 100644 --- a/public/src/admin/settings/api.js +++ b/public/src/admin/settings/api.js @@ -13,66 +13,155 @@ define('admin/settings/api', ['settings', 'clipboard', 'bootbox', 'benchpress', const copyEls = document.querySelectorAll('[data-component="acp/tokens"] [data-action="copy"]'); new clipboard(copyEls); - handleTokenCreation(); + handleActions(); }; - async function handleTokenCreation() { - const createEl = document.querySelector('[data-action="create"]'); - if (createEl) { - createEl.addEventListener('click', async () => { - 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, - }, - }, - }); - }); + function handleActions() { + const formEl = document.querySelector('#content form'); + if (!formEl) { + return; } + + 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() { - const modal = this; - const formEl = this.get(0).querySelector('form'); - const tokensTableBody = document.querySelector('[data-component="acp/tokens"] tbody'); - const valid = formEl.reportValidity(); - if (formEl && valid) { - const formData = new FormData(formEl); - const uid = formData.get('uid'); - const description = formData.get('description'); - // const qs = new URLSearchParams(payload).toString(); - - const token = await api.post('/admin/tokens', { uid, description }).catch(app.alertError); - - const now = new Date(); - const rowEl = (await app.parseAndTranslate(ajaxify.data.template.name, 'tokens', { - tokens: [{ - token, - uid, - description, - timestamp: now.getTime(), - timestampISO: now.toISOString(), - lastSeen: null, - lastSeenISO: new Date(0).toISOString(), - }], - })).get(0); - - if (tokensTableBody) { - tokensTableBody.append(rowEl); - $(rowEl).find('.timeago').timeago(); - } else { - ajaxify.refresh(); + async function handleTokenCreation() { + const html = await Benchpress.render('admin/partials/edit-token-modal', {}); + const parseForm = async function () { + const modal = this; + const formEl = this.get(0).querySelector('form'); + const tokensTableBody = document.querySelector('[data-component="acp/tokens"] tbody'); + const valid = formEl.reportValidity(); + if (formEl && valid) { + const formData = new FormData(formEl); + const uid = formData.get('uid'); + const description = formData.get('description'); + + try { + const token = await api.post('/admin/tokens', { uid, description }); + + if (!tokensTableBody) { + modal.modal('hide'); + return ajaxify.refresh(); + } + + const now = new Date(); + const tokenObj = { + token, + uid, + description, + timestamp: now.getTime(), + timestampISO: now.toISOString(), + lastSeen: null, + lastSeenISO: new Date(0).toISOString(), + }; + 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; diff --git a/src/controllers/write/admin.js b/src/controllers/write/admin.js index 6bb95e7492..a0c6f082e9 100644 --- a/src/controllers/write/admin.js +++ b/src/controllers/write/admin.js @@ -34,6 +34,10 @@ Admin.generateToken = async (req, res) => { 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) => { // todo: token rolling via req.body const { uid, description } = req.body; diff --git a/src/routes/write/admin.js b/src/routes/write/admin.js index 65df00d574..63645d40fc 100644 --- a/src/routes/write/admin.js +++ b/src/routes/write/admin.js @@ -16,6 +16,7 @@ module.exports = function () { setupApiRoute(router, 'get', '/analytics/:set', [...middlewares], controllers.write.admin.getAnalyticsData); 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, 'delete', '/tokens/:token', [...middlewares], controllers.write.admin.deleteToken); diff --git a/src/views/admin/settings/api.tpl b/src/views/admin/settings/api.tpl index 1f76b5e97c..2734b2989a 100644 --- a/src/views/admin/settings/api.tpl +++ b/src/views/admin/settings/api.tpl @@ -21,11 +21,12 @@ [[admin/settings/api:uid]] [[admin/settings/api:last-seen]] [[admin/settings/api:created]] + [[admin/settings/api:actions]] {{{ each tokens }}} - + @@ -55,6 +56,14 @@ + + + + {{{ end }}}