fix: propagate dynamic IPs/ports to service pages; add apply restart feedback
Service pages (Email, Calendar, Files) now read IPs and ports from the config API instead of hardcoded 172.20.0.x constants: - GET /api/config now includes service_ips (dns, vip_mail, vip_calendar, vip_files, vip_webdav) computed from ip_range via ip_utils - Email.jsx: mailIp, dnsIp, imapPort, smtpPort, webmailPort from context - Calendar.jsx: calendarIp, dnsIp, calendarPort from context - Files.jsx: filesIp, webdavIp, webdavPort, filegatorPort from context Apply button now shows restart progress: - "Restarting containers — please wait…" spinner while polling /health - "Containers restarted successfully" on success (clears after 4s) - "Timed out" / error message if health doesn't come back in 45s Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+53
-2
@@ -164,9 +164,39 @@ function App() {
|
||||
};
|
||||
}, [checkHealth, checkPending]);
|
||||
|
||||
const [applyStatus, setApplyStatus] = useState(null); // null | 'restarting' | 'done' | 'timeout' | 'error'
|
||||
const [applyError, setApplyError] = useState('');
|
||||
|
||||
const handleApply = useCallback(async () => {
|
||||
await cellAPI.applyPending();
|
||||
setApplyError('');
|
||||
try {
|
||||
await cellAPI.applyPending();
|
||||
} catch (err) {
|
||||
setApplyStatus('error');
|
||||
setApplyError(err?.response?.data?.error || 'Apply request failed');
|
||||
setTimeout(() => setApplyStatus(null), 6000);
|
||||
return;
|
||||
}
|
||||
setPending({ needs_restart: false, changes: [] });
|
||||
setApplyStatus('restarting');
|
||||
|
||||
// Poll health until API responds again (max 45 s; it may briefly drop if cell-api restarts)
|
||||
const deadline = Date.now() + 45000;
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
try {
|
||||
await healthAPI.check();
|
||||
setIsOnline(true);
|
||||
setApplyStatus('done');
|
||||
setTimeout(() => setApplyStatus(null), 4000);
|
||||
return;
|
||||
} catch {
|
||||
setIsOnline(false);
|
||||
}
|
||||
}
|
||||
setApplyStatus('timeout');
|
||||
setApplyError('Containers may still be starting — check docker logs if services are unavailable');
|
||||
setTimeout(() => setApplyStatus(null), 8000);
|
||||
}, []);
|
||||
|
||||
const handleCancel = useCallback(async () => {
|
||||
@@ -231,10 +261,31 @@ function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isOnline && pending.needs_restart && (
|
||||
{isOnline && pending.needs_restart && !applyStatus && (
|
||||
<PendingRestartBanner pending={pending} onApply={handleApply} onCancel={handleCancel} />
|
||||
)}
|
||||
|
||||
{applyStatus === 'restarting' && (
|
||||
<div className="mb-6 bg-blue-50 border border-blue-200 rounded-lg p-4 flex items-center gap-3">
|
||||
<RefreshCw className="h-5 w-5 text-blue-500 animate-spin flex-shrink-0" />
|
||||
<span className="text-sm font-medium text-blue-800">Restarting containers — please wait…</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{applyStatus === 'done' && (
|
||||
<div className="mb-6 bg-green-50 border border-green-200 rounded-lg p-4 flex items-center gap-3">
|
||||
<span className="h-5 w-5 text-green-500 flex-shrink-0 text-lg leading-none">✓</span>
|
||||
<span className="text-sm font-medium text-green-800">Containers restarted successfully</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(applyStatus === 'timeout' || applyStatus === 'error') && (
|
||||
<div className="mb-6 bg-danger-50 border border-danger-200 rounded-lg p-4 flex items-center gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-danger-500 flex-shrink-0" />
|
||||
<span className="text-sm font-medium text-danger-800">{applyError}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard isOnline={isOnline} />} />
|
||||
<Route path="/peers" element={<Peers />} />
|
||||
|
||||
Reference in New Issue
Block a user