Move Caddy/services box IP to Settings panel

Services box LAN IP is now a setting (gear icon → Network section),
not a panel in the Services tab. Set it once, saved via API on blur
when authenticated. Loaded on app startup from /api/services/config.

Removed: Services Host panel, caddyIpInput state, saveCaddyIp function
Added: Network section in SettingsPanel with caddy_ip field
- Auto-saves to backend on blur (requires active TOTP session)
- Loaded from /api/services/config on mount
- Services tab checklist reads from status.caddy_ip

https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
This commit is contained in:
Claude
2026-03-28 13:03:09 +00:00
parent 8a9fa02e25
commit 5781fdafd6
+33 -35
View File
@@ -417,7 +417,7 @@ function SessionBtn({ session, onUnlock }) {
//
// SETTINGS PANEL
//
function SettingsPanel({ settings, setSettings, onClose }) {
function SettingsPanel({ settings, setSettings, session, onClose }) {
return (
<div className="settings-overlay" onClick={onClose}>
<div className="settings-panel" onClick={e => e.stopPropagation()}>
@@ -454,6 +454,29 @@ function SettingsPanel({ settings, setSettings, onClose }) {
</div>
</div>
<div className="settings-section">
<h3>Network</h3>
<div className="setting-row">
<div>
<div className="setting-label">Services / Caddy box LAN IP</div>
<div className="setting-sub">The computer running Caddy and services (Plex, etc.) separate from this management computer</div>
</div>
<input value={settings.caddyIp || ""} onChange={e => setSettings(s => ({...s, caddyIp: e.target.value}))}
placeholder="192.168.1.50"
onBlur={() => {
if (settings.caddyIp && session?.token) {
fetch("/api/services/config", {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({ token: session.token, config: { caddy_ip: settings.caddyIp } })
}).catch(() => {});
}
}}
style={{background:"var(--bg)",border:"1px solid var(--b2)",color:"var(--tx)",
padding:"4px 8px",fontFamily:"var(--mono)",fontSize:11,borderRadius:3,width:140}}/>
</div>
</div>
<div className="settings-section">
<h3>About</h3>
<div style={{fontSize:11,color:"var(--dm)",lineHeight:1.7}}>
@@ -1473,7 +1496,7 @@ export default function App() {
const [selected, setSelected] = useState(null);
const [hostname, setHostname] = useState("ERS-5952");
const [switchIP, setSwitchIP] = useState("192.168.99.1");
const [settings, setSettings] = useState({ cliMode: false, defaultPushMode: "batch" });
const [settings, setSettings] = useState({ cliMode: false, defaultPushMode: "batch", caddyIp: "" });
const [showSettings, setShowSettings] = useState(false);
const [connState, setConnState] = useState("connecting");
@@ -1487,6 +1510,13 @@ export default function App() {
const updatePort = useCallback(p => setPorts(prev => prev.map(x => x.id===p.id?p:x)), []);
// Heartbeat
// Load services config (caddy IP) once on mount
useEffect(() => {
API("/services/config").then(cfg => {
if (cfg.caddy_ip) setSettings(s => ({...s, caddyIp: cfg.caddy_ip}));
}).catch(() => {});
}, []);
useEffect(() => {
let firstPoll = true;
const beat = async () => {
@@ -1660,6 +1690,7 @@ export default function App() {
{showSettings && <SettingsPanel
settings={settings} setSettings={setSettings}
session={session}
onClose={() => setShowSettings(false)}
/>}
</div>
@@ -5099,14 +5130,12 @@ function ServicesTab({ vlans, session, onNeedAuth, backendOk }) {
const [deploying, setDeploying] = useState(false);
const [deployResult, setDeployResult] = useState(null);
const [actionLoading, setActionLoading] = useState("");
const [caddyIpInput, setCaddyIpInput] = useState("");
const load = async () => {
try {
const [svc, st] = await Promise.all([API("/services"), API("/services/status")]);
setServices(svc.services || []);
setStatus(st);
if (st.caddy_ip && !caddyIpInput) setCaddyIpInput(st.caddy_ip);
} catch(e) { console.error(e); }
};
useEffect(() => { if (backendOk) load(); }, [backendOk]);
@@ -5127,17 +5156,6 @@ function ServicesTab({ vlans, session, onNeedAuth, backendOk }) {
await load();
};
const saveCaddyIp = async () => {
if (!session) { onNeedAuth(); return; }
if (!caddyIpInput.trim()) return;
try {
await API("/services/config", { method:"POST", body:{
token: session.token, config: { caddy_ip: caddyIpInput.trim() }
}});
await load();
} catch(e) { alert("Failed: " + e.message); }
};
const enableNatReflection = async () => {
if (!session) { onNeedAuth(); return; }
setActionLoading("nat");
@@ -5216,26 +5234,6 @@ function ServicesTab({ vlans, session, onNeedAuth, backendOk }) {
</div>
</div>
{/* Services Host Config */}
<div className="panel">
<div className="ph">Services Host (Caddy Computer)</div>
<div className="pb">
<div style={{fontSize:12,color:"var(--dm)",marginBottom:10,lineHeight:1.6}}>
The computer running Caddy and your services (Plex, etc.) on LAN, separate from the
VLAN 99 management computer.
</div>
<div style={{display:"flex",gap:8,alignItems:"flex-end"}}>
<div className="field" style={{margin:0,flex:"0 0 200px"}}>
<label>Caddy / Services Box LAN IP</label>
<input value={caddyIpInput} onChange={e => setCaddyIpInput(e.target.value)}
placeholder="192.168.1.50"/>
</div>
<button className="btn bp" onClick={saveCaddyIp} disabled={!caddyIpInput.trim()}
style={{padding:"8px 16px"}}>Save</button>
</div>
</div>
</div>
{/* Status Checklist */}
<div className="panel">
<div className="ph">Setup Checklist</div>