import { useState } from "react"; import { useLocation, Link } from "wouter"; import { useListUsers, useCreateUser, useUpdateUser, useDeleteUser, useSetUserPassword, useListAuditLogs, useGetVersion, getListUsersQueryKey, getListAuditLogsQueryKey, getGetVersionQueryKey, } from "@workspace/api-client-react"; import { useQueryClient } from "@tanstack/react-query"; import { useAuth } from "@/hooks/use-auth"; import { Layout } from "@/components/layout"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { PasswordInput } from "@/components/password-input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; import { useToast } from "@/hooks/use-toast"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Users, ScrollText, ShieldAlert, Plus, Pencil, Trash2, Clock, AlertTriangle, Wrench, Server } from "lucide-react"; import { format } from "date-fns"; import { AdminToolsTab } from "@/components/admin-tools-tab"; export default function Admin() { const [, setLocation] = useLocation(); const { user, isAdmin, isLoading: authLoading } = useAuth(); const { toast } = useToast(); const queryClient = useQueryClient(); const [createOpen, setCreateOpen] = useState(false); const [editUser, setEditUser] = useState<{ id: number; username: string; role: string; tier: string; authProvider?: string } | null>(null); const [editPassword, setEditPassword] = useState(""); const [deleteConfirm, setDeleteConfirm] = useState<{ id: number; username: string } | null>(null); const [newUsername, setNewUsername] = useState(""); const [newPassword, setNewPassword] = useState(""); const [newEmail, setNewEmail] = useState(""); const [newRole, setNewRole] = useState<"admin" | "user">("user"); const [newTier, setNewTier] = useState<"free" | "premium" | "enterprise">("free"); const { data: users, isLoading: loadingUsers } = useListUsers({ query: { queryKey: getListUsersQueryKey(), enabled: isAdmin }, }); const { data: auditLogs, isLoading: loadingLogs } = useListAuditLogs( { limit: 100 }, { query: { queryKey: getListAuditLogsQueryKey({ limit: 100 }), enabled: isAdmin } }, ); const { data: versionInfo } = useGetVersion({ query: { queryKey: getGetVersionQueryKey(), staleTime: Infinity, retry: false, enabled: isAdmin }, }); const createUser = useCreateUser(); const updateUser = useUpdateUser(); const deleteUser = useDeleteUser(); const setUserPassword = useSetUserPassword(); if (!authLoading && !isAdmin) { return (

Admin Access Required

You need admin rights to view this page.

); } const handleCreateUser = () => { if (!newUsername || !newPassword) return; createUser.mutate( { data: { username: newUsername, password: newPassword, email: newEmail || undefined, role: newRole, tier: newTier } }, { onSuccess: () => { toast({ title: "User created", description: `${newUsername} has been created.` }); queryClient.invalidateQueries({ queryKey: getListUsersQueryKey() }); setCreateOpen(false); setNewUsername(""); setNewPassword(""); setNewEmail(""); setNewRole("user"); }, onError: (err) => { toast({ title: "Failed to create user", description: err.data?.error ?? err.message, variant: "destructive" }); }, }, ); }; const handleUpdateUser = () => { if (!editUser) return; updateUser.mutate( { id: editUser.id, data: { role: editUser.role as "admin" | "user", tier: editUser.tier as "free" | "premium" | "enterprise" } }, { onSuccess: () => { toast({ title: "User updated" }); queryClient.invalidateQueries({ queryKey: getListUsersQueryKey() }); setEditUser(null); }, onError: (err) => { toast({ title: "Failed to update user", description: err.data?.error ?? err.message, variant: "destructive" }); }, }, ); }; const handleSetUserPassword = () => { if (!editUser || !editPassword) return; if (editPassword.length < 6) { toast({ title: "Password too short", description: "Minimum 6 characters.", variant: "destructive" }); return; } setUserPassword.mutate( { id: editUser.id, data: { password: editPassword } }, { onSuccess: () => { toast({ title: "Password updated", description: `Password for ${editUser.username} has been set.` }); setEditPassword(""); }, onError: (err) => { toast({ title: "Failed to set password", description: (err.data as { error?: string } | null)?.error ?? err.message, variant: "destructive" }); }, }, ); }; const handleDeleteUser = () => { if (!deleteConfirm) return; deleteUser.mutate( { id: deleteConfirm.id }, { onSuccess: () => { toast({ title: "User deleted", description: `${deleteConfirm.username} has been removed.` }); queryClient.invalidateQueries({ queryKey: getListUsersQueryKey() }); setDeleteConfirm(null); }, onError: (err) => { toast({ title: "Failed to delete user", description: (err.data as { error?: string } | null)?.error ?? err.message, variant: "destructive" }); }, }, ); }; function actionBadgeVariant(action: string): "default" | "secondary" | "destructive" | "outline" { if (action === "create") return "default"; if (action === "delete") return "destructive"; return "secondary"; } return (

Admin Panel

Manage users and review system changes.

Users Tools Audit Log System
Local Users Manage accounts for local authentication.
{loadingUsers ? (
{[1, 2, 3].map((i) => )}
) : (
{users?.map((u) => (
{u.username.charAt(0).toUpperCase()}

{u.username}

{u.email &&

{u.email}

}
{u.role} {u.tier ?? "free"} {u.username !== user?.preferredUsername && ( <> )}
))} {(!users || users.length === 0) && (

No users yet.

)}
)}
Audit Log All create, update and delete operations tracked by the system. {loadingLogs ? (
{[1, 2, 3, 4, 5].map((i) => )}
) : (
{auditLogs?.map((log) => (
{log.action} {log.entityType} {log.entityId && ( #{log.entityId} )} {format(new Date(log.createdAt), "dd.MM.yyyy HH:mm")}
by {log.username} {log.changes && ( {log.changes.length > 120 ? log.changes.slice(0, 120) + "…" : log.changes} )}
))} {(!auditLogs || auditLogs.length === 0) && (

No audit entries yet.

)}
)}
System Build information of the currently live deployment.
Version {versionInfo?.version || "dev"}
Commit {versionInfo?.commitSha ? ( {versionInfo.commitSha.slice(0, 7)} ) : ( )}
Build date {versionInfo?.buildDate ? format(new Date(versionInfo.buildDate), "dd.MM.yyyy HH:mm") : "—"}
Trash retention {(versionInfo?.trashRetentionDays ?? 0) > 0 ? `${versionInfo?.trashRetentionDays} days` : "Keep forever"}
Create New User
setNewUsername(e.target.value)} placeholder="username" />
setNewPassword(e.target.value)} placeholder="min. 6 characters" />
setNewEmail(e.target.value)} placeholder="user@example.com" />
{ if (!open) { setEditUser(null); setEditPassword(""); } }}> Edit User — {editUser?.username}
{editUser?.authProvider !== "oidc" ? (
setEditPassword(e.target.value)} placeholder="min. 6 characters" data-testid="input-set-password" />

Resets the user's password immediately.

) : (
Password is managed by the identity provider (Keycloak). Reset it there.
)}
{editUser?.authProvider !== "oidc" && ( )}
!open && setDeleteConfirm(null)}> Delete User

Are you sure you want to delete {deleteConfirm?.username}? This cannot be undone.

); }