feat: tab based changes in ui and and labeling of client system disks

This commit is contained in:
2026-06-04 16:39:06 +05:30
parent 8751a45579
commit 3c189b5776

View File

@@ -56,6 +56,7 @@ function App() {
const [toast, setToast] = useState(null); const [toast, setToast] = useState(null);
const [isSyncing, setIsSyncing] = useState(false); const [isSyncing, setIsSyncing] = useState(false);
const [filterClient, setFilterClient] = useState('all'); const [filterClient, setFilterClient] = useState('all');
const [nodeFilter, setNodeFilter] = useState('all'); // 'all' | 'online' | 'offline'
const [searchQuery, setSearchQuery] = useState(''); // Live search filter for clients const [searchQuery, setSearchQuery] = useState(''); // Live search filter for clients
const [expandedClients, setExpandedClients] = useState({}); // Expanded telemetry details for offline nodes const [expandedClients, setExpandedClients] = useState({}); // Expanded telemetry details for offline nodes
const terminalRef = useRef(null); const terminalRef = useRef(null);
@@ -249,10 +250,15 @@ function App() {
const activeClients = Object.values(clients).filter(c => c.active).length; const activeClients = Object.values(clients).filter(c => c.active).length;
const totalClients = Object.keys(clients).length; const totalClients = Object.keys(clients).length;
// Filter clients based on search query // Filter clients based on search query AND node status tab
const filteredClientEntries = Object.entries(clients).filter(([clientId]) => const filteredClientEntries = Object.entries(clients).filter(([clientId, info]) => {
clientId.toLowerCase().includes(searchQuery.toLowerCase()) const matchesSearch = clientId.toLowerCase().includes(searchQuery.toLowerCase());
); const matchesFilter =
nodeFilter === 'all' ||
(nodeFilter === 'online' && info.active) ||
(nodeFilter === 'offline' && !info.active);
return matchesSearch && matchesFilter;
});
return ( return (
<div className="min-h-screen pb-16 flex flex-col font-sans antialiased text-slate-100 bg-[#050811]"> <div className="min-h-screen pb-16 flex flex-col font-sans antialiased text-slate-100 bg-[#050811]">
@@ -342,40 +348,87 @@ function App() {
{/* Dashboard Vitals: Grid of Cards (Left/Center Column) */} {/* Dashboard Vitals: Grid of Cards (Left/Center Column) */}
<div className="lg:col-span-2 space-y-6"> <div className="lg:col-span-2 space-y-6">
{/* Central Metrics Board */} {/* Central Metrics Board — clickable filter tabs */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="glass-panel p-5 rounded-2xl flex items-center gap-4 relative overflow-hidden group">
{/* ALL tab */}
<button
onClick={() => setNodeFilter('all')}
className={`glass-panel p-5 rounded-2xl flex items-center gap-4 relative overflow-hidden group text-left transition-all duration-200 ${
nodeFilter === 'all'
? 'ring-2 ring-violet-500/60 shadow-[0_0_20px_rgba(139,92,246,0.15)]'
: 'hover:ring-1 hover:ring-violet-500/20 hover:shadow-[0_0_12px_rgba(139,92,246,0.08)]'
}`}
>
<div className="absolute top-0 right-0 w-24 h-24 bg-violet-600/5 rounded-full blur-2xl group-hover:bg-violet-600/10 transition-all"></div> <div className="absolute top-0 right-0 w-24 h-24 bg-violet-600/5 rounded-full blur-2xl group-hover:bg-violet-600/10 transition-all"></div>
<div className="w-12 h-12 rounded-xl bg-violet-500/10 border border-violet-500/20 flex items-center justify-center text-violet-400 shrink-0"> <div className={`w-12 h-12 rounded-xl flex items-center justify-center shrink-0 transition-all ${
nodeFilter === 'all'
? 'bg-violet-500/25 border-2 border-violet-500/60 text-violet-300'
: 'bg-violet-500/10 border border-violet-500/20 text-violet-400'
}`}>
<Server className="w-6 h-6" /> <Server className="w-6 h-6" />
</div> </div>
<div> <div>
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider block">Total Fleet Nodes</span> <span className="text-xs font-semibold text-slate-400 uppercase tracking-wider block">Total Fleet Nodes</span>
<span className="text-3xl font-extrabold text-white mt-0.5 block">{totalClients}</span> <span className="text-3xl font-extrabold text-white mt-0.5 block">{totalClients}</span>
{nodeFilter === 'all' && (
<span className="text-[9px] text-violet-400 font-bold uppercase tracking-wider mt-0.5 block"> Active Filter</span>
)}
</div> </div>
</div> </button>
<div className="glass-panel p-5 rounded-2xl flex items-center gap-4 relative overflow-hidden border-l-2 border-l-emerald-500 group"> {/* ONLINE tab */}
<button
onClick={() => setNodeFilter('online')}
className={`glass-panel p-5 rounded-2xl flex items-center gap-4 relative overflow-hidden border-l-2 border-l-emerald-500 group text-left transition-all duration-200 ${
nodeFilter === 'online'
? 'ring-2 ring-emerald-500/60 shadow-[0_0_20px_rgba(16,185,129,0.15)]'
: 'hover:ring-1 hover:ring-emerald-500/20 hover:shadow-[0_0_12px_rgba(16,185,129,0.08)]'
}`}
>
<div className="absolute top-0 right-0 w-24 h-24 bg-emerald-500/5 rounded-full blur-2xl group-hover:bg-emerald-500/10 transition-all"></div> <div className="absolute top-0 right-0 w-24 h-24 bg-emerald-500/5 rounded-full blur-2xl group-hover:bg-emerald-500/10 transition-all"></div>
<div className="w-12 h-12 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400 shrink-0"> <div className={`w-12 h-12 rounded-xl flex items-center justify-center shrink-0 transition-all ${
nodeFilter === 'online'
? 'bg-emerald-500/25 border-2 border-emerald-500/60 text-emerald-300'
: 'bg-emerald-500/10 border border-emerald-500/20 text-emerald-400'
}`}>
<Activity className="w-6 h-6" /> <Activity className="w-6 h-6" />
</div> </div>
<div> <div>
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider block">🟢 Online Nodes</span> <span className="text-xs font-semibold text-slate-400 uppercase tracking-wider block">🟢 Online Nodes</span>
<span className="text-3xl font-extrabold text-emerald-400 mt-0.5 block">{activeClients}</span> <span className="text-3xl font-extrabold text-emerald-400 mt-0.5 block">{activeClients}</span>
{nodeFilter === 'online' && (
<span className="text-[9px] text-emerald-400 font-bold uppercase tracking-wider mt-0.5 block"> Active Filter</span>
)}
</div> </div>
</div> </button>
<div className="glass-panel p-5 rounded-2xl flex items-center gap-4 relative overflow-hidden border-l-2 border-l-rose-500 group"> {/* OFFLINE tab */}
<button
onClick={() => setNodeFilter('offline')}
className={`glass-panel p-5 rounded-2xl flex items-center gap-4 relative overflow-hidden border-l-2 border-l-rose-500 group text-left transition-all duration-200 ${
nodeFilter === 'offline'
? 'ring-2 ring-rose-500/60 shadow-[0_0_20px_rgba(244,63,94,0.15)]'
: 'hover:ring-1 hover:ring-rose-500/20 hover:shadow-[0_0_12px_rgba(244,63,94,0.08)]'
}`}
>
<div className="absolute top-0 right-0 w-24 h-24 bg-rose-500/5 rounded-full blur-2xl group-hover:bg-rose-500/10 transition-all"></div> <div className="absolute top-0 right-0 w-24 h-24 bg-rose-500/5 rounded-full blur-2xl group-hover:bg-rose-500/10 transition-all"></div>
<div className="w-12 h-12 rounded-xl bg-rose-500/10 border border-rose-500/20 flex items-center justify-center text-rose-400 shrink-0"> <div className={`w-12 h-12 rounded-xl flex items-center justify-center shrink-0 transition-all ${
nodeFilter === 'offline'
? 'bg-rose-500/25 border-2 border-rose-500/60 text-rose-300'
: 'bg-rose-500/10 border border-rose-500/20 text-rose-400'
}`}>
<AlertTriangle className="w-6 h-6" /> <AlertTriangle className="w-6 h-6" />
</div> </div>
<div> <div>
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider block">🔴 Offline Nodes</span> <span className="text-xs font-semibold text-slate-400 uppercase tracking-wider block">🔴 Offline Nodes</span>
<span className="text-3xl font-extrabold text-rose-400 mt-0.5 block">{totalClients - activeClients}</span> <span className="text-3xl font-extrabold text-rose-400 mt-0.5 block">{totalClients - activeClients}</span>
{nodeFilter === 'offline' && (
<span className="text-[9px] text-rose-400 font-bold uppercase tracking-wider mt-0.5 block"> Active Filter</span>
)}
</div> </div>
</div> </button>
</div> </div>
{/* Vitals Node Grid Cards */} {/* Vitals Node Grid Cards */}
@@ -384,18 +437,38 @@ function App() {
<h2 className="text-xs font-bold text-slate-300 tracking-wider uppercase flex items-center gap-2"> <h2 className="text-xs font-bold text-slate-300 tracking-wider uppercase flex items-center gap-2">
<Sliders className="w-4 h-4 text-violet-400" /> <Sliders className="w-4 h-4 text-violet-400" />
Remote Fleet Systems Grid ({filteredClientEntries.length}) Remote Fleet Systems Grid ({filteredClientEntries.length})
{nodeFilter !== 'all' && (
<span className={`text-[9px] font-bold px-2 py-0.5 rounded-full uppercase tracking-wider ${
nodeFilter === 'online'
? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'
: 'bg-rose-500/10 text-rose-400 border border-rose-500/20'
}`}>
{nodeFilter === 'online' ? '🟢 Online' : '🔴 Offline'}
</span>
)}
</h2> </h2>
{/* Node Search Bar */} <div className="flex items-center gap-2 w-full sm:w-auto">
<div className="relative w-full sm:w-64"> {/* Clear filter shortcut */}
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-2.5" /> {nodeFilter !== 'all' && (
<input <button
type="text" onClick={() => setNodeFilter('all')}
placeholder="Search systems..." className="text-[10px] font-bold text-slate-400 hover:text-slate-200 uppercase tracking-wider bg-white/5 hover:bg-white/10 px-2.5 py-1.5 rounded-lg border border-white/5 transition-all whitespace-nowrap"
value={searchQuery} >
onChange={(e) => setSearchQuery(e.target.value)} Clear Filter
className="w-full bg-slate-950/60 border border-white/5 rounded-xl pl-9 pr-4 py-1.5 text-xs text-slate-300 placeholder-slate-500 focus:outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500/20 transition-all" </button>
/> )}
{/* Node Search Bar */}
<div className="relative w-full sm:w-64">
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-2.5" />
<input
type="text"
placeholder="Search systems..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-slate-950/60 border border-white/5 rounded-xl pl-9 pr-4 py-1.5 text-xs text-slate-300 placeholder-slate-500 focus:outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500/20 transition-all"
/>
</div>
</div> </div>
</div> </div>
@@ -580,23 +653,67 @@ function App() {
const diskTotal = d.total_gb || 0; const diskTotal = d.total_gb || 0;
const diskFree = d.free_gb || 0; const diskFree = d.free_gb || 0;
const diskUsed = (diskTotal - diskFree).toFixed(2); const diskUsed = (diskTotal - diskFree).toFixed(2);
// Get friendly label
const diskLabel = d.label || (d.mount === '/' ? 'Computer' : (d.mount.split(/[/\\]/).pop() || 'Volume'));
return ( return (
<div key={index} className="p-3 rounded-xl bg-slate-950/30 border border-white/5 space-y-2"> <div key={index} className="p-4 rounded-xl bg-slate-950/40 border border-white/5 hover:border-violet-500/15 transition-all duration-200 space-y-3">
<div className="flex items-center justify-between text-xs"> <div className="flex items-start justify-between gap-3">
<span className="font-bold text-slate-300">Disk [ {d.mount} ]</span> {/* Left side: Icon, Label, Device badge, Mount path */}
<span className="font-mono text-slate-400 text-[10px]"> <div className="flex items-center gap-3 min-w-0">
Used: <b className="text-slate-300">{diskUsed}G</b> / {diskTotal}G <div className="p-2 rounded-lg bg-slate-900 border border-white/5 text-indigo-400 shrink-0">
</span> <HardDrive className="w-4 h-4 text-indigo-400" />
</div>
<div className="min-w-0">
<span className="font-extrabold text-white text-xs block truncate">
{diskLabel}
</span>
<span className="text-[9px] text-slate-400 font-mono flex flex-wrap items-center gap-1 mt-0.5">
{d.device && (
<span className="px-1.5 py-0.5 rounded bg-slate-900 border border-white/5 font-semibold text-[8px] text-slate-300">
{d.device}
</span>
)}
{d.device && <span className="text-slate-600">on</span>}
<span className="text-slate-400 truncate max-w-[120px]" title={d.mount}>
{d.mount}
</span>
</span>
</div>
</div>
{/* Right side: Free Space / Total */}
<div className="text-right shrink-0">
<span className="text-[11px] font-bold text-slate-300 font-mono block">
{diskFree} GB free
</span>
<span className="text-[9px] text-slate-500 font-mono block mt-0.5">
of {diskTotal} GB
</span>
</div>
</div> </div>
<div className="w-full h-1.5 rounded-full bg-slate-900 overflow-hidden">
<div {/* Progress bar */}
className={`h-full rounded-full ${d.percent > 85 ? 'bg-rose-505' : 'bg-indigo-500'}`} <div className="space-y-1.5">
style={{ width: `${d.percent}%` }} <div className="w-full h-1.5 rounded-full bg-slate-900 overflow-hidden relative border border-white/5">
/> <div
</div> className={`h-full rounded-full transition-all duration-500 ${
<div className="flex justify-between text-[9px] text-slate-500 font-mono"> d.percent > 85
<span>Free: <b className="text-slate-400">{diskFree} GB</b></span> ? 'bg-gradient-to-r from-rose-500 to-red-500'
<span>{d.percent}% Used</span> : d.percent > 70
? 'bg-gradient-to-r from-amber-500 to-yellow-500'
: 'bg-gradient-to-r from-indigo-500 to-violet-500'
}`}
style={{ width: `${d.percent}%` }}
/>
</div>
<div className="flex justify-between text-[9px] text-slate-500 font-mono">
<span>{diskUsed} GB Used</span>
<span className={d.percent > 85 ? "text-rose-400 font-bold" : ""}>
{d.percent}% Used
</span>
</div>
</div> </div>
</div> </div>
); );