import React from "react"; import {useSidebar} from "@/components/ui/sidebar"; import {Separator} from "@/components/ui/separator.tsx"; import {Button} from "@/components/ui/button.tsx"; import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert.tsx"; import {Checkbox} from "@/components/ui/checkbox.tsx"; import {Input} from "@/components/ui/input.tsx"; import {Label} from "@/components/ui/label.tsx"; import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table.tsx"; import {Shield, Trash2, Users} from "lucide-react"; import axios from "axios"; const apiBase = import.meta.env.DEV ? "http://localhost:8081/users" : "/users"; const API = axios.create({ baseURL: apiBase }); function getCookie(name: string) { return document.cookie.split('; ').reduce((r, v) => { const parts = v.split('='); return parts[0] === name ? decodeURIComponent(parts[1]) : r; }, ""); } interface AdminSettingsProps { isTopbarOpen?: boolean; } export function AdminSettings({ isTopbarOpen = true }: AdminSettingsProps): React.ReactElement { const { state: sidebarState } = useSidebar(); // Registration toggle const [allowRegistration, setAllowRegistration] = React.useState(true); const [regLoading, setRegLoading] = React.useState(false); // OIDC config const [oidcConfig, setOidcConfig] = React.useState({ client_id: '', client_secret: '', issuer_url: '', authorization_url: '', token_url: '', identifier_path: 'sub', name_path: 'name', scopes: 'openid email profile' }); const [oidcLoading, setOidcLoading] = React.useState(false); const [oidcError, setOidcError] = React.useState(null); const [oidcSuccess, setOidcSuccess] = React.useState(null); // Users/admins const [users, setUsers] = React.useState>([]); const [usersLoading, setUsersLoading] = React.useState(false); const [newAdminUsername, setNewAdminUsername] = React.useState(""); const [makeAdminLoading, setMakeAdminLoading] = React.useState(false); const [makeAdminError, setMakeAdminError] = React.useState(null); const [makeAdminSuccess, setMakeAdminSuccess] = React.useState(null); React.useEffect(() => { const jwt = getCookie("jwt"); if (!jwt) return; // Preload OIDC config and users API.get("/oidc-config", { headers: { Authorization: `Bearer ${jwt}` } }) .then(res => { if (res.data) setOidcConfig(res.data); }) .catch(() => {}); fetchUsers(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Load initial registration toggle status React.useEffect(() => { API.get("/registration-allowed") .then(res => { if (typeof res?.data?.allowed === 'boolean') { setAllowRegistration(res.data.allowed); } }) .catch(() => {}); }, []); const fetchUsers = async () => { const jwt = getCookie("jwt"); if (!jwt) return; setUsersLoading(true); try { const response = await API.get("/list", { headers: { Authorization: `Bearer ${jwt}` } }); setUsers(response.data.users); } finally { setUsersLoading(false); } }; const handleToggleRegistration = async (checked: boolean) => { setRegLoading(true); const jwt = getCookie("jwt"); try { await API.patch("/registration-allowed", { allowed: checked }, { headers: { Authorization: `Bearer ${jwt}` } }); setAllowRegistration(checked); } finally { setRegLoading(false); } }; const handleOIDCConfigSubmit = async (e: React.FormEvent) => { e.preventDefault(); setOidcLoading(true); setOidcError(null); setOidcSuccess(null); const required = ['client_id','client_secret','issuer_url','authorization_url','token_url']; const missing = required.filter(f => !oidcConfig[f as keyof typeof oidcConfig]); if (missing.length > 0) { setOidcError(`Missing required fields: ${missing.join(', ')}`); setOidcLoading(false); return; } const jwt = getCookie("jwt"); try { await API.post("/oidc-config", oidcConfig, { headers: { Authorization: `Bearer ${jwt}` } }); setOidcSuccess("OIDC configuration updated successfully!"); } catch (err: any) { setOidcError(err?.response?.data?.error || "Failed to update OIDC configuration"); } finally { setOidcLoading(false); } }; const handleOIDCConfigChange = (field: string, value: string) => { setOidcConfig(prev => ({ ...prev, [field]: value })); }; const makeUserAdmin = async (e: React.FormEvent) => { e.preventDefault(); if (!newAdminUsername.trim()) return; setMakeAdminLoading(true); setMakeAdminError(null); setMakeAdminSuccess(null); const jwt = getCookie("jwt"); try { await API.post("/make-admin", { username: newAdminUsername.trim() }, { headers: { Authorization: `Bearer ${jwt}` } }); setMakeAdminSuccess(`User ${newAdminUsername} is now an admin`); setNewAdminUsername(""); fetchUsers(); } catch (err: any) { setMakeAdminError(err?.response?.data?.error || "Failed to make user admin"); } finally { setMakeAdminLoading(false); } }; const removeAdminStatus = async (username: string) => { if (!confirm(`Remove admin status from ${username}?`)) return; const jwt = getCookie("jwt"); try { await API.post("/remove-admin", { username }, { headers: { Authorization: `Bearer ${jwt}` } }); fetchUsers(); } catch {} }; const deleteUser = async (username: string) => { if (!confirm(`Delete user ${username}? This cannot be undone.`)) return; const jwt = getCookie("jwt"); try { await API.delete("/delete-user", { headers: { Authorization: `Bearer ${jwt}` }, data: { username } }); fetchUsers(); } catch {} }; const topMarginPx = isTopbarOpen ? 74 : 26; const leftMarginPx = sidebarState === 'collapsed' ? 26 : 8; const bottomMarginPx = 8; const wrapperStyle: React.CSSProperties = { marginLeft: leftMarginPx, marginRight: 17, marginTop: topMarginPx, marginBottom: bottomMarginPx, height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)` }; return (

Admin Settings

General OIDC Users Admins

User Registration

External Authentication (OIDC)

Configure external identity provider for OIDC/OAuth2 authentication.

{oidcError && ( Error {oidcError} )}
handleOIDCConfigChange('client_id', e.target.value)} placeholder="your-client-id" required />
handleOIDCConfigChange('client_secret', e.target.value)} placeholder="your-client-secret" required />
handleOIDCConfigChange('authorization_url', e.target.value)} placeholder="https://your-provider.com/application/o/authorize/" required />
handleOIDCConfigChange('issuer_url', e.target.value)} placeholder="https://your-provider.com/application/o/termix/" required />
handleOIDCConfigChange('token_url', e.target.value)} placeholder="https://your-provider.com/application/o/token/" required />
handleOIDCConfigChange('identifier_path', e.target.value)} placeholder="sub" required />
handleOIDCConfigChange('name_path', e.target.value)} placeholder="name" required />
handleOIDCConfigChange('scopes', (e.target as HTMLInputElement).value)} placeholder="openid email profile" required />
{oidcSuccess && ( Success {oidcSuccess} )}

User Management

{usersLoading ? (
Loading users...
) : (
Username Type Actions {users.map((user) => ( {user.username} {user.is_admin && ( Admin )} {user.is_oidc ? "External" : "Local"} ))}
)}

Admin Management

Make User Admin

setNewAdminUsername(e.target.value)} placeholder="Enter username to make admin" required />
{makeAdminError && ( Error {makeAdminError} )} {makeAdminSuccess && ( Success {makeAdminSuccess} )}

Current Admins

Username Type Actions {users.filter(u => u.is_admin).map((admin) => ( {admin.username} Admin {admin.is_oidc ? "External" : "Local"} ))}
); } export default AdminSettings;