Ignore:
Timestamp:
01/26/26 16:19:33 (5 months ago)
Author:
istevanoska <ilinastevanoska@…>
Branches:
master
Children:
ba17441
Parents:
e4c61dd
Message:

Added visualizations

File:
1 edited

Legend:

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

    re4c61dd ra762898  
    1 import React, { useEffect, useMemo, useState } from "react";
     1import React, {useEffect, useMemo, useState} from "react";
    22import "./styles/theme.css";
     3import "./styles/netinel.css";
    34
    45import Login from "./components/Login";
     
    78import ComputerDetailsModal from "./components/ComputerDetailsModal";
    89import AIAssistantDrawer from "./components/AIAssistantDrawer";
     10import NetIntelViz from "./components/NetIntelViz.jsx";
     11import SideMenu from "./components/SideMenu";
     12
    913
    1014export 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);
     15    const [stats, setStats] = useState(null);
     16    const [computers, setComputers] = useState([]);
     17    const [loading, setLoading] = useState(true);
     18    const [lastUpdated, setLastUpdated] = useState(null);
     19
     20    const [query, setQuery] = useState("");
     21    const [status, setStatus] = useState("");
     22
     23    const [selectedComputer, setSelectedComputer] = useState(null);
     24    const [aiOpen, setAiOpen] = useState(false);
     25    const [aiScope, setAiScope] = useState(null);
     26    const [selectedEnv, setSelectedEnv] = useState("default");
     27
     28    const [me, setMe] = useState(null);
     29    const [meLoading, setMeLoading] = useState(true);
     30
     31    const [activeView, setActiveView] = useState("dashboard");
     32// "dashboard" | "visualizations" | "account"
     33
     34
     35    // NEW: environment setting (switch)
     36    const [saveProcHistory, setSaveProcHistory] = useState(false);
     37
     38    async function loadMe() {
     39        setMeLoading(true);
     40        try {
     41            const r = await fetch("/api/me", {credentials: "include"});
     42            if (r.ok) {
     43                const data = await r.json();
     44                setMe(data.user);
     45            } else {
     46                setMe(null);
     47            }
     48        } catch (e) {
     49            console.error("loadMe error", e);
     50            setMe(null);
     51        } finally {
     52            setMeLoading(false);
     53        }
    4554    }
    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);
     55
     56    async function refresh() {
     57        try {
     58            setLoading(true);
     59            const [s, c] = await Promise.all([
     60                fetch("/api/stats", {credentials: "include"}).then((r) => r.json()),
     61                fetch("/api/computers", {
     62                    credentials: "include",
     63                    headers: {"X-Env": selectedEnv},
     64                }).then((r) => r.json()),
     65            ]);
     66
     67            setStats(s);
     68            setComputers(Array.isArray(c) ? c : []);
     69            setLastUpdated(new Date().toISOString());
     70        } catch (e) {
     71            console.error("refresh error", e);
     72        } finally {
     73            setLoading(false);
     74        }
    6675    }
    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);
     76
     77    // NEW: load env settings (switch state) for selected env
     78    async function loadEnvSettings(env) {
     79        try {
     80            // само admin ќе има пристап (ако не си admin, ќе падне во else и ќе стане false)
     81            const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
     82                credentials: "include",
     83            });
     84
     85            if (r.ok) {
     86                const data = await r.json();
     87                setSaveProcHistory(!!data.save_process_history);
     88            } else {
     89                setSaveProcHistory(false);
     90            }
     91        } catch (e) {
     92            console.error("loadEnvSettings error", e);
     93            setSaveProcHistory(false);
     94        }
    8695    }
    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);
     96
     97    // NEW: set env setting (toggle)
     98    async function setEnvProcessHistory(env, enabled) {
     99        // optimistic UI
     100        setSaveProcHistory(enabled);
     101
     102        try {
     103            const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
     104                method: "POST",
     105                credentials: "include",
     106                headers: {"Content-Type": "application/json"},
     107                body: JSON.stringify({save_process_history: enabled}),
     108            });
     109
     110            if (r.ok) {
     111                const data = await r.json();
     112                setSaveProcHistory(!!data.save_process_history);
     113            } else {
     114                // rollback
     115                setSaveProcHistory(!enabled);
     116                console.error("setEnvProcessHistory failed", await r.text());
     117            }
     118        } catch (e) {
     119            setSaveProcHistory(!enabled);
     120            console.error("setEnvProcessHistory error", e);
     121        }
    113122    }
    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>
     123
     124    // on mount: check session
     125    useEffect(() => {
     126        loadMe();
     127    }, []);
     128
     129    // when logged in OR env changed, refresh data + load env switch state
     130    useEffect(() => {
     131        if (!me) return;
     132        loadEnvSettings(selectedEnv);
     133        refresh();
     134        // eslint-disable-next-line react-hooks/exhaustive-deps
     135    }, [me, selectedEnv]);
     136
     137    const filtered = useMemo(() => {
     138        const q = query.trim().toLowerCase();
     139        return (computers || [])
     140            .filter((c) => (status ? c.status === status : true))
     141            .filter((c) => {
     142                if (!q) return true;
     143                const hay = [c.name, c.user, c.ip, c.os, c.status]
     144                    .filter(Boolean)
     145                    .join(" ")
     146                    .toLowerCase();
     147                return hay.includes(q);
     148            });
     149    }, [computers, query, status]);
     150
     151    // Gate UI
     152    if (meLoading) return <div style={{padding: 20}}>Loading…</div>;
     153    if (!me) return <Login onDone={loadMe}/>;
     154
     155    return (
     156        <div className="app-shell">
     157            <div className="layout">
     158                <SideMenu activeView={activeView} setActiveView={setActiveView}/>
     159                <main className="content">
     160                    {/* TopBar + pages */}
     161                </main>
     162            </div>
     163
     164            <div className="cont">
     165                <TopBar
     166                    onOpenAI={() => setAiOpen(true)}
     167                    onRefresh={refresh}
     168                    lastUpdated={lastUpdated}
     169                    onEnvChange={(env) => setSelectedEnv(env)}
     170                    selectedEnv={selectedEnv}
     171                    me={me}
     172                    onLoggedOut={loadMe}
     173                    saveProcHistory={saveProcHistory}
     174                    onToggleProcHistory={(enabled) => setEnvProcessHistory(selectedEnv, enabled)}
     175                />
     176
     177                <div className="app-main">
     178                    {/* ====== VIEW SWITCH ====== */}
     179                    {activeView === "dashboard" && (
     180                        <>
     181                            {/* STATS */}
     182                            <div className="stats-grid">
     183                                <div className="stat-card">
     184                                    <div className="label">Total Computers</div>
     185                                    <div className="value">{computers.length}</div>
     186                                    <div className="sub">Monitored Systems</div>
     187                                </div>
     188
     189                                <div className="stat-card">
     190                                    <div className="label">Sysmon Events (24h)</div>
     191                                    <div className="value">{stats?.total_sysmon_events ?? "—"}</div>
     192                                    <div className="sub">Security Events</div>
     193                                </div>
     194
     195                                <div className="stat-card">
     196                                    <div className="label">Network Connections (24h)</div>
     197                                    <div className="value">{stats?.total_network_connections ?? "—"}</div>
     198                                    <div className="sub">Captured Connections</div>
     199                                </div>
     200                            </div>
     201
     202                            {/* FILTERS */}
     203                            <div className="panel" style={{marginBottom: 14}}>
     204                                <div className="panel-head">
     205                                    <div className="panel-title">Filters</div>
     206                                    <div style={{color: "var(--dim)", fontSize: 12}}>
     207                                        {loading ? "Refreshing…" : `${filtered.length} shown`}
     208                                    </div>
     209                                </div>
     210                                <div className="panel-body">
     211                                    <div className="filters">
     212                                        <input
     213                                            className="input"
     214                                            value={query}
     215                                            onChange={(e) => setQuery(e.target.value)}
     216                                            placeholder="Search by name / user / ip / os…"
     217                                        />
     218
     219                                        <select
     220                                            className="select"
     221                                            value={status}
     222                                            onChange={(e) => setStatus(e.target.value)}
     223                                        >
     224                                            <option value="">All status</option>
     225                                            <option value="online">Online</option>
     226                                            <option value="idle">Idle</option>
     227                                            <option value="offline">Offline</option>
     228                                        </select>
     229
     230                                        <button className="btn primary" onClick={refresh}>
     231                                            Refresh
     232                                        </button>
     233                                    </div>
     234                                </div>
     235                            </div>
     236
     237                            {/* COMPUTERS */}
     238                            <div className="panel">
     239                                <div className="panel-head">
     240                                    <div className="panel-title">Connected Computers</div>
     241                                    <div style={{display: "flex", gap: 10, alignItems: "center"}}>
     242                                        <span className="pill">{filtered.length} computers</span>
     243                                        <button
     244                                            className="btn"
     245                                            onClick={() => {
     246                                                setQuery("");
     247                                                setStatus("");
     248                                            }}
     249                                        >
     250                                            Reset
     251                                        </button>
     252                                    </div>
     253                                </div>
     254                                <div className="panel-body">
     255                                    <div className="computers-grid">
     256                                        {filtered.map((c) => (
     257                                            <ComputerCard
     258                                                key={c.name}
     259                                                c={c}
     260                                                onOpen={(pc) => {
     261                                                    setSelectedComputer(pc);
     262                                                    setAiScope(pc.name);
     263                                                }}
     264                                            />
     265                                        ))}
     266                                        {filtered.length === 0 && (
     267                                            <div style={{color: "var(--dim)"}}>
     268                                                No computers match filter.
     269                                            </div>
     270                                        )}
     271                                    </div>
     272                                </div>
     273                            </div>
     274                        </>
     275                    )}
     276
     277                    {activeView === "visualizations" && (
     278                        <>
     279                            <div className="panel" style={{marginBottom: 14}}>
     280                                <div className="panel-head">
     281                                    <div className="panel-title">Visualizations</div>
     282                                    <div style={{color: "var(--dim)", fontSize: 12}}>
     283                                        Network graphs / charts / tables
     284                                    </div>
     285                                </div>
     286                                <div className="panel-body">
     287                                    <NetIntelViz baseUrl="http://localhost:5555"/>
     288                                </div>
     289                            </div>
     290                        </>
     291                    )}
     292
     293                    {activeView === "account" && (
     294                        <div className="panel">
     295                            <div className="panel-head">
     296                                <div className="panel-title">Account</div>
     297                                <div style={{color: "var(--dim)", fontSize: 12}}>
     298                                    Profile info
     299                                </div>
     300                            </div>
     301                            <div className="panel-body">
     302                                <div style={{display: "grid", gap: 10}}>
     303                                    <div className="pill">Email: {me.email}</div>
     304                                    <div className="pill">Role: {me.role}</div>
     305                                    <div className="pill">Tenant ID: {me.tenant_id}</div>
     306                                    <div className="pill">Name: {me.name || "—"}</div>
     307                                </div>
     308                            </div>
     309                        </div>
     310                    )}
     311                </div>
     312
     313                {/* DETAILS MODAL */}
     314                <ComputerDetailsModal
     315                    open={!!selectedComputer}
     316                    computer={selectedComputer}
     317                    onClose={() => setSelectedComputer(null)}
     318                    onAskAI={(name) => {
     319                        setAiScope(name);
     320                        setAiOpen(true);
     321                    }}
     322                />
     323
     324                {/* AI DRAWER */}
     325                <AIAssistantDrawer
     326                    open={aiOpen}
     327                    onClose={() => setAiOpen(false)}
     328                    computers={computers}
     329                    scope={aiScope}
     330                    setScope={setAiScope}
     331                    apiBase={import.meta.env.VITE_API_BASE}
     332                />
     333            </div>
    168334        </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   );
     335    );
    276336}
Note: See TracChangeset for help on using the changeset viewer.