feat: auth management card in Settings — edit master/viewer, enable/disable
This commit is contained in:
parent
a1f05012b4
commit
4a9266e249
1 changed files with 47 additions and 11 deletions
|
|
@ -11,6 +11,7 @@ let config = null;
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
setupTabs();
|
setupTabs();
|
||||||
await loadStatus();
|
await loadStatus();
|
||||||
|
await loadConfig();
|
||||||
await loadCameras();
|
await loadCameras();
|
||||||
renderLiveGrid();
|
renderLiveGrid();
|
||||||
renderPlaybackCameras();
|
renderPlaybackCameras();
|
||||||
|
|
@ -65,6 +66,14 @@ async function loadStatus() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadConfig() {
|
||||||
|
try {
|
||||||
|
const r = await fetch(API + '/config');
|
||||||
|
const j = await r.json();
|
||||||
|
if (j.success) config = j.data;
|
||||||
|
} catch(e) { config = {}; }
|
||||||
|
}
|
||||||
|
|
||||||
// ── Cameras ──
|
// ── Cameras ──
|
||||||
async function loadCameras() {
|
async function loadCameras() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -259,7 +268,27 @@ async function renderCameraCards() {
|
||||||
</div>`;
|
</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
container.innerHTML = cameras.map(c => `
|
container.innerHTML = `
|
||||||
|
<div class="camera-card" id="auth-card" style="grid-column:1/-1">
|
||||||
|
<h3>🔐 Authentication</h3>
|
||||||
|
<div style="display:flex;gap:16px;flex-wrap:wrap;margin-top:8px">
|
||||||
|
<div style="flex:1;min-width:200px">
|
||||||
|
<h4 style="font-size:13px;color:var(--accent);margin-bottom:6px">Master (full access)</h4>
|
||||||
|
<label>Username <input type="text" value="${escAttr(config?.auth?.master?.username || '')}" data-auth="master-user"></label>
|
||||||
|
<label>Password <input type="password" placeholder="new password" data-auth="master-pass"></label>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;min-width:200px">
|
||||||
|
<h4 style="font-size:13px;color:var(--accent);margin-bottom:6px">Viewer (live wall only, port 8090)</h4>
|
||||||
|
<label>Username <input type="text" value="${escAttr(config?.auth?.viewer?.username || '')}" data-auth="viewer-user"></label>
|
||||||
|
<label>Password <input type="password" placeholder="new password" data-auth="viewer-pass"></label>
|
||||||
|
<div class="row"><label>Enable viewer <input type="checkbox" ${config?.auth?.viewer?.enabled ? 'checked' : ''} data-auth="viewer-enabled" style="width:auto"></label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top:8px">
|
||||||
|
<label>Require login on port 8080 <input type="checkbox" ${config?.auth?.enabled ? 'checked' : ''} data-auth="auth-enabled" style="width:auto"></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` + cameras.map(c => `
|
||||||
<div class="camera-card" data-id="${c.id}">
|
<div class="camera-card" data-id="${c.id}">
|
||||||
<h3>📷 ${c.name || 'Unnamed'} <span style="font-size:11px;color:var(--text-muted)">${c.ip}</span>
|
<h3>📷 ${c.name || 'Unnamed'} <span style="font-size:11px;color:var(--text-muted)">${c.ip}</span>
|
||||||
<a href="http://${c.ip}" target="_blank" style="font-size:11px;color:var(--accent);margin-left:8px;text-decoration:none" title="Open camera web interface">⚙️ Config</a>
|
<a href="http://${c.ip}" target="_blank" style="font-size:11px;color:var(--accent);margin-left:8px;text-decoration:none" title="Open camera web interface">⚙️ Config</a>
|
||||||
|
|
@ -354,9 +383,23 @@ document.getElementById('btn-save').addEventListener('click', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Include auth settings if present (first-run wizard).
|
// Include auth settings from the management card.
|
||||||
const adminUser = document.getElementById('wiz-admin-user')?.value;
|
const authEnabled = document.querySelector('[data-auth="auth-enabled"]')?.checked || false;
|
||||||
const adminPass = document.getElementById('wiz-admin-pass')?.value;
|
const masterUser = document.querySelector('[data-auth="master-user"]')?.value || '';
|
||||||
|
const masterPass = document.querySelector('[data-auth="master-pass"]')?.value || '';
|
||||||
|
const viewerUser = document.querySelector('[data-auth="viewer-user"]')?.value || '';
|
||||||
|
const viewerPass = document.querySelector('[data-auth="viewer-pass"]')?.value || '';
|
||||||
|
const viewerEnabled = document.querySelector('[data-auth="viewer-enabled"]')?.checked || false;
|
||||||
|
|
||||||
|
cfg.auth = cfg.auth || {};
|
||||||
|
cfg.auth.enabled = authEnabled;
|
||||||
|
cfg.auth.master = cfg.auth.master || {};
|
||||||
|
cfg.auth.master.username = masterUser;
|
||||||
|
cfg.auth.viewer = cfg.auth.viewer || {};
|
||||||
|
cfg.auth.viewer.username = viewerUser;
|
||||||
|
cfg.auth.viewer.enabled = viewerEnabled || false;
|
||||||
|
if (masterPass) { cfg.auth.master.password = masterPass; cfg.auth.master.enabled = true; }
|
||||||
|
if (viewerPass) { cfg.auth.viewer.password = viewerPass; }
|
||||||
|
|
||||||
// Fetch current config, then post updated version.
|
// Fetch current config, then post updated version.
|
||||||
const cr = await fetch(API + '/config');
|
const cr = await fetch(API + '/config');
|
||||||
|
|
@ -364,13 +407,6 @@ document.getElementById('btn-save').addEventListener('click', async () => {
|
||||||
const cfg = cj.data || {};
|
const cfg = cj.data || {};
|
||||||
cfg.cameras = updatedCameras;
|
cfg.cameras = updatedCameras;
|
||||||
|
|
||||||
// Set auth if admin credentials were provided.
|
|
||||||
if (adminUser && adminPass) {
|
|
||||||
cfg.auth = cfg.auth || {};
|
|
||||||
cfg.auth.enabled = true;
|
|
||||||
cfg.auth.master = { username: adminUser, password: adminPass, enabled: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const r = await fetch(API + '/config', {
|
const r = await fetch(API + '/config', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue