import React from "react"; import { Checkbox } from "@/components/ui/checkbox.tsx"; import { Input } from "@/components/ui/input.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select.tsx"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { useConfirmation } from "@/hooks/use-confirmation.ts"; import { updateRegistrationAllowed, updatePasswordLoginAllowed, updatePasswordResetAllowed, getGlobalMonitoringSettings, updateGlobalMonitoringSettings, getGuacamoleSettings, updateGuacamoleSettings, getLogLevel, updateLogLevel, getSessionTimeout, updateSessionTimeout, } from "@/ui/main-axios.ts"; import { Button } from "@/components/ui/button.tsx"; interface GeneralSettingsTabProps { allowRegistration: boolean; setAllowRegistration: (value: boolean) => void; allowPasswordLogin: boolean; setAllowPasswordLogin: (value: boolean) => void; allowPasswordReset: boolean; setAllowPasswordReset: (value: boolean) => void; oidcConfig: { client_id: string; client_secret: string; issuer_url: string; authorization_url: string; token_url: string; }; } export function GeneralSettingsTab({ allowRegistration, setAllowRegistration, allowPasswordLogin, setAllowPasswordLogin, allowPasswordReset, setAllowPasswordReset, oidcConfig, }: GeneralSettingsTabProps): React.ReactElement { const { t } = useTranslation(); const { confirmWithToast } = useConfirmation(); const [regLoading, setRegLoading] = React.useState(false); const [passwordLoginLoading, setPasswordLoginLoading] = React.useState(false); const [passwordResetLoading, setPasswordResetLoading] = React.useState(false); const [statusInterval, setStatusInterval] = React.useState(60); const [metricsInterval, setMetricsInterval] = React.useState(30); const [statusInputValue, setStatusInputValue] = React.useState("60"); const [metricsInputValue, setMetricsInputValue] = React.useState("30"); const [statusUnit, setStatusUnit] = React.useState<"seconds" | "minutes">( "seconds", ); const [metricsUnit, setMetricsUnit] = React.useState<"seconds" | "minutes">( "seconds", ); const [monitoringLoading, setMonitoringLoading] = React.useState(false); const [logLevel, setLogLevel] = React.useState("info"); const [logLevelLoading, setLogLevelLoading] = React.useState(false); const [sessionTimeoutHours, setSessionTimeoutHours] = React.useState(24); const [sessionTimeoutInput, setSessionTimeoutInput] = React.useState("24"); const [sessionTimeoutLoading, setSessionTimeoutLoading] = React.useState(false); const [guacEnabled, setGuacEnabled] = React.useState(true); const [guacUrl, setGuacUrl] = React.useState("guacd:4822"); const [guacLoading, setGuacLoading] = React.useState(false); React.useEffect(() => { getLogLevel() .then((data) => { setLogLevel(data.level); }) .catch(() => {}); }, []); React.useEffect(() => { getSessionTimeout() .then((data) => { setSessionTimeoutHours(data.timeoutHours); setSessionTimeoutInput(String(data.timeoutHours)); }) .catch(() => {}); }, []); const handleLogLevelChange = async (value: string) => { setLogLevel(value); setLogLevelLoading(true); try { await updateLogLevel(value); toast.success(t("admin.logLevelSaved")); } catch { toast.error(t("admin.failedToSaveLogLevel")); } finally { setLogLevelLoading(false); } }; const handleSessionTimeoutBlur = async () => { const num = parseInt(sessionTimeoutInput) || 24; const clamped = Math.max(1, Math.min(720, num)); setSessionTimeoutHours(clamped); setSessionTimeoutInput(String(clamped)); setSessionTimeoutLoading(true); try { await updateSessionTimeout(clamped); toast.success(t("admin.sessionTimeoutSaved")); } catch { toast.error(t("admin.failedToSaveSessionTimeout")); } finally { setSessionTimeoutLoading(false); } }; React.useEffect(() => { getGuacamoleSettings() .then((data) => { setGuacEnabled(data.enabled); setGuacUrl(data.url); }) .catch(() => { toast.error(t("admin.failedToLoadGuacamoleSettings")); }); }, [t]); const saveGuacDebounce = React.useRef(null); const saveGuacSettings = React.useCallback( (newEnabled: boolean, newUrl: string) => { if (saveGuacDebounce.current) { clearTimeout(saveGuacDebounce.current); } saveGuacDebounce.current = setTimeout(async () => { setGuacLoading(true); try { await updateGuacamoleSettings({ enabled: newEnabled, url: newUrl }); toast.success(t("admin.guacamoleSettingsSaved")); } catch { toast.error(t("admin.failedToSaveGuacamoleSettings")); } finally { setGuacLoading(false); } }, 800); }, [t], ); React.useEffect(() => { getGlobalMonitoringSettings() .then((data) => { setStatusInterval(data.statusCheckInterval); setMetricsInterval(data.metricsInterval); setStatusInputValue(String(data.statusCheckInterval)); setMetricsInputValue(String(data.metricsInterval)); }) .catch(() => { // Use defaults silently }); }, []); const saveMonitoringSettings = React.useCallback( async (newStatus: number, newMetrics: number) => { setMonitoringLoading(true); try { await updateGlobalMonitoringSettings({ statusCheckInterval: newStatus, metricsInterval: newMetrics, }); toast.success(t("admin.globalSettingsSaved")); } catch (error) { const errorMessage = error instanceof Error ? error.message : t("admin.failedToSaveGlobalSettings"); toast.error(errorMessage); } finally { setMonitoringLoading(false); } }, [t], ); const handleStatusBlur = () => { const num = parseInt(statusInputValue) || 0; const seconds = statusUnit === "minutes" ? num * 60 : num; const clamped = Math.max(5, Math.min(3600, seconds)); setStatusInterval(clamped); setStatusInputValue( statusUnit === "minutes" ? String(Math.round(clamped / 60)) : String(clamped), ); saveMonitoringSettings(clamped, metricsInterval); }; const handleMetricsBlur = () => { const num = parseInt(metricsInputValue) || 0; const seconds = metricsUnit === "minutes" ? num * 60 : num; const clamped = Math.max(5, Math.min(3600, seconds)); setMetricsInterval(clamped); setMetricsInputValue( metricsUnit === "minutes" ? String(Math.round(clamped / 60)) : String(clamped), ); saveMonitoringSettings(statusInterval, clamped); }; const handleToggleRegistration = async (checked: boolean) => { setRegLoading(true); try { await updateRegistrationAllowed(checked); setAllowRegistration(checked); } finally { setRegLoading(false); } }; const handleTogglePasswordLogin = async (checked: boolean) => { if (!checked) { const hasOIDCConfigured = oidcConfig.client_id && oidcConfig.client_secret && oidcConfig.issuer_url && oidcConfig.authorization_url && oidcConfig.token_url; if (!hasOIDCConfigured) { toast.error(t("admin.cannotDisablePasswordLoginWithoutOIDC"), { duration: 5000, }); return; } confirmWithToast( t("admin.confirmDisablePasswordLogin"), async () => { setPasswordLoginLoading(true); try { await updatePasswordLoginAllowed(checked); setAllowPasswordLogin(checked); if (allowRegistration) { await updateRegistrationAllowed(false); setAllowRegistration(false); toast.success(t("admin.passwordLoginAndRegistrationDisabled")); } else { toast.success(t("admin.passwordLoginDisabled")); } } catch { toast.error(t("admin.failedToUpdatePasswordLoginStatus")); } finally { setPasswordLoginLoading(false); } }, "destructive", ); return; } setPasswordLoginLoading(true); try { await updatePasswordLoginAllowed(checked); setAllowPasswordLogin(checked); } finally { setPasswordLoginLoading(false); } }; const handleTogglePasswordReset = async (checked: boolean) => { setPasswordResetLoading(true); try { await updatePasswordResetAllowed(checked); setAllowPasswordReset(checked); } finally { setPasswordResetLoading(false); } }; return (

{t("admin.userRegistration")}

{t("admin.sessionTimeout")}

{t("admin.sessionTimeoutDesc")}

setSessionTimeoutInput(e.target.value)} onBlur={handleSessionTimeoutBlur} disabled={sessionTimeoutLoading} className="flex-1" /> {t("admin.hours")}

{t("admin.sessionTimeoutNote")}

{t("admin.monitoringDefaults")}

{t("admin.monitoringDefaultsDesc")}

setStatusInputValue(e.target.value)} onBlur={handleStatusBlur} disabled={monitoringLoading} className="flex-1" />
setMetricsInputValue(e.target.value)} onBlur={handleMetricsBlur} disabled={monitoringLoading} className="flex-1" />

{t("admin.guacamoleIntegration")}

{t("admin.guacamoleIntegrationDesc")}

{guacEnabled && (
{ setGuacUrl(e.target.value); saveGuacSettings(guacEnabled, e.target.value); }} />

{t("admin.guacdUrlNote")}

)}

{t("admin.logLevel")}

{t("admin.logLevelDesc")}

{t("admin.logLevelNote")}

); }