Merge tag '1.14.5_cleaned'

project was cleaned
This commit is contained in:
2026-04-23 11:47:44 +03:00
5758 changed files with 2046069 additions and 16179 deletions

View File

@@ -32,17 +32,17 @@ export default function AiManager({
onUpdatePrompt
}: AiManagerProps) {
return (
<section data-testid="ai-config" className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-5 md:p-8 shadow-2xl space-y-6 transition-all group/ai">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-4">
<section data-testid="ai-config" className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-4 md:p-6 shadow-2xl space-y-4 transition-all group/ai">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
<Brain size={20} />
</div>
<h2 className="text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-black text-white tracking-tight">AI Intelligence</h2>
<h2 className="text-xl font-normal text-white tracking-tight">AI Intelligence</h2>
</div>
</div>
<div className="grid sm:grid-cols-2 gap-4">
<div className="grid sm:grid-cols-2 gap-3">
{aiConfig?.providers?.map((p: any) => (
<button
key={p.id}
@@ -63,9 +63,9 @@ export default function AiManager({
{p.id === 'gemini' ? <Cpu size={16} /> : <Zap size={16} />}
</div>
<div>
<p className={cn("text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-black tracking-tight", p.active ? "text-white" : "text-secondary")}>{p.name}</p>
<p className={cn("text-xs font-normal tracking-tight", p.active ? "text-white" : "text-secondary")}>{p.name}</p>
<p className={cn(
"text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold mt-1 px-0.5 rounded",
"text-xs font-normal mt-1 px-0.5 rounded",
p.active
? (p.configured ? "text-emerald-200" : "text-rose-100")
: (p.configured ? "text-emerald-500" : "text-rose-500")
@@ -75,7 +75,7 @@ export default function AiManager({
</div>
</div>
{p.active && (
<div className="bg-white/20 px-2.5 py-1 rounded-full text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-black text-white tracking-tight">
<div className="bg-white/20 px-2.5 py-1 rounded-full text-xs font-normal text-white tracking-tight">
Active
</div>
)}
@@ -83,26 +83,26 @@ export default function AiManager({
))}
</div>
<div className="bg-primary/5 border border-primary/10 rounded-[2rem] p-6 lg:p-8 xl:p-10 space-y-6">
<div className="bg-primary/5 border border-primary/10 rounded-[2rem] p-5 space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-primary/10 rounded-lg text-primary"><Lock size={14} /></div>
<h3 className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight">Provider Access Keys</h3>
<div className="p-2 bg-primary/10 rounded-lg text-primary"><Lock size={14} /></div>
<h3 className="text-sm font-normal text-secondary tracking-tight">Provider Access Keys</h3>
</div>
<button
onClick={onSaveAiKeys}
disabled={isSavingKeys}
data-testid="save-settings-button"
className="px-6 py-2 lg:py-3 xl:py-4.5 bg-primary hover:bg-primary text-white rounded-xl text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black transition-all active:scale-95 shadow-xl shadow-primary/10 tracking-tight border border-primary/30 flex items-center gap-2"
className="px-5 py-2 bg-primary hover:bg-primary text-white rounded-xl text-sm font-normal transition-all active:scale-95 shadow-xl shadow-primary/10 tracking-tight border border-primary/30 flex items-center gap-2"
>
{isSavingKeys ? <RotateCcw size={14} className="animate-spin" /> : <Lock size={14} />}
{isSavingKeys ? "Storing..." : "Store API Keys"}
</button>
</div>
<div className="grid md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">Gemini Api Key</label>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Gemini Api Key</label>
<div className="flex gap-2">
<input
data-testid="ai-api-key-input"
@@ -110,14 +110,14 @@ export default function AiManager({
value={aiKeys.gemini}
onChange={(e) => setAiKeys({...aiKeys, gemini: e.target.value})}
placeholder={aiConfig?.providers?.find((p: any) => p.id === 'gemini')?.masked_key || "Enter Gemini Key..."}
className="flex-1 bg-background/80 border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl text-white outline-none focus:border-primary/50 transition-all placeholder:text-secondary"
className="flex-1 bg-background/80 border border-slate-800 rounded-xl py-1.5 px-4 text-xs text-white outline-none focus:border-primary/50 transition-all placeholder:text-secondary"
/>
<button
onClick={() => onTestAiKey('gemini')}
disabled={isTestingKeys.gemini}
className={cn(
"px-4 lg:px-5 xl:px-6 py-2 lg:py-3 xl:py-4 rounded-xl text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold tracking-tight transition-all border shadow-lg flex items-center gap-1.5",
isTestingKeys.gemini
"px-3 py-1.5 rounded-xl text-xs font-normal tracking-tight transition-all border shadow-lg flex items-center gap-1.5",
isTestingKeys.gemini
? "bg-slate-800 border-slate-700 text-muted cursor-wait"
: "bg-primary border-primary text-white hover:bg-primary"
)}
@@ -127,8 +127,8 @@ export default function AiManager({
</button>
</div>
</div>
<div className="space-y-2">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">Claude Api Key</label>
<div className="space-y-1">
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Claude Api Key</label>
<div className="flex gap-2">
<input
data-testid="ai-api-key-input"
@@ -136,14 +136,14 @@ export default function AiManager({
value={aiKeys.claude}
onChange={(e) => setAiKeys({...aiKeys, claude: e.target.value})}
placeholder={aiConfig?.providers?.find((p: any) => p.id === 'claude')?.masked_key || "Enter Claude Key..."}
className="flex-1 bg-background/80 border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl text-white outline-none focus:border-primary/50 transition-all placeholder:text-secondary"
className="flex-1 bg-background/80 border border-slate-800 rounded-xl py-1.5 px-4 text-xs text-white outline-none focus:border-primary/50 transition-all placeholder:text-secondary"
/>
<button
onClick={() => onTestAiKey('claude')}
disabled={isTestingKeys.claude}
className={cn(
"px-4 lg:px-5 xl:px-6 py-2 lg:py-3 xl:py-4 rounded-xl text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold tracking-tight transition-all border shadow-lg flex items-center gap-1.5",
isTestingKeys.claude
"px-3 py-1.5 rounded-xl text-xs font-normal tracking-tight transition-all border shadow-lg flex items-center gap-1.5",
isTestingKeys.claude
? "bg-slate-800 border-slate-700 text-muted cursor-wait"
: "bg-primary border-primary text-white hover:bg-primary"
)}
@@ -156,28 +156,28 @@ export default function AiManager({
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<div className="space-y-2 pt-0">
<div className="space-y-1">
<div className="flex items-center justify-between px-1">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight">System Prompt (Vision Extraction)</label>
<button
<label className="text-sm font-normal text-secondary tracking-tight">System Prompt (Vision Extraction)</label>
<button
onClick={onUpdatePrompt}
disabled={isSavingPrompt}
className="px-6 py-2 lg:py-3 xl:py-4 bg-primary hover:bg-primary text-white rounded-xl text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black transition-all active:scale-95 shadow-xl shadow-primary/10 tracking-tight border border-primary/30 flex items-center gap-2"
className="px-5 py-1.5 bg-primary hover:bg-primary text-white rounded-xl text-sm font-normal transition-all active:scale-95 shadow-xl shadow-primary/10 tracking-tight border border-primary/30 flex items-center gap-2"
>
{isSavingPrompt ? <RotateCcw size={14} className="animate-spin" /> : <FileText size={14} />}
{isSavingPrompt ? "Saving..." : "Save Prompt"}
</button>
</div>
<textarea
<textarea
value={aiPrompt}
onChange={(e) => setAiPrompt(e.target.value)}
className="w-full bg-background/80 border border-slate-800 rounded-2xl p-6 lg:p-8 xl:p-10 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-mono font-bold text-secondary leading-relaxed outline-none focus:border-purple-500/50 transition-all min-h-[200px] custom-scrollbar shadow-inner"
className="w-full bg-background/80 border border-slate-800 rounded-2xl p-4 text-xs font-mono font-normal text-secondary leading-relaxed outline-none focus:border-purple-500/50 transition-all min-h-[200px] custom-scrollbar shadow-inner"
/>
</div>
<div className="bg-primary/5 border border-purple-500/10 rounded-2xl p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 flex gap-4 items-start">
<div className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-primary/10 rounded-lg text-purple-400 shrink-0"><Shield size={14} /></div>
<p className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-medium text-secondary leading-relaxed">
<div className="bg-primary/5 border border-purple-500/10 rounded-2xl p-4 flex gap-3 items-start">
<div className="p-2 bg-primary/10 rounded-lg text-purple-400 shrink-0"><Shield size={14} /></div>
<p className="text-xs font-medium text-secondary leading-relaxed">
This prompt instructs the Vision AI core on label interpretation. Ensure it defines explicit mapping for technical attributes like <span className="text-purple-400">Item</span>, <span className="text-purple-400">Type</span>, and <span className="text-purple-400">Part Number</span> to avoid extraction null-pointers and ensure inventory data integrity.
</p>
</div>

View File

@@ -23,24 +23,24 @@ export default function CategoryManager({
onUpdateCategorySubmit
}: CategoryManagerProps) {
return (
<section className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-5 md:p-8 shadow-2xl space-y-6 transition-all group/categories">
<div className="flex items-center justify-between px-2">
<div className="flex items-center gap-4">
<section className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-4 md:p-6 shadow-2xl space-y-4 transition-all group/categories">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
<Layers size={20} />
</div>
<h2 className="text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-black text-white tracking-tight">Category Groups</h2>
<h2 className="text-xl font-normal text-white tracking-tight">Category Groups</h2>
</div>
<button
onClick={onAddCategory}
data-testid="add-category-button"
className="flex items-center gap-2 bg-primary hover:bg-primary text-white px-6 py-3 lg:py-4 xl:py-5 rounded-xl text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black transition-all shadow-xl shadow-primary/10 active:scale-95 border border-primary/30 tracking-tight"
className="flex items-center gap-2 bg-primary hover:bg-primary text-white px-5 py-2 rounded-xl text-sm font-normal transition-all shadow-xl shadow-primary/10 active:scale-95 border border-primary/30 tracking-tight"
>
<Plus size={14} /> New Group
</button>
</div>
<div data-testid="category-list" className="grid sm:grid-cols-2 lg:grid-cols-4 gap-3">
<div data-testid="category-list" className="grid sm:grid-cols-2 lg:grid-cols-4 gap-2.5">
{categories.map(cat => (
<div key={cat.id} data-testid="category-item" className="p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-background/40 border border-slate-800/50 rounded-2xl flex items-center justify-between group hover:border-primary/40 transition-all">
<div className="min-w-0 pr-4">
@@ -71,39 +71,39 @@ export default function CategoryManager({
{/* Edit Category Modal */}
{editingCategory && (
<div className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center p-0 sm:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-background/90 animate-in fade-in duration-300">
<div className="w-full max-w-lg bg-surface border-x border-t sm:border border-slate-800 rounded-t-[2.5rem] sm:rounded-3xl p-6 sm:p-10 shadow-2xl space-y-8 overflow-hidden animate-in slide-in-from-bottom-10">
<div className="flex justify-between items-center">
<div className="flex items-center gap-4">
<div className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center p-0 sm:p-4 bg-background/90 animate-in fade-in duration-300">
<div className="w-full max-w-lg bg-surface border-x border-t sm:border border-slate-800 rounded-t-[2.5rem] sm:rounded-3xl p-6 shadow-2xl space-y-3 overflow-hidden animate-in slide-in-from-bottom-10">
<div className="flex justify-between items-center mb-2">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
<Edit2 size={20} />
</div>
<h3 className="text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-black text-white tracking-tight">Modify Group</h3>
<h3 className="text-xl font-normal text-white tracking-tight">Modify Group</h3>
</div>
<button onClick={() => setEditingCategory(null)} className="p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 hover:bg-slate-800 rounded-2xl text-muted transition-colors border border-slate-800">
<button onClick={() => setEditingCategory(null)} className="p-2 hover:bg-slate-800 rounded-2xl text-muted transition-colors border border-slate-800">
<X size={20} />
</button>
</div>
<div data-testid="category-form" className="space-y-6">
<div className="space-y-1.5">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">Group Identifier</label>
<div data-testid="category-form" className="space-y-2">
<div className="space-y-1">
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Group Identifier</label>
<input
data-testid="category-name-input"
type="text"
value={editCatForm.name}
onChange={(e) => setEditCatForm({...editCatForm, name: e.target.value})}
className="w-full bg-background border border-slate-800 rounded-2xl py-3 lg:py-4 xl:py-5.5 px-5 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl text-white focus:border-primary outline-none transition-all"
className="w-full bg-background border border-slate-800 rounded-2xl py-1.5 px-4 text-sm text-white focus:border-primary outline-none transition-all"
/>
</div>
<div className="space-y-1.5">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">Strategic Description</label>
<textarea
<div className="space-y-1">
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Strategic Description</label>
<textarea
value={editCatForm.description}
onChange={(e) => setEditCatForm({...editCatForm, description: e.target.value})}
className="w-full bg-background border border-slate-800 rounded-2xl py-4 px-5 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl text-white focus:border-primary outline-none transition-all h-32 resize-none"
className="w-full bg-background border border-slate-800 rounded-2xl py-1.5 px-4 text-sm text-white focus:border-primary outline-none transition-all h-24 resize-none"
/>
</div>
<button data-testid="add-category-submit" onClick={onUpdateCategorySubmit} className="w-full bg-primary hover:bg-primary text-white font-black py-4 rounded-2xl shadow-xl shadow-primary/20 active:scale-95 transition-all text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl mb-4 tracking-tight">
<button data-testid="add-category-submit" onClick={onUpdateCategorySubmit} className="w-full bg-primary hover:bg-primary text-white font-normal py-2.5 rounded-2xl shadow-xl shadow-primary/20 active:scale-95 transition-all text-sm tracking-tight">
Update Asset Group
</button>
</div>

View File

@@ -34,33 +34,33 @@ export default function DatabaseManager({
};
return (
<div className="space-y-6 md:space-y-8 h-full">
<div data-testid="database-info" className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-5 md:p-8 shadow-2xl overflow-hidden relative group transition-all">
<div className="flex items-center gap-4 mb-6">
<div className="space-y-3 md:space-y-4 h-full">
<div data-testid="database-info" className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-4 md:p-6 shadow-2xl overflow-hidden relative group transition-all">
<div className="flex items-center gap-3 mb-3">
<div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
<Database size={20} />
</div>
<h2 className="text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-black text-white tracking-tight">System Integrity</h2>
<h2 className="text-xl font-normal text-white tracking-tight">System Integrity</h2>
</div>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-6">
<div className="space-y-1 sm:pr-6">
<p className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-muted tracking-tight">Database Health</p>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="space-y-1 sm:pr-4">
<p className="text-xs font-normal text-muted tracking-tight">Database Health</p>
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse shadow-[0_0_8px_rgba(34,197,94,0.3)]" />
<span className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary">Operational</span>
<span className="text-sm font-normal text-secondary">Operational</span>
</div>
</div>
<div className="sm:pl-6 sm:border-l border-slate-800">
<p className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-muted tracking-tight">Last Backup</p>
<p className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tabular-nums">{dbStats.backup_count > 0 ? 'Verified' : 'Pending...'}</p>
<div className="sm:pl-4 sm:border-l border-slate-800">
<p className="text-xs font-normal text-muted tracking-tight">Last Backup</p>
<p className="text-sm font-normal text-secondary tabular-nums">{dbStats.backup_count > 0 ? 'Verified' : 'Pending...'}</p>
</div>
<div className="ml-auto">
<button
onClick={onCreateBackup}
disabled={isBackingUp}
data-testid="backup-button"
className="flex items-center gap-2 px-5 py-3 lg:py-4 xl:py-5 bg-primary hover:bg-blue-600 text-white rounded-xl text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black transition-all active:scale-95 disabled:opacity-50 shadow-xl shadow-primary/10 tracking-tight focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none"
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 active:scale-95 disabled:opacity-50 shadow-xl shadow-primary/10 tracking-tight focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none"
aria-label="Create database backup"
>
{isBackingUp ? <RotateCcw size={14} className="animate-spin" /> : <Database size={14} />}
@@ -70,75 +70,75 @@ export default function DatabaseManager({
</div>
</div>
<div className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-5 md:p-8 shadow-2xl space-y-8">
<div className="space-y-6">
<div className="flex items-center gap-4">
<div className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-4 md:p-6 shadow-2xl space-y-4">
<div className="space-y-3">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
<History size={20} />
</div>
<div>
<h3 className="text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black text-white tracking-tight">Storage Policy</h3>
<p className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl text-secondary font-bold tracking-tight">Retention & Maintenance</p>
<h3 className="text-lg font-normal text-white tracking-tight">Storage Policy</h3>
<p className="text-sm text-secondary font-normal tracking-tight">Retention & Maintenance</p>
</div>
</div>
<div className="grid sm:grid-cols-3 gap-6">
<div className="space-y-2">
<label className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-muted tracking-tight ml-1 flex items-center gap-2">
<div className="grid sm:grid-cols-3 gap-3">
<div className="space-y-1">
<label className="text-sm font-normal text-muted tracking-tight ml-1 flex items-center gap-2">
<History size={10} /> Max Backups
</label>
<input
type="number"
<input
type="number"
value={dbSettings.retention_count}
onChange={(e) => onUpdateDbSettings({...dbSettings, retention_count: parseInt(e.target.value)})}
className="w-full bg-background/80 border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-white outline-none focus:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none transition-all tabular-nums"
className="w-full bg-background/80 border border-slate-800 rounded-xl py-1.5 px-4 text-xs font-normal text-white outline-none focus:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none transition-all tabular-nums"
/>
</div>
<div className="space-y-2">
<label className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-muted tracking-tight ml-1 flex items-center gap-2">
<div className="space-y-1">
<label className="text-sm font-normal text-muted tracking-tight ml-1 flex items-center gap-2">
<Clock size={10} /> Schedule (Hour)
</label>
<input
type="number"
<input
type="number"
min="0" max="23"
value={dbSettings.schedule_hour}
onChange={(e) => onUpdateDbSettings({...dbSettings, schedule_hour: parseInt(e.target.value)})}
className="w-full bg-background/80 border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-white outline-none focus:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none transition-all tabular-nums"
className="w-full bg-background/80 border border-slate-800 rounded-xl py-1.5 px-4 text-xs font-normal text-white outline-none focus:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none transition-all tabular-nums"
/>
</div>
<div className="space-y-2">
<label className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-muted tracking-tight ml-1 flex items-center gap-2">
<div className="space-y-1">
<label className="text-sm font-normal text-muted tracking-tight ml-1 flex items-center gap-2">
<Zap size={10} /> Frequency (Days)
</label>
<input
type="number"
<input
type="number"
min="1"
value={dbSettings.schedule_freq_days}
onChange={(e) => onUpdateDbSettings({...dbSettings, schedule_freq_days: parseInt(e.target.value)})}
className="w-full bg-background/80 border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-white outline-none focus:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none transition-all tabular-nums"
className="w-full bg-background/80 border border-slate-800 rounded-xl py-1.5 px-4 text-xs font-normal text-white outline-none focus:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none transition-all tabular-nums"
/>
</div>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between px-1">
<label className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-muted tracking-tight">Recovery Points</label>
<div className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-primary tracking-tight bg-primary/5 px-3 lg:px-4 xl:px-5 py-1 rounded-full border border-primary/10">
<label className="text-sm font-normal text-muted tracking-tight">Recovery Points</label>
<div className="text-xs font-normal text-primary tracking-tight bg-primary/5 px-3 py-1 rounded-full border border-primary/10">
{formatSize(dbStats.total_size_bytes)} Used
</div>
</div>
<div className="space-y-2 pr-2 overflow-y-auto max-h-[300px] scrollbar-thin scrollbar-thumb-slate-800 scrollbar-track-transparent">
<div className="space-y-1 pr-2 overflow-y-auto max-h-[300px] scrollbar-thin scrollbar-thumb-slate-800 scrollbar-track-transparent">
{backups.map((bak: any) => (
<div key={bak.filename} className="flex items-center justify-between p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8.5 bg-background/40 border border-slate-800/40 rounded-2xl group/item hover:border-primary/30 transition-all">
<div key={bak.filename} className="flex items-center justify-between p-3 bg-background/40 border border-slate-800/40 rounded-2xl group/item hover:border-primary/30 transition-all">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-slate-800 flex items-center justify-center text-muted">
<Database size={14} />
</div>
<div>
<p className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-secondary">{bak.filename}</p>
<p className="text-[11px] text-secondary font-medium tabular-nums">
<p className="text-xs font-normal text-secondary">{bak.filename}</p>
<p className="text-xs text-secondary font-medium tabular-nums">
{new Date(bak.created_at).toLocaleString()} {formatSize(bak.size_bytes)}
</p>
</div>
@@ -155,23 +155,23 @@ export default function DatabaseManager({
</div>
</div>
<div className="grid grid-cols-2 gap-4 pt-2">
<div className="grid grid-cols-2 gap-3 pt-1">
<button
onClick={onExport}
className="flex items-center justify-center gap-2 py-4 bg-background border border-slate-800 hover:border-primary/50 text-primary rounded-2xl text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black transition-all active:scale-95 tracking-tight shadow-xl shadow-black/20 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none"
className="flex items-center justify-center gap-2 py-2.5 bg-background border border-slate-800 hover:border-primary/50 text-primary rounded-2xl text-sm font-normal transition-all active:scale-95 tracking-tight shadow-xl shadow-black/20 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none"
aria-label="Export database"
>
<Download size={14} /> Export Database
</button>
<div className="relative">
<input
type="file"
<input
type="file"
accept=".db"
onChange={onImport}
className="absolute inset-0 opacity-0 cursor-pointer z-10"
/>
<button
className="w-full flex items-center justify-center gap-2 py-4 bg-background border border-slate-800 hover:border-rose-500/50 text-rose-500 rounded-2xl text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black transition-all tracking-tight shadow-xl shadow-black/20 focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:outline-none"
className="w-full flex items-center justify-center gap-2 py-2.5 bg-background border border-slate-800 hover:border-rose-500/50 text-rose-500 rounded-2xl text-sm font-normal transition-all tracking-tight shadow-xl shadow-black/20 focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:outline-none"
aria-label="Import database"
>
<Upload size={14} /> Import Database

View File

@@ -0,0 +1,137 @@
"use client";
import { useState } from "react";
import { FileDown, Loader2 } from "lucide-react";
import { useExport } from "@/hooks/useExport";
import { Toast } from "@/components/Toast";
export function ExportPanel() {
const { exportSnapshot, exportAuditTrail, isLoading, error } = useExport();
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const handleExportSnapshot = async (format: "csv" | "xlsx") => {
try {
setSuccessMessage(null);
await exportSnapshot(format);
const formatName = format === "csv" ? "CSV" : "Excel";
setSuccessMessage(`Inventory snapshot exported as ${formatName}`);
setTimeout(() => setSuccessMessage(null), 4000);
} catch (err) {
// Error handled by useExport hook
}
};
const handleExportAuditTrail = async (format: "csv" | "xlsx") => {
try {
setSuccessMessage(null);
await exportAuditTrail(format);
const formatName = format === "csv" ? "CSV" : "Excel";
setSuccessMessage(`Audit trail exported as ${formatName}`);
setTimeout(() => setSuccessMessage(null), 4000);
} catch (err) {
// Error handled by useExport hook
}
};
return (
<div className="space-y-6">
<div className="rounded-lg border border-slate-200 bg-white p-6">
<div className="mb-6 flex items-center gap-3">
<div className="rounded-lg bg-primary/10 p-4 border border-primary/20">
<FileDown className="text-primary" size={24} />
</div>
<div>
<h2 className="text-2xl font-normal">Export & Reports</h2>
<p className="text-xs text-slate-500">
Download inventory snapshots and audit trails in CSV or Excel formats
</p>
</div>
</div>
{/* Inventory Snapshot Section */}
<div className="mb-8 space-y-3">
<h3 className="font-normal text-slate-700">Inventory Snapshot</h3>
<p className="text-sm text-slate-500">
Export current inventory state with all item details
</p>
<div className="flex flex-col gap-2 sm:flex-row">
<button
onClick={() => handleExportSnapshot("csv")}
disabled={isLoading}
className="flex items-center justify-center gap-2 rounded-md bg-blue-500 px-4 py-2 text-sm font-normal text-white hover:bg-blue-600 disabled:bg-slate-400 disabled:cursor-not-allowed transition-colors"
aria-label="Export inventory snapshot as CSV"
>
{isLoading ? (
<Loader2 size={16} className="animate-spin" />
) : (
<FileDown size={16} />
)}
{isLoading ? "Exporting..." : "Export as CSV"}
</button>
<button
onClick={() => handleExportSnapshot("xlsx")}
disabled={isLoading}
className="flex items-center justify-center gap-2 rounded-md bg-green-500 px-4 py-2 text-sm font-normal text-white hover:bg-green-600 disabled:bg-slate-400 disabled:cursor-not-allowed transition-colors"
aria-label="Export inventory snapshot as Excel"
>
{isLoading ? (
<Loader2 size={16} className="animate-spin" />
) : (
<FileDown size={16} />
)}
{isLoading ? "Exporting..." : "Export as Excel"}
</button>
</div>
</div>
{/* Audit Trail Section */}
<div className="space-y-3">
<h3 className="font-normal text-slate-700">Audit Trail</h3>
<p className="text-sm text-slate-500">
Export complete audit log of all system actions and changes
</p>
<div className="flex flex-col gap-2 sm:flex-row">
<button
onClick={() => handleExportAuditTrail("csv")}
disabled={isLoading}
className="flex items-center justify-center gap-2 rounded-md bg-blue-500 px-4 py-2 text-sm font-normal text-white hover:bg-blue-600 disabled:bg-slate-400 disabled:cursor-not-allowed transition-colors"
aria-label="Export audit trail as CSV"
>
{isLoading ? (
<Loader2 size={16} className="animate-spin" />
) : (
<FileDown size={16} />
)}
{isLoading ? "Exporting..." : "Export as CSV"}
</button>
<button
onClick={() => handleExportAuditTrail("xlsx")}
disabled={isLoading}
className="flex items-center justify-center gap-2 rounded-md bg-green-500 px-4 py-2 text-sm font-normal text-white hover:bg-green-600 disabled:bg-slate-400 disabled:cursor-not-allowed transition-colors"
aria-label="Export audit trail as Excel"
>
{isLoading ? (
<Loader2 size={16} className="animate-spin" />
) : (
<FileDown size={16} />
)}
{isLoading ? "Exporting..." : "Export as Excel"}
</button>
</div>
</div>
</div>
{/* Status Messages */}
{error && (
<Toast
type="error"
message={`Export failed: ${error}`}
onClose={() => {}}
/>
)}
{successMessage && (
<Toast type="success" message={successMessage} onClose={() => {}} />
)}
</div>
);
}

View File

@@ -24,27 +24,27 @@ export default function IdentityManager({
onUpdateUserSubmit
}: IdentityManagerProps) {
return (
<div className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-5 md:p-8 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="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-3">
<div className="flex items-center gap-3">
<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 lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-black text-white tracking-tight">Identity Management</h2>
<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 lg:py-3 xl:py-4.5 bg-primary hover:bg-blue-600 text-white rounded-xl text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black 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"
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-2.5 pr-2 -mr-2 overflow-y-auto max-h-[400px] scrollbar-thin scrollbar-thumb-slate-800 scrollbar-track-transparent">
<div className="flex-1 space-y-1 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 lg:p-4 xl:p-5 lg:p-6 xl:p-8.5 bg-background/40 border border-slate-800/40 rounded-2xl hover:border-primary/30 transition-all group">
<div key={user.id} className="flex items-center justify-between p-3 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",
@@ -85,10 +85,10 @@ export default function IdentityManager({
{/* Edit User Modal */}
{editingUser && (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-background/80 animate-in fade-in duration-200">
<div className="bg-surface border border-slate-800 rounded-[2.5rem] p-8 w-full max-w-md shadow-2xl">
<div className="flex justify-between items-center mb-6">
<h3 className="text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-black text-white tracking-tight">Edit Identity</h3>
<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-3">
<h3 className="text-xl font-normal text-white tracking-tight">Edit Identity</h3>
<button
onClick={() => setEditingUser(null)}
className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 text-muted hover:text-white transition-colors focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none rounded"
@@ -98,41 +98,41 @@ export default function IdentityManager({
</button>
</div>
<div className="space-y-4">
<div className="space-y-1.5">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">Username</label>
<input
type="text"
<div className="space-y-2">
<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-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold 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"
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-1.5 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.5">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">New Password (Leave Empty To Keep)</label>
<input
type="password"
<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-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold 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"
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-1.5 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.5">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">Role</label>
<select
<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-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-white outline-none focus:border-primary/50 transition-all appearance-none"
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-1.5 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 lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black 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"
className="w-full bg-primary hover:bg-primary/80 text-white rounded-2xl py-2.5 text-sm font-normal transition-all active:scale-95 shadow-xl shadow-primary/20 tracking-tight mt-2 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>

View File

@@ -18,13 +18,13 @@ export default function LdapManager({
onUpdateLdap
}: LdapManagerProps) {
return (
<div className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-5 md:p-8 shadow-2xl space-y-6 transition-all group/ldap">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-4">
<div className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-4 md:p-6 shadow-2xl space-y-3 transition-all group/ldap">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
<Globe size={20} />
</div>
<h2 className="text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-black text-white tracking-tight">Enterprise LDAP</h2>
<h2 className="text-xl font-normal text-white tracking-tight">Enterprise LDAP</h2>
</div>
</div>
@@ -57,59 +57,59 @@ export default function LdapManager({
</button>
</div>
<div className="grid sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">LDAP URI</label>
<div className="grid sm:grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-sm font-normal text-secondary tracking-tight ml-1">LDAP URI</label>
<div className="relative">
<Server className="absolute left-3.5 top-1/2 -translate-y-1/2 text-muted" size={14} />
<input
type="text"
<Server className="absolute left-3 top-1/2 -translate-y-1/2 text-muted" size={14} />
<input
type="text"
placeholder="ldap://host:389"
value={ldapConfig.server_uri}
onChange={(e) => setLdapConfig({...ldapConfig, server_uri: e.target.value})}
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-2 lg:py-3 xl:py-4.5 pl-10 pr-4 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-white outline-none focus:border-primary/50 transition-all font-mono"
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-1.5 pl-9 pr-4 text-xs font-normal text-white outline-none focus:border-primary/50 transition-all font-mono"
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">Context DN</label>
<div className="space-y-1">
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Context DN</label>
<div className="relative">
<Shield className="absolute left-3.5 top-1/2 -translate-y-1/2 text-muted" size={14} />
<input
type="text"
<Shield className="absolute left-3 top-1/2 -translate-y-1/2 text-muted" size={14} />
<input
type="text"
placeholder="dc=example,dc=com"
value={ldapConfig.base_dn}
onChange={(e) => setLdapConfig({...ldapConfig, base_dn: e.target.value})}
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-2 lg:py-3 xl:py-4.5 pl-10 pr-4 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-white outline-none focus:border-primary/50 transition-all font-mono"
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-1.5 pl-9 pr-4 text-xs font-normal text-white outline-none focus:border-primary/50 transition-all font-mono"
/>
</div>
</div>
</div>
<div className="grid sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">User DN Template</label>
<div className="grid sm:grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-sm font-normal text-secondary tracking-tight ml-1">User DN Template</label>
<div className="relative">
<User className="absolute left-3.5 top-1/2 -translate-y-1/2 text-muted" size={14} />
<input
type="text"
<User className="absolute left-3 top-1/2 -translate-y-1/2 text-muted" size={14} />
<input
type="text"
placeholder="uid={username},ou=people..."
value={ldapConfig.user_template}
onChange={(e) => setLdapConfig({...ldapConfig, user_template: e.target.value})}
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-2 lg:py-3 xl:py-4.5 pl-10 pr-4 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-white outline-none focus:border-primary/50 transition-all font-mono"
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-1.5 pl-9 pr-4 text-xs font-normal text-white outline-none focus:border-primary/50 transition-all font-mono"
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-bold text-secondary tracking-tight ml-1">Groups Base DN</label>
<div className="space-y-1">
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Groups Base DN</label>
<div className="relative">
<Layers className="absolute left-3.5 top-1/2 -translate-y-1/2 text-muted" size={14} />
<input
type="text"
<Layers className="absolute left-3 top-1/2 -translate-y-1/2 text-muted" size={14} />
<input
type="text"
placeholder="ou=groups"
value={ldapConfig.groups_dn}
onChange={(e) => setLdapConfig({...ldapConfig, groups_dn: e.target.value})}
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-2 lg:py-3 xl:py-4.5 pl-10 pr-4 text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-bold text-white outline-none focus:border-primary/50 transition-all font-mono"
className="w-full bg-background/80 border border-slate-800 rounded-2xl py-1.5 pl-9 pr-4 text-xs font-normal text-white outline-none focus:border-primary/50 transition-all font-mono"
/>
</div>
</div>
@@ -144,18 +144,18 @@ export default function LdapManager({
</button>
</div>
<div className="flex gap-2 pt-2">
<button
<div className="flex gap-2 pt-0">
<button
onClick={onTestLdap}
disabled={testingLdap}
className="flex-1 bg-primary hover:bg-primary text-white rounded-xl py-3 lg:py-4 xl:py-5 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black shadow-xl shadow-primary/10 transition-all active:scale-95 disabled:opacity-50 flex items-center justify-center gap-2 border border-primary/30"
className="flex-1 bg-primary hover:bg-primary text-white rounded-xl py-1.5 text-sm font-normal shadow-xl shadow-primary/10 transition-all active:scale-95 disabled:opacity-50 flex items-center justify-center gap-2 border border-primary/30"
>
{testingLdap ? <RotateCcw size={14} className="animate-spin" /> : <Wifi size={14} />}
Test Connection
</button>
<button
<button
onClick={onUpdateLdap}
className="flex-[2] bg-primary hover:bg-primary text-white rounded-xl py-3 lg:py-4 xl:py-5 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-black shadow-xl shadow-primary/10 transition-all active:scale-95 flex items-center justify-center gap-2 border border-primary/30 tracking-tight"
className="flex-[2] bg-primary hover:bg-primary text-white rounded-xl py-2 text-sm font-normal shadow-xl shadow-primary/10 transition-all active:scale-95 flex items-center justify-center gap-2 border border-primary/30 tracking-tight"
>
<Shield size={14} /> Save LDAP Policy
</button>