Build [v1.4.1] - Security hardening, PWA and UI refinements
This commit is contained in:
@@ -42,7 +42,6 @@ export default function AdminPage() {
|
||||
server_uri: 'ldap://192.168.84.107:3890',
|
||||
base_dn: 'dc=example,dc=com',
|
||||
user_template: 'cn={username},ou=people,dc=example,dc=com',
|
||||
required_group: 'inventory',
|
||||
groups_dn: 'ou=groups',
|
||||
use_tls: false,
|
||||
role_mappings: []
|
||||
@@ -263,7 +262,7 @@ export default function AdminPage() {
|
||||
</header>
|
||||
|
||||
{/* User Management Section */}
|
||||
<section className="bg-slate-900/40 border border-slate-800/40 p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] shadow-2xl space-y-8">
|
||||
<section className="glass-card p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] space-y-8">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-primary/10 rounded-2xl text-primary border border-primary/20">
|
||||
@@ -370,7 +369,7 @@ export default function AdminPage() {
|
||||
</section>
|
||||
|
||||
{/* Enterprise Integration (LDAP) Section */}
|
||||
<section className="bg-slate-900/40 border border-slate-800/40 p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] shadow-2xl space-y-8">
|
||||
<section className="glass-card p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] space-y-8">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-6 px-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-indigo-500/10 rounded-2xl text-indigo-500 border border-indigo-500/20">
|
||||
@@ -438,24 +437,47 @@ export default function AdminPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] font-black text-slate-500 px-1">Groups DN (Search base for groups)</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="ou=groups,dc=example,dc=com"
|
||||
value={ldapConfig.groups_dn}
|
||||
onChange={(e) => setLdapConfig({ ...ldapConfig, groups_dn: e.target.value })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-2xl py-3 px-4 text-sm text-white outline-none focus:border-indigo-500/30 transition-all font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] font-black text-slate-500 px-1">Groups DN</label>
|
||||
<label className="text-[10px] font-black text-slate-500 px-1">Admin Security Group</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="ou=groups"
|
||||
value={ldapConfig.groups_dn}
|
||||
onChange={(e) => setLdapConfig({ ...ldapConfig, groups_dn: e.target.value })}
|
||||
placeholder="inventory_admins"
|
||||
value={ldapConfig.role_mappings?.find((m: any) => m.role === 'admin')?.group || ''}
|
||||
onChange={(e) => {
|
||||
const newMappings = [...(ldapConfig.role_mappings || [])];
|
||||
const idx = newMappings.findIndex((m: any) => m.role === 'admin');
|
||||
if (idx >= 0) newMappings[idx] = { ...newMappings[idx], group: e.target.value };
|
||||
else newMappings.push({ role: 'admin', group: e.target.value });
|
||||
setLdapConfig({ ...ldapConfig, role_mappings: newMappings });
|
||||
}}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-2xl py-3 px-4 text-sm text-white outline-none focus:border-indigo-500/30 transition-all font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] font-black text-slate-500 px-1">Required Security Group</label>
|
||||
<label className="text-[10px] font-black text-slate-500 px-1">User Security Group</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="inventory_admins"
|
||||
value={ldapConfig.required_group}
|
||||
onChange={(e) => setLdapConfig({ ...ldapConfig, required_group: e.target.value })}
|
||||
placeholder="inventory_users"
|
||||
value={ldapConfig.role_mappings?.find((m: any) => m.role === 'user')?.group || ''}
|
||||
onChange={(e) => {
|
||||
const newMappings = [...(ldapConfig.role_mappings || [])];
|
||||
const idx = newMappings.findIndex((m: any) => m.role === 'user');
|
||||
if (idx >= 0) newMappings[idx] = { ...newMappings[idx], group: e.target.value };
|
||||
else newMappings.push({ role: 'user', group: e.target.value });
|
||||
setLdapConfig({ ...ldapConfig, role_mappings: newMappings });
|
||||
}}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-2xl py-3 px-4 text-sm text-white outline-none focus:border-indigo-500/30 transition-all font-mono"
|
||||
/>
|
||||
</div>
|
||||
@@ -472,16 +494,22 @@ export default function AdminPage() {
|
||||
Enabling LDAP will allow users from your network domain to log in using their enterprise credentials.
|
||||
Local account passwords will still function as a fail-safe backup.
|
||||
</p>
|
||||
<div className="flex items-center gap-4 text-[10px] font-black">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className={cn("w-1.5 h-1.5 rounded-full", ldapConfig.use_tls ? "bg-green-500" : "bg-slate-800")} />
|
||||
<span className="text-slate-400">LDAPS / TLS</span>
|
||||
<div className="flex items-center justify-between gap-4 pt-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={14} className={cn("transition-colors", ldapConfig.use_tls ? "text-green-400" : "text-slate-600")} />
|
||||
<span className="text-[10px] font-black text-slate-400">LDAPS / TLS Encryption</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setLdapConfig({ ...ldapConfig, use_tls: !ldapConfig.use_tls })}
|
||||
className="text-indigo-500 hover:text-indigo-400 underline decoration-indigo-500/30 font-bold"
|
||||
className={cn(
|
||||
"relative inline-flex h-6 w-10 items-center rounded-full transition-all duration-300 outline-none",
|
||||
ldapConfig.use_tls ? "bg-green-600" : "bg-slate-800"
|
||||
)}
|
||||
>
|
||||
Toggle Security Layer
|
||||
<span className={cn(
|
||||
"inline-block h-4 w-4 transform rounded-full bg-white transition-transform duration-300",
|
||||
ldapConfig.use_tls ? "translate-x-5" : "translate-x-1"
|
||||
)} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -507,7 +535,7 @@ export default function AdminPage() {
|
||||
</section>
|
||||
|
||||
{/* Category Management Section */}
|
||||
<section className="bg-slate-900/40 border border-slate-800/40 p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] shadow-2xl space-y-8">
|
||||
<section className="glass-card p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] space-y-8">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-primary/10 rounded-2xl text-primary border border-primary/20">
|
||||
@@ -604,7 +632,7 @@ export default function AdminPage() {
|
||||
</section>
|
||||
|
||||
{/* Database & Continuity Card */}
|
||||
<section className="bg-slate-900/40 border border-slate-800/40 p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] shadow-2xl space-y-8">
|
||||
<section className="glass-card p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] space-y-8">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-6 px-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-amber-500/10 rounded-2xl text-amber-500 border border-amber-500/20">
|
||||
|
||||
@@ -36,8 +36,27 @@ body {
|
||||
background: #334155; /* slate-700 */
|
||||
}
|
||||
|
||||
/* Support for Firefox (limited) */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #1e293b #020617;
|
||||
/* Modern Utility for Premium Glassmorphism */
|
||||
@layer utilities {
|
||||
.glass-card {
|
||||
@apply bg-slate-900/40 backdrop-blur-xl border border-slate-800/50 shadow-2xl;
|
||||
background-image: linear-gradient(135deg, rgba(255,255,255,0.02) 0%, rgba(255,255,255,0) 100%);
|
||||
}
|
||||
|
||||
.text-fluid-lg {
|
||||
font-size: clamp(1.125rem, 3cqi, 1.5rem);
|
||||
}
|
||||
|
||||
.text-fluid-xl {
|
||||
font-size: clamp(1.5rem, 5cqi, 2.25rem);
|
||||
}
|
||||
}
|
||||
|
||||
/* Safe Area Insets for Modern Mobile Devices (iOS Notch/Home Bar) */
|
||||
.pb-safe {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.pt-safe {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,12 @@ export default function RootLayout({
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="apple-touch-icon" href="/icon-192x192.png" />
|
||||
<link rel="apple-touch-icon" href="/icons/icon-192x192.png" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="aInventory" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
</head>
|
||||
<body className="antialiased bg-slate-950 text-slate-100">
|
||||
{children}
|
||||
|
||||
@@ -470,7 +470,7 @@ export default function Home() {
|
||||
</div>
|
||||
|
||||
{/* Scanner Section */}
|
||||
<section className="bg-slate-900/50 border border-slate-800 rounded-3xl p-6 backdrop-blur-sm shadow-xl">
|
||||
<section className="glass-card rounded-3xl p-6">
|
||||
{showScanner ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function BottomNav({
|
||||
const isAdmin = pathname === '/admin';
|
||||
|
||||
return (
|
||||
<footer className="fixed bottom-0 left-0 right-0 p-4 bg-slate-950/80 backdrop-blur-md border-t border-slate-900 z-40">
|
||||
<footer className="fixed bottom-0 left-0 right-0 p-4 pb-safe bg-slate-950/80 backdrop-blur-md border-t border-slate-900 z-40">
|
||||
<div className="max-w-4xl mx-auto flex justify-around items-center text-slate-400">
|
||||
|
||||
<button
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
{
|
||||
"name": "Inventory PWA",
|
||||
"short_name": "Inventory",
|
||||
"name": "TFM aInventory",
|
||||
"short_name": "aInventory",
|
||||
"description": "Unified inventory management with offline scanning",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
"background_color": "#0a0a0a",
|
||||
"theme_color": "#3b82f6",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/maskable-icon.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
}
|
||||
],
|
||||
"categories": ["business", "productivity"],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Scanner",
|
||||
"url": "/",
|
||||
"icons": [{ "src": "/icons/icon-192x192.png", "sizes": "192x192" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user