feat(auth): Add password visibility toggle to auth forms (#166)
* added hide and unhide password button * Undo admin settings changes --------- Co-authored-by: LukeGus <bugattiguy527@gmail.com>
This commit was merged in pull request #166.
This commit is contained in:
committed by
GitHub
parent
f4d361f34b
commit
3dd4676479
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import {useSidebar} from "@/components/ui/sidebar.tsx";
|
||||
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";
|
||||
@@ -79,9 +79,7 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
.then(res => {
|
||||
if (res) setOidcConfig(res);
|
||||
})
|
||||
.catch(error => {
|
||||
// Silently ignore OIDC config fetch errors - this is expected when OIDC is not configured
|
||||
console.debug('OIDC config not available:', error.message);
|
||||
.catch(() => {
|
||||
});
|
||||
fetchUsers();
|
||||
}, []);
|
||||
@@ -208,7 +206,7 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
className="bg-[#18181b] text-white rounded-lg border-2 border-[#303032] overflow-hidden">
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<div className="flex items-center justify-between px-3 pt-2 pb-2">
|
||||
<h1 className="font-bold text-lg">Admin Settings</h1>
|
||||
<h1 className="font-bold text-lg">{t('admin.title')}</h1>
|
||||
</div>
|
||||
<Separator className="p-0.25 w-full"/>
|
||||
|
||||
@@ -221,11 +219,11 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="oidc" className="flex items-center gap-2">
|
||||
<Shield className="h-4 w-4"/>
|
||||
{t('admin.oidc')}
|
||||
OIDC
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="users" className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4"/>
|
||||
Users
|
||||
{t('admin.users')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="admins" className="flex items-center gap-2">
|
||||
<Shield className="h-4 w-4"/>
|
||||
@@ -246,9 +244,8 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
|
||||
<TabsContent value="oidc" className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">External Authentication (OIDC)</h3>
|
||||
<p className="text-sm text-muted-foreground">Configure external identity provider for
|
||||
OIDC/OAuth2 authentication.</p>
|
||||
<h3 className="text-lg font-semibold">{t('admin.externalAuthentication')}</h3>
|
||||
<p className="text-sm text-muted-foreground">{t('admin.configureExternalProvider')}</p>
|
||||
|
||||
{oidcError && (
|
||||
<Alert variant="destructive">
|
||||
@@ -259,60 +256,54 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
|
||||
<form onSubmit={handleOIDCConfigSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="client_id">Client ID</Label>
|
||||
<Label htmlFor="client_id">{t('admin.clientId')}</Label>
|
||||
<Input id="client_id" value={oidcConfig.client_id}
|
||||
onChange={(e) => handleOIDCConfigChange('client_id', e.target.value)}
|
||||
placeholder="your-client-id" required/>
|
||||
placeholder={t('placeholders.clientId')} required/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="client_secret">Client Secret</Label>
|
||||
<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="your-client-secret" required/>
|
||||
placeholder={t('placeholders.clientSecret')} required/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="authorization_url">Authorization URL</Label>
|
||||
<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="https://your-provider.com/application/o/authorize/"
|
||||
placeholder={t('placeholders.authUrl')}
|
||||
required/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="issuer_url">Issuer URL</Label>
|
||||
<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="https://your-provider.com/application/o/termix/" required/>
|
||||
placeholder={t('placeholders.redirectUrl')} required/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="token_url">Token URL</Label>
|
||||
<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="https://your-provider.com/application/o/token/" required/>
|
||||
placeholder={t('placeholders.tokenUrl')} required/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="identifier_path">User Identifier Path</Label>
|
||||
<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="sub" required/>
|
||||
placeholder={t('placeholders.userIdField')} required/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name_path">Display Name Path</Label>
|
||||
<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="name" required/>
|
||||
placeholder={t('placeholders.usernameField')} required/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="scopes">Scopes</Label>
|
||||
<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={t('placeholders.userinfoUrl')}/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="userinfo_url">{t('admin.overrideUserInfoUrl')}</Label>
|
||||
<Input id="userinfo_url" value={oidcConfig.userinfo_url}
|
||||
@@ -321,7 +312,7 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
</div>
|
||||
<div className="flex gap-2 pt-2">
|
||||
<Button type="submit" className="flex-1"
|
||||
disabled={oidcLoading}>{oidcLoading ? "Saving..." : "Save Configuration"}</Button>
|
||||
disabled={oidcLoading}>{oidcLoading ? t('admin.saving') : t('admin.saveConfiguration')}</Button>
|
||||
<Button type="button" variant="outline" onClick={() => setOidcConfig({
|
||||
client_id: '',
|
||||
client_secret: '',
|
||||
@@ -341,20 +332,20 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
<TabsContent value="users" className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">User Management</h3>
|
||||
<h3 className="text-lg font-semibold">{t('admin.userManagement')}</h3>
|
||||
<Button onClick={fetchUsers} disabled={usersLoading} variant="outline"
|
||||
size="sm">{usersLoading ? "Loading..." : "Refresh"}</Button>
|
||||
size="sm">{usersLoading ? t('admin.loading') : t('admin.refresh')}</Button>
|
||||
</div>
|
||||
{usersLoading ? (
|
||||
<div className="text-center py-8 text-muted-foreground">Loading users...</div>
|
||||
<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">Username</TableHead>
|
||||
<TableHead className="px-4">Type</TableHead>
|
||||
<TableHead className="px-4">Actions</TableHead>
|
||||
<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>
|
||||
@@ -364,15 +355,15 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
{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">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 ? "External" : "Local"}</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-900/20 dark:hover:bg-red-900/30"
|
||||
className="text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
disabled={user.is_admin}>
|
||||
<Trash2 className="h-4 w-4"/>
|
||||
</Button>
|
||||
@@ -388,18 +379,18 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
|
||||
<TabsContent value="admins" className="space-y-6">
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-semibold">Admin Management</h3>
|
||||
<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">Make User Admin</h4>
|
||||
<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">Username</Label>
|
||||
<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 ? "Adding..." : "Make Admin"}</Button>
|
||||
disabled={makeAdminLoading || !newAdminUsername.trim()}>{makeAdminLoading ? t('admin.adding') : t('admin.makeAdmin')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
{makeAdminError && (
|
||||
@@ -413,14 +404,14 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-medium">Current Admins</h4>
|
||||
<h4 className="font-medium">{t('admin.currentAdmins')}</h4>
|
||||
<div className="border rounded-md overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="px-4">Username</TableHead>
|
||||
<TableHead className="px-4">Type</TableHead>
|
||||
<TableHead className="px-4">Actions</TableHead>
|
||||
<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>
|
||||
@@ -432,13 +423,13 @@ export function AdminSettings({isTopbarOpen = true}: AdminSettingsProps): React.
|
||||
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 ? "External" : "Local"}</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-900/20 dark:hover:bg-orange-900/30">
|
||||
className="text-orange-600 hover:text-orange-700 hover:bg-orange-50">
|
||||
<Shield className="h-4 w-4"/>
|
||||
Remove Admin
|
||||
{t('admin.removeAdminButton')}
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
Reference in New Issue
Block a user