source: lan-frontend/src/App.jsx@ 640ed89

Last change on this file since 640ed89 was 640ed89, checked in by istevanoska <ilinastevanoska@…>, 7 months ago

Initial commit

  • Property mode set to 100644
File size: 46.6 KB
Line 
1import React, { useEffect, useState } from "react";
2import './App.css';
3
4function App() {
5 const [computers, setComputers] = useState([]);
6 const [filteredComputers, setFilteredComputers] = useState([]);
7 const [stats, setStats] = useState(null);
8 const [selectedComputer, setSelectedComputer] = useState(null);
9 const [computerDetails, setComputerDetails] = useState(null);
10 const [loadingDetails, setLoadingDetails] = useState(false);
11 const [performanceData, setPerformanceData] = useState([]);
12 const [eventDistribution, setEventDistribution] = useState([]);
13 const [networkTraffic, setNetworkTraffic] = useState([]);
14 const [topProcesses, setTopProcesses] = useState([]);
15 // Бои за графикони
16 const CHART_COLORS = {
17 primary: '#2563eb',
18 secondary: '#7c3aed',
19 accentGreen: '#10b981',
20 accentOrange: '#f59e0b',
21 accentRed: '#ef4444',
22 lightBlue: '#93c5fd',
23 lightPurple: '#c4b5fd'
24 };
25
26 // Функција за вчитување на податоци за графикони
27 const loadChartData = () => {
28 // Перформанс податоци (симулирани, треба да ги земеш од API)
29 const mockPerfData = Array.from({ length: 24 }, (_, i) => ({
30 hour: `${i}:00`,
31 cpu: 20 + Math.random() * 30,
32 memory: 30 + Math.random() * 40,
33 network: Math.random() * 100
34 }));
35 setPerformanceData(mockPerfData);
36
37 // Дистрибуција на настани
38 const mockEventDist = [
39 { name: 'Process Create', value: 45, color: CHART_COLORS.primary },
40 { name: 'Network', value: 25, color: CHART_COLORS.secondary },
41 { name: 'File Create', value: 15, color: CHART_COLORS.accentGreen },
42 { name: 'Registry', value: 10, color: CHART_COLORS.accentOrange },
43 { name: 'Other', value: 5, color: CHART_COLORS.accentRed }
44 ];
45 setEventDistribution(mockEventDist);
46
47 // Мрежен сообраќај
48 const mockNetworkData = Array.from({ length: 12 }, (_, i) => ({
49 time: `${i * 2}:00`,
50 inbound: 50 + Math.random() * 200,
51 outbound: 30 + Math.random() * 150
52 }));
53 setNetworkTraffic(mockNetworkData);
54
55 // Топ процеси
56 const mockTopProcesses = [
57 { name: 'chrome.exe', cpu: 25, memory: 1200 },
58 { name: 'python.exe', cpu: 18, memory: 800 },
59 { name: 'explorer.exe', cpu: 12, memory: 300 },
60 { name: 'svchost.exe', cpu: 8, memory: 200 },
61 { name: 'node.exe', cpu: 6, memory: 500 }
62 ];
63 setTopProcesses(mockTopProcesses);
64 };
65
66 useEffect(() => {
67 loadChartData();
68 // Автоматско освежување на графикони
69 const chartInterval = setInterval(loadChartData, 30000);
70 return () => clearInterval(chartInterval);
71 }, []);
72 // Filter state
73 const [filters, setFilters] = useState({
74 status: "all",
75 search: "",
76 os: "all",
77 minCpu: 0,
78 minRam: 0
79 });
80
81 // AI chat state
82 const [chatQuestion, setChatQuestion] = useState("");
83 const [chatAnswer, setChatAnswer] = useState("");
84 const [chatLoading, setChatLoading] = useState(false);
85 const [chatComputer, setChatComputer] = useState("");
86 const [chatHistory, setChatHistory] = useState([]);
87 const [isChatOpen, setIsChatOpen] = useState(true);
88 const [showWelcome, setShowWelcome] = useState(true);
89
90 // Modal tabs state
91 const [activeTab, setActiveTab] = useState('security');
92 const [sysmonData, setSysmonData] = useState(null);
93 const [networkData, setNetworkData] = useState(null);
94 const [loadingSysmon, setLoadingSysmon] = useState(false);
95 const [loadingNetwork, setLoadingNetwork] = useState(false);
96 const [selectedSysmonProcess, setSelectedSysmonProcess] = useState(null);
97 const [selectedNetworkItem, setSelectedNetworkItem] = useState(null);
98
99 // Load main data
100 useEffect(() => {
101 loadComputers();
102 loadStats();
103 const interval = setInterval(() => {
104 loadComputers();
105 loadStats();
106 }, 30000);
107 return () => clearInterval(interval);
108 }, []);
109
110 // Apply filters
111 useEffect(() => {
112 let result = computers;
113 if (filters.status !== "all") {
114 result = result.filter(c => c.status === filters.status);
115 }
116 if (filters.os !== "all") {
117 result = result.filter(c => c.os && c.os.toLowerCase().includes(filters.os.toLowerCase()));
118 }
119 if (filters.minCpu > 0) {
120 result = result.filter(c => (c.avg_cpu || 0) >= filters.minCpu);
121 }
122 if (filters.minRam > 0) {
123 result = result.filter(c => (c.avg_ram || 0) >= filters.minRam);
124 }
125 if (filters.search.trim()) {
126 const searchTerm = filters.search.toLowerCase();
127 result = result.filter(c =>
128 c.name.toLowerCase().includes(searchTerm) ||
129 (c.user && c.user.toLowerCase().includes(searchTerm)) ||
130 (c.ip && c.ip.includes(searchTerm))
131 );
132 }
133 setFilteredComputers(result);
134 }, [computers, filters]);
135
136 const loadComputers = () => {
137 fetch("/api/computers")
138 .then((r) => r.json())
139 .then(data => setComputers(data))
140 .catch((err) => console.error("API error:", err));
141 };
142
143 const loadStats = () => {
144 fetch("/api/stats")
145 .then((r) => r.json())
146 .then(setStats)
147 .catch((err) => console.error("API error:", err));
148 };
149
150 const openComputer = (name) => {
151 setSelectedComputer(name);
152 setLoadingDetails(true);
153 setComputerDetails(null);
154 setActiveTab('security');
155 setSysmonData(null);
156 setNetworkData(null);
157
158 fetch(`/api/computer/${encodeURIComponent(name)}`)
159 .then((r) => r.json())
160 .then((data) => {
161 setComputerDetails(data);
162 setLoadingDetails(false);
163 })
164 .catch((err) => {
165 console.error("API error:", err);
166 setLoadingDetails(false);
167 });
168 };
169
170 // Load Sysmon processes
171const loadSysmonProcesses = () => {
172 if (!selectedComputer) return;
173 setLoadingSysmon(true);
174 setSelectedSysmonProcess(null);
175
176 // КОРИГИРАЈ ГО ОВА URL
177 fetch(`/api/computer/${encodeURIComponent(selectedComputer)}/sysmon`)
178 .then((r) => {
179 if (!r.ok) throw new Error(`HTTP ${r.status}`);
180 return r.json();
181 })
182 .then((data) => {
183 console.log("Sysmon data received:", data);
184
185 // Ако data.processes е празна, пробај со детален endpoint
186 if (!data.processes || data.processes.length === 0) {
187 return fetch(`/api/computer/${encodeURIComponent(selectedComputer)}/detailed-sysmon`)
188 .then(r => r.json())
189 .then(detailedData => {
190 setSysmonData(detailedData);
191 setLoadingSysmon(false);
192 });
193 }
194
195 setSysmonData(data);
196 setLoadingSysmon(false);
197 })
198 .catch((err) => {
199 console.error("Sysmon API error:", err);
200
201 // Fallback - пробај да земеш Sysmon events од општиот endpoint
202 fetch(`/api/computer/${encodeURIComponent(selectedComputer)}`)
203 .then(r => r.json())
204 .then(fullData => {
205 if (fullData.sysmon_events) {
206 // Конвертирај Sysmon events во процес формат
207 const processes = fullData.sysmon_events.map((ev, idx) => ({
208 process_id: ev.event_id,
209 process_name: ev.event_type,
210 timestamp: ev.timestamp,
211 message: ev.message,
212 details: ev.details || {}
213 }));
214
215 setSysmonData({
216 computer_name: selectedComputer,
217 processes: processes,
218 count: processes.length
219 });
220 }
221 setLoadingSysmon(false);
222 })
223 .catch(() => {
224 setLoadingSysmon(false);
225 });
226 });
227};
228 // Load Network connections
229const loadNetworkConnections = () => {
230 if (!selectedComputer) return;
231 setLoadingNetwork(true);
232 setSelectedNetworkItem(null);
233
234 // КОРИГИРАЈ ГО ОВА URL
235 fetch(`/api/computer/${encodeURIComponent(selectedComputer)}/network`)
236 .then((r) => {
237 if (!r.ok) throw new Error(`HTTP ${r.status}`);
238 return r.json();
239 })
240 .then((data) => {
241 console.log("Network data received:", data);
242
243 setNetworkData(data);
244 setLoadingNetwork(false);
245 })
246 .catch((err) => {
247 console.error("Network API error:", err);
248
249 // Fallback - пробај да земеш network од општиот endpoint
250 fetch(`/api/computer/${encodeURIComponent(selectedComputer)}`)
251 .then(r => r.json())
252 .then(fullData => {
253 if (fullData.network_connections) {
254 const connections = fullData.network_connections.map((conn, idx) => ({
255 pid: conn.pid,
256 process_name: conn.process_name,
257 local_address: conn.local_address,
258 remote_address: conn.remote_address,
259 status: conn.status,
260 timestamp: conn.timestamp,
261 // Додади ги овие полиња за да match-не со очекуваниот формат
262 local_port: conn.local_address?.split(':')?.[1] || '',
263 remote_port: conn.remote_address?.split(':')?.[1] || '',
264 protocol: 'TCP'
265 }));
266
267 setNetworkData({
268 computer_name: selectedComputer,
269 connections: connections,
270 count: connections.length
271 });
272 }
273 setLoadingNetwork(false);
274 })
275 .catch(() => {
276 setLoadingNetwork(false);
277 });
278 });
279};
280 const formatTime = (iso) => {
281 if (!iso) return "-";
282 const d = new Date(iso);
283 return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
284 };
285
286 const formatDate = (iso) => {
287 if (!iso) return "-";
288 const d = new Date(iso);
289 return d.toLocaleDateString();
290 };
291
292 const askAssistant = async () => {
293 if (!chatQuestion.trim()) return;
294 const question = chatQuestion;
295 setChatLoading(true);
296 setChatQuestion("");
297 setShowWelcome(false);
298
299 const newHistory = [...chatHistory, {
300 type: "question",
301 content: question,
302 computer: chatComputer,
303 timestamp: new Date().toISOString()
304 }];
305 setChatHistory(newHistory);
306
307 try {
308 const res = await fetch("/api/chat", {
309 method: "POST",
310 headers: { "Content-Type": "application/json" },
311 body: JSON.stringify({
312 question: question,
313 computer_name: chatComputer || null,
314 }),
315 });
316
317 const answer = await res.json();
318 const finalAnswer = answer.answer || answer.error || "I couldn't process that request.";
319
320 setChatHistory([...newHistory, {
321 type: "answer",
322 content: finalAnswer,
323 timestamp: new Date().toISOString()
324 }]);
325 } catch (err) {
326 setChatHistory([...newHistory, {
327 type: "answer",
328 content: "⚠️ Connection error. Please check the server.",
329 timestamp: new Date().toISOString()
330 }]);
331 } finally {
332 setChatLoading(false);
333 }
334 };
335
336 const resetFilters = () => {
337 setFilters({
338 status: "all",
339 search: "",
340 os: "all",
341 minCpu: 0,
342 minRam: 0
343 });
344 };
345
346 const clearChat = () => {
347 setChatHistory([]);
348 setChatAnswer("");
349 setShowWelcome(true);
350 };
351
352 const closeModal = () => {
353 setSelectedComputer(null);
354 setComputerDetails(null);
355 setSysmonData(null);
356 setNetworkData(null);
357 setSelectedSysmonProcess(null);
358 setSelectedNetworkItem(null);
359 };
360
361 const quickQuestions = [
362 "Show security alerts from yesterday",
363 "Which devices have high CPU usage?",
364 "List suspicious network connections",
365 "Show recent Sysmon events",
366 "Display offline devices"
367 ];
368
369 return (
370 <div className="app-container">
371 {/* Main Content */}
372 <main className="main-content">
373 {/* Top Navigation */}
374 <nav className="top-nav">
375 <div className="nav-left">
376 <div className="logo">
377 <div className="logo-icon">🛡️</div>
378 <div className="logo-text">
379 <h1>LAN Sentinel</h1>
380 <span className="logo-subtitle">Enterprise Security Dashboard</span>
381 </div>
382 </div>
383 </div>
384
385 <div className="nav-center">
386 <div className="search-bar">
387 <input
388 type="text"
389 placeholder="Search devices..."
390 value={filters.search}
391 onChange={(e) => setFilters({...filters, search: e.target.value})}
392 />
393 <span className="search-icon">🔍</span>
394 </div>
395 </div>
396
397 <div className="nav-right">
398 <button
399 className={`ai-toggle-btn ${isChatOpen ? 'active' : ''}`}
400 onClick={() => setIsChatOpen(!isChatOpen)}
401 >
402 <span className="ai-icon">🤖</span>
403 <span>AI Assistant</span>
404 </button>
405 <button className="refresh-btn" onClick={loadComputers}>
406 <span className="refresh-icon">↻</span>
407 Refresh
408 </button>
409 <div className="user-profile">
410 <div className="profile-avatar">AD</div>
411 </div>
412 </div>
413 </nav>
414
415 {/* Stats Overview */}
416 <section className="stats-overview">
417 <div className="stats-header">
418 <h2>Network Overview</h2>
419 <div className="stats-time">
420 Last updated: {new Date().toLocaleTimeString()}
421 </div>
422 </div>
423
424 <div className="stats-grid">
425 <div className="stat-card">
426 <div className="stat-main">
427 <span className="stat-number">{computers.length}</span>
428 <span className="stat-label">Total Devices</span>
429 </div>
430 <div className="stat-icon">🖥️</div>
431 </div>
432
433 <div className="stat-card online">
434 <div className="stat-main">
435 <span className="stat-number">{computers.filter(c => c.status === 'online').length}</span>
436 <span className="stat-label">Online</span>
437 </div>
438 <div className="stat-trend">
439 <span className="trend-up">↑</span>
440 <span>{computers.length > 0 ? Math.round((computers.filter(c => c.status === 'online').length/computers.length)*100) : 0}%</span>
441 </div>
442 </div>
443
444 <div className="stat-card idle">
445 <div className="stat-main">
446 <span className="stat-number">{computers.filter(c => c.status === 'idle').length}</span>
447 <span className="stat-label">Idle</span>
448 </div>
449 <div className="stat-icon">⏸️</div>
450 </div>
451
452 <div className="stat-card offline">
453 <div className="stat-main">
454 <span className="stat-number">{computers.filter(c => c.status === 'offline').length}</span>
455 <span className="stat-label">Offline</span>
456 </div>
457 <div className="stat-icon">🔴</div>
458 </div>
459
460 <div className="stat-card security">
461 <div className="stat-main">
462 <span className="stat-number">
463 {stats ? stats.total_sysmon_events.toLocaleString() : "0"}
464 </span>
465 <span className="stat-label">Sysmon Events</span>
466 </div>
467 <div className="stat-badge">24h</div>
468 </div>
469
470 <div className="stat-card alerts">
471 <div className="stat-main">
472 <span className="stat-number">
473 {stats ? stats.security_alerts || 0 : "0"}
474 </span>
475 <span className="stat-label">Alerts</span>
476 </div>
477 <div className="stat-icon">🚨</div>
478 </div>
479 </div>
480 </section>
481
482 {/* Main Dashboard */}
483 <div className="dashboard-grid">
484 {/* Left Sidebar - Filters */}
485 <aside className="filter-sidebar">
486 <div className="filter-section">
487 <h3>Filters</h3>
488 <div className="filter-group">
489 <label>Status</label>
490 <div className="filter-tags">
491 {["all", "online", "idle", "offline"].map(status => (
492 <button
493 key={status}
494 className={`filter-tag ${filters.status === status ? 'active' : ''}`}
495 onClick={() => setFilters({...filters, status})}
496 >
497 {status === 'all' ? 'All' : status}
498 </button>
499 ))}
500 </div>
501 </div>
502
503 <div className="filter-group">
504 <label>Operating System</label>
505 <select
506 value={filters.os}
507 onChange={(e) => setFilters({...filters, os: e.target.value})}
508 className="filter-select"
509 >
510 <option value="all">All OS</option>
511 {[...new Set(computers.map(c => c.os).filter(Boolean))].map(os => (
512 <option key={os} value={os}>{os}</option>
513 ))}
514 </select>
515 </div>
516
517 <div className="filter-group">
518 <label>CPU Usage ≥ {filters.minCpu}%</label>
519 <input
520 type="range"
521 min="0"
522 max="100"
523 value={filters.minCpu}
524 onChange={(e) => setFilters({...filters, minCpu: parseInt(e.target.value)})}
525 className="range-slider"
526 />
527 </div>
528
529 <div className="filter-group">
530 <label>RAM Usage ≥ {filters.minRam}%</label>
531 <input
532 type="range"
533 min="0"
534 max="100"
535 value={filters.minRam}
536 onChange={(e) => setFilters({...filters, minRam: parseInt(e.target.value)})}
537 className="range-slider"
538 />
539 </div>
540
541 <button onClick={resetFilters} className="reset-filters-btn">
542 Reset Filters
543 </button>
544 </div>
545
546 <div className="filter-section">
547 <h3>Quick Actions</h3>
548 <button className="action-btn" onClick={loadComputers}>
549 <span className="action-icon">↻</span>
550 Refresh Data
551 </button>
552 <button className="action-btn" onClick={() => setIsChatOpen(true)}>
553 <span className="action-icon">🤖</span>
554 Open AI Assistant
555 </button>
556 </div>
557 </aside>
558
559 {/* Main Content Area */}
560 <div className="content-area">
561 <div className="content-header">
562 <div>
563 <h2>Network Devices</h2>
564 <p className="content-subtitle">{filteredComputers.length} devices found</p>
565 </div>
566 <div className="header-actions">
567 <select className="sort-select">
568 <option>Last Seen</option>
569 <option>Name</option>
570 <option>CPU Usage</option>
571 <option>Status</option>
572 </select>
573 </div>
574 </div>
575
576 {/* Devices Grid */}
577 <div className="devices-grid">
578 {filteredComputers.map((c) => (
579 <div
580 key={c.name}
581 className={`device-card ${c.status}`}
582 onClick={() => openComputer(c.name)}
583 >
584 <div className="device-header">
585 <div className="device-title">
586 <div className="device-avatar">
587 {c.os?.includes("Windows") ? "🪟" :
588 c.os?.includes("Linux") ? "🐧" :
589 c.os?.includes("Mac") ? "🍎" : "💻"}
590 </div>
591 <div>
592 <h3>{c.name}</h3>
593 <p className="device-user">{c.user || "Unknown"}</p>
594 </div>
595 </div>
596 <div className={`status-indicator ${c.status}`}>
597 <div className="status-dot"></div>
598 {c.status}
599 </div>
600 </div>
601
602 <div className="device-info">
603 <div className="info-row">
604 <span className="info-label">IP</span>
605 <span className="info-value">{c.ip}</span>
606 </div>
607 <div className="info-row">
608 <span className="info-label">OS</span>
609 <span className="info-value">{c.os}</span>
610 </div>
611 <div className="info-row">
612 <span className="info-label">Last Active</span>
613 <span className="info-value">{formatTime(c.last_seen)}</span>
614 </div>
615 </div>
616
617 <div className="device-metrics">
618 <div className="metric">
619 <div className="metric-header">
620 <span>CPU</span>
621 <span className="metric-value">{c.avg_cpu || 0}%</span>
622 </div>
623 <div className="progress-bar">
624 <div
625 className="progress-fill cpu"
626 style={{ width: `${Math.min(c.avg_cpu || 0, 100)}%` }}
627 ></div>
628 </div>
629 </div>
630 <div className="metric">
631 <div className="metric-header">
632 <span>Memory</span>
633 <span className="metric-value">{c.avg_ram || 0}%</span>
634 </div>
635 <div className="progress-bar">
636 <div
637 className="progress-fill memory"
638 style={{ width: `${Math.min(c.avg_ram || 0, 100)}%` }}
639 ></div>
640 </div>
641 </div>
642 </div>
643
644 <div className="device-footer">
645 {c.sysmon_available && (
646 <div className="sysmon-badge">
647 <span className="sysmon-icon">🛡️</span>
648 {c.recent_sysmon || 0} events
649 </div>
650 )}
651 </div>
652 </div>
653 ))}
654
655 {filteredComputers.length === 0 && (
656 <div className="empty-state">
657 <div className="empty-icon">🔍</div>
658 <h3>No devices match your filters</h3>
659 <button onClick={resetFilters} className="empty-action-btn">
660 Reset All Filters
661 </button>
662 </div>
663 )}
664 </div>
665 </div>
666 </div>
667 </main>
668
669 {/* AI Assistant Sidebar */}
670 <div className={`ai-sidebar ${isChatOpen ? 'open' : ''}`}>
671 <div className="ai-header">
672 <div className="ai-title">
673 <div className="ai-avatar">🤖</div>
674 <div>
675 <h3>Security AI Assistant</h3>
676 <p className="ai-subtitle">Powered by LAN Sentinel</p>
677 </div>
678 </div>
679 <button className="close-ai" onClick={() => setIsChatOpen(false)}>
680
681 </button>
682 </div>
683
684 <div className="ai-content">
685 <div className="chat-container">
686 <div className="chat-history">
687 {showWelcome && (
688 <div className="welcome-message">
689 <div className="welcome-avatar">🤖</div>
690 <div className="welcome-content">
691 <h4>Welcome to Security AI Assistant!</h4>
692 <p>I can help you analyze security events, monitor network activity, and investigate incidents.</p>
693 <div className="welcome-questions">
694 {quickQuestions.map((q, idx) => (
695 <button
696 key={idx}
697 className="quick-question"
698 onClick={() => {
699 setChatQuestion(q);
700 setTimeout(() => askAssistant(), 100);
701 }}
702 >
703 {q}
704 </button>
705 ))}
706 </div>
707 </div>
708 </div>
709 )}
710
711 {chatHistory.map((msg, idx) => (
712 <div key={idx} className={`chat-message ${msg.type}`}>
713 <div className="message-avatar">
714 {msg.type === 'question' ? '👤' : '🤖'}
715 </div>
716 <div className="message-content">
717 <div className="message-header">
718 <span className="message-sender">
719 {msg.type === 'question' ? 'You' : 'Security AI'}
720 </span>
721 {msg.computer && (
722 <span className="message-context">
723 Computer: {msg.computer}
724 </span>
725 )}
726 </div>
727 <div className="message-text">
728 {msg.content}
729 </div>
730 <div className="message-time">
731 {new Date(msg.timestamp).toLocaleTimeString([], {
732 hour: '2-digit',
733 minute: '2-digit'
734 })}
735 </div>
736 </div>
737 </div>
738 ))}
739
740 {chatLoading && (
741 <div className="chat-message answer">
742 <div className="message-avatar">🤖</div>
743 <div className="message-content">
744 <div className="typing-indicator">
745 <div></div>
746 <div></div>
747 <div></div>
748 </div>
749 </div>
750 </div>
751 )}
752 </div>
753
754 <div className="chat-input-area">
755 <select
756 value={chatComputer}
757 onChange={(e) => setChatComputer(e.target.value)}
758 className="chat-select"
759 >
760 <option value="">🌐 All Network</option>
761 {computers.map((c) => (
762 <option key={c.name} value={c.name}>
763 {c.name}
764 </option>
765 ))}
766 </select>
767
768 <div className="input-wrapper">
769 <input
770 type="text"
771 placeholder="Ask about security events..."
772 value={chatQuestion}
773 onChange={(e) => setChatQuestion(e.target.value)}
774 onKeyDown={(e) => e.key === 'Enter' && askAssistant()}
775 className="chat-input"
776 />
777 <button
778 onClick={askAssistant}
779 disabled={chatLoading || !chatQuestion.trim()}
780 className="send-btn"
781 >
782 {chatLoading ? '...' : '➤'}
783 </button>
784 </div>
785 </div>
786 </div>
787 </div>
788 </div>
789
790 {/* Enhanced Computer Details Modal */}
791 {selectedComputer && (
792 <div className="modal-overlay" onClick={closeModal}>
793 <div className="modal large" onClick={(e) => e.stopPropagation()}>
794 <div className="modal-header">
795 <div className="modal-title">
796 <h2>{selectedComputer}</h2>
797 <div className="modal-subtitle">
798 <span className="ip-badge">{computerDetails?.computer?.ip}</span>
799 <span className="os-badge">{computerDetails?.computer?.os}</span>
800 <span className={`status-badge ${computerDetails?.computer?.status}`}>
801 {computerDetails?.computer?.status}
802 </span>
803 </div>
804 </div>
805 <div className="modal-actions">
806 <button className="modal-btn">
807 <span className="btn-icon">📊</span>
808 Report
809 </button>
810 <button className="modal-close" onClick={closeModal}>
811
812 </button>
813 </div>
814 </div>
815
816 <div className="modal-content">
817 {loadingDetails ? (
818 <div className="loading-state">
819 <div className="loader"></div>
820 <p>Loading device details...</p>
821 </div>
822 ) : (
823 <>
824 {/* Quick Stats */}
825 <div className="quick-stats">
826 <div className="quick-stat">
827 <div className="stat-icon">⚡</div>
828 <div className="stat-info">
829 <div className="stat-value">
830 {computerDetails?.current_metrics?.cpu_usage || 0}%
831 </div>
832 <div className="stat-label">CPU</div>
833 </div>
834 </div>
835 <div className="quick-stat">
836 <div className="stat-icon">💾</div>
837 <div className="stat-info">
838 <div className="stat-value">
839 {computerDetails?.current_metrics?.ram_usage || 0}%
840 </div>
841 <div className="stat-label">RAM</div>
842 </div>
843 </div>
844 <div className="quick-stat">
845 <div className="stat-icon">📡</div>
846 <div className="stat-info">
847 <div className="stat-value">
848 {computerDetails?.current_metrics?.network_usage || 0}%
849 </div>
850 <div className="stat-label">Network</div>
851 </div>
852 </div>
853 <div className="quick-stat">
854 <div className="stat-icon">🛡️</div>
855 <div className="stat-info">
856 <div className="stat-value">
857 {computerDetails?.recent_sysmon || 0}
858 </div>
859 <div className="stat-label">Sysmon Events</div>
860 </div>
861 </div>
862 </div>
863
864 {/* Tabs */}
865 <div className="modal-tabs">
866 <div className="tabs-header">
867 <button
868 className={`tab ${activeTab === 'security' ? 'active' : ''}`}
869 onClick={() => setActiveTab('security')}
870 >
871 Security Events
872 </button>
873 <button
874 className={`tab ${activeTab === 'sysmon' ? 'active' : ''}`}
875 onClick={() => {
876 setActiveTab('sysmon');
877 if (!sysmonData) loadSysmonProcesses();
878 }}
879 >
880 Sysmon Processes
881 </button>
882 <button
883 className={`tab ${activeTab === 'network' ? 'active' : ''}`}
884 onClick={() => {
885 setActiveTab('network');
886 if (!networkData) loadNetworkConnections();
887 }}
888 >
889 Network Connections
890 </button>
891 <button
892 className={`tab ${activeTab === 'details' ? 'active' : ''}`}
893 onClick={() => setActiveTab('details')}
894 >
895 System Details
896 </button>
897 </div>
898
899 <div className="tabs-content">
900 {/* Security Events Tab */}
901 {activeTab === 'security' && (
902 <div className="tab-pane active">
903 <div className="data-table-container">
904 <table className="data-table">
905 <thead>
906 <tr>
907 <th>Time</th>
908 <th>Event ID</th>
909 <th>Type</th>
910 <th>Description</th>
911 </tr>
912 </thead>
913 <tbody>
914 {(computerDetails?.sysmon_events || []).map((ev, idx) => (
915 <tr key={idx}>
916 <td>{formatTime(ev.timestamp)}</td>
917 <td>
918 <span className="event-id">#{ev.event_id}</span>
919 </td>
920 <td>
921 <span className={`event-type ${ev.event_type?.toLowerCase()}`}>
922 {ev.event_type}
923 </span>
924 </td>
925 <td className="event-desc">
926 {ev.message?.slice(0, 100) || 'No description'}
927 </td>
928 </tr>
929 ))}
930 </tbody>
931 </table>
932 </div>
933 </div>
934 )}
935
936 {/* Sysmon Processes Tab */}
937 {activeTab === 'sysmon' && (
938 <div className="tab-pane active">
939 <div className="pane-header">
940 <h3>Sysmon Processes</h3>
941 <button
942 onClick={loadSysmonProcesses}
943 className="refresh-btn-small"
944 disabled={loadingSysmon}
945 >
946 {loadingSysmon ? 'Loading...' : '↻ Refresh'}
947 </button>
948 </div>
949
950 {loadingSysmon ? (
951 <div className="loading-state">
952 <div className="loader"></div>
953 <p>Loading Sysmon processes...</p>
954 </div>
955 ) : sysmonData ? (
956 <div className="data-grid">
957 <div className="grid-container">
958 {sysmonData.processes && sysmonData.processes.length > 0 ? (
959 <>
960 <div className="grid-sidebar">
961 <div className="process-list">
962 {sysmonData.processes.map((proc, idx) => (
963 <div
964 key={idx}
965 className={`process-item ${selectedSysmonProcess === idx ? 'active' : ''}`}
966 onClick={() => setSelectedSysmonProcess(idx)}
967 >
968 <div className="process-name">
969 <strong>{proc.process_name || 'Unknown'}</strong>
970 </div>
971 <div className="process-pid">
972 PID: {proc.process_id}
973 </div>
974 <div className="process-time">
975 {formatTime(proc.timestamp)}
976 </div>
977 </div>
978 ))}
979 </div>
980 </div>
981 <div className="grid-content">
982 {selectedSysmonProcess !== null ? (
983 <div className="json-viewer">
984 <div className="json-header">
985 <h4>Process Details (JSON)</h4>
986 <button
987 className="copy-json"
988 onClick={() => navigator.clipboard.writeText(
989 JSON.stringify(sysmonData.processes[selectedSysmonProcess], null, 2)
990 )}
991 >
992 📋 Copy JSON
993 </button>
994 </div>
995 <pre className="json-content">
996 {JSON.stringify(sysmonData.processes[selectedSysmonProcess], null, 2)}
997 </pre>
998 </div>
999 ) : (
1000 <div className="select-prompt">
1001 <div className="prompt-icon">👈</div>
1002 <p>Select a process from the list to view its JSON data</p>
1003 </div>
1004 )}
1005 </div>
1006 </>
1007 ) : (
1008 <div className="empty-data">
1009 <div className="empty-icon">📄</div>
1010 <p>No Sysmon processes found</p>
1011 </div>
1012 )}
1013 </div>
1014 </div>
1015 ) : (
1016 <div className="empty-data">
1017 <p>Click refresh to load Sysmon processes</p>
1018 <button onClick={loadSysmonProcesses} className="load-btn">
1019 Load Sysmon Data
1020 </button>
1021 </div>
1022 )}
1023 </div>
1024 )}
1025
1026 {/* Network Connections Tab */}
1027 {activeTab === 'network' && (
1028 <div className="tab-pane active">
1029 <div className="pane-header">
1030 <h3>Network Connections</h3>
1031 <button
1032 onClick={loadNetworkConnections}
1033 className="refresh-btn-small"
1034 disabled={loadingNetwork}
1035 >
1036 {loadingNetwork ? 'Loading...' : '↻ Refresh'}
1037 </button>
1038 </div>
1039
1040 {loadingNetwork ? (
1041 <div className="loading-state">
1042 <div className="loader"></div>
1043 <p>Loading network connections...</p>
1044 </div>
1045 ) : networkData ? (
1046 <div className="data-grid">
1047 <div className="grid-container">
1048 {networkData.connections && networkData.connections.length > 0 ? (
1049 <>
1050 <div className="grid-sidebar">
1051 <div className="network-list">
1052 {networkData.connections.map((conn, idx) => (
1053 <div
1054 key={idx}
1055 className={`network-item ${selectedNetworkItem === idx ? 'active' : ''}`}
1056 onClick={() => setSelectedNetworkItem(idx)}
1057 >
1058 <div className="network-address">
1059 <strong>{conn.local_address}:{conn.local_port}</strong>
1060 <div className="arrow">→</div>
1061 <strong>{conn.remote_address}:{conn.remote_port}</strong>
1062 </div>
1063 <div className="network-protocol">
1064 {conn.protocol} • {conn.state}
1065 </div>
1066 </div>
1067 ))}
1068 </div>
1069 </div>
1070 <div className="grid-content">
1071 {selectedNetworkItem !== null ? (
1072 <div className="json-viewer">
1073 <div className="json-header">
1074 <h4>Connection Details (JSON)</h4>
1075 <button
1076 className="copy-json"
1077 onClick={() => navigator.clipboard.writeText(
1078 JSON.stringify(networkData.connections[selectedNetworkItem], null, 2)
1079 )}
1080 >
1081 📋 Copy JSON
1082 </button>
1083 </div>
1084 <pre className="json-content">
1085 {JSON.stringify(networkData.connections[selectedNetworkItem], null, 2)}
1086 </pre>
1087 </div>
1088 ) : (
1089 <div className="select-prompt">
1090 <div className="prompt-icon">👈</div>
1091 <p>Select a connection to view its JSON data</p>
1092 </div>
1093 )}
1094 </div>
1095 </>
1096 ) : (
1097 <div className="empty-data">
1098 <div className="empty-icon">🌐</div>
1099 <p>No network connections found</p>
1100 </div>
1101 )}
1102 </div>
1103 </div>
1104 ) : (
1105 <div className="empty-data">
1106 <p>Click refresh to load network connections</p>
1107 <button onClick={loadNetworkConnections} className="load-btn">
1108 Load Network Data
1109 </button>
1110 </div>
1111 )}
1112 </div>
1113 )}
1114
1115 {/* System Details Tab */}
1116 {activeTab === 'details' && computerDetails && (
1117 <div className="tab-pane active">
1118 <div className="details-grid">
1119 <div className="detail-group">
1120 <h4>System Information</h4>
1121 <div className="detail-item">
1122 <span className="detail-label">Hostname:</span>
1123 <span className="detail-value">{computerDetails.computer.name}</span>
1124 </div>
1125 <div className="detail-item">
1126 <span className="detail-label">IP Address:</span>
1127 <span className="detail-value">{computerDetails.computer.ip}</span>
1128 </div>
1129 <div className="detail-item">
1130 <span className="detail-label">OS Version:</span>
1131 <span className="detail-value">{computerDetails.computer.os}</span>
1132 </div>
1133 <div className="detail-item">
1134 <span className="detail-label">Last Seen:</span>
1135 <span className="detail-value">{formatTime(computerDetails.computer.last_seen)}</span>
1136 </div>
1137 </div>
1138
1139 <div className="detail-group">
1140 <h4>Current Metrics</h4>
1141 <div className="detail-item">
1142 <span className="detail-label">CPU Usage:</span>
1143 <span className="detail-value">{computerDetails.current_metrics?.cpu_usage || 0}%</span>
1144 </div>
1145 <div className="detail-item">
1146 <span className="detail-label">Memory Usage:</span>
1147 <span className="detail-value">{computerDetails.current_metrics?.ram_usage || 0}%</span>
1148 </div>
1149 <div className="detail-item">
1150 <span className="detail-label">Disk Usage:</span>
1151 <span className="detail-value">{computerDetails.current_metrics?.disk_usage || 0}%</span>
1152 </div>
1153 </div>
1154 </div>
1155 </div>
1156 )}
1157 </div>
1158 </div>
1159 </>
1160 )}
1161 </div>
1162 </div>
1163 </div>
1164 )}
1165 </div>
1166 );
1167}
1168
1169export default App;
Note: See TracBrowser for help on using the repository browser.