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:
Brückner
2026-06-10 14:43:31 +02:00
parent 49cd0ae4f6
commit 515052fbda
5 changed files with 34 additions and 30 deletions

View File

@ -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 */}

View File

@ -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">