diff --git a/api/app.py b/api/app.py index b4d502c..c23eeb7 100644 --- a/api/app.py +++ b/api/app.py @@ -400,6 +400,16 @@ def get_config(): 'ip_range': identity.get('ip_range', os.environ.get('CELL_IP_RANGE', '172.20.0.0/16')), 'wireguard_port': identity.get('wireguard_port', int(os.environ.get('WG_PORT', '51820'))), } + # Expose computed per-service IPs so the frontend doesn't need to derive them + import ip_utils as _ip_utils_cfg + _ips = _ip_utils_cfg.get_service_ips(config['ip_range']) + config['service_ips'] = { + 'dns': _ips['dns'], + 'vip_mail': _ips['vip_mail'], + 'vip_calendar': _ips['vip_calendar'], + 'vip_files': _ips['vip_files'], + 'vip_webdav': _ips['vip_webdav'], + } config['service_configs'] = service_configs return jsonify(config) except Exception as e: diff --git a/webui/src/App.jsx b/webui/src/App.jsx index 1d3299c..b81f7eb 100644 --- a/webui/src/App.jsx +++ b/webui/src/App.jsx @@ -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() { )} - {isOnline && pending.needs_restart && ( + {isOnline && pending.needs_restart && !applyStatus && ( )} + {applyStatus === 'restarting' && ( +
+ + Restarting containers — please wait… +
+ )} + + {applyStatus === 'done' && ( +
+ + Containers restarted successfully +
+ )} + + {(applyStatus === 'timeout' || applyStatus === 'error') && ( +
+ + {applyError} +
+ )} + } /> } /> diff --git a/webui/src/pages/Calendar.jsx b/webui/src/pages/Calendar.jsx index 9b26d12..fd1a63d 100644 --- a/webui/src/pages/Calendar.jsx +++ b/webui/src/pages/Calendar.jsx @@ -3,7 +3,6 @@ import { Calendar as CalendarIcon, Users, Wifi, Copy, CheckCheck } from 'lucide- import { calendarAPI } from '../services/api'; import { useConfig } from '../contexts/ConfigContext'; -const CELL_IP = '172.20.0.21'; function CopyButton({ text }) { const [copied, setCopied] = useState(false); @@ -32,8 +31,11 @@ function InfoRow({ label, value }) { } function Calendar() { - const { domain = 'cell' } = useConfig(); - const cellHost = `calendar.${domain}`; + const { domain = 'cell', service_ips = {}, service_configs = {} } = useConfig(); + const cellHost = `calendar.${domain}`; + const calendarIp = service_ips.vip_calendar || '172.20.0.21'; + const dnsIp = service_ips.dns || '172.20.0.3'; + const calendarPort = service_configs.calendar?.port ?? 5232; const [users, setUsers] = useState([]); const [status, setStatus] = useState(null); const [isLoading, setIsLoading] = useState(true); @@ -87,11 +89,12 @@ function Calendar() { - + +

- Requires VPN connection. DNS server must be set to 172.20.0.3. + Requires VPN connection. DNS server must be set to {dnsIp}.

diff --git a/webui/src/pages/Email.jsx b/webui/src/pages/Email.jsx index 41852d3..97b978b 100644 --- a/webui/src/pages/Email.jsx +++ b/webui/src/pages/Email.jsx @@ -3,7 +3,6 @@ import { Mail, Users, Wifi, Copy, CheckCheck } from 'lucide-react'; import { emailAPI } from '../services/api'; import { useConfig } from '../contexts/ConfigContext'; -const CELL_IP = '172.20.0.23'; function CopyButton({ text }) { const [copied, setCopied] = useState(false); @@ -32,8 +31,14 @@ function InfoRow({ label, value }) { } function Email() { - const { domain = 'cell' } = useConfig(); + const { domain = 'cell', service_ips = {}, service_configs = {} } = useConfig(); const cellHost = `mail.${domain}`; + const emailCfg = service_configs.email || {}; + const mailIp = service_ips.vip_mail || '172.20.0.23'; + const dnsIp = service_ips.dns || '172.20.0.3'; + const imapPort = emailCfg.imap_port ?? 993; + const smtpPort = emailCfg.smtp_port ?? 25; + const webmailPort = emailCfg.webmail_port ?? 8888; const [users, setUsers] = useState([]); const [status, setStatus] = useState(null); const [isLoading, setIsLoading] = useState(true); @@ -81,9 +86,9 @@ function Email() {
- + - +
@@ -95,7 +100,7 @@ function Email() {
- +
@@ -110,10 +115,11 @@ function Email() {
- + +

- Requires VPN + DNS set to 172.20.0.3. + Requires VPN + DNS set to {dnsIp}.

diff --git a/webui/src/pages/Files.jsx b/webui/src/pages/Files.jsx index 4fdd0f3..aaeea44 100644 --- a/webui/src/pages/Files.jsx +++ b/webui/src/pages/Files.jsx @@ -3,8 +3,6 @@ import { FolderOpen, Users, HardDrive, Wifi, Copy, CheckCheck } from 'lucide-rea import { fileAPI } from '../services/api'; import { useConfig } from '../contexts/ConfigContext'; -const FILES_IP = '172.20.0.22'; -const WEBDAV_IP = '172.20.0.24'; function CopyButton({ text }) { const [copied, setCopied] = useState(false); @@ -33,9 +31,14 @@ function InfoRow({ label, value }) { } function Files() { - const { domain = 'cell' } = useConfig(); - const filesHost = `files.${domain}`; - const webdavHost = `webdav.${domain}`; + const { domain = 'cell', service_ips = {}, service_configs = {} } = useConfig(); + const filesHost = `files.${domain}`; + const webdavHost = `webdav.${domain}`; + const filesIp = service_ips.vip_files || '172.20.0.22'; + const webdavIp = service_ips.vip_webdav || '172.20.0.24'; + const filesCfg = service_configs.files || {}; + const webdavPort = filesCfg.port ?? 8080; + const filegatorPort = filesCfg.manager_port ?? 8082; const [users, setUsers] = useState([]); const [status, setStatus] = useState(null); const [isLoading, setIsLoading] = useState(true); @@ -83,8 +86,8 @@ function Files() {
- - + +

Browser-based file manager. Requires VPN. @@ -99,8 +102,8 @@ function Files() {

- - + +