refactor: replace CADDY_MANAGER with DEPLOY_ENV for instance-role awareness
DEPLOY_ENV=production now marks the primary instance globally - used for Caddy ownership, the Dev/Prod header badge, and Caddy UI gating. Removes build-time VITE_DEPLOY_ENV/import.meta.env.DEV from the header in favour of the runtime API response (isProduction field in /api/auth/config).
This commit is contained in:
@ -55,6 +55,7 @@ export default function App() {
|
||||
const [inventoryHighlightDevice, setInventoryHighlightDevice] = useState<Device | null>(null);
|
||||
const [checkmkEnabled, setCheckmkEnabled] = useState(false);
|
||||
const [checkmkBaseUrl, setCheckmkBaseUrl] = useState('');
|
||||
const [isProduction, setIsProduction] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
@ -143,7 +144,7 @@ export default function App() {
|
||||
if (bookingsRes.ok) setBookings(await bookingsRes.json());
|
||||
if (logsRes.ok) setLogs(await logsRes.json());
|
||||
if (linksRes.ok) setLinks(await linksRes.json());
|
||||
if (configRes.ok) { const cfg = await configRes.json(); setCheckmkEnabled(!!cfg.checkmkEnabled); setCheckmkBaseUrl(cfg.checkmkBaseUrl || ''); }
|
||||
if (configRes.ok) { const cfg = await configRes.json(); setCheckmkEnabled(!!cfg.checkmkEnabled); setCheckmkBaseUrl(cfg.checkmkBaseUrl || ''); setIsProduction(!!cfg.isProduction); }
|
||||
} catch (err) {
|
||||
console.error('[App] Failed to load data:', err);
|
||||
} finally {
|
||||
@ -484,6 +485,7 @@ export default function App() {
|
||||
theme={theme}
|
||||
onThemeToggle={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}
|
||||
onLogout={handleLogout}
|
||||
isProduction={isProduction}
|
||||
/>
|
||||
|
||||
<div className="flex-1 flex flex-col md:flex-row">
|
||||
|
||||
@ -11,6 +11,7 @@ interface HeaderProps {
|
||||
theme: 'dark' | 'light';
|
||||
onThemeToggle: () => void;
|
||||
onLogout: () => void;
|
||||
isProduction: boolean;
|
||||
}
|
||||
|
||||
export default function Header({
|
||||
@ -22,6 +23,7 @@ export default function Header({
|
||||
theme,
|
||||
onThemeToggle,
|
||||
onLogout,
|
||||
isProduction,
|
||||
}: HeaderProps) {
|
||||
const [showMailInbox, setShowMailInbox] = useState(false);
|
||||
const [showBellDropdown, setShowBellDropdown] = useState(false);
|
||||
@ -63,8 +65,8 @@ export default function Header({
|
||||
|
||||
{/* System Indicator */}
|
||||
<div className="hidden md:flex items-center gap-2 px-3 py-1 bg-slate-800/60 rounded-full border border-slate-700/50 text-xs font-mono text-slate-300">
|
||||
<span className={`w-2 h-2 rounded-full animate-pulse ${(import.meta.env.VITE_DEPLOY_ENV === 'dev' || import.meta.env.DEV) ? 'bg-amber-400' : 'bg-emerald-500'}`} />
|
||||
<span>System: {(import.meta.env.VITE_DEPLOY_ENV === 'dev' || import.meta.env.DEV) ? 'Development' : 'Production'}</span>
|
||||
<span className={`w-2 h-2 rounded-full animate-pulse ${isProduction ? 'bg-emerald-500' : 'bg-amber-400'}`} />
|
||||
<span>System: {isProduction ? 'Production' : 'Development'}</span>
|
||||
</div>
|
||||
|
||||
{/* Mail Inbox */}
|
||||
|
||||
@ -193,7 +193,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
|
||||
const [caddyEnabled, setCaddyEnabled] = useState(false);
|
||||
const [caddyManaged, setCaddyManaged] = useState(true);
|
||||
const [caddyAdminUrl, setCaddyAdminUrl] = useState('http://localhost:2019');
|
||||
const [caddyAdminUrl, setCaddyAdminUrl] = useState('http://127.0.0.1:2019');
|
||||
const [caddyStatus, setCaddyStatus] = useState<'unknown' | 'available' | 'unavailable'>('unknown');
|
||||
const [caddyRoutes, setCaddyRoutes] = useState<CaddyRoute[]>([]);
|
||||
const [addingRoute, setAddingRoute] = useState(false);
|
||||
@ -225,7 +225,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (d.effectiveRedirectUri) setEffectiveRedirectUri(d.effectiveRedirectUri);
|
||||
setCaddyManaged(d.caddyManaged !== false);
|
||||
setCaddyManaged(d.isProduction !== false);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
@ -265,7 +265,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
setSemaphoreApiToken('');
|
||||
setSemaphoreProjectId(data.semaphore_project_id || '');
|
||||
setCaddyEnabled(data.caddy_enabled === 'true');
|
||||
setCaddyAdminUrl(data.caddy_admin_url || 'http://localhost:2019');
|
||||
setCaddyAdminUrl(data.caddy_admin_url || 'http://127.0.0.1:2019');
|
||||
} catch {
|
||||
setError('Network error loading settings.');
|
||||
} finally {
|
||||
@ -350,7 +350,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
return;
|
||||
}
|
||||
const templates = await res.json() as any[];
|
||||
setSemaphoreTestResult(`Connected — ${templates.length} task template${templates.length !== 1 ? 's' : ''} found.`);
|
||||
setSemaphoreTestResult(`Connected - ${templates.length} task template${templates.length !== 1 ? 's' : ''} found.`);
|
||||
} catch {
|
||||
setSemaphoreTestResult('Error: Network error connecting to Semaphore.');
|
||||
} finally {
|
||||
@ -371,7 +371,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
const res = await authFetch('/api/caddy/routes');
|
||||
if (res.ok) setCaddyRoutes(await res.json());
|
||||
} catch {}
|
||||
// Status check runs separately — purely informational, never blocks the list
|
||||
// Status check runs separately - purely informational, never blocks the list
|
||||
authFetch('/api/caddy/status')
|
||||
.then(res => res.ok ? res.json() : null)
|
||||
.then(s => setCaddyStatus(s?.available ? 'available' : 'unavailable'))
|
||||
@ -570,7 +570,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Section tabs — switch between Integrations and System to keep the page light */}
|
||||
{/* Section tabs - switch between Integrations and System to keep the page light */}
|
||||
<div className="inline-flex items-center gap-0.5 p-0.5 bg-slate-900/50 border border-slate-800 rounded-lg w-fit">
|
||||
{([
|
||||
{ id: 'integrations', label: 'Integrations', icon: <Plug className="w-3.5 h-3.5" /> },
|
||||
@ -748,7 +748,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
/>
|
||||
</FieldRow>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||
<FieldRow label="Automation User" hint="Setup > Users > Automation user (e.g. automation)">
|
||||
<FieldRow label="Automation User" hint="Setup > Users > Automation user">
|
||||
<Input
|
||||
value={checkmkApiUser}
|
||||
onChange={setCheckmkApiUser}
|
||||
@ -944,19 +944,19 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
|
||||
{caddyManaged && (
|
||||
<div className={`space-y-5 transition-opacity duration-200 ${!caddyEnabled ? 'opacity-40 pointer-events-none' : ''}`}>
|
||||
<FieldRow label="Caddy Admin URL" hint="Default: http://localhost:2019">
|
||||
<Input value={caddyAdminUrl} onChange={setCaddyAdminUrl} placeholder="http://localhost:2019" monospace icon={<Globe className="w-3.5 h-3.5" />} />
|
||||
<FieldRow label="Caddy Admin URL" hint="Default: http://127.0.0.1:2019">
|
||||
<Input value={caddyAdminUrl} onChange={setCaddyAdminUrl} placeholder="http://172.0.0.1:2019" monospace icon={<Globe className="w-3.5 h-3.5" />} />
|
||||
</FieldRow>
|
||||
|
||||
{/* Route list */}
|
||||
{caddyEnabled && (
|
||||
<div className="space-y-2">
|
||||
<Label>Proxy Routes</Label>
|
||||
<Hint>Prefix the upstream with https:// for TLS backends (e.g. Semaphore) — the certificate is not verified.</Hint>
|
||||
<Hint>Prefix the upstream with https:// for TLS backends - the certificate is not verified.</Hint>
|
||||
|
||||
{caddyStatus === 'unavailable' && (
|
||||
<p className="text-[11px] font-mono text-amber-400 mb-2">
|
||||
Caddy Admin API not reachable — routes will be applied when Caddy starts.
|
||||
Caddy Admin API not reachable - routes will be applied when Caddy starts.
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -1003,7 +1003,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<Input value={editRedirect} onChange={setEditRedirect} placeholder="Root redirect (optional), e.g. /monitoring/check_mk/" monospace />
|
||||
<Input value={editRedirect} onChange={setEditRedirect} placeholder="root redirect" monospace />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between">
|
||||
@ -1071,7 +1071,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
{addingRoute ? 'Adding…' : 'Add'}
|
||||
</button>
|
||||
</div>
|
||||
<Input value={newRedirect} onChange={setNewRedirect} placeholder="Root redirect (optional), e.g. /monitoring/check_mk/" monospace />
|
||||
<Input value={newRedirect} onChange={setNewRedirect} placeholder="Root redirect (optional), e.g. /site1/" monospace />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -1095,7 +1095,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-xl font-bold text-white font-mono leading-none">
|
||||
{dbInfo ? formatBytes(dbInfo.sizeBytes) : '—'}
|
||||
{dbInfo ? formatBytes(dbInfo.sizeBytes) : '-'}
|
||||
</p>
|
||||
<p className="text-[10px] text-slate-500 font-mono mt-1">
|
||||
{dbInfo ? new Date(dbInfo.lastModified).toLocaleDateString() : 'Loading…'}
|
||||
@ -1172,7 +1172,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
<div className="flex items-start gap-2 bg-amber-950/40 border border-amber-900/50 rounded-xl px-3 py-2">
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-amber-400 shrink-0 mt-0.5" />
|
||||
<p className="text-[11px] text-amber-300 leading-relaxed">
|
||||
<strong>Import overwrites the entire database</strong> — this cannot be undone.
|
||||
<strong>Import overwrites the entire database</strong> - this cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
<label className="w-full flex items-center gap-2 cursor-pointer bg-slate-900 border border-slate-700 hover:border-slate-600 rounded-lg px-3 py-2 text-xs text-slate-400 hover:text-slate-200 transition-all">
|
||||
|
||||
Reference in New Issue
Block a user