import { useEffect, useState } from "react"; import { get } from "../../api/client"; import type { ModuleInfo, ModulesResponse, AdminSettingsResponse, UpdateSettingsResponse, } from "../../api/types"; import { ModuleCard } from "./ModuleCard"; const infraModules = ["core", "schemas", "database", "storage"]; const featureModules = [ "auth", "projects", "audit", "freecad", "odoo", "jobs", "dag", ]; export function AdminModules() { const [modules, setModules] = useState | null>( null, ); const [settings, setSettings] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [restartRequired, setRestartRequired] = useState(false); useEffect(() => { Promise.all([ get("/api/modules"), get("/api/admin/settings"), ]) .then(([modsResp, settingsResp]) => { setModules(modsResp.modules); setSettings(settingsResp); }) .catch((e) => setError(e instanceof Error ? e.message : "Failed to load settings"), ) .finally(() => setLoading(false)); }, []); const handleSaved = (moduleId: string, result: UpdateSettingsResponse) => { if (result.restart_required) setRestartRequired(true); // Refresh the single module's settings get>(`/api/admin/settings/${moduleId}`) .then((updated) => setSettings((prev) => (prev ? { ...prev, [moduleId]: updated } : prev)), ) .catch(() => {}); }; const handleToggled = (moduleId: string, enabled: boolean) => { setModules((prev) => { if (!prev || !prev[moduleId]) return prev; const updated: Record = { ...prev, [moduleId]: { ...prev[moduleId], enabled }, }; return updated; }); }; if (loading) { return (

Module Configuration

Loading modules...

); } if (error) { return (

Module Configuration

{error}

); } if (!modules || !settings) return null; const renderGroup = (title: string, ids: string[]) => { const available = ids.filter((id) => modules[id]); if (available.length === 0) return null; return (
{title}
{available.map((id) => { const meta = modules[id]; if (!meta) return null; return ( ); })}
); }; return (

Module Configuration

{restartRequired && (
Restart required Some changes require a server restart to take effect.
)} {renderGroup("Infrastructure", infraModules)} {renderGroup("Features", featureModules)}
); } // --- Styles --- const sectionStyle: React.CSSProperties = { marginTop: "0.5rem", }; const sectionTitleStyle: React.CSSProperties = { marginBottom: "1rem", fontSize: "var(--font-title)", }; const groupTitleStyle: React.CSSProperties = { fontSize: "0.7rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--ctp-overlay1)", marginBottom: "0.5rem", }; const restartBannerStyle: React.CSSProperties = { display: "flex", gap: "0.75rem", alignItems: "center", padding: "0.75rem 1rem", marginBottom: "1rem", borderRadius: "0.75rem", background: "rgba(249, 226, 175, 0.1)", border: "1px solid rgba(249, 226, 175, 0.3)", color: "var(--ctp-yellow)", fontSize: "var(--font-body)", }; const dismissBtnStyle: React.CSSProperties = { marginLeft: "auto", padding: "0.25rem 0.5rem", borderRadius: "0.25rem", border: "none", background: "rgba(249, 226, 175, 0.15)", color: "var(--ctp-yellow)", cursor: "pointer", fontSize: "0.7rem", fontWeight: 500, };