source: lan-frontend/src/App.jsx@ e4c61dd

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

Added history mmanage

  • Property mode set to 100644
File size: 8.2 KB
Line 
1import React, { useEffect, useMemo, useState } from "react";
2import "./styles/theme.css";
3
4import Login from "./components/Login";
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 const [me, setMe] = useState(null);
25 const [meLoading, setMeLoading] = useState(true);
26
27 // NEW: environment setting (switch)
28 const [saveProcHistory, setSaveProcHistory] = useState(false);
29
30 async function loadMe() {
31 setMeLoading(true);
32 try {
33 const r = await fetch("/api/me", { credentials: "include" });
34 if (r.ok) {
35 const data = await r.json();
36 setMe(data.user);
37 } else {
38 setMe(null);
39 }
40 } catch (e) {
41 console.error("loadMe error", e);
42 setMe(null);
43 } finally {
44 setMeLoading(false);
45 }
46 }
47
48 async function refresh() {
49 try {
50 setLoading(true);
51 const [s, c] = await Promise.all([
52 fetch("/api/stats", { credentials: "include" }).then((r) => r.json()),
53 fetch("/api/computers", {
54 credentials: "include",
55 headers: { "X-Env": selectedEnv },
56 }).then((r) => r.json()),
57 ]);
58
59 setStats(s);
60 setComputers(Array.isArray(c) ? c : []);
61 setLastUpdated(new Date().toISOString());
62 } catch (e) {
63 console.error("refresh error", e);
64 } finally {
65 setLoading(false);
66 }
67 }
68
69 // NEW: load env settings (switch state) for selected env
70 async function loadEnvSettings(env) {
71 try {
72 // само admin ќе има пристап (ако не си admin, ќе падне во else и ќе стане false)
73 const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
74 credentials: "include",
75 });
76
77 if (r.ok) {
78 const data = await r.json();
79 setSaveProcHistory(!!data.save_process_history);
80 } else {
81 setSaveProcHistory(false);
82 }
83 } catch (e) {
84 console.error("loadEnvSettings error", e);
85 setSaveProcHistory(false);
86 }
87 }
88
89 // NEW: set env setting (toggle)
90 async function setEnvProcessHistory(env, enabled) {
91 // optimistic UI
92 setSaveProcHistory(enabled);
93
94 try {
95 const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
96 method: "POST",
97 credentials: "include",
98 headers: { "Content-Type": "application/json" },
99 body: JSON.stringify({ save_process_history: enabled }),
100 });
101
102 if (r.ok) {
103 const data = await r.json();
104 setSaveProcHistory(!!data.save_process_history);
105 } else {
106 // rollback
107 setSaveProcHistory(!enabled);
108 console.error("setEnvProcessHistory failed", await r.text());
109 }
110 } catch (e) {
111 setSaveProcHistory(!enabled);
112 console.error("setEnvProcessHistory error", e);
113 }
114 }
115
116 // on mount: check session
117 useEffect(() => {
118 loadMe();
119 }, []);
120
121 // when logged in OR env changed, refresh data + load env switch state
122 useEffect(() => {
123 if (!me) return;
124 loadEnvSettings(selectedEnv);
125 refresh();
126 // eslint-disable-next-line react-hooks/exhaustive-deps
127 }, [me, selectedEnv]);
128
129 const filtered = useMemo(() => {
130 const q = query.trim().toLowerCase();
131 return (computers || [])
132 .filter((c) => (status ? c.status === status : true))
133 .filter((c) => {
134 if (!q) return true;
135 const hay = [c.name, c.user, c.ip, c.os, c.status]
136 .filter(Boolean)
137 .join(" ")
138 .toLowerCase();
139 return hay.includes(q);
140 });
141 }, [computers, query, status]);
142
143 // Gate UI
144 if (meLoading) return <div style={{ padding: 20 }}>Loading…</div>;
145 if (!me) return <Login onDone={loadMe} />;
146
147 return (
148 <div className="app-wrap">
149 <TopBar
150 onOpenAI={() => setAiOpen(true)}
151 onRefresh={refresh}
152 lastUpdated={lastUpdated}
153 onEnvChange={(env) => setSelectedEnv(env)}
154 selectedEnv={selectedEnv}
155 me={me}
156 onLoggedOut={loadMe}
157 // NEW props for switch:
158 saveProcHistory={saveProcHistory}
159 onToggleProcHistory={(enabled) => setEnvProcessHistory(selectedEnv, enabled)}
160 />
161
162 {/* STATS */}
163 <div className="stats-grid">
164 <div className="stat-card">
165 <div className="label">Total Computers</div>
166 <div className="value">{computers.length}</div>
167 <div className="sub">Monitored Systems</div>
168 </div>
169
170 <div className="stat-card">
171 <div className="label">Sysmon Events (24h)</div>
172 <div className="value">{stats?.total_sysmon_events ?? "—"}</div>
173 <div className="sub">Security Events</div>
174 </div>
175
176 <div className="stat-card">
177 <div className="label">Network Connections (24h)</div>
178 <div className="value">{stats?.total_network_connections ?? "—"}</div>
179 <div className="sub">Captured Connections</div>
180 </div>
181 </div>
182
183 {/* FILTERS */}
184 <div className="panel" style={{ marginBottom: 14 }}>
185 <div className="panel-head">
186 <div className="panel-title">Filters</div>
187 <div style={{ color: "var(--dim)", fontSize: 12 }}>
188 {loading ? "Refreshing…" : `${filtered.length} shown`}
189 </div>
190 </div>
191 <div className="panel-body">
192 <div className="filters">
193 <input
194 className="input"
195 value={query}
196 onChange={(e) => setQuery(e.target.value)}
197 placeholder="Search by name / user / ip / os…"
198 />
199
200 <select
201 className="select"
202 value={status}
203 onChange={(e) => setStatus(e.target.value)}
204 >
205 <option value="">All status</option>
206 <option value="online">Online</option>
207 <option value="idle">Idle</option>
208 <option value="offline">Offline</option>
209 </select>
210
211 <button className="btn primary" onClick={refresh}>
212 Refresh
213 </button>
214 </div>
215 </div>
216 </div>
217
218 {/* COMPUTERS */}
219 <div className="panel">
220 <div className="panel-head">
221 <div className="panel-title">Connected Computers</div>
222 <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
223 <span className="pill">{filtered.length} computers</span>
224 <button
225 className="btn"
226 onClick={() => {
227 setQuery("");
228 setStatus("");
229 }}
230 >
231 Reset
232 </button>
233 </div>
234 </div>
235 <div className="panel-body">
236 <div className="computers-grid">
237 {filtered.map((c) => (
238 <ComputerCard
239 key={c.name}
240 c={c}
241 onOpen={(pc) => {
242 setSelectedComputer(pc);
243 setAiScope(pc.name);
244 }}
245 />
246 ))}
247 {filtered.length === 0 && (
248 <div style={{ color: "var(--dim)" }}>No computers match filter.</div>
249 )}
250 </div>
251 </div>
252 </div>
253
254 {/* DETAILS MODAL */}
255 <ComputerDetailsModal
256 open={!!selectedComputer}
257 computer={selectedComputer}
258 onClose={() => setSelectedComputer(null)}
259 onAskAI={(name) => {
260 setAiScope(name);
261 setAiOpen(true);
262 }}
263 />
264
265 {/* AI DRAWER */}
266 <AIAssistantDrawer
267 open={aiOpen}
268 onClose={() => setAiOpen(false)}
269 computers={computers}
270 scope={aiScope}
271 setScope={setAiScope}
272 apiBase={import.meta.env.VITE_API_BASE}
273 />
274 </div>
275 );
276}
Note: See TracBrowser for help on using the repository browser.