Undo admin settings changes

This commit is contained in:
LukeGus
2025-09-08 00:34:34 -05:00
parent b0c76b2eef
commit b183f30df9

View File

@@ -8,474 +8,443 @@ import {Input} from "@/components/ui/input.tsx";
import {Label} from "@/components/ui/label.tsx"; import {Label} from "@/components/ui/label.tsx";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx"; import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx";
import { import {
    Table, Table,
    TableBody, TableBody,
    TableCell, TableCell,
    TableHead, TableHead,
    TableHeader, TableHeader,
    TableRow, TableRow,
} from "@/components/ui/table.tsx"; } from "@/components/ui/table.tsx";
// 🎯 Import the Eye and EyeOff icons from lucide-react import {Shield, Trash2, Users} from "lucide-react";
import {Shield, Trash2, Users, Eye, EyeOff} from "lucide-react";
import {toast} from "sonner"; import {toast} from "sonner";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import { import {
    getOIDCConfig, getOIDCConfig,
    getRegistrationAllowed, getRegistrationAllowed,
    getUserList, getUserList,
    updateRegistrationAllowed, updateRegistrationAllowed,
    updateOIDCConfig, updateOIDCConfig,
    makeUserAdmin, makeUserAdmin,
    removeAdminStatus, removeAdminStatus,
    deleteUser deleteUser
} from "@/ui/main-axios.ts"; } from "@/ui/main-axios.ts";
function getCookie(name: string) { function getCookie(name: string) {
    return document.cookie.split('; ').reduce((r, v) => { return document.cookie.split('; ').reduce((r, v) => {
        const parts = v = v.split('='); const parts = v.split('=');
        return parts[0] === name ? decodeURIComponent(parts[1]) : r; return parts[0] === name ? decodeURIComponent(parts[1]) : r;
    }, ""); }, "");
} }
interface AdminSettingsProps { interface AdminSettingsProps {
    isTopbarOpen?: boolean; isTopbarOpen?: boolean;
} }
export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.ReactElement { export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.ReactElement {
    const {t} = useTranslation(); const {t} = useTranslation();
    const {state: sidebarState} = useSidebar(); const {state: sidebarState} = useSidebar();
    const [allowRegistration, setAllowRegistration] = React.useState(true); const [allowRegistration, setAllowRegistration] = React.useState(true);
    const [regLoading, setRegLoading] = React.useState(false); const [regLoading, setRegLoading] = React.useState(false);
// 🎯 New state to manage password visibility const [oidcConfig, setOidcConfig] = React.useState({
const [showClientSecret, setShowClientSecret] = React.useState(false); client_id: '',
client_secret: '',
issuer_url: '',
authorization_url: '',
token_url: '',
identifier_path: 'sub',
name_path: 'name',
scopes: 'openid email profile',
userinfo_url: ''
});
const [oidcLoading, setOidcLoading] = React.useState(false);
const [oidcError, setOidcError] = React.useState<string | null>(null);
    const [oidcConfig, setOidcConfig] = React.useState({ const [users, setUsers] = React.useState<Array<{
        client_id: '', id: string;
        client_secret: '', username: string;
        issuer_url: '', is_admin: boolean;
        authorization_url: '', is_oidc: boolean
        token_url: '', }>>([]);
        identifier_path: 'sub', const [usersLoading, setUsersLoading] = React.useState(false);
        name_path: 'name', const [newAdminUsername, setNewAdminUsername] = React.useState("");
        scopes: 'openid email profile', const [makeAdminLoading, setMakeAdminLoading] = React.useState(false);
        userinfo_url: '' const [makeAdminError, setMakeAdminError] = React.useState<string | null>(null);
    });
    const [oidcLoading, setOidcLoading] = React.useState(false);
    const [oidcError, setOidcError] = React.useState<string | null>(null);
    const [users, setUsers] = React.useState<Array<{ React.useEffect(() => {
        id: string; const jwt = getCookie("jwt");
        username: string; if (!jwt) return;
        is_admin: boolean; getOIDCConfig()
        is_oidc: boolean .then(res => {
    }>>([]); if (res) setOidcConfig(res);
    const [usersLoading, setUsersLoading] = React.useState(false); })
    const [newAdminUsername, setNewAdminUsername] = React.useState(""); .catch(() => {
    const [makeAdminLoading, setMakeAdminLoading] = React.useState(false); });
    const [makeAdminError, setMakeAdminError] = React.useState<string | null>(null); fetchUsers();
}, []);
    React.useEffect(() => { React.useEffect(() => {
        const jwt = getCookie("jwt"); getRegistrationAllowed()
        if (!jwt) return; .then(res => {
        getOIDCConfig() if (typeof res?.allowed === 'boolean') {
            .then(res => { setAllowRegistration(res.allowed);
                if (res) setOidcConfig(res); }
            }) })
            .catch(() => { .catch(() => {
            }); });
        fetchUsers(); }, []);
    }, []);
    React.useEffect(() => { const fetchUsers = async () => {
        getRegistrationAllowed() const jwt = getCookie("jwt");
            .then(res => { if (!jwt) return;
                if (typeof res?.allowed === 'boolean') { setUsersLoading(true);
                    setAllowRegistration(res.allowed); try {
                } const response = await getUserList();
            }) setUsers(response.users);
            .catch(() => { } finally {
            }); setUsersLoading(false);
    }, []); }
};
    const fetchUsers = async () => { const handleToggleRegistration = async (checked: boolean) => {
        const jwt = getCookie("jwt"); setRegLoading(true);
        if (!jwt) return; const jwt = getCookie("jwt");
        setUsersLoading(true); try {
        try { await updateRegistrationAllowed(checked);
            const response = await getUserList(); setAllowRegistration(checked);
            setUsers(response.users); } finally {
        } finally { setRegLoading(false);
            setUsersLoading(false); }
        } };
    };
    const handleToggleRegistration = async (checked: boolean) => { const handleOIDCConfigSubmit = async (e: React.FormEvent) => {
        setRegLoading(true); e.preventDefault();
        const jwt = getCookie("jwt"); setOidcLoading(true);
        try { setOidcError(null);
            await updateRegistrationAllowed(checked);
            setAllowRegistration(checked);
        } finally {
            setRegLoading(false);
        }
    };
    const handleOIDCConfigSubmit = async (e: React.FormEvent) => { const required = ['client_id', 'client_secret', 'issuer_url', 'authorization_url', 'token_url'];
        e.preventDefault(); const missing = required.filter(f => !oidcConfig[f as keyof typeof oidcConfig]);
        setOidcLoading(true); if (missing.length > 0) {
        setOidcError(null); setOidcError(t('admin.missingRequiredFields', { fields: missing.join(', ') }));
setOidcLoading(false);
return;
}
        const required = ['client_id', 'client_secret', 'issuer_url', 'authorization_url', 'token_url']; const jwt = getCookie("jwt");
        const missing = required.filter(f => !oidcConfig[f as keyof typeof oidcConfig]); try {
        if (missing.length > 0) { await updateOIDCConfig(oidcConfig);
            setOidcError(t('admin.missingRequiredFields', { fields: missing.join(', ') })); toast.success(t('admin.oidcConfigurationUpdated'));
            setOidcLoading(false); } catch (err: any) {
            return; setOidcError(err?.response?.data?.error || t('admin.failedToUpdateOidcConfig'));
        } } finally {
setOidcLoading(false);
}
};
        const jwt = getCookie("jwt"); const handleOIDCConfigChange = (field: string, value: string) => {
        try { setOidcConfig(prev => ({...prev, [field]: value}));
            await updateOIDCConfig(oidcConfig); };
            toast.success(t('admin.oidcConfigurationUpdated'));
        } catch (err: any) {
            setOidcError(err?.response?.data?.error || t('admin.failedToUpdateOidcConfig'));
        } finally {
            setOidcLoading(false);
        }
    };
    const handleOIDCConfigChange = (field: string, value: string) => { const handleMakeUserAdmin = async (e: React.FormEvent) => {
        setOidcConfig(prev => ({...prev, [field]: value})); e.preventDefault();
    }; if (!newAdminUsername.trim()) return;
setMakeAdminLoading(true);
setMakeAdminError(null);
const jwt = getCookie("jwt");
try {
await makeUserAdmin(newAdminUsername.trim());
toast.success(t('admin.userIsNowAdmin', { username: newAdminUsername }));
setNewAdminUsername("");
fetchUsers();
} catch (err: any) {
setMakeAdminError(err?.response?.data?.error || t('admin.failedToMakeUserAdmin'));
} finally {
setMakeAdminLoading(false);
}
};
    const handleMakeUserAdmin = async (e: React.FormEvent) => { const handleRemoveAdminStatus = async (username: string) => {
        e.preventDefault(); if (!confirm(t('admin.removeAdminStatus', { username }))) return;
        if (!newAdminUsername.trim()) return; const jwt = getCookie("jwt");
        setMakeAdminLoading(true); try {
        setMakeAdminError(null); await removeAdminStatus(username);
        const jwt = getCookie("jwt"); toast.success(t('admin.adminStatusRemoved', { username }));
        try { fetchUsers();
            await makeUserAdmin(newAdminUsername.trim()); } catch (err: any) {
            toast.success(t('admin.userIsNowAdmin', { username: newAdminUsername })); console.error('Failed to remove admin status:', err);
            setNewAdminUsername(""); toast.error(t('admin.failedToRemoveAdminStatus'));
            fetchUsers(); }
        } catch (err: any) { };
            setMakeAdminError(err?.response?.data?.error || t('admin.failedToMakeUserAdmin'));
        } finally {
            setMakeAdminLoading(false);
        }
    };
    const handleRemoveAdminStatus = async (username: string) => { const handleDeleteUser = async (username: string) => {
        if (!confirm(t('admin.removeAdminStatus', { username }))) return; if (!confirm(t('admin.deleteUser', { username }))) return;
        const jwt = getCookie("jwt"); const jwt = getCookie("jwt");
        try { try {
            await removeAdminStatus(username); await deleteUser(username);
            toast.success(t('admin.adminStatusRemoved', { username })); toast.success(t('admin.userDeletedSuccessfully', { username }));
            fetchUsers(); fetchUsers();
        } catch (err: any) { } catch (err: any) {
            console.error('Failed to remove admin status:', err); console.error('Failed to delete user:', err);
            toast.error(t('admin.failedToRemoveAdminStatus')); toast.error(t('admin.failedToDeleteUser'));
        } }
    }; };
    const handleDeleteUser = async (username: string) => { const topMarginPx = isTopbarOpen ? 74 : 26;
        if (!confirm(t('admin.deleteUser', { username }))) return; const leftMarginPx = sidebarState === 'collapsed' ? 26 : 8;
        const jwt = getCookie("jwt"); const bottomMarginPx = 8;
        try { const wrapperStyle: React.CSSProperties = {
            await deleteUser(username); marginLeft: leftMarginPx,
            toast.success(t('admin.userDeletedSuccessfully', { username })); marginRight: 17,
            fetchUsers(); marginTop: topMarginPx,
        } catch (err: any) { marginBottom: bottomMarginPx,
            console.error('Failed to delete user:', err); height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`
            toast.error(t('admin.failedToDeleteUser')); };
        }
    };
    const topMarginPx = isTopbarOpen ? 74 : 26; return (
    const leftMarginPx = sidebarState === 'collapsed' ? 26 : 8; <div style={wrapperStyle}
    const bottomMarginPx = 8; className="bg-[#18181b] text-white rounded-lg border-2 border-[#303032] overflow-hidden">
    const wrapperStyle: React.CSSProperties = { <div className="h-full w-full flex flex-col">
        marginLeft: leftMarginPx, <div className="flex items-center justify-between px-3 pt-2 pb-2">
        marginRight: 17, <h1 className="font-bold text-lg">{t('admin.title')}</h1>
        marginTop: topMarginPx, </div>
        marginBottom: bottomMarginPx, <Separator className="p-0.25 w-full"/>
        height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`
    };
    return ( <div className="px-6 py-4 overflow-auto">
        <div style={wrapperStyle} <Tabs defaultValue="registration" className="w-full">
             className="bg-[#18181b] text-white rounded-lg border-2 border-[#303032] overflow-hidden"> <TabsList className="mb-4 bg-[#18181b] border-2 border-[#303032]">
            <div className="h-full w-full flex flex-col"> <TabsTrigger value="registration" className="flex items-center gap-2">
                <div className="flex items-center justify-between px-3 pt-2 pb-2"> <Users className="h-4 w-4"/>
                    <h1 className="font-bold text-lg">{t('admin.title')}</h1> {t('admin.general')}
                </div> </TabsTrigger>
                <Separator className="p-0.25 w-full"/> <TabsTrigger value="oidc" className="flex items-center gap-2">
<Shield className="h-4 w-4"/>
OIDC
</TabsTrigger>
<TabsTrigger value="users" className="flex items-center gap-2">
<Users className="h-4 w-4"/>
{t('admin.users')}
</TabsTrigger>
<TabsTrigger value="admins" className="flex items-center gap-2">
<Shield className="h-4 w-4"/>
{t('admin.adminManagement')}
</TabsTrigger>
</TabsList>
                <div className="px-6 py-4 overflow-auto"> <TabsContent value="registration" className="space-y-6">
                    <Tabs defaultValue="registration" className="w-full"> <div className="space-y-4">
                        <TabsList className="mb-4 bg-[#18181b] border-2 border-[#303032]"> <h3 className="text-lg font-semibold">{t('admin.userRegistration')}</h3>
                            <TabsTrigger value="registration" className="flex items-center gap-2"> <label className="flex items-center gap-2">
                                <Users className="h-4 w-4"/> <Checkbox checked={allowRegistration} onCheckedChange={handleToggleRegistration}
                                {t('admin.general')} disabled={regLoading}/>
                            </TabsTrigger> {t('admin.allowNewAccountRegistration')}
                            <TabsTrigger value="oidc" className="flex items-center gap-2"> </label>
                                <Shield className="h-4 w-4"/> </div>
                                OIDC </TabsContent>
                            </TabsTrigger>
                            <TabsTrigger value="users" className="flex items-center gap-2">
                                <Users className="h-4 w-4"/>
                                {t('admin.users')}
                            </TabsTrigger>
                            <TabsTrigger value="admins" className="flex items-center gap-2">
                                <Shield className="h-4 w-4"/>
                                {t('admin.adminManagement')}
                            </TabsTrigger>
                        </TabsList>
                        <TabsContent value="registration" className="space-y-6"> <TabsContent value="oidc" className="space-y-6">
                            <div className="space-y-4"> <div className="space-y-4">
                                <h3 className="text-lg font-semibold">{t('admin.userRegistration')}</h3> <h3 className="text-lg font-semibold">{t('admin.externalAuthentication')}</h3>
                                <label className="flex items-center gap-2"> <p className="text-sm text-muted-foreground">{t('admin.configureExternalProvider')}</p>
                                    <Checkbox checked={allowRegistration} onCheckedChange={handleToggleRegistration}
                                              disabled={regLoading}/>
                                    {t('admin.allowNewAccountRegistration')}
                                </label>
                            </div>
                        </TabsContent>
                        <TabsContent value="oidc" className="space-y-6"> {oidcError && (
                            <div className="space-y-4"> <Alert variant="destructive">
                                <h3 className="text-lg font-semibold">{t('admin.externalAuthentication')}</h3> <AlertTitle>{t('common.error')}</AlertTitle>
                                <p className="text-sm text-muted-foreground">{t('admin.configureExternalProvider')}</p> <AlertDescription>{oidcError}</AlertDescription>
</Alert>
)}
                                {oidcError && ( <form onSubmit={handleOIDCConfigSubmit} className="space-y-4">
                                    <Alert variant="destructive"> <div className="space-y-2">
                                        <AlertTitle>{t('common.error')}</AlertTitle> <Label htmlFor="client_id">{t('admin.clientId')}</Label>
                                        <AlertDescription>{oidcError}</AlertDescription> <Input id="client_id" value={oidcConfig.client_id}
                                    </Alert> onChange={(e) => handleOIDCConfigChange('client_id', e.target.value)}
                                )} placeholder={t('placeholders.clientId')} required/>
</div>
<div className="space-y-2">
<Label htmlFor="client_secret">{t('admin.clientSecret')}</Label>
<Input id="client_secret" type="password" value={oidcConfig.client_secret}
onChange={(e) => handleOIDCConfigChange('client_secret', e.target.value)}
placeholder={t('placeholders.clientSecret')} required/>
</div>
<div className="space-y-2">
<Label htmlFor="authorization_url">{t('admin.authorizationUrl')}</Label>
<Input id="authorization_url" value={oidcConfig.authorization_url}
onChange={(e) => handleOIDCConfigChange('authorization_url', e.target.value)}
placeholder={t('placeholders.authUrl')}
required/>
</div>
<div className="space-y-2">
<Label htmlFor="issuer_url">{t('admin.issuerUrl')}</Label>
<Input id="issuer_url" value={oidcConfig.issuer_url}
onChange={(e) => handleOIDCConfigChange('issuer_url', e.target.value)}
placeholder={t('placeholders.redirectUrl')} required/>
</div>
<div className="space-y-2">
<Label htmlFor="token_url">{t('admin.tokenUrl')}</Label>
<Input id="token_url" value={oidcConfig.token_url}
onChange={(e) => handleOIDCConfigChange('token_url', e.target.value)}
placeholder={t('placeholders.tokenUrl')} required/>
</div>
<div className="space-y-2">
<Label htmlFor="identifier_path">{t('admin.userIdentifierPath')}</Label>
<Input id="identifier_path" value={oidcConfig.identifier_path}
onChange={(e) => handleOIDCConfigChange('identifier_path', e.target.value)}
placeholder={t('placeholders.userIdField')} required/>
</div>
<div className="space-y-2">
<Label htmlFor="name_path">{t('admin.displayNamePath')}</Label>
<Input id="name_path" value={oidcConfig.name_path}
onChange={(e) => handleOIDCConfigChange('name_path', e.target.value)}
placeholder={t('placeholders.usernameField')} required/>
</div>
<div className="space-y-2">
<Label htmlFor="scopes">{t('admin.scopes')}</Label>
<Input id="scopes" value={oidcConfig.scopes}
onChange={(e) => handleOIDCConfigChange('scopes', e.target.value)}
placeholder={t('placeholders.scopes')} required/>
</div>
<div className="space-y-2">
<Label htmlFor="userinfo_url">{t('admin.overrideUserInfoUrl')}</Label>
<Input id="userinfo_url" value={oidcConfig.userinfo_url}
onChange={(e) => handleOIDCConfigChange('userinfo_url', e.target.value)}
placeholder="https://your-provider.com/application/o/userinfo/"/>
</div>
<div className="flex gap-2 pt-2">
<Button type="submit" className="flex-1"
disabled={oidcLoading}>{oidcLoading ? t('admin.saving') : t('admin.saveConfiguration')}</Button>
<Button type="button" variant="outline" onClick={() => setOidcConfig({
client_id: '',
client_secret: '',
issuer_url: '',
authorization_url: '',
token_url: '',
identifier_path: 'sub',
name_path: 'name',
scopes: 'openid email profile',
userinfo_url: ''
})}>{t('admin.reset')}</Button>
</div>
</form>
</div>
</TabsContent>
                                <form onSubmit={handleOIDCConfigSubmit} className="space-y-4"> <TabsContent value="users" className="space-y-6">
                                    <div className="space-y-2"> <div className="space-y-4">
                                        <Label htmlFor="client_id">{t('admin.clientId')}</Label> <div className="flex items-center justify-between">
                                        <Input id="client_id" value={oidcConfig.client_id} <h3 className="text-lg font-semibold">{t('admin.userManagement')}</h3>
                                               onChange={(e) => handleOIDCConfigChange('client_id', e.target.value)} <Button onClick={fetchUsers} disabled={usersLoading} variant="outline"
                                               placeholder={t('placeholders.clientId')} required/> size="sm">{usersLoading ? t('admin.loading') : t('admin.refresh')}</Button>
                                    </div> </div>
{/* 🎯 Updated block for client_secret input */} {usersLoading ? (
                                    <div className="space-y-2"> <div className="text-center py-8 text-muted-foreground">{t('admin.loadingUsers')}</div>
                                        <Label htmlFor="client_secret">{t('admin.clientSecret')}</Label> ) : (
     <div className="relative"> <div className="border rounded-md overflow-hidden">
<Input <Table>
id="client_secret" <TableHeader>
// 🎯 Set input type based on showClientSecret state <TableRow>
type={showClientSecret ? "text" : "password"} <TableHead className="px-4">{t('admin.username')}</TableHead>
value={oidcConfig.client_secret} <TableHead className="px-4">{t('admin.type')}</TableHead>
onChange={(e) => handleOIDCConfigChange('client_secret', e.target.value)} <TableHead className="px-4">{t('admin.actions')}</TableHead>
placeholder={t('placeholders.clientSecret')} </TableRow>
required </TableHeader>
// 🎯 Add padding to the right for the button <TableBody>
className="pr-10" {users.map((user) => (
/> <TableRow key={user.id}>
<Button <TableCell className="px-4 font-medium">
type="button" {user.username}
variant="ghost" {user.is_admin && (
size="sm" <span
// 🎯 Toggle the state on click className="ml-2 inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-muted/50 text-muted-foreground border border-border">{t('admin.adminBadge')}</span>
onClick={() => setShowClientSecret((prev) => !prev)} )}
className="absolute right-0 top-0 h-full px-3 py-2" </TableCell>
> <TableCell
{/* 🎯 Conditionally render the correct icon */} className="px-4">{user.is_oidc ? t('admin.external') : t('admin.local')}</TableCell>
{showClientSecret ? ( <TableCell className="px-4">
<EyeOff className="h-4 w-4 text-muted-foreground" /> <Button variant="ghost" size="sm"
) : ( onClick={() => handleDeleteUser(user.username)}
<Eye className="h-4 w-4 text-muted-foreground" /> className="text-red-600 hover:text-red-700 hover:bg-red-50"
)} disabled={user.is_admin}>
</Button> <Trash2 className="h-4 w-4"/>
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</div>
</TabsContent>
<TabsContent value="admins" className="space-y-6">
<div className="space-y-6">
<h3 className="text-lg font-semibold">{t('admin.adminManagement')}</h3>
<div className="space-y-4 p-6 border rounded-md bg-muted/50">
<h4 className="font-medium">{t('admin.makeUserAdmin')}</h4>
<form onSubmit={handleMakeUserAdmin} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="new-admin-username">{t('admin.username')}</Label>
<div className="flex gap-2">
<Input id="new-admin-username" value={newAdminUsername}
onChange={(e) => setNewAdminUsername(e.target.value)}
placeholder={t('admin.enterUsernameToMakeAdmin')} required/>
<Button type="submit"
disabled={makeAdminLoading || !newAdminUsername.trim()}>{makeAdminLoading ? t('admin.adding') : t('admin.makeAdmin')}</Button>
</div>
</div> </div>
                                    </div> {makeAdminError && (
{/* 🎯 End of updated block */} <Alert variant="destructive">
                                    <div className="space-y-2"> <AlertTitle>{t('common.error')}</AlertTitle>
                                        <Label htmlFor="authorization_url">{t('admin.authorizationUrl')}</Label> <AlertDescription>{makeAdminError}</AlertDescription>
                                        <Input id="authorization_url" value={oidcConfig.authorization_url} </Alert>
                                               onChange={(e) => handleOIDCConfigChange('authorization_url', e.target.value)} )}
                                               placeholder={t('placeholders.authUrl')}
                                               required/>
                                    </div>
                                    <div className="space-y-2">
                                        <Label htmlFor="issuer_url">{t('admin.issuerUrl')}</Label>
                                        <Input id="issuer_url" value={oidcConfig.issuer_url}
                                               onChange={(e) => handleOIDCConfigChange('issuer_url', e.target.value)}
                                               placeholder={t('placeholders.redirectUrl')} required/>
                                    </div>
                                    <div className="space-y-2">
                                        <Label htmlFor="token_url">{t('admin.tokenUrl')}</Label>
                                        <Input id="token_url" value={oidcConfig.token_url}
                                               onChange={(e) => handleOIDCConfigChange('token_url', e.target.value)}
                                               placeholder={t('placeholders.tokenUrl')} required/>
                                    </div>
                                    <div className="space-y-2">
                                        <Label htmlFor="identifier_path">{t('admin.userIdentifierPath')}</Label>
                                        <Input id="identifier_path" value={oidcConfig.identifier_path}
                                               onChange={(e) => handleOIDCConfigChange('identifier_path', e.target.value)}
                                               placeholder={t('placeholders.userIdField')} required/>
                                    </div>
                                    <div className="space-y-2">
                                        <Label htmlFor="name_path">{t('admin.displayNamePath')}</Label>
                                        <Input id="name_path" value={oidcConfig.name_path}
                                               onChange={(e) => handleOIDCConfigChange('name_path', e.target.value)}
                                               placeholder={t('placeholders.usernameField')} required/>
                                    </div>
                                    <div className="space-y-2">
                                        <Label htmlFor="scopes">{t('admin.scopes')}</Label>
                                        <Input id="scopes" value={oidcConfig.scopes}
                                               onChange={(e) => handleOIDCConfigChange('scopes', e.target.value)}
                                               placeholder={t('placeholders.scopes')} required/>
                                    </div>
                                    <div className="space-y-2">
                                        <Label htmlFor="userinfo_url">{t('admin.overrideUserInfoUrl')}</Label>
                                        <Input id="userinfo_url" value={oidcConfig.userinfo_url}
                                               onChange={(e) => handleOIDCConfigChange('userinfo_url', e.target.value)}
                                               placeholder="https://your-provider.com/application/o/userinfo/"/>
                                    </div>
                                    <div className="flex gap-2 pt-2">
                                        <Button type="submit" className="flex-1"
                                                disabled={oidcLoading}>{oidcLoading ? t('admin.saving') : t('admin.saveConfiguration')}</Button>
                                        <Button type="button" variant="outline" onClick={() => setOidcConfig({
                                            client_id: '',
                                            client_secret: '',
                                            issuer_url: '',
                                            authorization_url: '',
                                            token_url: '',
                                            identifier_path: 'sub',
                                            name_path: 'name',
                                            scopes: 'openid email profile',
                                            userinfo_url: ''
                                        })}>{t('admin.reset')}</Button>
                                    </div>
                                </form>
                            </div>
                        </TabsContent>
                        <TabsContent value="users" className="space-y-6"> </form>
                            <div className="space-y-4"> </div>
                                <div className="flex items-center justify-between">
                                    <h3 className="text-lg font-semibold">{t('admin.userManagement')}</h3>
                                    <Button onClick={fetchUsers} disabled={usersLoading} variant="outline"
                                            size="sm">{usersLoading ? t('admin.loading') : t('admin.refresh')}</Button>
                                </div>
                                {usersLoading ? (
                                    <div className="text-center py-8 text-muted-foreground">{t('admin.loadingUsers')}</div>
                                ) : (
                                    <div className="border rounded-md overflow-hidden">
                                        <Table>
                                            <TableHeader>
                                                <TableRow>
                                                    <TableHead className="px-4">{t('admin.username')}</TableHead>
                                                    <TableHead className="px-4">{t('admin.type')}</TableHead>
                                                    <TableHead className="px-4">{t('admin.actions')}</TableHead>
                                                </TableRow>
                                            </TableHeader>
                                            <TableBody>
                                                {users.map((user) => (
                                                    <TableRow key={user.id}>
                                                        <TableCell className="px-4 font-medium">
                                                            {user.username}
                                                            {user.is_admin && (
                                                                <span
                                                                    className="ml-2 inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-muted/50 text-muted-foreground border border-border">{t('admin.adminBadge')}</span>
                                                            )}
                                                        </TableCell>
                                                        <TableCell
                                                            className="px-4">{user.is_oidc ? t('admin.external') : t('admin.local')}</TableCell>
                                                        <TableCell className="px-4">
                                                            <Button variant="ghost" size="sm"
                                                                    onClick={() => handleDeleteUser(user.username)}
                                                                    className="text-red-600 hover:text-red-700 hover:bg-red-50"
                                                                    disabled={user.is_admin}>
                                                                <Trash2 className="h-4 w-4"/>
                                                            </Button>
                                                        </TableCell>
                                                    </TableRow>
                                                ))}
                                            </TableBody>
                                        </Table>
                                    </div>
                                )}
                            </div>
                        </TabsContent>
                        <TabsContent value="admins" className="space-y-6"> <div className="space-y-4">
                            <div className="space-y-6"> <h4 className="font-medium">{t('admin.currentAdmins')}</h4>
                                <h3 className="text-lg font-semibold">{t('admin.adminManagement')}</h3> <div className="border rounded-md overflow-hidden">
                                <div className="space-y-4 p-6 border rounded-md bg-muted/50"> <Table>
                                    <h4 className="font-medium">{t('admin.makeUserAdmin')}</h4> <TableHeader>
                                    <form onSubmit={handleMakeUserAdmin} className="space-y-4"> <TableRow>
                                        <div className="space-y-2"> <TableHead className="px-4">{t('admin.username')}</TableHead>
                                            <Label htmlFor="new-admin-username">{t('admin.username')}</Label> <TableHead className="px-4">{t('admin.type')}</TableHead>
                                            <div className="flex gap-2"> <TableHead className="px-4">{t('admin.actions')}</TableHead>
                                                <Input id="new-admin-username" value={newAdminUsername} </TableRow>
                                                       onChange={(e) => setNewAdminUsername(e.target.value)} </TableHeader>
                                                       placeholder={t('admin.enterUsernameToMakeAdmin')} required/> <TableBody>
                                                <Button type="submit" {users.filter(u => u.is_admin).map((admin) => (
                                                        disabled={makeAdminLoading || !newAdminUsername.trim()}>{makeAdminLoading ? t('admin.adding') : t('admin.makeAdmin')}</Button> <TableRow key={admin.id}>
                                            </div> <TableCell className="px-4 font-medium">
                                        </div> {admin.username}
                                        {makeAdminError && ( <span
                                            <Alert variant="destructive"> className="ml-2 inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-muted/50 text-muted-foreground border border-border">{t('admin.adminBadge')}</span>
                                                <AlertTitle>{t('common.error')}</AlertTitle> </TableCell>
                                                <AlertDescription>{makeAdminError}</AlertDescription> <TableCell
                                            </Alert> className="px-4">{admin.is_oidc ? t('admin.external') : t('admin.local')}</TableCell>
                                        )} <TableCell className="px-4">
<Button variant="ghost" size="sm"
                                    </form> onClick={() => handleRemoveAdminStatus(admin.username)}
                                </div> className="text-orange-600 hover:text-orange-700 hover:bg-orange-50">
<Shield className="h-4 w-4"/>
                                <div className="space-y-4"> {t('admin.removeAdminButton')}
                                    <h4 className="font-medium">{t('admin.currentAdmins')}</h4> </Button>
                                    <div className="border rounded-md overflow-hidden"> </TableCell>
                                        <Table> </TableRow>
                                            <TableHeader> ))}
                                                <TableRow> </TableBody>
                                                    <TableHead className="px-4">{t('admin.username')}</TableHead> </Table>
                                                    <TableHead className="px-4">{t('admin.type')}</TableHead> </div>
                                                    <TableHead className="px-4">{t('admin.actions')}</TableHead> </div>
                                                </TableRow> </div>
                                            </TableHeader> </TabsContent>
                                            <TableBody> </Tabs>
                                                {users.filter(u => u.is_admin).map((admin) => ( </div>
                                                    <TableRow key={admin.id}> </div>
                                                        <TableCell className="px-4 font-medium"> </div>
                                                            {admin.username} );
                                                            <span
                                                                className="ml-2 inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-muted/50 text-muted-foreground border border-border">{t('admin.adminBadge')}</span>
                                                        </TableCell>
                                                        <TableCell
                                                            className="px-4">{admin.is_oidc ? t('admin.external') : t('admin.local')}</TableCell>
                                                        <TableCell className="px-4">
                                                            <Button variant="ghost" size="sm"
                                                                    onClick={() => handleRemoveAdminStatus(admin.username)}
                                                                    className="text-orange-600 hover:text-orange-700 hover:bg-orange-50">
                                                                <Shield className="h-4 w-4"/>
                                                                {t('admin.removeAdminButton')}
                                                            </Button>
                                                        </TableCell>
                                                    </TableRow>
                                                ))}
                                            </TableBody>
                                        </Table>
                                    </div>
                                </div>
                            </div>
                        </TabsContent>
                    </Tabs>
                </div>
            </div>
        </div>
    );
} }
export default AdminSettings; export default AdminSettings;