/** * Permission grants: dashboard permissions and in-game commands given to (or taken from) one account or character. * Grants.mount(el, {type, id}): the grants of one account or character, with a form to add one when the viewer may. * Grants.mount(el, null): every grant in force and the history, with a form that asks whose it is (Permissions page). * The server decides what may be granted (only what the viewer holds, on accounts they may manage); the form offers * only those. */ (function () { var KINDS = [ ['permission', 'Dashboard permission'], ['command', 'In-game command'], ['permission_group', 'Every permission of a category'], ['command_group', 'Every command up to a GM level'] ]; var catalog = null; function loadCatalog() { if (!catalog) { catalog = api.get('/api/grants/catalog').then(function (d) { if (!d.success) throw new Error(d.error || 'Could not load what can be granted'); return d; }); catalog.catch(function () { catalog = null; }); } return catalog; } // What can be picked for a kind: [{value, label, detail}], only what the viewer may grant function choices(d, kind, text) { var list; if (kind === 'permission') { list = d.permissions.map(function (p) { return { value: p.key, label: p.title + ' (' + p.key + ')', detail: p.category + ' · GM ' + p.level + '+ · ' + p.description, ok: p.grantable, why: p.reason }; }); } else if (kind === 'command') { list = d.commands.map(function (c) { return { value: c.name, label: '/' + c.aliases[0] + (c.aliases.length > 1 ? ' (also /' + c.aliases.slice(1).join(', /') + ')' : ''), detail: 'GM ' + c.level + '+' + (c.permission ? ' · follows ' + c.permission : '') + ' · ' + c.help, ok: c.grantable, why: c.reason }; }); } else if (kind === 'permission_group') { list = d.permissionGroups.map(function (g) { return { value: g.name, label: g.name, detail: g.permissions.join(', '), ok: g.grantable, why: g.reason }; }); } else { list = d.commandGroups.map(function (g) { return { value: g.name, label: 'Every command up to GM ' + g.name, detail: '', ok: g.grantable, why: g.reason }; }); } var query = (text || '').toLowerCase(); return list.filter(function (item) { return item.ok && (!query || (item.label + ' ' + item.value + ' ' + item.detail).toLowerCase().indexOf(query) !== -1); }).slice(0, 60); } function status(g) { if (g.status === 'removed') return fmt.badge('Removed', 'secondary'); if (g.status === 'expired') return fmt.badge('Expired', 'secondary'); return fmt.badge('In force', 'success'); } function targetLink(g) { if (g.targetType === 'account') return 'Account ' + fmt.link('/accounts/' + g.targetId, g.targetName || g.targetId); return 'Character ' + fmt.character(g.targetId, g.targetName); } function rows(list, options) { if (!list.length) return '' + esc(options.empty) + ''; return list.map(function (g) { var when = 'By ' + esc(g.grantedBy || '?') + ', ' + fmt.unix(g.grantedAt) + (g.revokedAt ? '
Removed by ' + esc(g.revokedBy) + ', ' + fmt.unix(g.revokedAt) : ''); return '' + (g.deny ? fmt.badge('Deny', 'danger') : fmt.badge('Grant', 'primary')) + '' + '
' + esc(g.label) + '
' + (options.showTarget ? '
' + targetLink(g) + '
' : '') + (g.note ? '
' + esc(g.note) + '
' : '') + '' + '' + (g.expiresAt ? fmt.unix(g.expiresAt) : 'Never') + '' + '' + when + '' + status(g) + '' + '' + (g.canRemove ? '' : '') + ''; }).join(''); } function table(id, heading) { return (heading ? '
' + heading + '
' : '') + '
' + '
WhatExpiresGivenStatus
'; } function form(chooseTarget) { var target = chooseTarget ? '
' + '' + '
' : ''; return '
' + target + '
' + '
' + '
' + '
' + '
' + '
' + '
Only what you have yourself can be picked. A deny takes it away even when the GM level allows it (never from GM 9).
'; } function searchTargets(type, text) { var url = type === 'account' ? '/api/tables/accounts' : '/api/tables/characters'; return api.post(url, { search: text, start: 0, length: 10 }).then(function (d) { return (d.data || []).map(function (r) { return { value: String(r.id), label: r.name, detail: type === 'account' ? 'Account ' + r.id + ' · GM ' + (r.gm_level || 0) : 'Character ' + r.id + (r.account_name ? ' · account ' + r.account_name : '') }; }); }); } window.Grants = { mount: function (el, target) { var chooseTarget = !target; el.innerHTML = '
' + table('active', chooseTarget ? 'In force' : '') + '
History (removed and expired ones too)' + table('history', '') + '
'; var formHost = el.querySelector('[data-form]'); var nameSelect = null, targetSelect = null; function load() { var url = chooseTarget ? '/api/grants' : '/api/grants?' + target.type + '=' + encodeURIComponent(target.id); return api.get(url).then(function (d) { if (!d.success) { el.querySelector('[data-rows="active"]').innerHTML = '' + esc(d.error || 'Could not load the grants') + ''; return; } var active = chooseTarget ? d.active : d.grants.filter(function (g) { return g.status === 'active'; }); var history = chooseTarget ? d.history : d.grants; el.querySelector('[data-rows="active"]').innerHTML = rows(active, { showTarget: chooseTarget, empty: 'Nothing granted or denied.' }); el.querySelector('[data-rows="history"]').innerHTML = rows(history, { showTarget: chooseTarget, empty: 'No grants yet.' }); if ((chooseTarget || d.canManage) && !formHost.firstChild) setUpForm(); }); } function setUpForm() { formHost.innerHTML = form(chooseTarget); var f = formHost.querySelector('form'); var kind = f.querySelector('[data-kind]'); nameSelect = SearchSelect(f.querySelector('[data-name]'), { search: function (text) { return loadCatalog().then(function (d) { return choices(d, kind.value, text); }).catch(function (e) { toast(e.message, 'danger'); return []; }); } }); kind.addEventListener('change', function () { nameSelect.set('', ''); }); if (chooseTarget) { var type = f.querySelector('[data-target-type]'); targetSelect = SearchSelect(f.querySelector('[data-target]'), { search: function (text) { return searchTargets(type.value, text); } }); type.addEventListener('change', function () { targetSelect.set('', ''); }); } f.addEventListener('submit', function (e) { e.preventDefault(); var who = chooseTarget ? { type: f.querySelector('[data-target-type]').value, id: targetSelect.input.dataset.value } : target; var name = nameSelect.input.dataset.value; if (!who.id) return toast('Pick an account or character', 'warning'); if (!name) return toast('Pick what to grant', 'warning'); var expires = f.querySelector('[data-expires]').value; var body = { targetType: who.type, target: who.id, kind: kind.value, name: name, deny: f.querySelector('[data-deny]').value === '1', expiresAt: expires ? Math.floor(new Date(expires).getTime() / 1000) : 0, note: f.querySelector('[data-note]').value }; api.action('/api/grants', body).then(function (r) { toast(r.message, 'success'); nameSelect.set('', ''); f.querySelector('[data-note]').value = ''; load(); }).catch(function () {}); }); } el.addEventListener('click', function (e) { var button = e.target.closest('[data-remove]'); if (!button || !confirm('Remove this grant? It stops applying at once.')) return; api.action('/api/grants/' + button.dataset.remove + '/remove', {}).then(function (r) { toast(r.message, 'success'); load(); }).catch(function () {}); }); if (window.Live) Live.on('grants', Live.throttle(load, 500)); load(); } }; })();