| 1 | const auth = require('../lib/auth'); |
| 2 | const config = require('../lib/config'); |
| 3 | const render = require('../lib/render'); |
| 4 | const respond = require('../lib/respond'); |
| 5 | |
| 6 | function requireAdmin(req, res) { |
| 7 | const session = auth.requireAuth(req, res); |
| 8 | if (!session) return null; |
| 9 | if (session.username !== config.get('admin.user')) { |
| 10 | respond.forbidden(res); |
| 11 | return null; |
| 12 | } |
| 13 | return session; |
| 14 | } |
| 15 | |
| 16 | function buildSettingsForm() { |
| 17 | const schema = config.schema(); |
| 18 | const sections = {}; |
| 19 | for (const [key, def] of Object.entries(schema)) { |
| 20 | if (def.secret) continue; |
| 21 | const section = key.split('.')[0]; |
| 22 | if (!sections[section]) sections[section] = []; |
| 23 | sections[section].push({ key, def, value: config.get(key) }); |
| 24 | } |
| 25 | |
| 26 | const out = ['<form id="adminSettingsForm">']; |
| 27 | for (const [section, fields] of Object.entries(sections)) { |
| 28 | out.push(`<div class="panel"><div class="head">${render.escape(section)}</div><div class="body admin-section">`); |
| 29 | for (const { key, def, value } of fields) { |
| 30 | const fid = 'f_' + key.replace(/\./g, '_'); |
| 31 | const restartNote = config.requiresRestart(key) |
| 32 | ? ' <em class="restart-note">(requires restart)</em>' : ''; |
| 33 | out.push(`<div class="setting-row">`); |
| 34 | out.push(`<label for="${fid}">${render.escape(key)}${restartNote}</label>`); |
| 35 | if (def.type === 'boolean') { |
| 36 | out.push(`<select id="${fid}" data-key="${key}" data-type="boolean"> |
| 37 | <option value="true"${value === true ? ' selected' : ''}>true</option> |
| 38 | <option value="false"${value === false ? ' selected' : ''}>false</option> |
| 39 | </select>`); |
| 40 | } else if (def.type === 'enum') { |
| 41 | out.push(`<select id="${fid}" data-key="${key}" data-type="enum">`); |
| 42 | for (const opt of def.values) { |
| 43 | out.push(`<option value="${render.escape(opt)}"${value === opt ? ' selected' : ''}>${render.escape(opt)}</option>`); |
| 44 | } |
| 45 | out.push('</select>'); |
| 46 | } else if (def.type === 'number') { |
| 47 | out.push(`<input type="number" id="${fid}" data-key="${key}" data-type="number" value="${value}" step="1">`); |
| 48 | } else { |
| 49 | out.push(`<input type="text" id="${fid}" data-key="${key}" data-type="string" value="${render.escape(String(value || ''))}">`); |
| 50 | } |
| 51 | out.push('</div>'); |
| 52 | } |
| 53 | out.push('</div></div>'); |
| 54 | } |
| 55 | out.push('<div style="padding:10px"><button type="submit">Save settings</button> <span class="form-msg" id="adminMsg"></span></div>'); |
| 56 | out.push('</form>'); |
| 57 | return out.join('\n'); |
| 58 | } |
| 59 | |
| 60 | function settingsPage(req, res) { |
| 61 | const session = requireAdmin(req, res); |
| 62 | if (!session) return; |
| 63 | const content = render.fill(render.loadView('admin-settings.html'), { |
| 64 | SETTINGS_FORM: buildSettingsForm() |
| 65 | }); |
| 66 | res.end(render.page({ title: 'Admin settings', content, session, addressUrl: req.url })); |
| 67 | } |
| 68 | |
| 69 | function saveSettings(req, res) { |
| 70 | const session = requireAdmin(req, res); |
| 71 | if (!session) return; |
| 72 | respond.readJson(req, (err, body) => { |
| 73 | if (err) return respond.badRequest(res, err.message); |
| 74 | try { |
| 75 | config.setMany(body); |
| 76 | respond.json(res, 200, { ok: true }); |
| 77 | } catch (e) { |
| 78 | respond.badRequest(res, e.message); |
| 79 | } |
| 80 | }); |
| 81 | } |
| 82 | |
| 83 | module.exports = function register(router) { |
| 84 | router.get('/admin', (_req, res) => respond.redirect(res, '/admin/settings')); |
| 85 | router.get('/admin/settings', settingsPage); |
| 86 | router.post('/admin/settings/save', saveSettings); |
| 87 | }; |
| 88 | |