feat: initialize RMM dashboard frontend with telemetry, command dispatch, and configuration management capabilities

This commit is contained in:
2026-06-03 18:17:58 +05:30
parent 6ca9ba51e9
commit 8751a45579
5 changed files with 650 additions and 637 deletions

1
.env.development Normal file
View File

@@ -0,0 +1 @@
VITE_API_BASE_URL="http://localhost:8000"

1
.env.production Normal file
View File

@@ -0,0 +1 @@
VITE_API_BASE_URL="https://rmm-backend.seekright.com"

582
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,12 +12,24 @@ import {
Flame, Flame,
Server, Server,
AlertTriangle, AlertTriangle,
Play Play,
Search,
Code,
FileCode,
Clock,
ChevronDown,
ChevronUp,
Database,
ArrowRight,
Sliders
} from 'lucide-react'; } from 'lucide-react';
const API_BASE = window.location.protocol === 'https:' const API_BASE = import.meta.env.VITE_API_BASE_URL ||
(window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
? 'http://localhost:8000'
: (window.location.protocol === 'https:'
? 'https://rmm-backend.seekright.com' ? 'https://rmm-backend.seekright.com'
: 'http://rmm-backend.seekright.com'; // Target FastAPI central server in production VM : 'http://rmm-backend.seekright.com'));
// Secure API fetch wrapper // Secure API fetch wrapper
const apiFetch = (url, options = {}) => { const apiFetch = (url, options = {}) => {
@@ -39,10 +51,13 @@ function App() {
// Whitelist config editor state // Whitelist config editor state
const [editorText, setEditorText] = useState('{\n "nt": {},\n "posix": {}\n}'); const [editorText, setEditorText] = useState('{\n "nt": {},\n "posix": {}\n}');
const [jsonError, setJsonError] = useState(null); // Real-time JSON validation error
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 [searchQuery, setSearchQuery] = useState(''); // Live search filter for clients
const [expandedClients, setExpandedClients] = useState({}); // Expanded telemetry details for offline nodes
const terminalRef = useRef(null); const terminalRef = useRef(null);
const showToast = (message, type = 'success') => { const showToast = (message, type = 'success') => {
@@ -120,6 +135,20 @@ function App() {
} }
}, [logs]); }, [logs]);
// Real-time JSON validation
useEffect(() => {
try {
if (editorText.trim() === '') {
setJsonError('JSON configuration cannot be empty.');
return;
}
JSON.parse(editorText);
setJsonError(null);
} catch (err) {
setJsonError(err.message);
}
}, [editorText]);
// Dispatch command to client execution queue // Dispatch command to client execution queue
const handleDispatch = async (e) => { const handleDispatch = async (e) => {
e.preventDefault(); e.preventDefault();
@@ -165,6 +194,10 @@ function App() {
// Dynamic Whitelist Save Config // Dynamic Whitelist Save Config
const handleSaveWhitelist = async () => { const handleSaveWhitelist = async () => {
if (jsonError) {
showToast('Cannot save configuration. Please resolve JSON errors first.', 'error');
return;
}
try { try {
const parsedConfig = JSON.parse(editorText); const parsedConfig = JSON.parse(editorText);
const response = await apiFetch(`${API_BASE}/api/save-commands`, { const response = await apiFetch(`${API_BASE}/api/save-commands`, {
@@ -184,6 +217,13 @@ function App() {
} }
}; };
const toggleClientDetails = (clientId) => {
setExpandedClients(prev => ({
...prev,
[clientId]: !prev[clientId]
}));
};
// Compile individual client arrays into a single global chronological timeline for terminal display // Compile individual client arrays into a single global chronological timeline for terminal display
const getSortedTimeline = () => { const getSortedTimeline = () => {
const timeline = []; const timeline = [];
@@ -209,81 +249,93 @@ 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
const filteredClientEntries = Object.entries(clients).filter(([clientId]) =>
clientId.toLowerCase().includes(searchQuery.toLowerCase())
);
return ( return (
<div className="min-h-screen pb-16 flex flex-col"> <div className="min-h-screen pb-16 flex flex-col font-sans antialiased text-slate-100 bg-[#050811]">
{/* Dynamic Toast Alerts */} {/* Dynamic Toast Alerts */}
{toast && ( {toast && (
<div className={`fixed bottom-6 right-6 z-50 px-6 py-4 rounded-xl shadow-2xl flex items-center gap-3 border transition-all duration-300 transform translate-y-0 ${ <div className={`fixed bottom-6 right-6 z-50 px-5 py-4 rounded-2xl shadow-2xl flex items-center gap-3 border transition-all duration-300 transform translate-y-0 max-w-sm ${
toast.type === 'error' toast.type === 'error'
? 'bg-rose-950/85 border-rose-500/30 text-rose-200' ? 'bg-rose-950/90 border-rose-500/30 text-rose-200 shadow-rose-950/20'
: 'bg-emerald-950/85 border-emerald-500/30 text-emerald-200' : 'bg-emerald-950/90 border-emerald-500/30 text-emerald-200 shadow-emerald-950/20'
}`}> }`}>
{toast.type === 'error' ? <XCircle className="w-5 h-5 text-rose-400" /> : <CheckCircle className="w-5 h-5 text-emerald-400" />} {toast.type === 'error' ? (
<span className="font-semibold text-sm">{toast.message}</span> <div className="p-1 rounded-full bg-rose-500/10"><XCircle className="w-5 h-5 text-rose-400" /></div>
) : (
<div className="p-1 rounded-full bg-emerald-500/10"><CheckCircle className="w-5 h-5 text-emerald-400" /></div>
)}
<span className="font-semibold text-sm tracking-wide">{toast.message}</span>
</div> </div>
)} )}
{/* Modern Glass Header Section */} {/* Modern Glass Header Section */}
<header className="sticky top-0 z-30 px-6 py-4 glass-panel border-b border-white/5 flex items-center justify-between"> <header className="sticky top-0 z-30 w-full px-4 py-4 md:px-6 glass-panel border-b border-white/5 flex flex-col md:flex-row gap-4 items-stretch md:items-center justify-between shadow-[0_4px_30px_rgba(0,0,0,0.4)]">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-tr from-violet-600 to-indigo-600 flex items-center justify-center shadow-lg shadow-violet-900/30"> <div className="w-10 h-10 rounded-xl bg-gradient-to-tr from-violet-600 to-indigo-600 flex items-center justify-center shadow-lg shadow-violet-500/20 relative group overflow-hidden">
<Zap className="w-5 h-5 text-white" /> <Zap className="w-5 h-5 text-white animate-pulse" />
<span className="absolute inset-0 bg-white/20 translate-y-full group-hover:translate-y-0 transition-transform duration-300"></span>
</div> </div>
<div> <div>
<h1 className="font-bold text-lg tracking-tight text-white flex items-center gap-2"> <h1 className="font-bold text-lg tracking-tight text-white flex items-center gap-2">
SEEKRIGHT PULSE RMM SEEKRIGHT PULSE RMM
<span className="text-[10px] uppercase font-mono px-2 py-0.5 rounded bg-violet-500/10 border border-violet-500/20 text-violet-300">CORE-v1.0</span> <span className="text-[9px] uppercase font-mono px-2 py-0.5 rounded-full bg-violet-500/10 border border-violet-500/20 text-violet-300 font-semibold">CORE-v1.0</span>
</h1> </h1>
<p className="text-xs text-slate-400 font-medium">Enterprise Global Fleet & Whitelist Agent Orchestrator</p> <p className="text-[11px] text-slate-400 font-medium">Enterprise Global Fleet & Whitelist Agent Orchestrator</p>
</div> </div>
</div> </div>
{/* Action Controls & Navigation tabs */} {/* Action Controls & Navigation tabs */}
<div className="flex items-center gap-4"> <div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
<nav className="flex items-center gap-1.5 p-1 rounded-xl bg-slate-950/60 border border-white/5"> <nav className="flex items-center gap-1.5 p-1 rounded-xl bg-slate-950/60 border border-white/5">
<button <button
onClick={() => setActiveTab('dashboard')} onClick={() => setActiveTab('dashboard')}
className={`px-4 py-2 rounded-lg font-semibold text-xs transition-all ${ className={`flex-1 sm:flex-initial px-4 py-2 rounded-lg font-semibold text-xs transition-all flex items-center justify-center gap-1.5 ${
activeTab === 'dashboard' activeTab === 'dashboard'
? 'bg-gradient-to-r from-violet-600 to-indigo-600 text-white shadow-md' ? 'bg-gradient-to-r from-violet-600 to-indigo-600 text-white shadow-md shadow-violet-500/10'
: 'text-slate-400 hover:text-slate-200 hover:bg-white/5' : 'text-slate-400 hover:text-slate-200 hover:bg-white/5'
}`} }`}
> >
<Activity className="w-3.5 h-3.5 inline mr-1.5 -mt-0.5" /> <Activity className="w-3.5 h-3.5" />
Fleet Status Fleet Status
</button> </button>
<button <button
onClick={() => setActiveTab('editor')} onClick={() => setActiveTab('editor')}
className={`px-4 py-2 rounded-lg font-semibold text-xs transition-all ${ className={`flex-1 sm:flex-initial px-4 py-2 rounded-lg font-semibold text-xs transition-all flex items-center justify-center gap-1.5 ${
activeTab === 'editor' activeTab === 'editor'
? 'bg-gradient-to-r from-violet-600 to-indigo-600 text-white shadow-md' ? 'bg-gradient-to-r from-violet-600 to-indigo-600 text-white shadow-md shadow-violet-500/10'
: 'text-slate-400 hover:text-slate-200 hover:bg-white/5' : 'text-slate-400 hover:text-slate-200 hover:bg-white/5'
}`} }`}
> >
<Settings className="w-3.5 h-3.5 inline mr-1.5 -mt-0.5" /> <Settings className="w-3.5 h-3.5" />
Commands Whitelist Whitelist Editor
</button> </button>
</nav> </nav>
{/* Sync indicator status badge */} {/* Sync indicator status badge */}
<div className="flex items-center gap-2 px-3 py-1.5 rounded-xl bg-slate-900/50 border border-white/5"> <div className="flex items-center justify-between sm:justify-start gap-2.5 px-3 py-2 rounded-xl bg-slate-950/40 border border-white/5 text-xs">
<span className={`w-2 h-2 rounded-full ${isSyncing ? 'bg-violet-400 animate-spin' : 'bg-emerald-400 pulse-glow-emerald'}`}></span> <div className="flex items-center gap-2">
<span className="text-xs font-mono text-slate-300"> <span className={`w-2 h-2 rounded-full ${isSyncing ? 'bg-violet-400 animate-spin' : 'bg-emerald-400 glow-emerald'}`}></span>
<span className="font-mono text-slate-300 font-medium">
API Status: <b className="text-emerald-400 font-bold">Online</b> API Status: <b className="text-emerald-400 font-bold">Online</b>
</span> </span>
</div>
<button <button
onClick={syncFleetData} onClick={syncFleetData}
title="Force Sync Fleet Vitals" title="Force Sync Fleet Vitals"
className="p-1 rounded hover:bg-white/5 text-slate-400 hover:text-slate-200 transition-colors" className="p-1.5 rounded-lg bg-white/5 hover:bg-white/10 text-slate-400 hover:text-slate-200 transition-all border border-white/5"
> >
<RefreshCw className={`w-3 h-3 ${isSyncing ? 'animate-spin text-violet-400' : ''}`} /> <RefreshCw className={`w-3.5 h-3.5 ${isSyncing ? 'animate-spin text-violet-400' : ''}`} />
</button> </button>
</div> </div>
</div> </div>
</header> </header>
{/* Main Container */} {/* Main Container */}
<main className="max-w-7xl w-full mx-auto px-6 mt-8 flex-grow grid grid-cols-1 lg:grid-cols-3 gap-8"> <main className="max-w-7xl w-full mx-auto px-4 md:px-6 mt-8 flex-grow grid grid-cols-1 lg:grid-cols-3 gap-6">
{activeTab === 'dashboard' ? ( {activeTab === 'dashboard' ? (
<> <>
@@ -291,70 +343,150 @@ function App() {
<div className="lg:col-span-2 space-y-6"> <div className="lg:col-span-2 space-y-6">
{/* Central Metrics Board */} {/* Central Metrics Board */}
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="glass-panel p-4 rounded-2xl flex flex-col justify-between"> <div className="glass-panel p-5 rounded-2xl flex items-center gap-4 relative overflow-hidden group">
<span className="text-xs font-semibold text-slate-400">Total Systems</span> <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>
<span className="text-2xl font-bold text-white mt-1">{totalClients}</span> <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">
<Server className="w-6 h-6" />
</div> </div>
<div className="glass-panel p-4 rounded-2xl flex flex-col justify-between border-l-2 border-l-emerald-500"> <div>
<span className="text-xs font-semibold text-slate-400">🟢 Active Systems</span> <span className="text-xs font-semibold text-slate-400 uppercase tracking-wider block">Total Fleet Nodes</span>
<span className="text-2xl font-bold text-emerald-400 mt-1">{activeClients}</span> <span className="text-3xl font-extrabold text-white mt-0.5 block">{totalClients}</span>
</div>
</div>
<div className="glass-panel p-5 rounded-2xl flex items-center gap-4 relative overflow-hidden border-l-2 border-l-emerald-500 group">
<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">
<Activity className="w-6 h-6" />
</div>
<div>
<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>
</div>
</div>
<div className="glass-panel p-5 rounded-2xl flex items-center gap-4 relative overflow-hidden border-l-2 border-l-rose-500 group">
<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">
<AlertTriangle className="w-6 h-6" />
</div>
<div>
<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>
</div> </div>
<div className="glass-panel p-4 rounded-2xl flex flex-col justify-between border-l-2 border-l-rose-500">
<span className="text-xs font-semibold text-slate-400">🔴 Offline Systems</span>
<span className="text-2xl font-bold text-rose-400 mt-1">{totalClients - activeClients}</span>
</div> </div>
</div> </div>
{/* Vitals Node Grid Cards */} {/* Vitals Node Grid Cards */}
<div className="space-y-4"> <div className="space-y-4">
<h2 className="text-sm font-bold text-white tracking-wide uppercase flex items-center gap-2"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<Server className="w-4 h-4 text-violet-400" /> <h2 className="text-xs font-bold text-slate-300 tracking-wider uppercase flex items-center gap-2">
Remote Fleet Systems Grid <Sliders className="w-4 h-4 text-violet-400" />
Remote Fleet Systems Grid ({filteredClientEntries.length})
</h2> </h2>
{Object.keys(clients).length === 0 ? ( {/* 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>
{filteredClientEntries.length === 0 ? (
<div className="glass-panel p-12 rounded-2xl text-center text-slate-400"> <div className="glass-panel p-12 rounded-2xl text-center text-slate-400">
<AlertTriangle className="w-8 h-8 mx-auto text-yellow-400 mb-2 opacity-60" /> <AlertTriangle className="w-8 h-8 mx-auto text-yellow-500/60 mb-3" />
<p className="font-semibold text-sm">Searching for active RMM telemetry agents...</p> <p className="font-semibold text-sm">No systems matching your query.</p>
<p className="text-xs text-slate-500 mt-1">Boot up an agent using deploy_agent.sh to register</p> <p className="text-xs text-slate-500 mt-1">Make sure the agent services are actively running</p>
</div> </div>
) : ( ) : (
Object.entries(clients).map(([clientId, info]) => { filteredClientEntries.map(([clientId, info]) => {
const clientTelemetry = telemetry[clientId]; const clientTelemetry = telemetry[clientId];
const isOnline = info.active; const isOnline = info.active;
const lastSeenDate = info.last_seen ? new Date(info.last_seen).toLocaleString() : 'Never'; const lastSeenDate = info.last_seen ? new Date(info.last_seen).toLocaleString() : 'Never';
const isExpanded = expandedClients[clientId] || false;
{/* Render Compact Card for Offline System */}
if (!isOnline) {
return ( return (
<div <div
key={clientId} key={clientId}
className="glass-panel p-6 rounded-2xl hover:border-violet-500/40 hover:-translate-y-1 hover:shadow-[0_8px_30px_rgb(99,102,241,0.12)] transition-all duration-300 relative overflow-hidden group bg-gradient-to-b from-slate-900/30 to-slate-950/50" className="glass-panel p-4 rounded-xl border border-white/5 hover:border-rose-500/20 hover:shadow-[0_4px_20px_rgba(244,63,94,0.04)] transition-all duration-300 bg-gradient-to-r from-slate-950/40 to-slate-900/10"
> >
{/* Status bar */} <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div className={`absolute top-0 left-0 right-0 h-1 ${isOnline ? 'bg-gradient-to-r from-emerald-500 to-teal-400' : 'bg-gradient-to-r from-rose-500 to-red-400'}`} /> <div className="flex items-center gap-3">
<div className="w-3.5 h-3.5 rounded-full flex items-center justify-center bg-rose-500/10">
<span className="relative flex h-2.5 w-2.5">
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-rose-500 glow-rose"></span>
</span>
</div>
<span className="font-bold text-slate-400 text-base">{clientId}</span>
<span className="text-[9px] uppercase tracking-wider font-extrabold px-2 py-0.5 rounded bg-rose-500/10 border border-rose-500/20 text-rose-300/80">
Offline
</span>
</div>
<div className="flex items-center justify-between sm:justify-end gap-4">
<div className="text-left sm:text-right">
<span className="text-[9px] text-slate-500 block uppercase font-bold tracking-wider">Last Contact</span>
<span className="text-[11px] font-semibold text-slate-400 font-mono">{lastSeenDate}</span>
</div>
<button
type="button"
onClick={() => toggleClientDetails(clientId)}
className="p-1.5 rounded-lg bg-white/5 hover:bg-white/10 text-slate-400 hover:text-slate-200 transition-colors border border-white/5 flex items-center gap-1 text-[10px] font-semibold uppercase tracking-wider"
>
{isExpanded ? <ChevronUp className="w-3.5 h-3.5" /> : <ChevronDown className="w-3.5 h-3.5" />}
Info
</button>
</div>
</div>
{/* Expanded Empty Telemetry Status */}
{isExpanded && (
<div className="mt-3 p-4 bg-black/40 rounded-lg border border-white/5 text-center text-xs text-slate-500 italic">
<AlertTriangle className="w-4 h-4 mx-auto text-rose-500/40 mb-1" />
Telemetry check-in pending. System is offline and not responding to pings.
</div>
)}
</div>
);
}
{/* Render Full Card for Active/Online System */}
return (
<div
key={clientId}
className="glass-panel p-5 md:p-6 rounded-2xl hover:border-violet-500/40 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(139,92,246,0.06)] transition-all duration-300 relative overflow-hidden group bg-gradient-to-b from-slate-900/40 to-slate-950/60"
>
{/* Status top glow line */}
<div className="absolute top-0 left-0 right-0 h-[3px] bg-gradient-to-r from-emerald-500 via-teal-400 to-emerald-400" />
{/* Title block */} {/* Title block */}
<div className="flex items-center justify-between mb-5"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className={`w-3.5 h-3.5 rounded-full flex items-center justify-center ${isOnline ? 'bg-emerald-500/10' : 'bg-rose-500/10'}`}> <div className="w-3.5 h-3.5 rounded-full flex items-center justify-center bg-emerald-500/10">
<span className="relative flex h-2 w-2"> <span className="relative flex h-2.5 w-2.5">
{isOnline && <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>} <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span className={`relative inline-flex rounded-full h-2 w-2 ${isOnline ? 'bg-emerald-500' : 'bg-rose-500'}`}></span> <span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500 glow-emerald"></span>
</span> </span>
</div> </div>
<span className="font-extrabold text-white text-lg tracking-tight group-hover:text-violet-300 transition-colors">{clientId}</span> <span className="font-extrabold text-white text-lg tracking-tight group-hover:text-violet-300 transition-colors">{clientId}</span>
<span className={`text-[9px] uppercase tracking-wider font-extrabold px-2 py-0.5 rounded ${ <span className="text-[9px] uppercase tracking-wider font-extrabold px-2 py-0.5 rounded bg-emerald-500/10 border border-emerald-500/20 text-emerald-300">
isOnline Active
? 'bg-emerald-500/10 border border-emerald-500/20 text-emerald-300'
: 'bg-rose-500/10 border border-rose-500/20 text-rose-300'
}`}>
{isOnline ? 'Active' : 'Offline'}
</span> </span>
</div> </div>
<div className="text-right"> <div className="text-left sm:text-right">
<span className="text-[10px] text-slate-500 block">LAST CONTACT</span> <span className="text-[9px] text-slate-500 block uppercase font-bold tracking-wider">LAST CHECK-IN</span>
<span className="text-[11px] font-bold text-slate-300 font-mono">{lastSeenDate}</span> <span className="text-xs font-bold text-slate-300 font-mono">{lastSeenDate}</span>
</div> </div>
</div> </div>
@@ -362,21 +494,21 @@ function App() {
{clientTelemetry ? ( {clientTelemetry ? (
<div className="space-y-5"> <div className="space-y-5">
{/* CPU & RAM progress */} {/* CPU & RAM progress */}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* CPU Progress block */} {/* CPU Progress block */}
{(() => { {(() => {
const cpu = clientTelemetry.cpu_percent || 0; const cpu = clientTelemetry.cpu_percent || 0;
const isWarning = cpu > 80; const isWarning = cpu > 80;
return ( return (
<div className="p-3.5 bg-slate-950/40 border border-white/5 rounded-2xl flex flex-col justify-between"> <div className="p-4 bg-slate-950/40 border border-white/5 rounded-xl flex flex-col justify-between">
<div className="flex justify-between items-center text-xs font-bold text-slate-400 mb-2"> <div className="flex justify-between items-center text-xs font-bold text-slate-400 mb-2.5">
<span className="flex items-center gap-1.5"> <span className="flex items-center gap-1.5">
<Cpu className={`w-3.5 h-3.5 ${isWarning ? 'text-rose-400 animate-pulse' : 'text-violet-400'}`} /> <Cpu className={`w-4 h-4 ${isWarning ? 'text-rose-400 animate-pulse' : 'text-violet-400'}`} />
CPU Load CPU Load
</span> </span>
<span className={`font-bold ${isWarning ? 'text-rose-400' : 'text-white'}`}>{cpu}%</span> <span className={`font-bold font-mono ${isWarning ? 'text-rose-400' : 'text-white'}`}>{cpu}%</span>
</div> </div>
<div className="w-full h-2.5 rounded-full bg-slate-900 overflow-hidden relative border border-white/5"> <div className="w-full h-2 rounded-full bg-slate-900 overflow-hidden relative border border-white/5">
<div <div
className={`h-full rounded-full transition-all duration-500 ${ className={`h-full rounded-full transition-all duration-500 ${
isWarning isWarning
@@ -389,8 +521,8 @@ function App() {
/> />
</div> </div>
{isWarning && ( {isWarning && (
<div className="text-[9px] text-rose-400 font-bold flex items-center gap-1 mt-1.5 animate-pulse"> <div className="text-[9px] text-rose-400 font-bold flex items-center gap-1 mt-2 animate-pulse">
<AlertTriangle className="w-2.5 h-2.5" /> High Utilization <AlertTriangle className="w-2.5 h-2.5" /> High CPU Warning
</div> </div>
)} )}
</div> </div>
@@ -405,15 +537,15 @@ function App() {
const ram = clientTelemetry.memory_percent || 0; const ram = clientTelemetry.memory_percent || 0;
const isWarning = ram > 85; const isWarning = ram > 85;
return ( return (
<div className="p-3.5 bg-slate-950/40 border border-white/5 rounded-2xl flex flex-col justify-between"> <div className="p-4 bg-slate-950/40 border border-white/5 rounded-xl flex flex-col justify-between">
<div className="flex justify-between items-center text-xs font-bold text-slate-400 mb-2"> <div className="flex justify-between items-center text-xs font-bold text-slate-400 mb-2.5">
<span className="flex items-center gap-1.5"> <span className="flex items-center gap-1.5">
<Activity className={`w-3.5 h-3.5 ${isWarning ? 'text-rose-400 animate-pulse' : 'text-violet-400'}`} /> <Activity className={`w-4 h-4 ${isWarning ? 'text-rose-400 animate-pulse' : 'text-violet-400'}`} />
Virtual RAM Virtual RAM
</span> </span>
<span className={`font-bold ${isWarning ? 'text-rose-400' : 'text-white'}`}>{ram}%</span> <span className={`font-bold font-mono ${isWarning ? 'text-rose-400' : 'text-white'}`}>{ram}%</span>
</div> </div>
<div className="w-full h-2.5 rounded-full bg-slate-900 overflow-hidden relative border border-white/5"> <div className="w-full h-2 rounded-full bg-slate-900 overflow-hidden relative border border-white/5">
<div <div
className={`h-full rounded-full transition-all duration-500 ${ className={`h-full rounded-full transition-all duration-500 ${
isWarning isWarning
@@ -425,7 +557,7 @@ function App() {
style={{ width: `${ram}%` }} style={{ width: `${ram}%` }}
/> />
</div> </div>
<div className="flex flex-wrap justify-between text-[9px] text-slate-500 font-mono font-medium mt-2 border-t border-white/5 pt-1.5 font-bold gap-x-1"> <div className="flex justify-between text-[9px] text-slate-500 font-mono mt-2.5 border-t border-white/5 pt-2">
<span>Used: <b className="text-slate-300">{used}G</b></span> <span>Used: <b className="text-slate-300">{used}G</b></span>
<span>Free: <b className="text-slate-300">{free}G</b></span> <span>Free: <b className="text-slate-300">{free}G</b></span>
<span>Total: <b className="text-slate-300">{total}G</b></span> <span>Total: <b className="text-slate-300">{total}G</b></span>
@@ -436,11 +568,11 @@ function App() {
</div> </div>
{/* GPU and Partition details */} {/* GPU and Partition details */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4 border-t border-white/5"> <div className="grid grid-cols-1 md:grid-cols-2 gap-5 pt-4 border-t border-white/5">
{/* Partition Storage */} {/* Partition Storage */}
<div className="space-y-2"> <div className="space-y-2.5">
<span className="text-[10px] uppercase font-extrabold tracking-wider text-slate-400 flex items-center gap-1.5"> <span className="text-[10px] uppercase font-extrabold tracking-wider text-slate-400 flex items-center gap-1.5">
<HardDrive className="w-3.5 h-3.5 text-indigo-400" /> Disk Drive Space <HardDrive className="w-4 h-4 text-indigo-400" /> Disk Drive Space
</span> </span>
<div className="space-y-2.5"> <div className="space-y-2.5">
{clientTelemetry.disks && clientTelemetry.disks.length > 0 ? ( {clientTelemetry.disks && clientTelemetry.disks.length > 0 ? (
@@ -449,20 +581,20 @@ function App() {
const diskFree = d.free_gb || 0; const diskFree = d.free_gb || 0;
const diskUsed = (diskTotal - diskFree).toFixed(2); const diskUsed = (diskTotal - diskFree).toFixed(2);
return ( return (
<div key={index} className="p-2.5 rounded-xl bg-slate-950/30 border border-white/5 space-y-1.5"> <div key={index} className="p-3 rounded-xl bg-slate-950/30 border border-white/5 space-y-2">
<div className="flex items-center justify-between text-xs"> <div className="flex items-center justify-between text-xs">
<span className="font-semibold text-slate-300">Disk [ {d.mount} ]</span> <span className="font-bold text-slate-300">Disk [ {d.mount} ]</span>
<span className="font-mono text-slate-400 text-[10px]"> <span className="font-mono text-slate-400 text-[10px]">
Used: <b className="text-slate-300">{diskUsed}G</b> / {diskTotal}G Used: <b className="text-slate-300">{diskUsed}G</b> / {diskTotal}G
</span> </span>
</div> </div>
<div className="w-full h-1.5 rounded-full bg-slate-900 overflow-hidden"> <div className="w-full h-1.5 rounded-full bg-slate-900 overflow-hidden">
<div <div
className={`h-full rounded-full ${d.percent > 85 ? 'bg-rose-500' : 'bg-indigo-500'}`} className={`h-full rounded-full ${d.percent > 85 ? 'bg-rose-505' : 'bg-indigo-500'}`}
style={{ width: `${d.percent}%` }} style={{ width: `${d.percent}%` }}
/> />
</div> </div>
<div className="flex justify-between text-[8px] text-slate-500 font-mono"> <div className="flex justify-between text-[9px] text-slate-500 font-mono">
<span>Free: <b className="text-slate-400">{diskFree} GB</b></span> <span>Free: <b className="text-slate-400">{diskFree} GB</b></span>
<span>{d.percent}% Used</span> <span>{d.percent}% Used</span>
</div> </div>
@@ -476,35 +608,35 @@ function App() {
</div> </div>
{/* Nvidia Core GPU Vitals */} {/* Nvidia Core GPU Vitals */}
<div className="space-y-2"> <div className="space-y-2.5">
<span className="text-[10px] uppercase font-extrabold tracking-wider text-slate-400 flex items-center gap-1.5"> <span className="text-[10px] uppercase font-extrabold tracking-wider text-slate-400 flex items-center gap-1.5">
<Flame className="w-3.5 h-3.5 text-rose-400 animate-pulse" /> Nvidia GPU Accelerator <Flame className="w-4 h-4 text-rose-400 animate-pulse" /> Nvidia GPU Accelerator
</span> </span>
<div className="space-y-1.5"> <div className="space-y-2.5">
{clientTelemetry.gpus && clientTelemetry.gpus.length > 0 ? ( {clientTelemetry.gpus && clientTelemetry.gpus.length > 0 ? (
clientTelemetry.gpus.map((gpu, index) => ( clientTelemetry.gpus.map((gpu, index) => (
<div key={index} className="p-2.5 rounded-xl bg-slate-950/40 border border-white/5 space-y-2"> <div key={index} className="p-3 rounded-xl bg-slate-950/40 border border-white/5 space-y-2">
<div className="flex justify-between items-center text-xs border-b border-white/5 pb-1"> <div className="flex justify-between items-center text-xs border-b border-white/5 pb-1.5">
<span className="font-bold text-slate-200 truncate max-w-[130px]">🎮 {gpu.name}</span> <span className="font-extrabold text-slate-200 truncate max-w-[150px]">🎮 {gpu.name}</span>
<span className="text-violet-400 font-extrabold font-mono text-[11px]">Load: {gpu.utilization}</span> <span className="text-violet-400 font-bold font-mono text-xs">Load: {gpu.utilization}</span>
</div> </div>
<div className="grid grid-cols-2 gap-x-2 gap-y-1 text-[10px] text-slate-400 font-mono"> <div className="grid grid-cols-2 gap-x-3 gap-y-1.5 text-[10px] text-slate-400 font-mono">
<div className="flex justify-between"><span>Temp:</span> <b className="text-slate-200 font-semibold">{gpu.temp}</b></div> <div className="flex justify-between border-r border-white/5 pr-1.5"><span>Temp:</span> <b className="text-slate-200">{gpu.temp}</b></div>
<div className="flex justify-between"><span>Fans:</span> <b className="text-slate-200 font-semibold">{gpu.fan_speed}</b></div> <div className="flex justify-between pl-1.5"><span>Fans:</span> <b className="text-slate-200">{gpu.fan_speed}</b></div>
<div className="col-span-2 flex justify-between border-t border-white/5 pt-1 mt-0.5"> <div className="col-span-2 flex justify-between border-t border-white/5 pt-1.5 mt-1">
<span>VRAM Used:</span> <span>VRAM:</span>
<b className="text-slate-200 font-semibold">{gpu.memory_used} / {gpu.memory_total || 'N/A'}</b> <b className="text-slate-200">{gpu.memory_used} / {gpu.memory_total || 'N/A'}</b>
</div> </div>
<div className="col-span-2 flex justify-between"> <div className="col-span-2 flex justify-between">
<span>Power Draw:</span> <span>Power:</span>
<b className="text-slate-200 font-semibold">{gpu.power_draw} / {gpu.power_limit || 'N/A'}</b> <b className="text-slate-200">{gpu.power_draw} / {gpu.power_limit || 'N/A'}</b>
</div> </div>
</div> </div>
</div> </div>
)) ))
) : ( ) : (
<div className="text-xs text-slate-500 py-3 text-center font-medium bg-slate-950/15 border border-white/5 rounded-xl italic"> <div className="text-xs text-slate-500 py-6 text-center font-medium bg-slate-950/15 border border-white/5 rounded-xl italic">
No NVIDIA GPU hardware reported. No NVIDIA GPU hardware detected.
</div> </div>
)} )}
</div> </div>
@@ -513,7 +645,7 @@ function App() {
</div> </div>
) : ( ) : (
<div className="text-center py-6 bg-slate-950/20 rounded-xl border border-white/5 my-4"> <div className="text-center py-8 bg-slate-950/20 rounded-2xl border border-white/5 my-4">
<p className="text-xs text-slate-400 font-medium italic">Telemetry check-in pending... Waiting for dynamic vitals transmission</p> <p className="text-xs text-slate-400 font-medium italic">Telemetry check-in pending... Waiting for dynamic vitals transmission</p>
</div> </div>
)} )}
@@ -526,46 +658,48 @@ function App() {
{/* Maintenance Command Dispatch deck (Right sidebar) */} {/* Maintenance Command Dispatch deck (Right sidebar) */}
<div className="lg:col-span-1"> <div className="lg:col-span-1">
<div className="glass-panel p-6 rounded-2xl sticky top-24 border border-white/5 space-y-6"> <div className="glass-panel p-5 md:p-6 rounded-2xl sticky top-24 border border-white/5 space-y-6">
<div className="border-b border-white/5 pb-3"> <div className="border-b border-white/5 pb-3">
<h2 className="text-sm font-bold text-white uppercase tracking-wider flex items-center gap-2"> <h2 className="text-xs font-bold text-slate-300 uppercase tracking-wider flex items-center gap-2">
<Play className="w-4 h-4 text-violet-400 fill-violet-400" /> <Play className="w-4 h-4 text-violet-400 fill-violet-400" />
Maintenance Dispatcher Maintenance Dispatcher
</h2> </h2>
<p className="text-xs text-slate-400 mt-0.5">Send a whitelisted command down to a node</p> <p className="text-[11px] text-slate-400 mt-0.5">Send a whitelisted command down to a node</p>
</div> </div>
<form onSubmit={handleDispatch} className="space-y-4"> <form onSubmit={handleDispatch} className="space-y-5">
{/* Select target node check list */} {/* Select target node check list */}
<div> <div>
<label className="block text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2 flex justify-between"> <div className="flex items-center justify-between mb-2">
<span>Select Fleet Targets</span> <label className="text-xs font-bold text-slate-400 uppercase tracking-wider">
<div className="flex gap-2.5"> Select Targets
</label>
<div className="flex gap-2">
<button <button
type="button" type="button"
onClick={() => setSelectedClients(Object.keys(clients))} onClick={() => setSelectedClients(Object.keys(clients))}
className="text-[10px] text-violet-400 font-extrabold hover:text-violet-300 transition-colors uppercase tracking-wider" className="text-[10px] text-violet-400 font-bold hover:text-violet-300 transition-colors uppercase tracking-wider bg-violet-500/5 px-2 py-0.5 rounded border border-violet-500/10"
> >
All All
</button> </button>
<button <button
type="button" type="button"
onClick={() => setSelectedClients(Object.entries(clients).filter(([_, info]) => info.active).map(([id]) => id))} onClick={() => setSelectedClients(Object.entries(clients).filter(([_, info]) => info.active).map(([id]) => id))}
className="text-[10px] text-emerald-400 font-extrabold hover:text-emerald-300 transition-colors uppercase tracking-wider" className="text-[10px] text-emerald-400 font-bold hover:text-emerald-300 transition-colors uppercase tracking-wider bg-emerald-500/5 px-2 py-0.5 rounded border border-emerald-500/10"
> >
Active Active
</button> </button>
<button <button
type="button" type="button"
onClick={() => setSelectedClients([])} onClick={() => setSelectedClients([])}
className="text-[10px] text-slate-500 font-extrabold hover:text-slate-400 transition-colors uppercase tracking-wider" className="text-[10px] text-slate-500 font-bold hover:text-slate-400 transition-colors uppercase tracking-wider bg-white/5 px-2 py-0.5 rounded border border-white/5"
> >
None Clear
</button> </button>
</div> </div>
</label> </div>
<div className="grid grid-cols-2 gap-2 max-h-48 overflow-y-auto p-1 bg-slate-950/40 border border-white/5 rounded-xl"> <div className="grid grid-cols-2 gap-2 max-h-48 overflow-y-auto p-2 bg-slate-950 border border-white/5 rounded-xl">
{Object.entries(clients).length === 0 ? ( {Object.entries(clients).length === 0 ? (
<span className="col-span-2 text-xs text-slate-500 italic text-center py-6 block font-medium">No registered nodes found.</span> <span className="col-span-2 text-xs text-slate-500 italic text-center py-6 block font-medium">No registered nodes found.</span>
) : ( ) : (
@@ -584,21 +718,21 @@ function App() {
}} }}
className={`p-2.5 rounded-xl border text-left transition-all duration-200 relative flex flex-col justify-between overflow-hidden ${ className={`p-2.5 rounded-xl border text-left transition-all duration-200 relative flex flex-col justify-between overflow-hidden ${
isSelected isSelected
? 'bg-gradient-to-br from-violet-600/15 to-indigo-600/15 border-violet-500/80 shadow-md shadow-violet-900/15 text-white' ? 'bg-gradient-to-br from-violet-600/20 to-indigo-600/20 border-violet-500/80 shadow-md shadow-violet-900/15 text-white'
: 'bg-slate-950/60 border-white/5 hover:border-white/15 text-slate-400 hover:text-slate-200' : 'bg-slate-900/40 border-white/5 hover:border-white/10 text-slate-400 hover:text-slate-200'
}`} }`}
> >
{/* Glow element */} {/* Glow dot */}
{isSelected && <span className="absolute top-0 right-0 w-2 h-2 bg-violet-400 rounded-full blur-[1px] -mr-0.5 -mt-0.5" />} {isSelected && <span className="absolute top-0 right-0 w-2 h-2 bg-violet-400 rounded-full blur-[1px] -mr-0.5 -mt-0.5" />}
<div className="flex items-center justify-between w-full gap-2"> <div className="flex items-center justify-between w-full gap-2">
<span className={`text-xs font-extrabold truncate ${isSelected ? 'text-violet-200' : 'text-slate-300'}`}> <span className={`text-xs font-extrabold truncate ${isSelected ? 'text-violet-200' : 'text-slate-300'}`}>
{id} {id}
</span> </span>
<span className={`w-1.5 h-1.5 rounded-full shrink-0 ${info.active ? 'bg-emerald-400' : 'bg-rose-400'}`} /> <span className={`w-1.5 h-1.5 rounded-full shrink-0 ${info.active ? 'bg-emerald-400 glow-emerald' : 'bg-rose-400'}`} />
</div> </div>
<span className="text-[9px] font-mono text-slate-500 mt-1 font-semibold uppercase tracking-wider"> <span className="text-[9px] font-mono text-slate-500 mt-1 font-semibold uppercase tracking-wider">
{info.active ? '🟢 Online' : '🔴 Offline'} {info.active ? 'Online' : 'Offline'}
</span> </span>
</button> </button>
); );
@@ -609,25 +743,25 @@ function App() {
{/* Manual Target target ID */} {/* Manual Target target ID */}
<div> <div>
<label className="block text-xs font-semibold text-slate-400 uppercase tracking-wider mb-1.5">Or Type Target Name manually (comma-separated for batch)</label> <label className="block text-[11px] font-semibold text-slate-400 uppercase tracking-wider mb-1.5">Or Type Custom Node Target</label>
<input <input
type="text" type="text"
placeholder="e.g. kawsik, client_1, client_2" placeholder="e.g. client_1, client_2"
value={manualClient} value={manualClient}
onChange={(e) => setManualClient(e.target.value)} onChange={(e) => setManualClient(e.target.value)}
className="w-full bg-slate-950 border border-white/10 rounded-xl p-3 text-sm text-slate-300 font-semibold focus:outline-none focus:border-violet-500 placeholder-slate-600 transition-colors" className="w-full bg-slate-950 border border-white/10 rounded-xl p-3 text-xs text-slate-200 font-semibold focus:outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500/20 placeholder-slate-600 transition-all focus-glow-violet"
/> />
</div> </div>
{/* Select command key */} {/* Select command key */}
<div> <div>
<label className="block text-xs font-semibold text-slate-400 uppercase tracking-wider mb-1.5">Pick Whitelisted Operation</label> <label className="block text-[11px] font-semibold text-slate-400 uppercase tracking-wider mb-1.5">Select Whitelisted Operation Key</label>
<select <select
value={selectedCommand} value={selectedCommand}
onChange={(e) => setSelectedCommand(e.target.value)} onChange={(e) => setSelectedCommand(e.target.value)}
className="w-full bg-slate-950 border border-white/10 rounded-xl p-3 text-sm text-slate-300 font-semibold focus:outline-none focus:border-violet-500 transition-colors" className="w-full bg-slate-950 border border-white/10 rounded-xl p-3 text-xs text-slate-200 font-semibold focus:outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500/20 transition-all"
> >
<option value="">-- Choose Whitelisted Command --</option> <option value="">-- Select Permitted Operation --</option>
{Array.from(new Set([ {Array.from(new Set([
...Object.keys(whitelist.nt || {}), ...Object.keys(whitelist.nt || {}),
...Object.keys(whitelist.posix || {}) ...Object.keys(whitelist.posix || {})
@@ -641,23 +775,23 @@ function App() {
{/* Manual command command key */} {/* Manual command command key */}
<div> <div>
<label className="block text-xs font-semibold text-slate-400 uppercase tracking-wider mb-1.5">Or Type Operation Key manually</label> <label className="block text-[11px] font-semibold text-slate-400 uppercase tracking-wider mb-1.5">Or Type Custom Operation Key</label>
<input <input
type="text" type="text"
placeholder="e.g. repair_mysql" placeholder="e.g. check_drives"
value={manualCommand} value={manualCommand}
onChange={(e) => setManualCommand(e.target.value)} onChange={(e) => setManualCommand(e.target.value)}
className="w-full bg-slate-950 border border-white/10 rounded-xl p-3 text-sm text-slate-300 font-semibold focus:outline-none focus:border-violet-500 placeholder-slate-600 transition-colors" className="w-full bg-slate-950 border border-white/10 rounded-xl p-3 text-xs text-slate-200 font-semibold focus:outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500/20 placeholder-slate-600 transition-all focus-glow-violet"
/> />
</div> </div>
{/* Fire execution button */} {/* Fire execution button */}
<button <button
type="submit" type="submit"
className="w-full bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-500 hover:to-indigo-500 text-white font-bold py-3 px-4 rounded-xl text-sm flex items-center justify-center gap-2 shadow-lg shadow-violet-950/45 hover:shadow-violet-900/50 hover:translate-y-[-1px] active:translate-y-[1px] transition-all" className="w-full bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-500 hover:to-indigo-500 text-white font-bold py-3.5 px-4 rounded-xl text-xs flex items-center justify-center gap-2 shadow-lg shadow-violet-950/45 hover:shadow-violet-900/50 hover:translate-y-[-1px] active:translate-y-[1px] active:scale-[0.98] transition-all"
> >
<Zap className="w-4 h-4 fill-white" /> <Zap className="w-3.5 h-3.5 fill-white" />
Dispatch Operation to Node Dispatch Command Batch
</button> </button>
</form> </form>
</div> </div>
@@ -666,37 +800,81 @@ function App() {
) : ( ) : (
/* Visual Whitelist JSON Configuration Editor */ /* Visual Whitelist JSON Configuration Editor */
<div className="col-span-3"> <div className="col-span-3">
<div className="glass-panel p-6 rounded-2xl border border-white/5 space-y-6"> <div className="glass-panel p-5 md:p-6 rounded-2xl border border-white/5 space-y-6">
<div className="border-b border-white/5 pb-3"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-b border-white/5 pb-4">
<div>
<h2 className="text-sm font-bold text-white uppercase tracking-wider flex items-center gap-2"> <h2 className="text-sm font-bold text-white uppercase tracking-wider flex items-center gap-2">
<Settings className="w-4 h-4 text-violet-400" /> <Settings className="w-4 h-4 text-violet-400" />
Visual Command Whitelist Editor Visual Commands whitelist Editor
</h2> </h2>
<p className="text-xs text-slate-400 mt-0.5"> <p className="text-[11px] text-slate-400 mt-0.5">
Directly configure permitted cluster arrays. Permitted operations are pushed dynamically to remote agents without any central restart required! Permitted operations are checked against whitelist config and pushed to nodes without restarts.
</p> </p>
</div> </div>
{/* JSON Validation Status Badge */}
<div>
{jsonError ? (
<span className="inline-flex items-center gap-1 px-3 py-1.5 rounded-lg bg-rose-500/10 border border-rose-500/20 text-rose-300 text-xs font-semibold">
<XCircle className="w-4 h-4 text-rose-400 shrink-0" />
Invalid JSON Config
</span>
) : (
<span className="inline-flex items-center gap-1 px-3 py-1.5 rounded-lg bg-emerald-500/10 border border-emerald-500/20 text-emerald-300 text-xs font-semibold">
<CheckCircle className="w-4 h-4 text-emerald-400 shrink-0" />
Valid JSON Config
</span>
)}
</div>
</div>
{/* IDE Editor UI wrapper */}
<div className="space-y-4"> <div className="space-y-4">
<div className="rounded-xl overflow-hidden border border-white/5 bg-[#02040a] shadow-inner relative">
{/* Editor Tab Bar */}
<div className="bg-[#0b0f19] px-4 py-2 border-b border-white/5 flex items-center justify-between text-xs text-slate-400 font-mono">
<div className="flex items-center gap-2">
<FileCode className="w-4 h-4 text-violet-400" />
<span className="font-semibold text-slate-300">commands.json</span>
</div>
<span className="text-[10px] text-slate-500 font-semibold uppercase tracking-wider bg-slate-900 px-2 py-0.5 rounded border border-white/5">JSON</span>
</div>
<textarea <textarea
className="w-full h-[400px] bg-slate-950 border border-white/10 rounded-xl p-4 font-mono text-sm text-emerald-400 leading-relaxed focus:outline-none focus:border-violet-500 transition-colors focus:ring-1 focus:ring-violet-500/25" className="w-full h-[450px] bg-transparent p-4 font-mono text-xs text-emerald-400 leading-relaxed focus:outline-none resize-y"
spellCheck="false" spellCheck="false"
value={editorText} value={editorText}
onChange={(e) => setEditorText(e.target.value)} onChange={(e) => setEditorText(e.target.value)}
/> />
</div>
<div className="flex justify-end gap-3"> {jsonError && (
<div className="p-3.5 bg-rose-950/40 border border-rose-500/30 rounded-xl text-rose-300 font-mono text-xs leading-relaxed flex gap-2 items-start">
<AlertTriangle className="w-4 h-4 text-rose-400 shrink-0 mt-0.5" />
<div>
<span className="font-bold text-rose-200">JSON Parse Error:</span> {jsonError}
</div>
</div>
)}
<div className="flex flex-col sm:flex-row justify-end gap-3 pt-2">
<button <button
onClick={fetchWhitelist} onClick={fetchWhitelist}
className="px-5 py-2.5 rounded-xl border border-white/10 hover:bg-white/5 font-semibold text-xs text-slate-300 transition-colors" className="w-full sm:w-auto px-5 py-2.5 rounded-xl border border-white/10 hover:bg-white/5 font-semibold text-xs text-slate-300 transition-colors"
> >
Discard Changes Discard Changes
</button> </button>
<button <button
onClick={handleSaveWhitelist} onClick={handleSaveWhitelist}
className="px-6 py-2.5 rounded-xl bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-500 hover:to-indigo-500 text-white font-bold text-xs shadow-lg shadow-violet-950/40 hover:shadow-violet-900/50 hover:translate-y-[-1px] active:translate-y-[1px] transition-all" disabled={jsonError !== null}
className={`w-full sm:w-auto px-6 py-2.5 rounded-xl text-white font-bold text-xs flex items-center justify-center gap-1.5 transition-all ${
jsonError
? 'bg-slate-800 text-slate-500 border border-slate-700 cursor-not-allowed'
: 'bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-500 hover:to-indigo-500 shadow-lg shadow-violet-950/40 hover:shadow-violet-900/50 hover:translate-y-[-1px] active:translate-y-[1px]'
}`}
> >
Deploy updated JSON Configuration <Check className="w-3.5 h-3.5" />
Deploy Whitelist Configuration
</button> </button>
</div> </div>
</div> </div>
@@ -706,51 +884,60 @@ function App() {
</main> </main>
{/* Scrolling DevOps CLI output shell terminal */} {/* Scrolling DevOps CLI output shell terminal */}
<footer className="max-w-7xl w-full mx-auto px-6 mt-8"> <footer className="max-w-7xl w-full mx-auto px-4 md:px-6 mt-8">
<div className="bg-slate-950/95 border border-white/5 rounded-2xl overflow-hidden shadow-2xl"> <div className="bg-slate-950/95 border border-white/5 rounded-2xl overflow-hidden shadow-2xl">
<div className="bg-slate-900/80 px-4 py-2 border-b border-white/5 flex items-center justify-between"> {/* Terminal Title Bar */}
<div className="bg-[#0b0f19] px-4 py-3 border-b border-white/5 flex flex-col sm:flex-row gap-3 sm:items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* Window Controls */}
<div className="flex gap-1.5"> <div className="flex gap-1.5">
<span className="w-2.5 h-2.5 rounded-full bg-rose-500/60 block"></span> <span className="w-2.5 h-2.5 rounded-full bg-rose-500/70 block"></span>
<span className="w-2.5 h-2.5 rounded-full bg-yellow-500/60 block"></span> <span className="w-2.5 h-2.5 rounded-full bg-yellow-500/70 block"></span>
<span className="w-2.5 h-2.5 rounded-full bg-emerald-500/60 block"></span> <span className="w-2.5 h-2.5 rounded-full bg-emerald-500/70 block"></span>
</div> </div>
<span className="text-[11px] uppercase tracking-wider font-bold text-slate-400 font-mono ml-2 flex items-center gap-1.5"> <span className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 font-mono ml-2.5 flex items-center gap-2">
<Terminal className="w-3.5 h-3.5 text-violet-400" /> <Terminal className="w-4 h-4 text-violet-400" />
Live Fleet Activity Logs & Webhook outputs Live Fleet Activity & Webhook Output Shell
</span> </span>
</div> </div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-3 w-full sm:w-auto justify-between sm:justify-end">
<div className="relative">
<select <select
value={filterClient} value={filterClient}
onChange={(e) => setFilterClient(e.target.value)} onChange={(e) => setFilterClient(e.target.value)}
className="bg-slate-950 border border-white/10 rounded-lg px-2.5 py-1 text-[11px] text-slate-300 font-semibold focus:outline-none focus:border-violet-500 transition-colors" className="bg-slate-900 border border-white/10 rounded-lg pl-3 pr-8 py-1.5 text-xs text-slate-300 font-semibold focus:outline-none focus:border-violet-500 transition-colors appearance-none cursor-pointer"
> >
<option value="all">🔍 All Fleet Logs</option> <option value="all">🔍 Show All Nodes</option>
{Object.keys(clients).map((cid) => ( {Object.keys(clients).map((cid) => (
<option key={cid} value={cid}> <option key={cid} value={cid}>
{cid} logs {cid} logs
</option> </option>
))} ))}
</select> </select>
<span className="text-[10px] text-slate-500 font-mono font-medium">Terminal Shell v1.0</span> <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-slate-400">
<ChevronDown className="w-3.5 h-3.5" />
</div>
</div>
<span className="text-[10px] text-slate-500 font-mono font-medium hidden sm:inline">Shell v1.0</span>
</div> </div>
</div> </div>
<div ref={terminalRef} className="p-4 h-64 overflow-y-auto font-mono text-xs space-y-2.5 terminal-scroll bg-black/90"> {/* Terminal Console output */}
<div ref={terminalRef} className="p-4 h-72 overflow-y-auto font-mono text-[11px] space-y-3 terminal-scroll bg-black/95 select-text">
{sortedTimelineLogs.length === 0 ? ( {sortedTimelineLogs.length === 0 ? (
<span className="text-slate-500 block italic py-2">Waiting for check-in events to stream... No historical logs recorded yet.</span> <span className="text-slate-600 block italic py-2">Waiting for fleet check-ins and dispatches to stream... No historical logs recorded yet.</span>
) : ( ) : (
sortedTimelineLogs.map((log, index) => { sortedTimelineLogs.map((log, index) => {
const formattedTime = new Date(log.timestamp).toLocaleTimeString(); const formattedTime = new Date(log.timestamp).toLocaleTimeString();
if (log.type === 'telemetry') { if (log.type === 'telemetry') {
return ( return (
<div key={index} className="text-slate-400 flex items-start gap-1 leading-relaxed"> <div key={index} className="text-slate-400 flex items-start gap-1 leading-relaxed break-words whitespace-pre-wrap">
<span className="text-slate-600 flex-shrink-0">[{formattedTime}]</span> <span className="text-slate-600 shrink-0 select-none">[{formattedTime}]</span>
<span className="text-sky-400 flex-shrink-0 font-bold">INFO:</span> <span className="text-sky-400 shrink-0 font-bold select-none">INFO:</span>
<span> <span>
Client <b className="text-white">{log.client_id}</b> check-in telemetry pushed. (CPU: {log.detail.cpu_percent}%, RAM: {log.detail.memory_percent}%) Client <b className="text-white font-semibold">{log.client_id}</b> check-in telemetry pushed. (CPU: <b className="text-sky-300">{log.detail.cpu_percent}%</b>, RAM: <b className="text-sky-300">{log.detail.memory_percent}%</b>)
</span> </span>
</div> </div>
); );
@@ -758,11 +945,11 @@ function App() {
if (log.type === 'command_scheduled') { if (log.type === 'command_scheduled') {
return ( return (
<div key={index} className="text-yellow-200 flex items-start gap-1 leading-relaxed"> <div key={index} className="text-amber-200/90 flex items-start gap-1 leading-relaxed break-words whitespace-pre-wrap">
<span className="text-slate-600 flex-shrink-0">[{formattedTime}]</span> <span className="text-slate-600 shrink-0 select-none">[{formattedTime}]</span>
<span className="text-yellow-500 flex-shrink-0 font-bold">DISPATCH:</span> <span className="text-amber-400 shrink-0 font-bold select-none">DISPATCH:</span>
<span> <span>
Operation <b className="text-yellow-400 font-semibold">"{log.detail.command}"</b> successfully queued for node <b className="text-white">{log.client_id}</b>. Operation <b className="text-amber-300 font-bold">"{log.detail.command}"</b> successfully queued for node <b className="text-white font-semibold">{log.client_id}</b>.
</span> </span>
</div> </div>
); );
@@ -770,11 +957,11 @@ function App() {
if (log.type === 'command_polled') { if (log.type === 'command_polled') {
return ( return (
<div key={index} className="text-orange-200/90 flex items-start gap-1 leading-relaxed"> <div key={index} className="text-orange-200/95 flex items-start gap-1 leading-relaxed break-words whitespace-pre-wrap">
<span className="text-slate-600 flex-shrink-0">[{formattedTime}]</span> <span className="text-slate-600 shrink-0 select-none">[{formattedTime}]</span>
<span className="text-orange-400 flex-shrink-0 font-bold">POLL:</span> <span className="text-orange-400 shrink-0 font-bold select-none">POLL:</span>
<span> <span>
Agent <b className="text-white">{log.client_id}</b> retrieved pending command <b className="text-orange-300 font-semibold">"{log.detail.command}"</b>. Starting execution... Agent <b className="text-white font-semibold">{log.client_id}</b> retrieved pending command <b className="text-orange-300 font-bold">"{log.detail.command}"</b>. Executing process...
</span> </span>
</div> </div>
); );
@@ -783,22 +970,24 @@ function App() {
if (log.type === 'command_result') { if (log.type === 'command_result') {
const isSuccess = log.detail.returncode === 0; const isSuccess = log.detail.returncode === 0;
return ( return (
<div key={index} className="p-2.5 rounded-lg border bg-slate-950/70 border-white/5 space-y-1 my-1"> <div key={index} className="p-3.5 rounded-xl border bg-slate-950/80 border-white/5 space-y-2.5 my-1.5">
<div className="flex items-center justify-between"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-white/5 pb-2">
<div className="flex items-center gap-1.5"> <div className="flex flex-wrap items-center gap-1.5">
<span className="text-slate-600 font-mono">[{formattedTime}]</span> <span className="text-slate-600 select-none">[{formattedTime}]</span>
<span className={`font-bold ${isSuccess ? 'text-emerald-400' : 'text-rose-400'}`}> <span className={`font-bold text-xs ${isSuccess ? 'text-emerald-400' : 'text-rose-400'}`}>
{isSuccess ? 'OUTCOME SUCCESS:' : 'OUTCOME FAILURE:'} {isSuccess ? ' SUCCESS' : ' FAILURE'}
</span> </span>
<span className="text-slate-200"> <span className="text-slate-300">
Command <b className="text-white">"{log.detail.command}"</b> on <b className="text-white">{log.client_id}</b> finished with code <b className="font-mono">{log.detail.returncode}</b>. Command <b className="text-white font-semibold">"{log.detail.command}"</b> on <b className="text-white font-semibold">{log.client_id}</b> finished with code <b className="font-mono text-slate-100">{log.detail.returncode}</b>.
</span> </span>
</div> </div>
</div> </div>
{/* Subprocess console stdout/stderr */} {/* Subprocess console stdout/stderr */}
{(log.detail.stdout || log.detail.stderr) && ( {(log.detail.stdout || log.detail.stderr) && (
<pre className={`p-2 rounded bg-black/60 font-mono text-[11px] leading-normal overflow-x-auto ${isSuccess ? 'text-slate-300 border-l border-emerald-500/45' : 'text-rose-300 border-l border-rose-500/45'}`}> <pre className={`p-3 rounded-lg bg-black/80 font-mono text-[10px] leading-relaxed overflow-x-auto break-all whitespace-pre-wrap ${
isSuccess ? 'text-slate-300 border-l-2 border-emerald-500/50' : 'text-rose-300 border-l-2 border-rose-500/50'
}`}>
{log.detail.stdout && <code className="block">{log.detail.stdout}</code>} {log.detail.stdout && <code className="block">{log.detail.stdout}</code>}
{log.detail.stderr && <code className="block text-rose-400 font-semibold">{log.detail.stderr}</code>} {log.detail.stderr && <code className="block text-rose-400 font-semibold">{log.detail.stderr}</code>}
</pre> </pre>
@@ -810,7 +999,6 @@ function App() {
return null; return null;
}) })
)} )}
{/* Removed internal dummy anchor to avoid viewport jumping */}
</div> </div>
</div> </div>
</footer> </footer>
@@ -818,4 +1006,25 @@ function App() {
); );
} }
// Simple custom Check icon component since lucide-react Check may sometimes fail or need explicit import mapping
function Check({ className, ...props }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
{...props}
>
<polyline points="20 6 9 17 4 12" />
</svg>
);
}
export default App; export default App;

View File

@@ -1,4 +1,4 @@
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@@ -7,12 +7,15 @@
body { body {
margin: 0; margin: 0;
font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: #080c14; background-color: #050811;
background-image: radial-gradient(circle at 12% 18%, rgba(139, 92, 246, 0.05) 0%, transparent 45%), background-image:
radial-gradient(circle at 88% 82%, rgba(59, 130, 246, 0.05) 0%, transparent 45%); radial-gradient(circle at 10% 20%, rgba(124, 58, 237, 0.06) 0%, transparent 40%),
radial-gradient(circle at 90% 80%, rgba(59, 130, 246, 0.06) 0%, transparent 40%),
radial-gradient(circle at 50% 50%, rgba(16, 185, 129, 0.02) 0%, transparent 50%);
color: #f3f4f6; color: #f3f4f6;
min-height: 100vh; min-height: 100vh;
overflow-x: hidden; overflow-x: hidden;
letter-spacing: -0.01em;
} }
code { code {
@@ -21,44 +24,85 @@ code {
/* Custom scrollbars */ /* Custom scrollbars */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 6px;
height: 8px; height: 6px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.3);
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.08);
border-radius: 4px; border-radius: 9999px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.18);
} }
/* Glassmorphism Panel styles */ /* Premium Glassmorphism Panel styles */
.glass-panel { .glass-panel {
background: rgba(13, 18, 30, 0.75); background: rgba(10, 15, 28, 0.7);
backdrop-filter: blur(20px); backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.04);
box-shadow: 0 10px 40px 0 rgba(0, 0, 0, 0.45); box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
} }
/* Smooth active pulses */ .glass-panel:hover {
.pulse-glow-emerald { border-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 0 12px #10b981; box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.45);
animation: pulse-emerald 2s infinite;
} }
@keyframes pulse-emerald { /* Glowing Animations for Statuses */
0% { transform: scale(0.92); opacity: 1; } .glow-emerald {
50% { transform: scale(1.15); opacity: 0.6; } box-shadow: 0 0 12px rgba(16, 185, 129, 0.4);
100% { transform: scale(0.92); opacity: 1; } animation: pulse-glow-emerald 2.5s infinite alternate;
} }
.glow-rose {
box-shadow: 0 0 12px rgba(244, 63, 94, 0.3);
animation: pulse-glow-rose 2.5s infinite alternate;
}
.glow-violet {
box-shadow: 0 0 12px rgba(139, 92, 246, 0.4);
animation: pulse-glow-violet 2.5s infinite alternate;
}
@keyframes pulse-glow-emerald {
0% { transform: scale(0.95); opacity: 0.8; box-shadow: 0 0 4px rgba(16, 185, 129, 0.2); }
100% { transform: scale(1.05); opacity: 1; box-shadow: 0 0 14px rgba(16, 185, 129, 0.6); }
}
@keyframes pulse-glow-rose {
0% { transform: scale(0.95); opacity: 0.7; box-shadow: 0 0 4px rgba(244, 63, 94, 0.15); }
100% { transform: scale(1.05); opacity: 1; box-shadow: 0 0 12px rgba(244, 63, 94, 0.45); }
}
@keyframes pulse-glow-violet {
0% { transform: scale(0.95); opacity: 0.8; box-shadow: 0 0 4px rgba(139, 92, 246, 0.2); }
100% { transform: scale(1.05); opacity: 1; box-shadow: 0 0 14px rgba(139, 92, 246, 0.6); }
}
/* Smooth Scrolling */
.terminal-scroll { .terminal-scroll {
scroll-behavior: smooth; scroll-behavior: smooth;
} }
/* Animated border glow on focus */
.focus-glow-violet:focus {
border-color: rgba(139, 92, 246, 0.5);
box-shadow: 0 0 12px rgba(139, 92, 246, 0.15);
}
/* Textarea customization */
.code-textarea {
background: #02040a;
color: #34d399; /* emerald-400 */
font-family: 'JetBrains Mono', monospace;
line-height: 1.6;
}