// ─── Settings Page ───────────────────────────────────────────────────────────
const { useState: uSTS, useEffect: uSTE } = React;
function SettingsPage() {
const [activeTab, setActiveTab] = uSTS('dashboard');
const [toast, setToast] = uSTS(null);
const [settings, setSettings] = uSTS(null);
const [newPath, setNewPath] = uSTS('');
const [editSvcIdx, setEditSvcIdx] = uSTS(null);
const [newSvc, setNewSvc] = uSTS({ name: '', compose: '', backup: '', update: '' });
const showToast = (msg, color = '#00ff88') => { setToast({ msg, color }); setTimeout(() => setToast(null), 2500); };
uSTE(() => {
apiClient('/api/settings').then(d => { if (d) setSettings(d); }).catch(() => {});
}, []);
const upd = (patch) => setSettings(prev => ({ ...prev, ...patch }));
const save = async () => {
try {
await apiClient('/api/settings', { method: 'PUT', body: JSON.stringify(settings) });
showToast('Settings saved');
} catch (err) { showToast(`Save failed: ${err.message}`, '#ff2244'); }
};
const testNtfy = async () => {
try {
const r = await apiClient('/api/settings/ntfy/test', { method: 'POST' });
showToast(r?.ok ? 'Test notification sent!' : 'NTFY test failed', r?.ok ? '#00ff88' : '#ff2244');
} catch (err) { showToast(`Error: ${err.message}`, '#ff2244'); }
};
const tabs = [
{ id: 'dashboard', label: 'DASHBOARD' },
{ id: 'docker', label: 'DOCKER' },
{ id: 'scripts', label: 'SCRIPTS' },
{ id: 'cron', label: 'CRON' },
{ id: 'ntfy', label: 'NTFY' },
];
if (!settings) return (
LOADING...
);
const storagePaths = settings.storage_paths || [];
const ntfy = { server: settings.ntfy_server, topic: settings.ntfy_topic, events: settings.ntfy_events || {} };
return (
{/* Sidebar tabs */}
CONFIGURATION
{tabs.map(t => (
setActiveTab(t.id)}
style={{ padding: '13px 16px', cursor: 'pointer', fontSize: 14, letterSpacing: '0.1em', fontFamily: "'Share Tech Mono',monospace", color: activeTab === t.id ? '#00c8ff' : 'rgba(180,220,255,0.5)', background: activeTab === t.id ? 'rgba(0,200,255,0.1)' : 'transparent', borderLeft: activeTab === t.id ? '2px solid #00c8ff' : '2px solid transparent', transition: 'all 0.15s' }}>
{t.label}
))}
{/* Content */}
t.id===activeTab)?.label}`}
headerRight={SAVE CHANGES}>
{/* ── Dashboard ── */}
{activeTab === 'dashboard' && (
STORAGE MONITORING PATHS
{storagePaths.map((p, i) => (
{p}
upd({ storage_paths: storagePaths.filter((_,j) => j !== i) })}>✕
))}
setNewPath(e.target.value)} placeholder="/path/to/monitor" style={{ flex: 1 }} />
{ if (newPath) { upd({ storage_paths: [...storagePaths, newPath] }); setNewPath(''); } }}>ADD PATH
DISPLAY
)}
{/* ── Docker ── */}
{activeTab === 'docker' && (
DOCKER ROOT
upd({ docker_root: v })} placeholder="/home/ingo/docker" />
)}
{/* ── Scripts ── */}
{activeTab === 'scripts' && (
PATHS
upd({ scripts_path: v })} placeholder="/opt/scripts" />
upd({ python_path: v })} placeholder="/usr/bin/python3" />
)}
{/* ── Cron ── */}
{activeTab === 'cron' && (
CRON DAEMON
CRON USER: ingo
Managed via crontab -u ingo. Only scripts in /opt/scripts/ can be scheduled.
)}
{/* ── NTFY ── */}
{activeTab === 'ntfy' && (
CONNECTION
upd({ ntfy_server: v })} placeholder="https://ntfy.sh" />
upd({ ntfy_topic: v })} placeholder="scp-alerts" />
⚠ NTFY token is set via SCP_NTFY_TOKEN env var — not editable here for security.
NOTIFY ON
{[['onBackup','Backup completed'], ['onUpdate','Update completed'], ['onCron','Cron job finished'], ['onError','Any error'], ['onStart','Container start/stop']].map(([k, label]) => (
upd({ ntfy_events: { ...(settings.ntfy_events || {}), [k]: v } })} />
))}
SEND TEST NOTIFICATION
)}
{toast && (
{toast.msg}
)}
);
}
function SectionHeader({ children, style }) {
return (
{children}
);
}
Object.assign(window, { SettingsPage });