source: lan-frontend/src/App.jsx@ 2058e5c

Last change on this file since 2058e5c was 2058e5c, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Working / before login

  • Property mode set to 100644
File size: 6.5 KB
Line 
1import React from "react";
2import {useEffect, useMemo, useState} from "react";
3import "./styles/theme.css";
4
5import TopBar from "./components/TopBar";
6import ComputerCard from "./components/ComputerCard";
7import ComputerDetailsModal from "./components/ComputerDetailsModal";
8import AIAssistantDrawer from "./components/AIAssistantDrawer";
9
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);
15
16 const [query, setQuery] = useState("");
17 const [status, setStatus] = useState("");
18
19 const [selectedComputer, setSelectedComputer] = useState(null);
20 const [aiOpen, setAiOpen] = useState(false);
21 const [aiScope, setAiScope] = useState(null);
22 const [selectedEnv, setSelectedEnv] = useState("default");
23
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 }
42
43 useEffect(() => {
44 refresh();
45 }, [selectedEnv]);
46
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]);
57
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 />
67
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>
75
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>
81
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>
87 </div>
88
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>
116 </div>
117
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>
148 </div>
149
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 />
160
161 {/* AI DRAWER */}
162 <AIAssistantDrawer
163 open={aiOpen}
164 onClose={() => setAiOpen(false)}
165 computers={computers}
166 scope={aiScope}
167 setScope={setAiScope}
168 />
169 </div>
170 );
171}
Note: See TracBrowser for help on using the repository browser.