Ignore:
Timestamp:
01/22/26 17:32:29 (6 months ago)
Author:
istevanoska <ilinastevanoska@…>
Branches:
master
Children:
e4c61dd
Parents:
505f39a
Message:

Added history mmanage

File:
1 edited

Legend:

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

    r505f39a rd42aac3  
    1 import React, {useEffect, useMemo, useState} from "react";
     1import React, { useEffect, useMemo, useState } from "react";
    22import "./styles/theme.css";
    33
     
    99
    1010export 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     // 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 
    49     async function refresh() {
    50         try {
    51             setLoading(true);
    52             const [s, c] = await Promise.all([
    53                 fetch("/api/stats", {credentials: "include"}).then((r) => r.json()),
    54                 fetch("/api/computers", {
    55                     credentials: "include",
    56                     headers: {"X-Env": selectedEnv},
    57                 }).then((r) => r.json()),
    58             ]);
    59 
    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         }
    68     }
    69 
    70     // on mount: check session
    71     useEffect(() => {
    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]);
    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;
    87                 const hay = [c.name, c.user, c.ip, c.os, c.status]
    88                     .filter(Boolean)
    89                     .join(" ")
    90                     .toLowerCase();
    91                 return hay.includes(q);
    92             });
    93     }, [computers, query, status]);
    94 
    95     // Gate UI
    96     if (meLoading) return <div style={{padding: 20}}>Loading…</div>;
    97     if (!me) return <Login onDone={loadMe}/>;
    98 
    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}
    107                 me={me}
    108                 onLoggedOut={loadMe}
     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…"
    109198            />
    110199
    111 
    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>
    118                 </div>
    119 
    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>
    124                 </div>
    125 
    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>
    130                 </div>
    131             </div>
    132 
    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`}
    139                     </div>
    140                 </div>
    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 
    150                         <select
    151                             className="select"
    152                             value={status}
    153                             onChange={(e) => setStatus(e.target.value)}
    154                         >
    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 
    161                         <button className="btn primary" onClick={refresh}>
    162                             Refresh
    163                         </button>
    164                     </div>
    165                 </div>
    166             </div>
    167 
    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>
    174                         <button
    175                             className="btn"
    176                             onClick={() => {
    177                                 setQuery("");
    178                                 setStatus("");
    179                             }}
    180                         >
    181                             Reset
    182                         </button>
    183                     </div>
    184                 </div>
    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 && (
    198                             <div style={{color: "var(--dim)"}}>
    199                                 No computers match filter.
    200                             </div>
    201                         )}
    202                     </div>
    203                 </div>
    204             </div>
    205 
    206             {/* DETAILS MODAL */}
    207             <ComputerDetailsModal
    208                 open={!!selectedComputer}
    209                 computer={selectedComputer}
    210                 onClose={() => setSelectedComputer(null)}
    211                 onAskAI={(name) => {
    212                     setAiScope(name);
    213                     setAiOpen(true);
     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);
    214244                }}
    215             />
    216 
    217             {/* AI DRAWER */}
    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 
    229             <AIAssistantDrawer
    230                 open={aiOpen}
    231                 onClose={() => setAiOpen(false)}
    232                 computers={computers}
    233                 scope={aiScope}
    234                 setScope={setAiScope}
    235                 apiBase={import.meta.env.VITE_API_BASE}
    236             />
    237 
    238 
    239         </div>
    240     );
     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  );
    241276}
Note: See TracChangeset for help on using the changeset viewer.