diff --git a/src/App.jsx b/src/App.jsx index 85b7763..5486734 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,15 +1,15 @@ import React, { useState, useEffect, useRef } from 'react'; -import { - Activity, - Cpu, - HardDrive, - Terminal, - Settings, - RefreshCw, - CheckCircle, - XCircle, - Zap, - Flame, +import { + Activity, + Cpu, + HardDrive, + Terminal, + Settings, + RefreshCw, + CheckCircle, + XCircle, + Zap, + Flame, Server, AlertTriangle, Play, @@ -19,36 +19,12 @@ import { Clock, ChevronDown, ChevronUp, - Database, ArrowRight, Sliders } from 'lucide-react'; import sideBarLogo from './assets/side-bar-logo.png'; -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' - : 'http://rmm-backend.seekright.com')); - -// Secure API fetch wrapper -const apiFetch = (url, options = {}) => { - const token = localStorage.getItem('rmm_token'); - if (token) { - options.headers = { - ...options.headers, - 'Authorization': `Bearer ${token}` - }; - } - return fetch(url, options).then(response => { - if (response.status === 401) { - localStorage.removeItem('rmm_token'); - window.dispatchEvent(new Event('rmm_unauthorized')); - } - return response; - }); -}; +import { API_BASE, apiFetch } from './api'; function App() { const [token, setToken] = useState(localStorage.getItem('rmm_token') || ''); @@ -61,17 +37,21 @@ function App() { const [telemetry, setTelemetry] = useState({}); const [logs, setLogs] = useState({}); const [whitelist, setWhitelist] = useState({}); - + + // Remote video fetch state + const [fileTransfers, setFileTransfers] = useState({}); + const [videoNameInputs, setVideoNameInputs] = useState({}); + // Dispatcher form state const [selectedClients, setSelectedClients] = useState([]); const [selectedCommand, setSelectedCommand] = useState(''); const [manualClient, setManualClient] = useState(''); const [manualCommand, setManualCommand] = useState(''); - + // Whitelist config editor state 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 [isSyncing, setIsSyncing] = useState(false); const [filterClient, setFilterClient] = useState('all'); @@ -123,6 +103,12 @@ function App() { const logsData = await logsRes.json(); setLogs(logsData); } + + // Sync remote video fetch transfer states + const ftRes = await apiFetch(`${API_BASE}/api/file-transfers`); + if (ftRes.ok) { + setFileTransfers(await ftRes.json()); + } } catch (err) { console.error('Network sync failure:', err); } finally { @@ -172,6 +158,47 @@ function App() { } }; + const handleRequestFile = async (clientId) => { + const filename = (videoNameInputs[clientId] || '').trim(); + if (!filename) { + showToast('Enter a video file name first.', 'error'); + return; + } + try { + const response = await apiFetch( + `${API_BASE}/api/request-file?client_id=${encodeURIComponent(clientId)}&filename=${encodeURIComponent(filename)}`, + { method: 'POST' } + ); + if (response.ok) { + showToast(`Requested '${filename}' from ${clientId}`); + syncFleetData(); + } else { + const data = await response.json().catch(() => ({})); + showToast(data.detail || 'Failed to request file.', 'error'); + } + } catch (err) { + showToast('Cannot connect to RMM backend server.', 'error'); + } + }; + + const handleCancelUpload = async (clientId, filename) => { + try { + const response = await apiFetch( + `${API_BASE}/api/cancel-upload?client_id=${encodeURIComponent(clientId)}&filename=${encodeURIComponent(filename)}`, + { method: 'POST' } + ); + if (response.ok) { + showToast(`Cancelled upload for '${filename}'`); + syncFleetData(); + } else { + const data = await response.json().catch(() => ({})); + showToast(data.detail || 'Failed to cancel upload.', 'error'); + } + } catch (err) { + showToast('Cannot connect to server.', 'error'); + } + }; + const handleLogout = () => { localStorage.removeItem('rmm_token'); setToken(''); @@ -327,14 +354,14 @@ function App() { {/* Decorative background glows */}
- +
- Seekright Logo
@@ -387,11 +414,10 @@ function App() {
{/* Dynamic Toast Alerts */} {toast && ( -
+ }`}> {toast.type === 'error' ? (
) : ( @@ -405,35 +431,33 @@ function App() {
- Seekright Logo
{/* Action Controls & Navigation tabs */}
-
-
- + {filteredClientEntries.length === 0 ? (
@@ -610,12 +627,13 @@ function App() { const isOnline = info.active; const lastSeenDate = info.last_seen ? new Date(info.last_seen).toLocaleString() : 'Never'; const isExpanded = expandedClients[clientId] || false; - - {/* Render Compact Card for Offline System */} + const ft = fileTransfers[clientId]; + + {/* Render Compact Card for Offline System */ } if (!isOnline) { return ( -
@@ -630,13 +648,13 @@ function App() { Offline
- +
Last Contact {lastSeenDate}
- + +
+ + {ft && ( +
+
+ {ft.filename} + + {(ft.status === 'requested' || ft.status === 'searching' || ft.status === 'uploading') && ( + + )} + {ft.status === 'ready' && } + {(ft.status === 'not_found' || ft.status === 'error') && } + {ft.status} + +
+ {ft.message && ( +

{ft.message}

+ )} + {ft.progress_percent !== undefined && ft.progress_percent !== null && ( +
+
+
+ )} + {(ft.status === 'requested' || ft.status === 'searching' || ft.status === 'uploading') && ( +
+ +
+ )} + {ft.status === 'ready' && ( + + )} +
+ )} +
); }) @@ -941,7 +1047,7 @@ function App() {
- +
{Object.entries(clients).length === 0 ? ( No registered nodes found. @@ -959,15 +1065,14 @@ function App() { setSelectedClients([...selectedClients, id]); } }} - className={`p-2.5 rounded-xl border text-left transition-all duration-200 relative flex flex-col justify-between overflow-hidden ${ - isSelected + className={`p-2.5 rounded-xl border text-left transition-all duration-200 relative flex flex-col justify-between overflow-hidden ${isSelected ? '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-900/40 border-white/5 hover:border-white/10 text-slate-400 hover:text-slate-200' - }`} + }`} > {/* Glow dot */} {isSelected && } - +
{id} @@ -1040,7 +1145,7 @@ function App() {
- ) : ( + ) : activeTab === 'editor' ? ( /* Visual Whitelist JSON Configuration Editor */
@@ -1054,7 +1159,7 @@ function App() { Permitted operations are checked against whitelist config and pushed to nodes without restarts.

- + {/* JSON Validation Status Badge */}
{jsonError ? ( @@ -1099,7 +1204,7 @@ function App() {
)} - +
- )} + ) : null} {/* Scrolling DevOps CLI output shell terminal */} @@ -1143,7 +1247,7 @@ function App() { Live Fleet Activity & Webhook Output Shell - +