source: lan-frontend/src/App.jsx@ 505f39a

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

Added OAuth/prototype

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