Applied systematic spacing optimizations across admin manager components (40px savings): DatabaseManager.tsx: - Input field padding: py-3 → py-2 (3 inputs: retention_count, schedule_hour, schedule_freq_days) - Form field spacing: space-y-2 → space-y-1.5 (Max Backups field group) - Grid gaps: gap-4 → gap-3 (storage policy fields) - Item list spacing: space-y-2 → space-y-1.5 (backup list) - Modal form spacing: space-y-4 → space-y-3 (recovery points section) - Backup metadata text: text-[11px] → text-xs (created_at + filesize display) LdapManager.tsx: - Input field padding: py-2.5 → py-2 (4 inputs: server_uri, base_dn, user_template, groups_dn) - Form field spacing: space-y-1.5 → space-y-1 (all label+input groups) - Button padding: py-2.5 → py-2 (test connection), py-3 → py-2.5 (save policy) IdentityManager.tsx: - Input field padding: py-3 → py-2 (username, password, role in edit modal) - Form field spacing: space-y-4 → space-y-3 (edit modal form) - Form field labels: space-y-1.5 → space-y-1 (all 3 fields) - User list spacing: space-y-2 → space-y-1.5 (user items) AiManager.tsx: - Grid gaps: gap-4 → gap-3 (provider options grid) - Input field padding: py-3 → py-2 (Gemini/Claude API key inputs) - Form field spacing: space-y-2 → space-y-1.5 (API key field groups) - Modal spacing: space-y-6 → space-y-3 (provider access keys section) - Prompt section spacing: space-y-4 → space-y-3 (system prompt area) CategoryManager.tsx: - Grid gaps: gap-3 → gap-2.5 (category items grid) - Input field padding: py-3.5 → py-2.5, py-4 → py-2.5 (name + description inputs) - Form field spacing: space-y-6 → space-y-3, space-y-1.5 → space-y-1 (edit modal) Test Results: - Frontend (Vitest): 291/291 tests passing ✓ - Backend (Pytest): 41/41 tests passing ✓ - Total: 332 tests validated, zero regressions All changes maintain visual hierarchy and premium density standards without sacrificing usability.
146 lines
7.3 KiB
TypeScript
146 lines
7.3 KiB
TypeScript
import React from 'react';
|
|
import { User, UserPlus, Shield, Edit2, Trash2, X } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface IdentityManagerProps {
|
|
users: any[];
|
|
onAddUser: () => void;
|
|
onDeleteUser: (id: number, username: string) => void;
|
|
editingUser: any | null;
|
|
setEditingUser: (user: any | null) => void;
|
|
editUserForm: any;
|
|
setEditUserForm: (form: any) => void;
|
|
onUpdateUserSubmit: () => void;
|
|
}
|
|
|
|
export default function IdentityManager({
|
|
users,
|
|
onAddUser,
|
|
onDeleteUser,
|
|
editingUser,
|
|
setEditingUser,
|
|
editUserForm,
|
|
setEditUserForm,
|
|
onUpdateUserSubmit
|
|
}: IdentityManagerProps) {
|
|
return (
|
|
<div className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-4 md:p-6 flex flex-col shadow-2xl transition-all group/identity h-full">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
|
|
<User size={20} />
|
|
</div>
|
|
<h2 className="text-xl font-normal text-white tracking-tight">Identity Management</h2>
|
|
</div>
|
|
<button
|
|
onClick={onAddUser}
|
|
data-testid="add-user-button"
|
|
className="flex items-center gap-2 px-5 py-2.5 bg-primary hover:bg-blue-600 text-white rounded-xl text-sm font-normal transition-all shadow-xl shadow-primary/10 active:scale-95 border border-primary/30 tracking-tight focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none"
|
|
aria-label="Add new user"
|
|
>
|
|
<UserPlus size={16} /> Add User
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex-1 space-y-1.5 pr-2 -mr-2 overflow-y-auto max-h-[400px] scrollbar-thin scrollbar-thumb-slate-800 scrollbar-track-transparent">
|
|
{users.map((user) => (
|
|
<div key={user.id} className="flex items-center justify-between p-3.5 bg-background/40 border border-slate-800/40 rounded-2xl hover:border-primary/30 transition-all group">
|
|
<div className="flex items-center gap-3 min-w-0">
|
|
<div className={cn(
|
|
"w-9 h-9 rounded-xl flex items-center justify-center transition-colors shadow-inner shrink-0",
|
|
user.role === 'admin' ? "bg-primary/10 text-primary border border-primary/20" : "bg-slate-800 text-muted border border-slate-700"
|
|
)}>
|
|
{user.role === 'admin' ? <Shield size={16} /> : <User size={16} />}
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="card-title truncate">{user.username}</p>
|
|
<p className="card-subtitle">{user.role}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-1.5 ml-2">
|
|
<button
|
|
onClick={() => {
|
|
setEditingUser(user);
|
|
setEditUserForm({ username: user.username, password: '', role: user.role });
|
|
}}
|
|
className="p-2 text-secondary hover:text-primary hover:bg-primary/5 rounded-xl transition-all focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none"
|
|
aria-label={`Edit user ${user.username}`}
|
|
>
|
|
<Edit2 size={16} />
|
|
</button>
|
|
{user.username !== 'Admin' && (
|
|
<button
|
|
onClick={() => onDeleteUser(user.id, user.username)}
|
|
data-testid="delete-user-button"
|
|
className="p-2 text-secondary hover:text-rose-500 hover:bg-rose-500/5 rounded-xl transition-all focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:outline-none"
|
|
aria-label={`Delete user ${user.username}`}
|
|
>
|
|
<Trash2 size={16} />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Edit User Modal */}
|
|
{editingUser && (
|
|
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-background/80 animate-in fade-in duration-200">
|
|
<div className="bg-surface border border-slate-800 rounded-[2.5rem] p-6 w-full max-w-md shadow-2xl">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h3 className="text-xl font-normal text-white tracking-tight">Edit Identity</h3>
|
|
<button
|
|
onClick={() => setEditingUser(null)}
|
|
className="p-2 text-muted hover:text-white transition-colors focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none rounded"
|
|
aria-label="Close edit dialog"
|
|
>
|
|
<X size={20} />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<div className="space-y-1">
|
|
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Username</label>
|
|
<input
|
|
type="text"
|
|
value={editUserForm.username}
|
|
onChange={(e) => setEditUserForm({ ...editUserForm, username: e.target.value })}
|
|
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-2 px-4 text-sm font-normal text-white outline-none focus:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-0 transition-all font-mono"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-sm font-normal text-secondary tracking-tight ml-1">New Password (Leave Empty To Keep)</label>
|
|
<input
|
|
type="password"
|
|
value={editUserForm.password}
|
|
onChange={(e) => setEditUserForm({ ...editUserForm, password: e.target.value })}
|
|
placeholder="********"
|
|
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-2 px-4 text-sm font-normal text-white outline-none focus:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-0 transition-all font-mono"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Role</label>
|
|
<select
|
|
value={editUserForm.role}
|
|
onChange={(e) => setEditUserForm({ ...editUserForm, role: e.target.value })}
|
|
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-2 px-4 text-sm font-normal text-white outline-none focus:border-primary/50 transition-all appearance-none"
|
|
>
|
|
<option value="user">Standard User</option>
|
|
<option value="admin">Administrator</option>
|
|
</select>
|
|
</div>
|
|
|
|
<button
|
|
onClick={onUpdateUserSubmit}
|
|
className="w-full bg-primary hover:bg-primary/80 text-white rounded-2xl py-4 text-sm font-normal transition-all active:scale-95 shadow-xl shadow-primary/20 tracking-tight mt-4 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 focus-visible:ring-primary focus-visible:outline-none"
|
|
>
|
|
Apply Changes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|