Files
reflector-dashboard/auth/admin_config.js
T
bjorneandClaude Fable 5.1 25b481e2ae Public web-only release: SvxPortal front-end + auth backend, install templates and INSTALL.md
Stripped from the internal repo: per-box deploy trees, Go services (nodelist/astmeta/proxy/svxdr),
deploy/sync scripts and all secrets. Config templates for nginx, PHP-FPM, Redis and tmpfiles
live in install/ with CHANGEME placeholders.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-26 23:07:05 +02:00

56 lines
2.9 KiB
JavaScript

'use strict';
var $ = function(id) { return document.getElementById(id); };
function setMsg(el, txt, err) {
if (!el) return;
if (!txt) { el.style.display='none'; return; }
el.style.display='block'; el.textContent=txt; el.style.color=err?'#ff8080':'#00ff80';
}
function esc(s) { var d=document.createElement('div'); d.textContent=s; return d.innerHTML; }
async function rapi(body) {
var r = await fetch('/auth/admin_reflector_api.php', { method:'POST', credentials:'same-origin', headers:{'Content-Type':'application/json','X-CSRF-Token':window._csrfToken||''}, body:JSON.stringify(body) });
return r.json().catch(function(){ return null; });
}
var configOriginal = '';
async function loadConfig() {
var j = await rapi({action:'config_read'});
if (!j || !j.ok) { setMsg($('cfgMsg'), (j&&j.error)||'Failed', true); return; }
configOriginal = j.raw || '';
$('cfgView').textContent = configOriginal;
// The backend decides who may edit: LVL2/LVL3 → true (LVL2 sees secrets masked
// and they're restored on save), LVL1 → false (read-only).
$('btnEdit').style.display = j.can_edit ? '' : 'none';
showView();
}
function showView() { $('viewMode').style.display=''; $('editMode').style.display='none'; }
function showEdit() { $('cfgEditor').value=configOriginal; $('viewMode').style.display='none'; $('editMode').style.display=''; setMsg($('cfgMsg'),null); }
async function saveConfig() {
var raw = $('cfgEditor').value;
if (!raw.trim()) { setMsg($('cfgMsg'),'Config is empty',true); return; }
var j = await rapi({action:'config_save', raw:raw});
if (j && j.ok) { setMsg($('cfgMsg'),'Config saved. Reload service to apply.',false); configOriginal=raw; loadConfig(); }
else setMsg($('cfgMsg'),(j&&j.error)||'Failed',true);
}
document.addEventListener('DOMContentLoaded', function() {
fetch('/auth/me.php',{credentials:'same-origin'}).then(function(r){return r.json();}).then(function(j){if(j&&j.csrf_token)window._csrfToken=j.csrf_token;}).catch(function(){});
loadConfig();
$('btnView').addEventListener('click', showView);
$('btnEdit').addEventListener('click', showEdit);
$('btnSave').addEventListener('click', saveConfig);
$('btnSaveReload').addEventListener('click', async function() {
var raw = $('cfgEditor').value;
if (!raw.trim()) { setMsg($('cfgMsg'),'Config is empty',true); return; }
var j = await rapi({action:'config_save', raw:raw});
if (!j || !j.ok) { setMsg($('cfgMsg'),(j&&j.error)||'Save failed',true); return; }
configOriginal = raw;
var r = await rapi({action:'service_reload'});
if (r && r.ok) { setMsg($('cfgMsg'),'Config saved and service reloaded.',false); loadConfig(); }
else { setMsg($('cfgMsg'),'Saved but reload failed: '+((r&&r.error)||''),true); }
});
$('btnDiscard').addEventListener('click', function() { $('cfgEditor').value=configOriginal; showView(); setMsg($('cfgMsg'),'Discarded',false); });
});