Index: lan-frontend/src/components/TopBar.jsx
===================================================================
--- lan-frontend/src/components/TopBar.jsx	(revision 2058e5c694bb6097866a5fe6503c519e8f74970f)
+++ lan-frontend/src/components/TopBar.jsx	(revision 2058e5c694bb6097866a5fe6503c519e8f74970f)
@@ -0,0 +1,532 @@
+import React, { useEffect, useMemo, useState } from "react";
+
+export default function TopBar({ onOpenAI, onRefresh, lastUpdated, onEnvChange }) {
+  const [open, setOpen] = useState(false);
+
+  // admin auth
+  const [adminKey, setAdminKey] = useState("");
+  const [adminSession, setAdminSession] = useState("");
+
+  // env + tokens
+  const [envs, setEnvs] = useState(["default"]);
+  const [selectedEnv, setSelectedEnv] = useState("default");
+  const [newEnvName, setNewEnvName] = useState("");
+  const [token, setToken] = useState("");
+
+  const [busy, setBusy] = useState(false);
+  const [error, setError] = useState("");
+
+  // helper: fetch env list after login
+  async function loadEnvironments(session) {
+    const r = await fetch("/api/admin/environments", {
+      headers: { "X-Admin-Session": session },
+    });
+    const data = await r.json();
+    if (!r.ok) throw new Error(data?.error || `HTTP ${r.status}`);
+
+    const list = data.environments?.length ? data.environments : ["default"];
+    setEnvs(list);
+
+    // ако тековниот не постои, стави првиот од листата
+    if (!list.includes(selectedEnv)) {
+      setSelectedEnv(list[0]);
+      onEnvChange?.(list[0]);
+    }
+  }
+
+  async function login() {
+    setError("");
+    setToken("");
+
+    if (!adminKey.trim()) {
+      setError("Внеси admin key.");
+      return;
+    }
+
+    setBusy(true);
+    try {
+      const r = await fetch("/api/admin/login", {
+        method: "POST",
+        headers: { "Content-Type": "application/json" },
+        body: JSON.stringify({ admin_key: adminKey.trim() }),
+      });
+      const data = await r.json();
+      if (!r.ok) throw new Error(data?.error || `HTTP ${r.status}`);
+
+      const session = data.session;
+      setAdminSession(session);
+      await loadEnvironments(session);
+    } catch (e) {
+      setError(String(e.message || e));
+    } finally {
+      setBusy(false);
+    }
+  }
+
+  function logout() {
+    setAdminSession("");
+    setAdminKey("");
+    setToken("");
+    setError("");
+    setEnvs(["default"]);
+    setSelectedEnv("default");
+    onEnvChange?.("default");
+  }
+
+  async function createEnvironment() {
+    setError("");
+    setToken("");
+    if (!newEnvName.trim()) {
+      setError("Внеси име за environment.");
+      return;
+    }
+
+    setBusy(true);
+    try {
+      const r = await fetch("/api/admin/environments", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+          "X-Admin-Session": adminSession,
+        },
+        body: JSON.stringify({ name: newEnvName.trim() }),
+      });
+
+      const data = await r.json();
+      if (!r.ok) throw new Error(data?.error || `HTTP ${r.status}`);
+
+      // reload list, select new env
+      await loadEnvironments(adminSession);
+      setSelectedEnv(newEnvName.trim());
+      onEnvChange?.(newEnvName.trim());
+      setNewEnvName("");
+    } catch (e) {
+      setError(String(e.message || e));
+    } finally {
+      setBusy(false);
+    }
+  }
+
+  async function generateToken() {
+    setError("");
+    setToken("");
+    if (!selectedEnv) {
+      setError("Избери environment.");
+      return;
+    }
+
+    setBusy(true);
+    try {
+      const r = await fetch("/api/admin/tokens", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+          "X-Admin-Session": adminSession,
+        },
+        body: JSON.stringify({ env: selectedEnv }),
+      });
+
+      const data = await r.json();
+      if (!r.ok) throw new Error(data?.error || `HTTP ${r.status}`);
+
+      setToken(data.token || "");
+    } catch (e) {
+      setError(String(e.message || e));
+    } finally {
+      setBusy(false);
+    }
+  }
+
+  async function copyToken() {
+    if (!token) return;
+    await navigator.clipboard.writeText(token);
+  }
+
+  function changeEnv(val) {
+    setSelectedEnv(val);
+    setToken("");
+    onEnvChange?.(val);
+  }
+
+  const prettyTime = useMemo(() => {
+    if (!lastUpdated) return "Loading…";
+    try {
+      return `Updated ${new Date(lastUpdated).toLocaleTimeString()}`;
+    } catch {
+      return "Updated";
+    }
+  }, [lastUpdated]);
+
+  return (
+    <>
+      <style>{`
+        /* ---- TopBar scoped theme ---- */
+        .tb-wrap{
+          position: sticky;
+          top: 0;
+          z-index: 20;
+          backdrop-filter: blur(10px);
+          background: linear-gradient(180deg, rgba(10,16,32,0.92), rgba(10,16,32,0.75));
+          border-bottom: 1px solid rgba(96,165,250,0.18);
+        }
+        .tb-inner{
+          max-width: 1200px;
+          margin: 0 auto;
+          padding: 14px 18px;
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          gap: 14px;
+        }
+
+        .tb-brand{
+          display:flex;
+          align-items:center;
+          gap: 12px;
+          min-width: 260px;
+        }
+        .tb-badge{
+          width: 44px;
+          height: 44px;
+          border-radius: 14px;
+          display:flex;
+          align-items:center;
+          justify-content:center;
+          background:
+            radial-gradient(120px 80px at 30% 20%, rgba(96,165,250,0.55), transparent 60%),
+            linear-gradient(135deg, rgba(30,64,175,0.75), rgba(15,23,42,0.85));
+          border: 1px solid rgba(96,165,250,0.25);
+          box-shadow: 0 10px 25px rgba(0,0,0,0.25);
+        }
+        .tb-title{
+          margin:0;
+          font-size: 16px;
+          font-weight: 900;
+          letter-spacing: 0.4px;
+          color: rgba(255,255,255,0.95);
+          line-height: 1.2;
+        }
+        .tb-sub{
+          margin: 2px 0 0 0;
+          font-size: 12px;
+          color: rgba(191,219,254,0.85);
+        }
+        .tb-sub span{
+          color: rgba(96,165,250,0.95);
+          font-weight: 800;
+        }
+
+        .tb-actions{
+          display:flex;
+          align-items:center;
+          gap: 10px;
+          flex-wrap: wrap;
+          justify-content: flex-end;
+        }
+
+        .tb-pill{
+          display:inline-flex;
+          align-items:center;
+          gap: 8px;
+          padding: 8px 12px;
+          border-radius: 999px;
+          background: rgba(30,41,59,0.65);
+          border: 1px solid rgba(96,165,250,0.22);
+          color: rgba(255,255,255,0.9);
+          font-weight: 800;
+          font-size: 12px;
+          box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
+        }
+        .tb-pill .dot{
+          width: 8px;
+          height: 8px;
+          border-radius: 999px;
+          background: rgba(96,165,250,0.95);
+          box-shadow: 0 0 0 3px rgba(96,165,250,0.15);
+        }
+
+        .tb-btn{
+          appearance: none;
+          border: 1px solid rgba(96,165,250,0.22);
+          background: rgba(15,23,42,0.55);
+          color: rgba(255,255,255,0.9);
+          padding: 9px 12px;
+          border-radius: 12px;
+          font-weight: 800;
+          font-size: 13px;
+          cursor: pointer;
+          transition: transform .12s ease, background .12s ease, border-color .12s ease, opacity .12s ease;
+          box-shadow: 0 8px 20px rgba(0,0,0,0.18);
+        }
+        .tb-btn:hover{
+          transform: translateY(-1px);
+          background: rgba(30,41,59,0.65);
+          border-color: rgba(96,165,250,0.38);
+        }
+        .tb-btn:disabled{
+          opacity: .6;
+          cursor: not-allowed;
+          transform: none;
+        }
+        .tb-btn.primary{
+          background: linear-gradient(135deg, rgba(37,99,235,0.95), rgba(56,189,248,0.7));
+          border-color: rgba(147,197,253,0.35);
+          color: rgba(5,12,24,0.95);
+        }
+        .tb-btn.primary:hover{
+          background: linear-gradient(135deg, rgba(59,130,246,0.95), rgba(34,211,238,0.75));
+        }
+
+        /* ---- Modal ---- */
+        .tb-backdrop{
+          position: fixed;
+          inset: 0;
+          background: rgba(2,6,23,0.72);
+          backdrop-filter: blur(6px);
+          display:flex;
+          align-items: center;
+          justify-content: center;
+          padding: 20px;
+          z-index: 50;
+        }
+        .tb-modal{
+          width: min(860px, 96vw);
+          border-radius: 18px;
+          background:
+            radial-gradient(900px 420px at 20% 0%, rgba(96,165,250,0.18), transparent 60%),
+            radial-gradient(700px 350px at 80% 10%, rgba(34,211,238,0.10), transparent 55%),
+            rgba(10,16,32,0.92);
+          border: 1px solid rgba(96,165,250,0.22);
+          box-shadow: 0 30px 80px rgba(0,0,0,0.45);
+          overflow: hidden;
+        }
+        .tb-modal-head{
+          padding: 16px 18px;
+          display:flex;
+          align-items: center;
+          justify-content: space-between;
+          gap: 10px;
+          border-bottom: 1px solid rgba(96,165,250,0.18);
+        }
+        .tb-modal-title{
+          color: rgba(255,255,255,0.96);
+          font-weight: 900;
+          letter-spacing: .3px;
+        }
+        .tb-modal-sub{
+          color: rgba(191,219,254,0.8);
+          font-size: 12px;
+          margin-top: 2px;
+        }
+        .tb-grid{
+          padding: 16px;
+          display:grid;
+          gap: 12px;
+        }
+        .tb-card{
+          border-radius: 16px;
+          background: rgba(15,23,42,0.55);
+          border: 1px solid rgba(96,165,250,0.18);
+          padding: 14px;
+        }
+        .tb-card-head{
+          display:flex;
+          align-items:center;
+          justify-content: space-between;
+          margin-bottom: 10px;
+        }
+        .tb-card-title{
+          color: rgba(255,255,255,0.92);
+          font-weight: 900;
+        }
+        .tb-help{
+          color: rgba(148,163,184,0.95);
+          font-size: 12px;
+          margin-top: 8px;
+          line-height: 1.35;
+        }
+        .tb-input, .tb-select{
+          width: 100%;
+          padding: 10px 12px;
+          border-radius: 12px;
+          border: 1px solid rgba(96,165,250,0.18);
+          background: rgba(2,6,23,0.35);
+          color: rgba(255,255,255,0.92);
+          outline: none;
+          font-weight: 700;
+        }
+        .tb-input::placeholder{ color: rgba(148,163,184,0.75); }
+        .tb-row{
+          display:grid;
+          gap: 10px;
+        }
+        .tb-token{
+          display:flex;
+          align-items:center;
+          justify-content: space-between;
+          gap: 10px;
+          padding: 10px 12px;
+          border-radius: 14px;
+          background: rgba(2,6,23,0.38);
+          border: 1px dashed rgba(147,197,253,0.32);
+          color: rgba(255,255,255,0.92);
+        }
+        .tb-mono{
+          font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+          font-weight: 800;
+          color: rgba(191,219,254,0.95);
+        }
+        .tb-error{
+          border-color: rgba(239,68,68,0.45) !important;
+          background: rgba(127,29,29,0.15) !important;
+          color: rgba(254,202,202,0.95);
+        }
+      `}</style>
+
+      {/* TOPBAR */}
+      <div className="tb-wrap">
+        <div className="tb-inner">
+          <div className="tb-brand">
+            <div className="tb-badge">🛡️</div>
+            <div>
+              <h1 className="tb-title">LAN Security Monitor</h1>
+              <p className="tb-sub">
+                Sysmon Edition • <span>{prettyTime}</span>
+              </p>
+            </div>
+          </div>
+
+          <div className="tb-actions">
+            <span className="tb-pill">
+              <span className="dot" />
+              Env: {selectedEnv}
+            </span>
+            <button className="tb-btn" onClick={onRefresh} disabled={busy}>Refresh</button>
+            <button className="tb-btn" onClick={() => setOpen(true)}>Admin</button>
+            <button className="tb-btn primary" onClick={onOpenAI}>AI Assistant</button>
+          </div>
+        </div>
+      </div>
+
+      {/* MODAL */}
+      {open && (
+        <div className="tb-backdrop" onMouseDown={() => setOpen(false)}>
+          <div className="tb-modal" onMouseDown={(e) => e.stopPropagation()}>
+            <div className="tb-modal-head">
+              <div>
+                <div className="tb-modal-title">Admin Panel</div>
+                <div className="tb-modal-sub">Login ➜ Environments ➜ Generate token</div>
+              </div>
+              <button className="tb-btn" onClick={() => setOpen(false)}>Close</button>
+            </div>
+
+            <div className="tb-grid">
+              {/* 1) LOGIN */}
+              {!adminSession ? (
+                <div className="tb-card">
+                  <div className="tb-card-head">
+                    <div className="tb-card-title">Step 1: Admin login</div>
+                  </div>
+
+                  <div className="tb-row">
+                    <input
+                      className="tb-input"
+                      type="password"
+                      value={adminKey}
+                      onChange={(e) => setAdminKey(e.target.value)}
+                      placeholder="Enter admin key"
+                    />
+                    <button className="tb-btn primary" onClick={login} disabled={busy}>
+                      {busy ? "Logging in…" : "Login"}
+                    </button>
+                    <div className="tb-help">
+                      Admin key е само за логирање. После тоа користиме <b>session token</b>.
+                    </div>
+                  </div>
+                </div>
+              ) : (
+                <>
+                  {/* 2) ENV SELECT */}
+                  <div className="tb-card">
+                    <div className="tb-card-head">
+                      <div className="tb-card-title">Step 2: Choose environment</div>
+                      <button className="tb-btn" onClick={logout}>Logout</button>
+                    </div>
+
+                    <div className="tb-row">
+                      <select className="tb-select" value={selectedEnv} onChange={(e) => changeEnv(e.target.value)}>
+                        {envs.map((e) => (
+                          <option key={e} value={e}>{e}</option>
+                        ))}
+                      </select>
+                      <div className="tb-help">
+                        Овој env ќе се користи за dashboard филтрирање + token generation.
+                      </div>
+                    </div>
+                  </div>
+
+                  {/* 3) CREATE ENV */}
+                  <div className="tb-card">
+                    <div className="tb-card-head">
+                      <div className="tb-card-title">Create new environment</div>
+                    </div>
+
+                    <div className="tb-row">
+                      <input
+                        className="tb-input"
+                        value={newEnvName}
+                        onChange={(e) => setNewEnvName(e.target.value)}
+                        placeholder="e.g. school-lab / staging"
+                      />
+                      <button className="tb-btn" onClick={createEnvironment} disabled={busy}>
+                        {busy ? "Creating…" : "Create"}
+                      </button>
+                    </div>
+                  </div>
+
+                  {/* 4) TOKEN */}
+                  <div className="tb-card">
+                    <div className="tb-card-head">
+                      <div className="tb-card-title">Step 3: Generate token (for clients)</div>
+                    </div>
+
+                    <div className="tb-row">
+                      <button className="tb-btn primary" onClick={generateToken} disabled={busy}>
+                        {busy ? "Generating…" : `Generate token for "${selectedEnv}"`}
+                      </button>
+
+                      {token && (
+                        <>
+                          <div className="tb-token">
+                            <div>
+                              <div style={{ fontWeight: 900, color: "rgba(255,255,255,0.92)" }}>Token</div>
+                              <div className="tb-mono">
+                                {token.slice(0, 12)}…{token.slice(-10)}
+                              </div>
+                            </div>
+                            <button className="tb-btn" onClick={copyToken}>Copy</button>
+                          </div>
+
+                          <div className="tb-help">
+                            Клиентот мора да праќа header: <b>X-Env-Token</b> со овој token на <b>/receive</b>.
+                          </div>
+                        </>
+                      )}
+                    </div>
+                  </div>
+                </>
+              )}
+
+              {error && (
+                <div className="tb-card tb-error">
+                  <div style={{ fontWeight: 900 }}>Error</div>
+                  <div style={{ marginTop: 6 }}>{error}</div>
+                </div>
+              )}
+            </div>
+          </div>
+        </div>
+      )}
+    </>
+  );
+}
