diff --git a/src/App.jsx b/src/App.jsx index 9d2cb28..65a4ae8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -56,6 +56,7 @@ function App() { const [toast, setToast] = useState(null); const [isSyncing, setIsSyncing] = useState(false); const [filterClient, setFilterClient] = useState('all'); + const [nodeFilter, setNodeFilter] = useState('all'); // 'all' | 'online' | 'offline' const [searchQuery, setSearchQuery] = useState(''); // Live search filter for clients const [expandedClients, setExpandedClients] = useState({}); // Expanded telemetry details for offline nodes const terminalRef = useRef(null); @@ -249,10 +250,15 @@ function App() { const activeClients = Object.values(clients).filter(c => c.active).length; const totalClients = Object.keys(clients).length; - // Filter clients based on search query - const filteredClientEntries = Object.entries(clients).filter(([clientId]) => - clientId.toLowerCase().includes(searchQuery.toLowerCase()) - ); + // Filter clients based on search query AND node status tab + const filteredClientEntries = Object.entries(clients).filter(([clientId, info]) => { + const matchesSearch = clientId.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesFilter = + nodeFilter === 'all' || + (nodeFilter === 'online' && info.active) || + (nodeFilter === 'offline' && !info.active); + return matchesSearch && matchesFilter; + }); return (
@@ -342,40 +348,87 @@ function App() { {/* Dashboard Vitals: Grid of Cards (Left/Center Column) */}
- {/* Central Metrics Board */} + {/* Central Metrics Board — clickable filter tabs */}
-
+ + {/* ALL tab */} + + + {/* ONLINE tab */} + -
+ {/* OFFLINE tab */} + +
{/* Vitals Node Grid Cards */} @@ -384,18 +437,38 @@ function App() {

Remote Fleet Systems Grid ({filteredClientEntries.length}) + {nodeFilter !== 'all' && ( + + {nodeFilter === 'online' ? '🟢 Online' : '🔴 Offline'} + + )}

- {/* Node Search Bar */} -
- - 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" - /> +
+ {/* Clear filter shortcut */} + {nodeFilter !== 'all' && ( + + )} + {/* Node Search Bar */} +
+ + 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" + /> +
@@ -580,23 +653,67 @@ function App() { const diskTotal = d.total_gb || 0; const diskFree = d.free_gb || 0; const diskUsed = (diskTotal - diskFree).toFixed(2); + + // Get friendly label + const diskLabel = d.label || (d.mount === '/' ? 'Computer' : (d.mount.split(/[/\\]/).pop() || 'Volume')); + return ( -
-
- Disk [ {d.mount} ] - - Used: {diskUsed}G / {diskTotal}G - +
+
+ {/* Left side: Icon, Label, Device badge, Mount path */} +
+
+ +
+
+ + {diskLabel} + + + {d.device && ( + + {d.device} + + )} + {d.device && on} + + {d.mount} + + +
+
+ + {/* Right side: Free Space / Total */} +
+ + {diskFree} GB free + + + of {diskTotal} GB + +
-
-
85 ? 'bg-rose-505' : 'bg-indigo-500'}`} - style={{ width: `${d.percent}%` }} - /> -
-
- Free: {diskFree} GB - {d.percent}% Used + + {/* Progress bar */} +
+
+
85 + ? 'bg-gradient-to-r from-rose-500 to-red-500' + : 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}%` }} + /> +
+
+ {diskUsed} GB Used + 85 ? "text-rose-400 font-bold" : ""}> + {d.percent}% Used + +
);