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:
+33
-35
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user