diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx
index 41b8c59..074ea6d 100644
--- a/src/components/Settings.tsx
+++ b/src/components/Settings.tsx
@@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react';
import { authFetch } from '../lib/auth';
import { User } from '../types';
import {
- Shield, Activity, Save, CheckCircle, AlertCircle, Eye, EyeOff, Settings2,
+ Shield, Activity, Save, CheckCircle, AlertCircle, Eye, EyeOff,
+ Settings2, ExternalLink, KeyRound, Globe, Clock, ChevronRight,
} from 'lucide-react';
const SECRET_SENTINEL = '__SET__';
@@ -22,36 +23,67 @@ interface SettingsProps {
currentUser: User;
}
-function FieldLabel({ children }: { children: React.ReactNode }) {
+function Label({ children }: { children: React.ReactNode }) {
+ return ;
+}
+
+function Hint({ children }: { children: React.ReactNode }) {
+ return
{children}
;
+}
+
+function ConfiguredBadge() {
return (
-
+
+
+ CONFIGURED
+
);
}
-function TextInput({
- value, onChange, placeholder, disabled, monospace,
-}: {
+function FieldRow({ label, hint, badge, children }: {
+ label: string;
+ hint?: string;
+ badge?: React.ReactNode;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {badge}
+
+ {children}
+ {hint &&
{hint}}
+
+ );
+}
+
+function Input({ value, onChange, placeholder, monospace, icon }: {
value: string;
onChange: (v: string) => void;
placeholder?: string;
- disabled?: boolean;
monospace?: boolean;
+ icon?: React.ReactNode;
}) {
return (
- onChange(e.target.value)}
- placeholder={placeholder}
- disabled={disabled}
- className={`w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2.5 text-sm text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 transition-all disabled:opacity-50 disabled:cursor-not-allowed ${monospace ? 'font-mono' : ''}`}
- />
+
+ {icon && (
+
+ {icon}
+
+ )}
+ onChange(e.target.value)}
+ placeholder={placeholder}
+ className={`w-full bg-slate-950 border border-slate-800 rounded-lg py-2.5 text-sm text-white placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-cyan-500/40 focus:border-cyan-500/40 transition-all ${icon ? 'pl-9 pr-3' : 'px-3'} ${monospace ? 'font-mono text-xs' : ''}`}
+ />
+
);
}
-function SecretInput({
- value, onChange, alreadySet, show, onToggleShow,
-}: {
+function SecretInput({ value, onChange, alreadySet, show, onToggleShow }: {
value: string;
onChange: (v: string) => void;
alreadySet: boolean;
@@ -60,32 +92,48 @@ function SecretInput({
}) {
return (
+
+
+
onChange(e.target.value)}
- placeholder={alreadySet ? 'Already configured – leave blank to keep' : 'Enter value'}
- className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2.5 pr-10 text-sm text-white placeholder-slate-500 font-mono focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 transition-all"
+ placeholder={alreadySet ? '•••••••• (leave blank to keep)' : 'Paste secret value'}
+ className="w-full bg-slate-950 border border-slate-800 rounded-lg pl-9 pr-10 py-2.5 text-xs font-mono text-white placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-cyan-500/40 focus:border-cyan-500/40 transition-all"
/>
);
}
+function SectionCard({ accentColor, children }: {
+ accentColor: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
+
export default function Settings({ currentUser: _currentUser }: SettingsProps) {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState('');
const [successMsg, setSuccessMsg] = useState('');
- // Entra ID
const [azureEnabled, setAzureEnabled] = useState(false);
const [azureClientId, setAzureClientId] = useState('');
const [azureTenantId, setAzureTenantId] = useState('');
@@ -94,7 +142,6 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
const [azureRedirectUri, setAzureRedirectUri] = useState('');
const [showAzureSecret, setShowAzureSecret] = useState(false);
- // CheckMK
const [checkmkApiUrl, setCheckmkApiUrl] = useState('');
const [checkmkApiSecret, setCheckmkApiSecret] = useState('');
const [checkmkSecretSet, setCheckmkSecretSet] = useState(false);
@@ -108,7 +155,12 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
setError('');
try {
const res = await authFetch('/api/settings');
- if (!res.ok) { setError('Failed to load settings.'); return; }
+ if (!res.ok) {
+ let msg = `Failed to load settings (HTTP ${res.status}).`;
+ try { const d = await res.json(); if (d.error) msg = `${d.error} (HTTP ${res.status})`; } catch {}
+ setError(msg);
+ return;
+ }
const data: RawSettings = await res.json();
setAzureEnabled(data.azure_enabled === 'true');
setAzureClientId(data.azure_client_id || '');
@@ -153,7 +205,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
setCheckmkSecretSet(data.checkmk_api_secret === SECRET_SENTINEL);
setAzureClientSecret('');
setCheckmkApiSecret('');
- setSuccessMsg('Settings saved.');
+ setSuccessMsg('Settings saved successfully.');
setTimeout(() => setSuccessMsg(''), 4000);
} catch {
setError('Network error saving settings.');
@@ -165,72 +217,90 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
if (loading) {
return (
-
+
);
}
return (
-
+
{/* Page header */}
-
-
-
-
-
-
Settings
-
Integrations and service configuration
+
+
+
+ SYSTEM
+
+ SETTINGS
+
Settings
+
Configure integrations and authentication providers.
- {/* Feedback */}
+ {/* Feedback banners */}
{error && (
-
-
- {error}
+
)}
{successMsg && (
-
+
{successMsg}
)}
- {/* Microsoft Entra ID */}
-
+ {/* ── Microsoft Entra ID ── */}
+
+ {/* Card header */}
-
+
-
Microsoft Entra ID
-
OAuth 2.0 login for organizational accounts
+
+
Microsoft Entra ID
+ {azureEnabled && azureSecretSet && (
+
+
+ ACTIVE
+
+ )}
+
+
OAuth 2.0 SSO for organizational accounts
-
+ {/* Toggle */}
+
+
+ {azureEnabled ? 'ENABLED' : 'DISABLED'}
+
+
+
-
-
- Tenant ID
-
-
-
- Client ID (Application ID)
-
-
-
-
+
- {/* CheckMK */}
-
+ {/* ── CheckMK ── */}
+
-
+
-
CheckMK
-
Device status synchronization via the CheckMK REST API
+
+
CheckMK
+ {checkmkApiUrl && checkmkSecretSet && (
+
+
+ ACTIVE
+
+ )}
+
+
Device status sync via CheckMK REST API
-
-
-
API URL
-
+
+
+
+ }
/>
-
-
- Automation Secret
- setShowCheckmkSecret(v => !v)}
- />
-
-
-
Sync Interval (ms)
-
+
+
+ : undefined}
+ >
+ setShowCheckmkSecret(v => !v)}
+ />
+
+
+ }
+ />
+
-
+
- {/* Save */}
-
+ {/* Save bar */}
+
+
Changes are applied after saving and a server restart.