Build [v1.10.4]

This commit is contained in:
2026-04-15 18:10:04 +03:00
parent 6760ab0abf
commit 2d162ff3c0
10 changed files with 237 additions and 226 deletions

View File

@@ -12,12 +12,12 @@ export default function StatCard({ label, value, icon: Icon }: StatCardProps) {
<div className="flex justify-between items-center gap-2 p-3.5 bg-slate-900/50 backdrop-blur-md border border-slate-800/50 rounded-2xl shadow-sm transition-all hover:bg-slate-900/80" role="status">
<div className="flex items-center gap-2.5 min-w-0">
{Icon && <Icon className="w-5 h-5 text-primary flex-shrink-0" aria-hidden="true" />}
<span className="text-sm md:text-base text-slate-400 font-medium truncate">
<span className="text-base md:text-lg text-slate-300 font-semibold truncate">
{label}
</span>
</div>
<span className="text-lg md:text-xl font-black text-white whitespace-nowrap tabular-nums">
<span className="text-2xl md:text-3xl font-black text-white whitespace-nowrap tabular-nums">
{value}
</span>
</div>

View File

@@ -64,7 +64,7 @@ export default function AiManager({
<div>
<p className={cn("text-xs font-black tracking-tight", p.active ? "text-white" : "text-slate-200")}>{p.name}</p>
<p className={cn(
"text-[9px] font-black mt-0.5 px-0.5 rounded",
"text-[11px] font-bold mt-1 px-0.5 rounded",
p.active
? (p.configured ? "text-emerald-200" : "text-rose-100")
: (p.configured ? "text-emerald-500" : "text-rose-500")
@@ -74,7 +74,7 @@ export default function AiManager({
</div>
</div>
{p.active && (
<div className="bg-white/20 px-2 py-0.5 rounded-full text-[8px] font-black text-white tracking-tighter">
<div className="bg-white/20 px-2.5 py-1 rounded-full text-[10px] font-black text-white tracking-tight">
Active
</div>
)}
@@ -86,12 +86,12 @@ export default function AiManager({
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 bg-indigo-500/10 rounded-lg text-indigo-400"><Lock size={14} /></div>
<h3 className="text-xs font-black text-slate-200 tracking-tight">Provider Access Keys</h3>
<h3 className="text-sm font-bold text-slate-200 tracking-tight">Provider Access Keys</h3>
</div>
<button
onClick={onSaveAiKeys}
disabled={isSavingKeys}
className="px-6 py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-[10px] font-black transition-all active:scale-95 shadow-xl shadow-indigo-600/10 tracking-tight border border-indigo-500/30 flex items-center gap-2"
className="px-6 py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-sm font-black transition-all active:scale-95 shadow-xl shadow-indigo-600/10 tracking-tight border border-indigo-500/30 flex items-center gap-2"
>
{isSavingKeys ? <RotateCcw size={14} className="animate-spin" /> : <Lock size={14} />}
{isSavingKeys ? "Storing..." : "Store Keys"}
@@ -100,7 +100,7 @@ export default function AiManager({
<div className="grid md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-[9px] font-black text-slate-500 tracking-tight ml-1">Gemini Api Key</label>
<label className="text-sm font-bold text-slate-400 tracking-tight ml-1">Gemini Api Key</label>
<div className="flex gap-2">
<input
type="password"
@@ -113,7 +113,7 @@ export default function AiManager({
onClick={() => onTestAiKey('gemini')}
disabled={isTestingKeys.gemini}
className={cn(
"px-4 py-2 rounded-xl text-[9px] font-black tracking-tighter transition-all border shadow-lg flex items-center gap-1.5",
"px-4 py-2 rounded-xl text-[11px] font-bold tracking-tight transition-all border shadow-lg flex items-center gap-1.5",
isTestingKeys.gemini
? "bg-slate-800 border-slate-700 text-slate-500 cursor-wait"
: "bg-indigo-600 border-indigo-500 text-white hover:bg-indigo-500"
@@ -125,7 +125,7 @@ export default function AiManager({
</div>
</div>
<div className="space-y-2">
<label className="text-[9px] font-black text-slate-500 tracking-tight ml-1">Claude Api Key</label>
<label className="text-sm font-bold text-slate-400 tracking-tight ml-1">Claude Api Key</label>
<div className="flex gap-2">
<input
type="password"
@@ -138,7 +138,7 @@ export default function AiManager({
onClick={() => onTestAiKey('claude')}
disabled={isTestingKeys.claude}
className={cn(
"px-4 py-2 rounded-xl text-[9px] font-black uppercase tracking-tighter transition-all border shadow-lg flex items-center gap-1.5",
"px-4 py-2 rounded-xl text-[11px] font-bold tracking-tight transition-all border shadow-lg flex items-center gap-1.5",
isTestingKeys.claude
? "bg-slate-800 border-slate-700 text-slate-500 cursor-wait"
: "bg-indigo-600 border-indigo-500 text-white hover:bg-indigo-500"
@@ -155,11 +155,11 @@ export default function AiManager({
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between px-1">
<label className="text-[10px] font-black text-slate-500 tracking-tight">System Prompt (Vision Extraction)</label>
<label className="text-sm font-bold text-slate-400 tracking-tight">System Prompt (Vision Extraction)</label>
<button
onClick={onUpdatePrompt}
disabled={isSavingPrompt}
className="px-6 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-[10px] font-black transition-all active:scale-95 shadow-xl shadow-indigo-600/10 tracking-tight border border-indigo-500/30 flex items-center gap-2"
className="px-6 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-sm font-black transition-all active:scale-95 shadow-xl shadow-indigo-600/10 tracking-tight border border-indigo-500/30 flex items-center gap-2"
>
{isSavingPrompt ? <RotateCcw size={14} className="animate-spin" /> : <FileText size={14} />}
{isSavingPrompt ? "Saving..." : "Save Prompt"}
@@ -168,12 +168,12 @@ export default function AiManager({
<textarea
value={aiPrompt}
onChange={(e) => setAiPrompt(e.target.value)}
className="w-full bg-slate-950/80 border border-slate-800 rounded-2xl p-6 text-[11px] font-mono font-bold text-slate-300 leading-relaxed outline-none focus:border-purple-500/50 transition-all min-h-[200px] custom-scrollbar shadow-inner"
className="w-full bg-slate-950/80 border border-slate-800 rounded-2xl p-6 text-xs font-mono font-bold text-slate-300 leading-relaxed outline-none focus:border-purple-500/50 transition-all min-h-[200px] custom-scrollbar shadow-inner"
/>
</div>
<div className="bg-purple-500/5 border border-purple-500/10 rounded-2xl p-4 flex gap-4 items-start">
<div className="p-2 bg-purple-500/10 rounded-lg text-purple-400 shrink-0"><Shield size={14} /></div>
<p className="text-[10px] font-bold text-slate-500 leading-relaxed">
<p className="text-xs font-medium text-slate-400 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

@@ -33,7 +33,7 @@ export default function CategoryManager({
</div>
<button
onClick={onAddCategory}
className="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-500 text-white px-6 py-3 rounded-xl text-xs font-black transition-all shadow-xl shadow-indigo-600/10 active:scale-95 border border-indigo-500/30 tracking-tight"
className="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-500 text-white px-6 py-3 rounded-xl text-sm font-black transition-all shadow-xl shadow-indigo-600/10 active:scale-95 border border-indigo-500/30 tracking-tight"
>
<Plus size={14} /> New Group
</button>
@@ -44,7 +44,7 @@ export default function CategoryManager({
<div key={cat.id} className="p-4 bg-slate-950/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">
<p className="text-sm font-bold text-slate-200 group-hover:text-primary transition-colors">{cat.name}</p>
<p className="text-[10px] text-slate-500 font-medium mt-0.5">{cat.description || 'General storage'}</p>
<p className="text-xs text-slate-500 font-medium mt-0.5">{cat.description || 'General storage'}</p>
</div>
<div className="flex gap-1 shrink-0">
<button
@@ -84,7 +84,7 @@ export default function CategoryManager({
</div>
<div className="space-y-6">
<div className="space-y-1.5">
<label className="text-[10px] font-black text-slate-500 tracking-tight ml-1">Group Identifier</label>
<label className="text-sm font-bold text-slate-400 tracking-tight ml-1">Group Identifier</label>
<input
type="text"
value={editCatForm.name}
@@ -93,14 +93,14 @@ export default function CategoryManager({
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-slate-500 tracking-tight ml-1">Strategic Description</label>
<label className="text-sm font-bold text-slate-400 tracking-tight ml-1">Strategic Description</label>
<textarea
value={editCatForm.description}
onChange={(e) => setEditCatForm({...editCatForm, description: e.target.value})}
className="w-full bg-slate-950 border border-slate-800 rounded-2xl py-4 px-5 text-sm text-white focus:border-primary outline-none transition-all h-32 resize-none"
/>
</div>
<button onClick={onUpdateCategorySubmit} className="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-black py-4 rounded-2xl shadow-xl shadow-indigo-600/20 active:scale-95 transition-all text-xs mb-4 tracking-tight">
<button onClick={onUpdateCategorySubmit} className="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-black py-4 rounded-2xl shadow-xl shadow-indigo-600/20 active:scale-95 transition-all text-sm mb-4 tracking-tight">
Update Asset Group
</button>
</div>

View File

@@ -45,22 +45,22 @@ export default function DatabaseManager({
<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-[10px] font-black text-slate-500 tracking-tight">Database Health</p>
<p className="text-xs font-bold text-slate-500 tracking-tight uppercase">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 font-bold text-slate-200">Operational</span>
</div>
</div>
<div className="sm:pl-6 sm:border-l border-slate-800">
<p className="text-[10px] font-black text-slate-500 tracking-tight">Last Backup</p>
<p className="text-xs font-bold text-slate-500 tracking-tight uppercase">Last Backup</p>
<p className="text-sm font-bold text-slate-200 tabular-nums">{dbStats.backup_count > 0 ? 'Verified' : 'Pending...'}</p>
</div>
<div className="ml-auto">
<button
onClick={onCreateBackup}
disabled={isBackingUp}
className="flex items-center gap-2 px-5 py-3 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-[10px] font-black transition-all active:scale-95 disabled:opacity-50 shadow-xl shadow-indigo-600/10 tracking-tight"
>
<button
onClick={onCreateBackup}
disabled={isBackingUp}
className="flex items-center gap-2 px-5 py-3 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-sm font-black transition-all active:scale-95 disabled:opacity-50 shadow-xl shadow-indigo-600/10 tracking-tight"
>
{isBackingUp ? <RotateCcw size={14} className="animate-spin" /> : <Database size={14} />}
{isBackingUp ? "Snapshotting..." : "Force Backup"}
</button>
@@ -75,14 +75,14 @@ export default function DatabaseManager({
<History size={20} />
</div>
<div>
<h3 className="text-lg font-black text-white tracking-tight">Storage Policy</h3>
<p className="text-[10px] text-slate-500 font-bold tracking-tight">Retention & Maintenance</p>
</div>
<h3 className="text-lg font-black text-white tracking-tight">Storage Policy</h3>
<p className="text-xs text-slate-400 font-bold tracking-tight">Retention & Maintenance</p>
</div>
</div>
<div className="grid sm:grid-cols-3 gap-6">
<div className="space-y-2">
<label className="text-[9px] font-black text-slate-500 tracking-tight ml-1 flex items-center gap-2">
<label className="text-xs font-bold text-slate-500 tracking-tight ml-1 flex items-center gap-2 uppercase">
<History size={10} /> Max Backups
</label>
<input
@@ -93,7 +93,7 @@ export default function DatabaseManager({
/>
</div>
<div className="space-y-2">
<label className="text-[9px] font-black text-slate-500 tracking-tight ml-1 flex items-center gap-2">
<label className="text-xs font-bold text-slate-500 tracking-tight ml-1 flex items-center gap-2 uppercase">
<Clock size={10} /> Schedule (Hour)
</label>
<input
@@ -105,7 +105,7 @@ export default function DatabaseManager({
/>
</div>
<div className="space-y-2">
<label className="text-[9px] font-black text-slate-500 tracking-tight ml-1 flex items-center gap-2">
<label className="text-xs font-bold text-slate-500 tracking-tight ml-1 flex items-center gap-2 uppercase">
<Zap size={10} /> Frequency (Days)
</label>
<input
@@ -121,10 +121,10 @@ export default function DatabaseManager({
<div className="space-y-4">
<div className="flex items-center justify-between px-1">
<label className="text-[10px] font-black text-slate-500 tracking-tight">Recovery Points</label>
<div className="text-[10px] font-black text-indigo-400 tracking-tight bg-indigo-500/5 px-3 py-1 rounded-full border border-indigo-500/10">
{formatSize(dbStats.total_size_bytes)} Used
</div>
<label className="text-xs font-bold text-slate-500 tracking-tight">Recovery Points</label>
<div className="text-xs font-bold text-indigo-400 tracking-tight bg-indigo-500/5 px-3 py-1 rounded-full border border-indigo-500/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">
@@ -135,10 +135,10 @@ export default function DatabaseManager({
<Database size={14} />
</div>
<div>
<p className="text-[11px] font-bold text-slate-200">{bak.filename}</p>
<p className="text-[9px] text-slate-500 font-bold tabular-nums">
{new Date(bak.created_at).toLocaleString()} {formatSize(bak.size_bytes)}
</p>
<p className="text-xs font-bold text-slate-200">{bak.filename}</p>
<p className="text-[11px] text-slate-400 font-medium tabular-nums">
{new Date(bak.created_at).toLocaleString()} {formatSize(bak.size_bytes)}
</p>
</div>
</div>
<button
@@ -154,12 +154,12 @@ export default function DatabaseManager({
</div>
<div className="grid grid-cols-2 gap-4 pt-2">
<button
onClick={onExport}
className="flex items-center justify-center gap-2 py-4 bg-slate-950 border border-slate-800 hover:border-indigo-500/50 text-indigo-400 rounded-2xl text-[10px] font-black transition-all active:scale-95 tracking-tight shadow-xl shadow-black/20"
>
<Download size={14} /> Export Database
</button>
<button
onClick={onExport}
className="flex items-center justify-center gap-2 py-4 bg-slate-950 border border-slate-800 hover:border-indigo-500/50 text-indigo-400 rounded-2xl text-sm font-black transition-all active:scale-95 tracking-tight shadow-xl shadow-black/20"
>
<Download size={14} /> Export Database
</button>
<div className="relative">
<input
type="file"
@@ -167,9 +167,9 @@ export default function DatabaseManager({
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-slate-950 border border-slate-800 hover:border-rose-500/50 text-rose-500 rounded-2xl text-[10px] font-black transition-all tracking-tight shadow-xl shadow-black/20">
<Upload size={14} /> Import Database
</button>
<button className="w-full flex items-center justify-center gap-2 py-4 bg-slate-950 border border-slate-800 hover:border-rose-500/50 text-rose-500 rounded-2xl text-sm font-black transition-all tracking-tight shadow-xl shadow-black/20">
<Upload size={14} /> Import Database
</button>
</div>
</div>
</div>

View File

@@ -34,7 +34,7 @@ export default function IdentityManager({
</div>
<button
onClick={onAddUser}
className="flex items-center gap-2 px-5 py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-xs font-black transition-all shadow-xl shadow-indigo-600/10 active:scale-95 border border-indigo-500/30 tracking-tight"
className="flex items-center gap-2 px-5 py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-sm font-black transition-all shadow-xl shadow-indigo-600/10 active:scale-95 border border-indigo-500/30 tracking-tight"
>
<UserPlus size={16} /> Add User
</button>
@@ -52,7 +52,7 @@ export default function IdentityManager({
</div>
<div className="min-w-0">
<p className="text-sm font-bold text-slate-200 truncate">{user.username}</p>
<p className="text-[9px] text-slate-500 font-black tracking-tight capitalize">{user.role}</p>
<p className="text-[11px] text-slate-400 font-bold tracking-tight capitalize">{user.role}</p>
</div>
</div>
<div className="flex items-center gap-1.5 ml-2">
@@ -91,7 +91,7 @@ export default function IdentityManager({
<div className="space-y-4">
<div className="space-y-1.5">
<label className="text-[10px] font-black text-slate-500 tracking-tight ml-1">Username</label>
<label className="text-sm font-bold text-slate-400 tracking-tight ml-1">Username</label>
<input
type="text"
value={editUserForm.username}
@@ -100,7 +100,7 @@ export default function IdentityManager({
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-slate-500 tracking-tight ml-1">New Password (Leave Empty To Keep)</label>
<label className="text-sm font-bold text-slate-400 tracking-tight ml-1">New Password (Leave Empty To Keep)</label>
<input
type="password"
value={editUserForm.password}
@@ -110,7 +110,7 @@ export default function IdentityManager({
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-slate-500 tracking-tight ml-1">Role</label>
<label className="text-sm font-bold text-slate-400 tracking-tight ml-1">Role</label>
<select
value={editUserForm.role}
onChange={(e) => setEditUserForm({ ...editUserForm, role: e.target.value })}

View File

@@ -37,8 +37,8 @@ export default function LdapManager({
<Power size={14} />
</div>
<div>
<p className="text-[10px] font-black text-slate-200 tracking-tight leading-none">Ldap Authorization</p>
<p className={cn("text-[8px] font-bold mt-1", ldapConfig.ldap_enabled ? "text-green-500/80" : "text-slate-600")}>
<p className="text-xs font-bold text-slate-200 tracking-tight leading-none">Ldap Authorization</p>
<p className={cn("text-[11px] font-bold mt-1.5", ldapConfig.ldap_enabled ? "text-green-500/80" : "text-slate-500")}>
{ldapConfig.ldap_enabled ? "External Directory Active" : "Internal Authentication Only"}
</p>
</div>
@@ -59,7 +59,7 @@ export default function LdapManager({
<div className="grid sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-[10px] font-black text-slate-500 tracking-tight ml-1">Ldap Uri</label>
<label className="text-sm font-bold text-slate-400 tracking-tight ml-1">Ldap Uri</label>
<div className="relative">
<Server className="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-700" size={14} />
<input
@@ -72,7 +72,7 @@ export default function LdapManager({
</div>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-slate-500 tracking-tight ml-1">Context Dn</label>
<label className="text-sm font-bold text-slate-400 tracking-tight ml-1">Context Dn</label>
<div className="relative">
<Shield className="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-700" size={14} />
<input
@@ -88,7 +88,7 @@ export default function LdapManager({
<div className="grid sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-[10px] font-black text-slate-500 tracking-tight ml-1">User Dn Template</label>
<label className="text-sm font-bold text-slate-400 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-slate-700" size={14} />
<input
@@ -101,7 +101,7 @@ export default function LdapManager({
</div>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-slate-500 tracking-tight ml-1">Groups Base Dn</label>
<label className="text-sm font-bold text-slate-400 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-slate-700" size={14} />
<input
@@ -124,8 +124,8 @@ export default function LdapManager({
<Lock size={14} />
</div>
<div>
<p className="text-[10px] font-black text-slate-200 tracking-tight leading-none">Use Tls</p>
<p className={cn("text-[8px] font-bold mt-1", ldapConfig.use_tls ? "text-indigo-400/80" : "text-slate-600")}>
<p className="text-xs font-bold text-slate-200 tracking-tight leading-none">Use Tls</p>
<p className={cn("text-[11px] font-bold mt-1.5", ldapConfig.use_tls ? "text-indigo-400/80" : "text-slate-500")}>
{ldapConfig.use_tls ? "Encrypted Channel" : "Standard Link"}
</p>
</div>
@@ -148,14 +148,14 @@ export default function LdapManager({
<button
onClick={onTestLdap}
disabled={testingLdap}
className="flex-1 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl py-3 text-xs font-black shadow-xl shadow-indigo-600/10 transition-all active:scale-95 disabled:opacity-50 flex items-center justify-center gap-2 border border-indigo-500/30"
className="flex-1 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl py-3 text-sm font-black shadow-xl shadow-indigo-600/10 transition-all active:scale-95 disabled:opacity-50 flex items-center justify-center gap-2 border border-indigo-500/30"
>
{testingLdap ? <RotateCcw size={14} className="animate-spin" /> : <Wifi size={14} />}
Test Connection
</button>
<button
onClick={onUpdateLdap}
className="flex-[2] bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl py-3 text-xs font-black shadow-xl shadow-indigo-600/10 transition-all active:scale-95 flex items-center justify-center gap-2 border border-indigo-500/30 tracking-tight"
className="flex-[2] bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl py-3 text-sm font-black shadow-xl shadow-indigo-600/10 transition-all active:scale-95 flex items-center justify-center gap-2 border border-indigo-500/30 tracking-tight"
>
<Shield size={14} /> Save LDAP Policy
</button>