Ignore:
Timestamp:
01/20/26 17:28:10 (6 months ago)
Author:
istevanoska <ilinastevanoska@…>
Branches:
master
Children:
505f39a
Parents:
640ed89
Message:

Working / before login

File:
1 edited

Legend:

Unmodified
Added
Removed
  • lan-frontend/src/App.jsx

    r640ed89 r2058e5c  
    1 import React, { useEffect, useState } from "react";
    2 import './App.css';
     1import React from "react";
     2import {useEffect, useMemo, useState} from "react";
     3import "./styles/theme.css";
    34
    4 function 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   };
     5import TopBar from "./components/TopBar";
     6import ComputerCard from "./components/ComputerCard";
     7import ComputerDetailsModal from "./components/ComputerDetailsModal";
     8import AIAssistantDrawer from "./components/AIAssistantDrawer";
    259
    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);
     10export default function App() {
     11    const [stats, setStats] = useState(null);
     12    const [computers, setComputers] = useState([]);
     13    const [loading, setLoading] = useState(true);
     14    const [lastUpdated, setLastUpdated] = useState(null);
    3615
    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);
     16    const [query, setQuery] = useState("");
     17    const [status, setStatus] = useState("");
    4618
    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);
     19    const [selectedComputer, setSelectedComputer] = useState(null);
     20    const [aiOpen, setAiOpen] = useState(false);
     21    const [aiScope, setAiScope] = useState(null);
     22    const [selectedEnv, setSelectedEnv] = useState("default");
    5423
    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   };
     24    async function refresh() {
     25        try {
     26            setLoading(true);
     27            const [s, c] = await Promise.all([
     28                fetch("/api/stats").then((r) => r.json()),
     29                fetch("/api/computers", {
     30                    headers: {"X-Env": selectedEnv},
     31                }).then((r) => r.json()),
     32            ]);
     33            setStats(s);
     34            setComputers(Array.isArray(c) ? c : []);
     35            setLastUpdated(new Date().toISOString());
     36        } catch (e) {
     37            console.error("refresh error", e);
     38        } finally {
     39            setLoading(false);
     40        }
     41    }
    6542
    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   });
     43    useEffect(() => {
     44        refresh();
     45    }, [selectedEnv]);
    8046
    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);
     47    const filtered = useMemo(() => {
     48        const q = query.trim().toLowerCase();
     49        return (computers || [])
     50            .filter((c) => (status ? c.status === status : true))
     51            .filter((c) => {
     52                if (!q) return true;
     53                const hay = [c.name, c.user, c.ip, c.os, c.status].filter(Boolean).join(" ").toLowerCase();
     54                return hay.includes(q);
     55            });
     56    }, [computers, query, status]);
    8957
    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);
     58    return (
     59        <div className="app-wrap">
     60            <TopBar
     61                onOpenAI={() => setAiOpen(true)}
     62                onRefresh={refresh}
     63                lastUpdated={lastUpdated}
     64                onEnvChange={(env) => setSelectedEnv(env)}
     65                selectedEnv={selectedEnv}
     66            />
    9867
    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   }, []);
     68            {/* STATS */}
     69            <div className="stats-grid">
     70                <div className="stat-card">
     71                    <div className="label">Total Computers</div>
     72                    <div className="value">{computers.length}</div>
     73                    <div className="sub">Monitored Systems</div>
     74                </div>
    10975
    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]);
     76                <div className="stat-card">
     77                    <div className="label">Sysmon Events (24h)</div>
     78                    <div className="value">{stats?.total_sysmon_events ?? "—"}</div>
     79                    <div className="sub">Security Events</div>
     80                </div>
    13581
    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
    171 const 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
    229 const 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>
     82                <div className="stat-card">
     83                    <div className="label">Network Connections (24h)</div>
     84                    <div className="value">{stats?.total_network_connections ?? "—"}</div>
     85                    <div className="sub">Captured Connections</div>
     86                </div>
    43187            </div>
    43288
    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>
     89            {/* FILTERS */}
     90            <div className="panel" style={{marginBottom: 14}}>
     91                <div className="panel-head">
     92                    <div className="panel-title">Filters</div>
     93                    <div style={{color: "var(--dim)", fontSize: 12}}>
     94                        {loading ? "Refreshing…" : `${filtered.length} shown`}
     95                    </div>
     96                </div>
     97                <div className="panel-body">
     98                    <div className="filters">
     99                        <input
     100                            className="input"
     101                            value={query}
     102                            onChange={(e) => setQuery(e.target.value)}
     103                            placeholder="Search by name / user / ip / os…"
     104                        />
     105
     106                        <select className="select" value={status} onChange={(e) => setStatus(e.target.value)}>
     107                            <option value="">All status</option>
     108                            <option value="online">Online</option>
     109                            <option value="idle">Idle</option>
     110                            <option value="offline">Offline</option>
     111                        </select>
     112
     113                        <button className="btn primary" onClick={refresh}>Refresh</button>
     114                    </div>
     115                </div>
    442116            </div>
    443117
    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>
     118            {/* COMPUTERS */}
     119            <div className="panel">
     120                <div className="panel-head">
     121                    <div className="panel-title">Connected Computers</div>
     122                    <div style={{display: "flex", gap: 10, alignItems: "center"}}>
     123                        <span className="pill">{filtered.length} computers</span>
     124                        <button className="btn" onClick={() => {
     125                            setQuery("");
     126                            setStatus("");
     127                        }}>Reset
     128                        </button>
     129                    </div>
     130                </div>
     131                <div className="panel-body">
     132                    <div className="computers-grid">
     133                        {filtered.map((c) => (
     134                            <ComputerCard
     135                                key={c.name}
     136                                c={c}
     137                                onOpen={(pc) => {
     138                                    setSelectedComputer(pc);
     139                                    setAiScope(pc.name);
     140                                }}
     141                            />
     142                        ))}
     143                        {filtered.length === 0 && (
     144                            <div style={{color: "var(--dim)"}}>No computers match filter.</div>
     145                        )}
     146                    </div>
     147                </div>
    450148            </div>
    451149
    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>
     150            {/* DETAILS MODAL */}
     151            <ComputerDetailsModal
     152                open={!!selectedComputer}
     153                computer={selectedComputer}
     154                onClose={() => setSelectedComputer(null)}
     155                onAskAI={(name) => {
     156                    setAiScope(name);
     157                    setAiOpen(true);
     158                }}
     159            />
    459160
    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>
     161            {/* AI DRAWER */}
     162            <AIAssistantDrawer
     163                open={aiOpen}
     164                onClose={() => setAiOpen(false)}
     165                computers={computers}
     166                scope={aiScope}
     167                setScope={setAiScope}
     168            />
    666169        </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   );
     170    );
    1167171}
    1168 
    1169 export default App;
Note: See TracChangeset for help on using the changeset viewer.