Index: lan-frontend/.env
===================================================================
--- lan-frontend/.env	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,3 +1,0 @@
-VITE_API_BASE=http://localhost:5555
-VITE_GOOGLE_CLIENT_ID=43542590862-pt18e0hb33o41889bu976oshddoemivs.apps.googleusercontent.com
-GRAFANA_API_TOKEN=KIC7rMwr4U8V-GI8suP5rGgNjdcQvB0cAvAwkwevTKw
Index: lan-frontend/package-lock.json
===================================================================
--- lan-frontend/package-lock.json	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ lan-frontend/package-lock.json	(revision 640ed89213ad0086dc8379de00b8d102057ead1f)
@@ -9,5 +9,4 @@
       "version": "0.0.0",
       "dependencies": {
-        "@react-oauth/google": "^0.13.4",
         "chart.js": "^4.5.1",
         "react": "^19.2.0",
@@ -20,25 +19,9 @@
         "@types/react-dom": "^19.2.3",
         "@vitejs/plugin-react": "^5.1.1",
-        "autoprefixer": "^10.4.23",
         "eslint": "^9.39.1",
         "eslint-plugin-react-hooks": "^7.0.1",
         "eslint-plugin-react-refresh": "^0.4.24",
         "globals": "^16.5.0",
-        "postcss": "^8.5.6",
-        "tailwindcss": "^3.4.17",
         "vite": "^7.2.4"
-      }
-    },
-    "node_modules/@alloc/quick-lru": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
-      "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
@@ -1032,52 +1015,4 @@
       "license": "MIT"
     },
-    "node_modules/@nodelib/fs.scandir": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
-      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@nodelib/fs.stat": "2.0.5",
-        "run-parallel": "^1.1.9"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/@nodelib/fs.stat": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
-      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/@nodelib/fs.walk": {
-      "version": "1.2.8",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
-      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@nodelib/fs.scandir": "2.1.5",
-        "fastq": "^1.6.0"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/@react-oauth/google": {
-      "version": "0.13.4",
-      "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.13.4.tgz",
-      "integrity": "sha512-hGKyNEH+/PK8M0sFEuo3MAEk0txtHpgs94tDQit+s2LXg7b6z53NtzHfqDvoB2X8O6lGB+FRg80hY//X6hfD+w==",
-      "license": "MIT",
-      "peerDependencies": {
-        "react": ">=16.8.0",
-        "react-dom": ">=16.8.0"
-      }
-    },
     "node_modules/@reduxjs/toolkit": {
       "version": "2.11.1",
@@ -1668,45 +1603,4 @@
       }
     },
-    "node_modules/any-promise": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
-      "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/anymatch": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
-      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "normalize-path": "^3.0.0",
-        "picomatch": "^2.0.4"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/anymatch/node_modules/picomatch": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=8.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
-    "node_modules/arg": {
-      "version": "5.0.2",
-      "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
-      "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/argparse": {
       "version": "2.0.1",
@@ -1716,41 +1610,4 @@
       "license": "Python-2.0"
     },
-    "node_modules/autoprefixer": {
-      "version": "10.4.23",
-      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
-      "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/postcss/"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/autoprefixer"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "browserslist": "^4.28.1",
-        "caniuse-lite": "^1.0.30001760",
-        "fraction.js": "^5.3.4",
-        "picocolors": "^1.1.1",
-        "postcss-value-parser": "^4.2.0"
-      },
-      "bin": {
-        "autoprefixer": "bin/autoprefixer"
-      },
-      "engines": {
-        "node": "^10 || ^12 || >=14"
-      },
-      "peerDependencies": {
-        "postcss": "^8.1.0"
-      }
-    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
@@ -1770,17 +1627,4 @@
       }
     },
-    "node_modules/binary-extensions": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
-      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/brace-expansion": {
       "version": "1.1.12",
@@ -1792,17 +1636,4 @@
         "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
-      }
-    },
-    "node_modules/braces": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
-      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "fill-range": "^7.1.1"
-      },
-      "engines": {
-        "node": ">=8"
       }
     },
@@ -1851,14 +1682,4 @@
       }
     },
-    "node_modules/camelcase-css": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
-      "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 6"
-      }
-    },
     "node_modules/caniuse-lite": {
       "version": "1.0.30001760",
@@ -1911,42 +1732,4 @@
       }
     },
-    "node_modules/chokidar": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
-      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "anymatch": "~3.1.2",
-        "braces": "~3.0.2",
-        "glob-parent": "~5.1.2",
-        "is-binary-path": "~2.1.0",
-        "is-glob": "~4.0.1",
-        "normalize-path": "~3.0.0",
-        "readdirp": "~3.6.0"
-      },
-      "engines": {
-        "node": ">= 8.10.0"
-      },
-      "funding": {
-        "url": "https://paulmillr.com/funding/"
-      },
-      "optionalDependencies": {
-        "fsevents": "~2.3.2"
-      }
-    },
-    "node_modules/chokidar/node_modules/glob-parent": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
-      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "is-glob": "^4.0.1"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
     "node_modules/clsx": {
       "version": "2.1.1",
@@ -1978,14 +1761,4 @@
       "license": "MIT"
     },
-    "node_modules/commander": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
-      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 6"
-      }
-    },
     "node_modules/concat-map": {
       "version": "0.0.1",
@@ -2015,17 +1788,4 @@
       "engines": {
         "node": ">= 8"
-      }
-    },
-    "node_modules/cssesc": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
-      "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
-      "dev": true,
-      "license": "MIT",
-      "bin": {
-        "cssesc": "bin/cssesc"
-      },
-      "engines": {
-        "node": ">=4"
       }
     },
@@ -2186,18 +1946,4 @@
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/didyoumean": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
-      "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
-      "dev": true,
-      "license": "Apache-2.0"
-    },
-    "node_modules/dlv": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
-      "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
       "dev": true,
       "license": "MIT"
@@ -2482,34 +2228,4 @@
       "license": "MIT"
     },
-    "node_modules/fast-glob": {
-      "version": "3.3.3",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
-      "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@nodelib/fs.stat": "^2.0.2",
-        "@nodelib/fs.walk": "^1.2.3",
-        "glob-parent": "^5.1.2",
-        "merge2": "^1.3.0",
-        "micromatch": "^4.0.8"
-      },
-      "engines": {
-        "node": ">=8.6.0"
-      }
-    },
-    "node_modules/fast-glob/node_modules/glob-parent": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
-      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "is-glob": "^4.0.1"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
     "node_modules/fast-json-stable-stringify": {
       "version": "2.1.0",
@@ -2526,14 +2242,4 @@
       "license": "MIT"
     },
-    "node_modules/fastq": {
-      "version": "1.20.1",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
-      "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "reusify": "^1.0.4"
-      }
-    },
     "node_modules/fdir": {
       "version": "6.5.0",
@@ -2567,17 +2273,4 @@
       }
     },
-    "node_modules/fill-range": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
-      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "to-regex-range": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/find-up": {
       "version": "5.0.0",
@@ -2618,18 +2311,4 @@
       "license": "ISC"
     },
-    "node_modules/fraction.js": {
-      "version": "5.3.4",
-      "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
-      "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": "*"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/rawify"
-      }
-    },
     "node_modules/fsevents": {
       "version": "2.3.3",
@@ -2647,14 +2326,4 @@
       }
     },
-    "node_modules/function-bind": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
-      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "dev": true,
-      "license": "MIT",
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/gensync": {
       "version": "1.0.0-beta.2",
@@ -2701,17 +2370,4 @@
       "engines": {
         "node": ">=8"
-      }
-    },
-    "node_modules/hasown": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
-      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "function-bind": "^1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
       }
     },
@@ -2789,33 +2445,4 @@
       }
     },
-    "node_modules/is-binary-path": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
-      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "binary-extensions": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/is-core-module": {
-      "version": "2.16.1",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
-      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "hasown": "^2.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/is-extglob": {
       "version": "2.1.1",
@@ -2841,14 +2468,4 @@
       }
     },
-    "node_modules/is-number": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
-      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.12.0"
-      }
-    },
     "node_modules/isexe": {
       "version": "2.0.0",
@@ -2858,14 +2475,4 @@
       "license": "ISC"
     },
-    "node_modules/jiti": {
-      "version": "1.21.7",
-      "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
-      "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
-      "dev": true,
-      "license": "MIT",
-      "bin": {
-        "jiti": "bin/jiti.js"
-      }
-    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
@@ -2959,24 +2566,4 @@
       }
     },
-    "node_modules/lilconfig": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
-      "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=14"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/antonk52"
-      }
-    },
-    "node_modules/lines-and-columns": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
-      "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/locate-path": {
       "version": "6.0.0",
@@ -3012,41 +2599,4 @@
       }
     },
-    "node_modules/merge2": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
-      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/micromatch": {
-      "version": "4.0.8",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
-      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "braces": "^3.0.3",
-        "picomatch": "^2.3.1"
-      },
-      "engines": {
-        "node": ">=8.6"
-      }
-    },
-    "node_modules/micromatch/node_modules/picomatch": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=8.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
     "node_modules/minimatch": {
       "version": "3.1.2",
@@ -3068,16 +2618,4 @@
       "dev": true,
       "license": "MIT"
-    },
-    "node_modules/mz": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
-      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "any-promise": "^1.0.0",
-        "object-assign": "^4.0.1",
-        "thenify-all": "^1.0.0"
-      }
     },
     "node_modules/nanoid": {
@@ -3113,34 +2651,4 @@
       "dev": true,
       "license": "MIT"
-    },
-    "node_modules/normalize-path": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
-      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/object-hash": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
-      "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 6"
-      }
     },
     "node_modules/optionator": {
@@ -3227,11 +2735,4 @@
       }
     },
-    "node_modules/path-parse": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
-      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/picocolors": {
       "version": "1.1.1",
@@ -3252,24 +2753,4 @@
       "funding": {
         "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
-    "node_modules/pify": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-      "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/pirates": {
-      "version": "4.0.7",
-      "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
-      "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 6"
       }
     },
@@ -3303,131 +2784,4 @@
       }
     },
-    "node_modules/postcss-import": {
-      "version": "15.1.0",
-      "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
-      "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "postcss-value-parser": "^4.0.0",
-        "read-cache": "^1.0.0",
-        "resolve": "^1.1.7"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      },
-      "peerDependencies": {
-        "postcss": "^8.0.0"
-      }
-    },
-    "node_modules/postcss-js": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
-      "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/postcss/"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "camelcase-css": "^2.0.1"
-      },
-      "engines": {
-        "node": "^12 || ^14 || >= 16"
-      },
-      "peerDependencies": {
-        "postcss": "^8.4.21"
-      }
-    },
-    "node_modules/postcss-load-config": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
-      "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/postcss/"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "lilconfig": "^3.0.0",
-        "yaml": "^2.3.4"
-      },
-      "engines": {
-        "node": ">= 14"
-      },
-      "peerDependencies": {
-        "postcss": ">=8.0.9",
-        "ts-node": ">=9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "postcss": {
-          "optional": true
-        },
-        "ts-node": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/postcss-nested": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
-      "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/postcss/"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "postcss-selector-parser": "^6.1.1"
-      },
-      "engines": {
-        "node": ">=12.0"
-      },
-      "peerDependencies": {
-        "postcss": "^8.2.14"
-      }
-    },
-    "node_modules/postcss-selector-parser": {
-      "version": "6.1.2",
-      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
-      "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "cssesc": "^3.0.0",
-        "util-deprecate": "^1.0.2"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/postcss-value-parser": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
-      "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/prelude-ls": {
       "version": "1.2.1",
@@ -3449,25 +2803,4 @@
         "node": ">=6"
       }
-    },
-    "node_modules/queue-microtask": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
-      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "license": "MIT"
     },
     "node_modules/react": {
@@ -3530,40 +2863,4 @@
       "engines": {
         "node": ">=0.10.0"
-      }
-    },
-    "node_modules/read-cache": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
-      "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "pify": "^2.3.0"
-      }
-    },
-    "node_modules/readdirp": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
-      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "picomatch": "^2.2.1"
-      },
-      "engines": {
-        "node": ">=8.10.0"
-      }
-    },
-    "node_modules/readdirp/node_modules/picomatch": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=8.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
@@ -3619,25 +2916,4 @@
       "license": "MIT"
     },
-    "node_modules/resolve": {
-      "version": "1.22.11",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
-      "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "is-core-module": "^2.16.1",
-        "path-parse": "^1.0.7",
-        "supports-preserve-symlinks-flag": "^1.0.0"
-      },
-      "bin": {
-        "resolve": "bin/resolve"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/resolve-from": {
       "version": "4.0.0",
@@ -3648,15 +2924,4 @@
       "engines": {
         "node": ">=4"
-      }
-    },
-    "node_modules/reusify": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
-      "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "iojs": ">=1.0.0",
-        "node": ">=0.10.0"
       }
     },
@@ -3703,28 +2968,4 @@
       }
     },
-    "node_modules/run-parallel": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
-      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "queue-microtask": "^1.2.2"
-      }
-    },
     "node_modules/scheduler": {
       "version": "0.27.0",
@@ -3789,27 +3030,4 @@
       }
     },
-    "node_modules/sucrase": {
-      "version": "3.35.1",
-      "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
-      "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@jridgewell/gen-mapping": "^0.3.2",
-        "commander": "^4.0.0",
-        "lines-and-columns": "^1.1.6",
-        "mz": "^2.7.0",
-        "pirates": "^4.0.1",
-        "tinyglobby": "^0.2.11",
-        "ts-interface-checker": "^0.1.9"
-      },
-      "bin": {
-        "sucrase": "bin/sucrase",
-        "sucrase-node": "bin/sucrase-node"
-      },
-      "engines": {
-        "node": ">=16 || 14 >=14.17"
-      }
-    },
     "node_modules/supports-color": {
       "version": "7.2.0",
@@ -3825,78 +3043,4 @@
       }
     },
-    "node_modules/supports-preserve-symlinks-flag": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
-      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/tailwindcss": {
-      "version": "3.4.17",
-      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
-      "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@alloc/quick-lru": "^5.2.0",
-        "arg": "^5.0.2",
-        "chokidar": "^3.6.0",
-        "didyoumean": "^1.2.2",
-        "dlv": "^1.1.3",
-        "fast-glob": "^3.3.2",
-        "glob-parent": "^6.0.2",
-        "is-glob": "^4.0.3",
-        "jiti": "^1.21.6",
-        "lilconfig": "^3.1.3",
-        "micromatch": "^4.0.8",
-        "normalize-path": "^3.0.0",
-        "object-hash": "^3.0.0",
-        "picocolors": "^1.1.1",
-        "postcss": "^8.4.47",
-        "postcss-import": "^15.1.0",
-        "postcss-js": "^4.0.1",
-        "postcss-load-config": "^4.0.2",
-        "postcss-nested": "^6.2.0",
-        "postcss-selector-parser": "^6.1.2",
-        "resolve": "^1.22.8",
-        "sucrase": "^3.35.0"
-      },
-      "bin": {
-        "tailwind": "lib/cli.js",
-        "tailwindcss": "lib/cli.js"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      }
-    },
-    "node_modules/thenify": {
-      "version": "3.3.1",
-      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
-      "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "any-promise": "^1.0.0"
-      }
-    },
-    "node_modules/thenify-all": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
-      "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "thenify": ">= 3.1.0 < 4"
-      },
-      "engines": {
-        "node": ">=0.8"
-      }
-    },
     "node_modules/tiny-invariant": {
       "version": "1.3.3",
@@ -3921,24 +3065,4 @@
         "url": "https://github.com/sponsors/SuperchupuDev"
       }
-    },
-    "node_modules/to-regex-range": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "is-number": "^7.0.0"
-      },
-      "engines": {
-        "node": ">=8.0"
-      }
-    },
-    "node_modules/ts-interface-checker": {
-      "version": "0.1.13",
-      "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
-      "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
-      "dev": true,
-      "license": "Apache-2.0"
     },
     "node_modules/type-check": {
@@ -4004,11 +3128,4 @@
         "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
       }
-    },
-    "node_modules/util-deprecate": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
-      "dev": true,
-      "license": "MIT"
     },
     "node_modules/victory-vendor": {
@@ -4142,20 +3259,4 @@
       "license": "ISC"
     },
-    "node_modules/yaml": {
-      "version": "2.8.2",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
-      "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "yaml": "bin.mjs"
-      },
-      "engines": {
-        "node": ">= 14.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/eemeli"
-      }
-    },
     "node_modules/yocto-queue": {
       "version": "0.1.0",
Index: lan-frontend/package.json
===================================================================
--- lan-frontend/package.json	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ lan-frontend/package.json	(revision 640ed89213ad0086dc8379de00b8d102057ead1f)
@@ -11,5 +11,4 @@
   },
   "dependencies": {
-    "@react-oauth/google": "^0.13.4",
     "chart.js": "^4.5.1",
     "react": "^19.2.0",
@@ -22,11 +21,8 @@
     "@types/react-dom": "^19.2.3",
     "@vitejs/plugin-react": "^5.1.1",
-    "autoprefixer": "^10.4.23",
     "eslint": "^9.39.1",
     "eslint-plugin-react-hooks": "^7.0.1",
     "eslint-plugin-react-refresh": "^0.4.24",
     "globals": "^16.5.0",
-    "postcss": "^8.5.6",
-    "tailwindcss": "^3.4.17",
     "vite": "^7.2.4"
   }
Index: lan-frontend/postcss.config.js
===================================================================
--- lan-frontend/postcss.config.js	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,6 +1,0 @@
-export default {
-  plugins: {
-    tailwindcss: {},
-    autoprefixer: {},
-  },
-}
Index: lan-frontend/src/App.jsx
===================================================================
--- lan-frontend/src/App.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ lan-frontend/src/App.jsx	(revision 640ed89213ad0086dc8379de00b8d102057ead1f)
@@ -1,336 +1,1169 @@
-import React, {useEffect, useMemo, useState} from "react";
-import "./styles/theme.css";
-import "./styles/netinel.css";
-
-import Login from "./components/Login";
-import TopBar from "./components/TopBar";
-import ComputerCard from "./components/ComputerCard";
-import ComputerDetailsModal from "./components/ComputerDetailsModal";
-import AIAssistantDrawer from "./components/AIAssistantDrawer";
-import NetIntelViz from "./components/NetIntelViz.jsx";
-import SideMenu from "./components/SideMenu";
-
-
-export default function App() {
-    const [stats, setStats] = useState(null);
-    const [computers, setComputers] = useState([]);
-    const [loading, setLoading] = useState(true);
-    const [lastUpdated, setLastUpdated] = useState(null);
-
-    const [query, setQuery] = useState("");
-    const [status, setStatus] = useState("");
-
-    const [selectedComputer, setSelectedComputer] = useState(null);
-    const [aiOpen, setAiOpen] = useState(false);
-    const [aiScope, setAiScope] = useState(null);
-    const [selectedEnv, setSelectedEnv] = useState("default");
-
-    const [me, setMe] = useState(null);
-    const [meLoading, setMeLoading] = useState(true);
-
-    const [activeView, setActiveView] = useState("dashboard");
-// "dashboard" | "visualizations" | "account"
-
-
-    // NEW: environment setting (switch)
-    const [saveProcHistory, setSaveProcHistory] = useState(false);
-
-    async function loadMe() {
-        setMeLoading(true);
-        try {
-            const r = await fetch("/api/me", {credentials: "include"});
-            if (r.ok) {
-                const data = await r.json();
-                setMe(data.user);
-            } else {
-                setMe(null);
-            }
-        } catch (e) {
-            console.error("loadMe error", e);
-            setMe(null);
-        } finally {
-            setMeLoading(false);
-        }
+import React, { useEffect, useState } from "react";
+import './App.css';
+
+function App() {
+  const [computers, setComputers] = useState([]);
+  const [filteredComputers, setFilteredComputers] = useState([]);
+  const [stats, setStats] = useState(null);
+  const [selectedComputer, setSelectedComputer] = useState(null);
+  const [computerDetails, setComputerDetails] = useState(null);
+  const [loadingDetails, setLoadingDetails] = useState(false);
+  const [performanceData, setPerformanceData] = useState([]);
+  const [eventDistribution, setEventDistribution] = useState([]);
+  const [networkTraffic, setNetworkTraffic] = useState([]);
+  const [topProcesses, setTopProcesses] = useState([]);
+   // Бои за графикони
+  const CHART_COLORS = {
+    primary: '#2563eb',
+    secondary: '#7c3aed',
+    accentGreen: '#10b981',
+    accentOrange: '#f59e0b',
+    accentRed: '#ef4444',
+    lightBlue: '#93c5fd',
+    lightPurple: '#c4b5fd'
+  };
+
+  // Функција за вчитување на податоци за графикони
+  const loadChartData = () => {
+    // Перформанс податоци (симулирани, треба да ги земеш од API)
+    const mockPerfData = Array.from({ length: 24 }, (_, i) => ({
+      hour: `${i}:00`,
+      cpu: 20 + Math.random() * 30,
+      memory: 30 + Math.random() * 40,
+      network: Math.random() * 100
+    }));
+    setPerformanceData(mockPerfData);
+
+    // Дистрибуција на настани
+    const mockEventDist = [
+      { name: 'Process Create', value: 45, color: CHART_COLORS.primary },
+      { name: 'Network', value: 25, color: CHART_COLORS.secondary },
+      { name: 'File Create', value: 15, color: CHART_COLORS.accentGreen },
+      { name: 'Registry', value: 10, color: CHART_COLORS.accentOrange },
+      { name: 'Other', value: 5, color: CHART_COLORS.accentRed }
+    ];
+    setEventDistribution(mockEventDist);
+
+    // Мрежен сообраќај
+    const mockNetworkData = Array.from({ length: 12 }, (_, i) => ({
+      time: `${i * 2}:00`,
+      inbound: 50 + Math.random() * 200,
+      outbound: 30 + Math.random() * 150
+    }));
+    setNetworkTraffic(mockNetworkData);
+
+    // Топ процеси
+    const mockTopProcesses = [
+      { name: 'chrome.exe', cpu: 25, memory: 1200 },
+      { name: 'python.exe', cpu: 18, memory: 800 },
+      { name: 'explorer.exe', cpu: 12, memory: 300 },
+      { name: 'svchost.exe', cpu: 8, memory: 200 },
+      { name: 'node.exe', cpu: 6, memory: 500 }
+    ];
+    setTopProcesses(mockTopProcesses);
+  };
+
+  useEffect(() => {
+    loadChartData();
+    // Автоматско освежување на графикони
+    const chartInterval = setInterval(loadChartData, 30000);
+    return () => clearInterval(chartInterval);
+  }, []);
+  // Filter state
+  const [filters, setFilters] = useState({
+    status: "all",
+    search: "",
+    os: "all",
+    minCpu: 0,
+    minRam: 0
+  });
+
+  // AI chat state
+  const [chatQuestion, setChatQuestion] = useState("");
+  const [chatAnswer, setChatAnswer] = useState("");
+  const [chatLoading, setChatLoading] = useState(false);
+  const [chatComputer, setChatComputer] = useState("");
+  const [chatHistory, setChatHistory] = useState([]);
+  const [isChatOpen, setIsChatOpen] = useState(true);
+  const [showWelcome, setShowWelcome] = useState(true);
+
+  // Modal tabs state
+  const [activeTab, setActiveTab] = useState('security');
+  const [sysmonData, setSysmonData] = useState(null);
+  const [networkData, setNetworkData] = useState(null);
+  const [loadingSysmon, setLoadingSysmon] = useState(false);
+  const [loadingNetwork, setLoadingNetwork] = useState(false);
+  const [selectedSysmonProcess, setSelectedSysmonProcess] = useState(null);
+  const [selectedNetworkItem, setSelectedNetworkItem] = useState(null);
+
+  // Load main data
+  useEffect(() => {
+    loadComputers();
+    loadStats();
+    const interval = setInterval(() => {
+      loadComputers();
+      loadStats();
+    }, 30000);
+    return () => clearInterval(interval);
+  }, []);
+
+  // Apply filters
+  useEffect(() => {
+    let result = computers;
+    if (filters.status !== "all") {
+      result = result.filter(c => c.status === filters.status);
     }
-
-    async function refresh() {
-        try {
-            setLoading(true);
-            const [s, c] = await Promise.all([
-                fetch("/api/stats", {credentials: "include"}).then((r) => r.json()),
-                fetch("/api/computers", {
-                    credentials: "include",
-                    headers: {"X-Env": selectedEnv},
-                }).then((r) => r.json()),
-            ]);
-
-            setStats(s);
-            setComputers(Array.isArray(c) ? c : []);
-            setLastUpdated(new Date().toISOString());
-        } catch (e) {
-            console.error("refresh error", e);
-        } finally {
-            setLoading(false);
-        }
+    if (filters.os !== "all") {
+      result = result.filter(c => c.os && c.os.toLowerCase().includes(filters.os.toLowerCase()));
     }
-
-    // NEW: load env settings (switch state) for selected env
-    async function loadEnvSettings(env) {
-        try {
-            // само admin ќе има пристап (ако не си admin, ќе падне во else и ќе стане false)
-            const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
-                credentials: "include",
+    if (filters.minCpu > 0) {
+      result = result.filter(c => (c.avg_cpu || 0) >= filters.minCpu);
+    }
+    if (filters.minRam > 0) {
+      result = result.filter(c => (c.avg_ram || 0) >= filters.minRam);
+    }
+    if (filters.search.trim()) {
+      const searchTerm = filters.search.toLowerCase();
+      result = result.filter(c =>
+        c.name.toLowerCase().includes(searchTerm) ||
+        (c.user && c.user.toLowerCase().includes(searchTerm)) ||
+        (c.ip && c.ip.includes(searchTerm))
+      );
+    }
+    setFilteredComputers(result);
+  }, [computers, filters]);
+
+  const loadComputers = () => {
+    fetch("/api/computers")
+      .then((r) => r.json())
+      .then(data => setComputers(data))
+      .catch((err) => console.error("API error:", err));
+  };
+
+  const loadStats = () => {
+    fetch("/api/stats")
+      .then((r) => r.json())
+      .then(setStats)
+      .catch((err) => console.error("API error:", err));
+  };
+
+  const openComputer = (name) => {
+    setSelectedComputer(name);
+    setLoadingDetails(true);
+    setComputerDetails(null);
+    setActiveTab('security');
+    setSysmonData(null);
+    setNetworkData(null);
+
+    fetch(`/api/computer/${encodeURIComponent(name)}`)
+      .then((r) => r.json())
+      .then((data) => {
+        setComputerDetails(data);
+        setLoadingDetails(false);
+      })
+      .catch((err) => {
+        console.error("API error:", err);
+        setLoadingDetails(false);
+      });
+  };
+
+  // Load Sysmon processes
+const loadSysmonProcesses = () => {
+  if (!selectedComputer) return;
+  setLoadingSysmon(true);
+  setSelectedSysmonProcess(null);
+
+  // КОРИГИРАЈ ГО ОВА URL
+  fetch(`/api/computer/${encodeURIComponent(selectedComputer)}/sysmon`)
+    .then((r) => {
+      if (!r.ok) throw new Error(`HTTP ${r.status}`);
+      return r.json();
+    })
+    .then((data) => {
+      console.log("Sysmon data received:", data);
+
+      // Ако data.processes е празна, пробај со детален endpoint
+      if (!data.processes || data.processes.length === 0) {
+        return fetch(`/api/computer/${encodeURIComponent(selectedComputer)}/detailed-sysmon`)
+          .then(r => r.json())
+          .then(detailedData => {
+            setSysmonData(detailedData);
+            setLoadingSysmon(false);
+          });
+      }
+
+      setSysmonData(data);
+      setLoadingSysmon(false);
+    })
+    .catch((err) => {
+      console.error("Sysmon API error:", err);
+
+      // Fallback - пробај да земеш Sysmon events од општиот endpoint
+      fetch(`/api/computer/${encodeURIComponent(selectedComputer)}`)
+        .then(r => r.json())
+        .then(fullData => {
+          if (fullData.sysmon_events) {
+            // Конвертирај Sysmon events во процес формат
+            const processes = fullData.sysmon_events.map((ev, idx) => ({
+              process_id: ev.event_id,
+              process_name: ev.event_type,
+              timestamp: ev.timestamp,
+              message: ev.message,
+              details: ev.details || {}
+            }));
+
+            setSysmonData({
+              computer_name: selectedComputer,
+              processes: processes,
+              count: processes.length
             });
-
-            if (r.ok) {
-                const data = await r.json();
-                setSaveProcHistory(!!data.save_process_history);
-            } else {
-                setSaveProcHistory(false);
-            }
-        } catch (e) {
-            console.error("loadEnvSettings error", e);
-            setSaveProcHistory(false);
-        }
+          }
+          setLoadingSysmon(false);
+        })
+        .catch(() => {
+          setLoadingSysmon(false);
+        });
+    });
+};
+  // Load Network connections
+const loadNetworkConnections = () => {
+  if (!selectedComputer) return;
+  setLoadingNetwork(true);
+  setSelectedNetworkItem(null);
+
+  // КОРИГИРАЈ ГО ОВА URL
+  fetch(`/api/computer/${encodeURIComponent(selectedComputer)}/network`)
+    .then((r) => {
+      if (!r.ok) throw new Error(`HTTP ${r.status}`);
+      return r.json();
+    })
+    .then((data) => {
+      console.log("Network data received:", data);
+
+      setNetworkData(data);
+      setLoadingNetwork(false);
+    })
+    .catch((err) => {
+      console.error("Network API error:", err);
+
+      // Fallback - пробај да земеш network од општиот endpoint
+      fetch(`/api/computer/${encodeURIComponent(selectedComputer)}`)
+        .then(r => r.json())
+        .then(fullData => {
+          if (fullData.network_connections) {
+            const connections = fullData.network_connections.map((conn, idx) => ({
+              pid: conn.pid,
+              process_name: conn.process_name,
+              local_address: conn.local_address,
+              remote_address: conn.remote_address,
+              status: conn.status,
+              timestamp: conn.timestamp,
+              // Додади ги овие полиња за да match-не со очекуваниот формат
+              local_port: conn.local_address?.split(':')?.[1] || '',
+              remote_port: conn.remote_address?.split(':')?.[1] || '',
+              protocol: 'TCP'
+            }));
+
+            setNetworkData({
+              computer_name: selectedComputer,
+              connections: connections,
+              count: connections.length
+            });
+          }
+          setLoadingNetwork(false);
+        })
+        .catch(() => {
+          setLoadingNetwork(false);
+        });
+    });
+};
+  const formatTime = (iso) => {
+    if (!iso) return "-";
+    const d = new Date(iso);
+    return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
+  };
+
+  const formatDate = (iso) => {
+    if (!iso) return "-";
+    const d = new Date(iso);
+    return d.toLocaleDateString();
+  };
+
+  const askAssistant = async () => {
+    if (!chatQuestion.trim()) return;
+    const question = chatQuestion;
+    setChatLoading(true);
+    setChatQuestion("");
+    setShowWelcome(false);
+
+    const newHistory = [...chatHistory, {
+      type: "question",
+      content: question,
+      computer: chatComputer,
+      timestamp: new Date().toISOString()
+    }];
+    setChatHistory(newHistory);
+
+    try {
+      const res = await fetch("/api/chat", {
+        method: "POST",
+        headers: { "Content-Type": "application/json" },
+        body: JSON.stringify({
+          question: question,
+          computer_name: chatComputer || null,
+        }),
+      });
+
+      const answer = await res.json();
+      const finalAnswer = answer.answer || answer.error || "I couldn't process that request.";
+
+      setChatHistory([...newHistory, {
+        type: "answer",
+        content: finalAnswer,
+        timestamp: new Date().toISOString()
+      }]);
+    } catch (err) {
+      setChatHistory([...newHistory, {
+        type: "answer",
+        content: "⚠️ Connection error. Please check the server.",
+        timestamp: new Date().toISOString()
+      }]);
+    } finally {
+      setChatLoading(false);
     }
-
-    // NEW: set env setting (toggle)
-    async function setEnvProcessHistory(env, enabled) {
-        // optimistic UI
-        setSaveProcHistory(enabled);
-
-        try {
-            const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
-                method: "POST",
-                credentials: "include",
-                headers: {"Content-Type": "application/json"},
-                body: JSON.stringify({save_process_history: enabled}),
-            });
-
-            if (r.ok) {
-                const data = await r.json();
-                setSaveProcHistory(!!data.save_process_history);
-            } else {
-                // rollback
-                setSaveProcHistory(!enabled);
-                console.error("setEnvProcessHistory failed", await r.text());
-            }
-        } catch (e) {
-            setSaveProcHistory(!enabled);
-            console.error("setEnvProcessHistory error", e);
-        }
-    }
-
-    // on mount: check session
-    useEffect(() => {
-        loadMe();
-    }, []);
-
-    // when logged in OR env changed, refresh data + load env switch state
-    useEffect(() => {
-        if (!me) return;
-        loadEnvSettings(selectedEnv);
-        refresh();
-        // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [me, selectedEnv]);
-
-    const filtered = useMemo(() => {
-        const q = query.trim().toLowerCase();
-        return (computers || [])
-            .filter((c) => (status ? c.status === status : true))
-            .filter((c) => {
-                if (!q) return true;
-                const hay = [c.name, c.user, c.ip, c.os, c.status]
-                    .filter(Boolean)
-                    .join(" ")
-                    .toLowerCase();
-                return hay.includes(q);
-            });
-    }, [computers, query, status]);
-
-    // Gate UI
-    if (meLoading) return <div style={{padding: 20}}>Loading…</div>;
-    if (!me) return <Login onDone={loadMe}/>;
-
-    return (
-        <div className="app-shell">
-            <div className="layout">
-                <SideMenu activeView={activeView} setActiveView={setActiveView}/>
-                <main className="content">
-                    {/* TopBar + pages */}
-                </main>
-            </div>
-
-            <div className="cont">
-                <TopBar
-                    onOpenAI={() => setAiOpen(true)}
-                    onRefresh={refresh}
-                    lastUpdated={lastUpdated}
-                    onEnvChange={(env) => setSelectedEnv(env)}
-                    selectedEnv={selectedEnv}
-                    me={me}
-                    onLoggedOut={loadMe}
-                    saveProcHistory={saveProcHistory}
-                    onToggleProcHistory={(enabled) => setEnvProcessHistory(selectedEnv, enabled)}
+  };
+
+  const resetFilters = () => {
+    setFilters({
+      status: "all",
+      search: "",
+      os: "all",
+      minCpu: 0,
+      minRam: 0
+    });
+  };
+
+  const clearChat = () => {
+    setChatHistory([]);
+    setChatAnswer("");
+    setShowWelcome(true);
+  };
+
+  const closeModal = () => {
+    setSelectedComputer(null);
+    setComputerDetails(null);
+    setSysmonData(null);
+    setNetworkData(null);
+    setSelectedSysmonProcess(null);
+    setSelectedNetworkItem(null);
+  };
+
+  const quickQuestions = [
+    "Show security alerts from yesterday",
+    "Which devices have high CPU usage?",
+    "List suspicious network connections",
+    "Show recent Sysmon events",
+    "Display offline devices"
+  ];
+
+  return (
+    <div className="app-container">
+      {/* Main Content */}
+      <main className="main-content">
+        {/* Top Navigation */}
+        <nav className="top-nav">
+          <div className="nav-left">
+            <div className="logo">
+              <div className="logo-icon">🛡️</div>
+              <div className="logo-text">
+                <h1>LAN Sentinel</h1>
+                <span className="logo-subtitle">Enterprise Security Dashboard</span>
+              </div>
+            </div>
+          </div>
+
+          <div className="nav-center">
+            <div className="search-bar">
+              <input
+                type="text"
+                placeholder="Search devices..."
+                value={filters.search}
+                onChange={(e) => setFilters({...filters, search: e.target.value})}
+              />
+              <span className="search-icon">🔍</span>
+            </div>
+          </div>
+
+          <div className="nav-right">
+            <button
+              className={`ai-toggle-btn ${isChatOpen ? 'active' : ''}`}
+              onClick={() => setIsChatOpen(!isChatOpen)}
+            >
+              <span className="ai-icon">🤖</span>
+              <span>AI Assistant</span>
+            </button>
+            <button className="refresh-btn" onClick={loadComputers}>
+              <span className="refresh-icon">↻</span>
+              Refresh
+            </button>
+            <div className="user-profile">
+              <div className="profile-avatar">AD</div>
+            </div>
+          </div>
+        </nav>
+
+        {/* Stats Overview */}
+        <section className="stats-overview">
+          <div className="stats-header">
+            <h2>Network Overview</h2>
+            <div className="stats-time">
+              Last updated: {new Date().toLocaleTimeString()}
+            </div>
+          </div>
+
+          <div className="stats-grid">
+            <div className="stat-card">
+              <div className="stat-main">
+                <span className="stat-number">{computers.length}</span>
+                <span className="stat-label">Total Devices</span>
+              </div>
+              <div className="stat-icon">🖥️</div>
+            </div>
+
+            <div className="stat-card online">
+              <div className="stat-main">
+                <span className="stat-number">{computers.filter(c => c.status === 'online').length}</span>
+                <span className="stat-label">Online</span>
+              </div>
+              <div className="stat-trend">
+                <span className="trend-up">↑</span>
+                <span>{computers.length > 0 ? Math.round((computers.filter(c => c.status === 'online').length/computers.length)*100) : 0}%</span>
+              </div>
+            </div>
+
+            <div className="stat-card idle">
+              <div className="stat-main">
+                <span className="stat-number">{computers.filter(c => c.status === 'idle').length}</span>
+                <span className="stat-label">Idle</span>
+              </div>
+              <div className="stat-icon">⏸️</div>
+            </div>
+
+            <div className="stat-card offline">
+              <div className="stat-main">
+                <span className="stat-number">{computers.filter(c => c.status === 'offline').length}</span>
+                <span className="stat-label">Offline</span>
+              </div>
+              <div className="stat-icon">🔴</div>
+            </div>
+
+            <div className="stat-card security">
+              <div className="stat-main">
+                <span className="stat-number">
+                  {stats ? stats.total_sysmon_events.toLocaleString() : "0"}
+                </span>
+                <span className="stat-label">Sysmon Events</span>
+              </div>
+              <div className="stat-badge">24h</div>
+            </div>
+
+            <div className="stat-card alerts">
+              <div className="stat-main">
+                <span className="stat-number">
+                  {stats ? stats.security_alerts || 0 : "0"}
+                </span>
+                <span className="stat-label">Alerts</span>
+              </div>
+              <div className="stat-icon">🚨</div>
+            </div>
+          </div>
+        </section>
+
+        {/* Main Dashboard */}
+        <div className="dashboard-grid">
+          {/* Left Sidebar - Filters */}
+          <aside className="filter-sidebar">
+            <div className="filter-section">
+              <h3>Filters</h3>
+              <div className="filter-group">
+                <label>Status</label>
+                <div className="filter-tags">
+                  {["all", "online", "idle", "offline"].map(status => (
+                    <button
+                      key={status}
+                      className={`filter-tag ${filters.status === status ? 'active' : ''}`}
+                      onClick={() => setFilters({...filters, status})}
+                    >
+                      {status === 'all' ? 'All' : status}
+                    </button>
+                  ))}
+                </div>
+              </div>
+
+              <div className="filter-group">
+                <label>Operating System</label>
+                <select
+                  value={filters.os}
+                  onChange={(e) => setFilters({...filters, os: e.target.value})}
+                  className="filter-select"
+                >
+                  <option value="all">All OS</option>
+                  {[...new Set(computers.map(c => c.os).filter(Boolean))].map(os => (
+                    <option key={os} value={os}>{os}</option>
+                  ))}
+                </select>
+              </div>
+
+              <div className="filter-group">
+                <label>CPU Usage ≥ {filters.minCpu}%</label>
+                <input
+                  type="range"
+                  min="0"
+                  max="100"
+                  value={filters.minCpu}
+                  onChange={(e) => setFilters({...filters, minCpu: parseInt(e.target.value)})}
+                  className="range-slider"
                 />
-
-                <div className="app-main">
-                    {/* ====== VIEW SWITCH ====== */}
-                    {activeView === "dashboard" && (
-                        <>
-                            {/* STATS */}
-                            <div className="stats-grid">
-                                <div className="stat-card">
-                                    <div className="label">Total Computers</div>
-                                    <div className="value">{computers.length}</div>
-                                    <div className="sub">Monitored Systems</div>
-                                </div>
-
-                                <div className="stat-card">
-                                    <div className="label">Sysmon Events (24h)</div>
-                                    <div className="value">{stats?.total_sysmon_events ?? "—"}</div>
-                                    <div className="sub">Security Events</div>
-                                </div>
-
-                                <div className="stat-card">
-                                    <div className="label">Network Connections (24h)</div>
-                                    <div className="value">{stats?.total_network_connections ?? "—"}</div>
-                                    <div className="sub">Captured Connections</div>
-                                </div>
+              </div>
+
+              <div className="filter-group">
+                <label>RAM Usage ≥ {filters.minRam}%</label>
+                <input
+                  type="range"
+                  min="0"
+                  max="100"
+                  value={filters.minRam}
+                  onChange={(e) => setFilters({...filters, minRam: parseInt(e.target.value)})}
+                  className="range-slider"
+                />
+              </div>
+
+              <button onClick={resetFilters} className="reset-filters-btn">
+                Reset Filters
+              </button>
+            </div>
+
+            <div className="filter-section">
+              <h3>Quick Actions</h3>
+              <button className="action-btn" onClick={loadComputers}>
+                <span className="action-icon">↻</span>
+                Refresh Data
+              </button>
+              <button className="action-btn" onClick={() => setIsChatOpen(true)}>
+                <span className="action-icon">🤖</span>
+                Open AI Assistant
+              </button>
+            </div>
+          </aside>
+
+          {/* Main Content Area */}
+          <div className="content-area">
+            <div className="content-header">
+              <div>
+                <h2>Network Devices</h2>
+                <p className="content-subtitle">{filteredComputers.length} devices found</p>
+              </div>
+              <div className="header-actions">
+                <select className="sort-select">
+                  <option>Last Seen</option>
+                  <option>Name</option>
+                  <option>CPU Usage</option>
+                  <option>Status</option>
+                </select>
+              </div>
+            </div>
+
+            {/* Devices Grid */}
+            <div className="devices-grid">
+              {filteredComputers.map((c) => (
+                <div
+                  key={c.name}
+                  className={`device-card ${c.status}`}
+                  onClick={() => openComputer(c.name)}
+                >
+                  <div className="device-header">
+                    <div className="device-title">
+                      <div className="device-avatar">
+                        {c.os?.includes("Windows") ? "🪟" :
+                         c.os?.includes("Linux") ? "🐧" :
+                         c.os?.includes("Mac") ? "🍎" : "💻"}
+                      </div>
+                      <div>
+                        <h3>{c.name}</h3>
+                        <p className="device-user">{c.user || "Unknown"}</p>
+                      </div>
+                    </div>
+                    <div className={`status-indicator ${c.status}`}>
+                      <div className="status-dot"></div>
+                      {c.status}
+                    </div>
+                  </div>
+
+                  <div className="device-info">
+                    <div className="info-row">
+                      <span className="info-label">IP</span>
+                      <span className="info-value">{c.ip}</span>
+                    </div>
+                    <div className="info-row">
+                      <span className="info-label">OS</span>
+                      <span className="info-value">{c.os}</span>
+                    </div>
+                    <div className="info-row">
+                      <span className="info-label">Last Active</span>
+                      <span className="info-value">{formatTime(c.last_seen)}</span>
+                    </div>
+                  </div>
+
+                  <div className="device-metrics">
+                    <div className="metric">
+                      <div className="metric-header">
+                        <span>CPU</span>
+                        <span className="metric-value">{c.avg_cpu || 0}%</span>
+                      </div>
+                      <div className="progress-bar">
+                        <div
+                          className="progress-fill cpu"
+                          style={{ width: `${Math.min(c.avg_cpu || 0, 100)}%` }}
+                        ></div>
+                      </div>
+                    </div>
+                    <div className="metric">
+                      <div className="metric-header">
+                        <span>Memory</span>
+                        <span className="metric-value">{c.avg_ram || 0}%</span>
+                      </div>
+                      <div className="progress-bar">
+                        <div
+                          className="progress-fill memory"
+                          style={{ width: `${Math.min(c.avg_ram || 0, 100)}%` }}
+                        ></div>
+                      </div>
+                    </div>
+                  </div>
+
+                  <div className="device-footer">
+                    {c.sysmon_available && (
+                      <div className="sysmon-badge">
+                        <span className="sysmon-icon">🛡️</span>
+                        {c.recent_sysmon || 0} events
+                      </div>
+                    )}
+                  </div>
+                </div>
+              ))}
+
+              {filteredComputers.length === 0 && (
+                <div className="empty-state">
+                  <div className="empty-icon">🔍</div>
+                  <h3>No devices match your filters</h3>
+                  <button onClick={resetFilters} className="empty-action-btn">
+                    Reset All Filters
+                  </button>
+                </div>
+              )}
+            </div>
+          </div>
+        </div>
+      </main>
+
+      {/* AI Assistant Sidebar */}
+      <div className={`ai-sidebar ${isChatOpen ? 'open' : ''}`}>
+        <div className="ai-header">
+          <div className="ai-title">
+            <div className="ai-avatar">🤖</div>
+            <div>
+              <h3>Security AI Assistant</h3>
+              <p className="ai-subtitle">Powered by LAN Sentinel</p>
+            </div>
+          </div>
+          <button className="close-ai" onClick={() => setIsChatOpen(false)}>
+            ✕
+          </button>
+        </div>
+
+        <div className="ai-content">
+          <div className="chat-container">
+            <div className="chat-history">
+              {showWelcome && (
+                <div className="welcome-message">
+                  <div className="welcome-avatar">🤖</div>
+                  <div className="welcome-content">
+                    <h4>Welcome to Security AI Assistant!</h4>
+                    <p>I can help you analyze security events, monitor network activity, and investigate incidents.</p>
+                    <div className="welcome-questions">
+                      {quickQuestions.map((q, idx) => (
+                        <button
+                          key={idx}
+                          className="quick-question"
+                          onClick={() => {
+                            setChatQuestion(q);
+                            setTimeout(() => askAssistant(), 100);
+                          }}
+                        >
+                          {q}
+                        </button>
+                      ))}
+                    </div>
+                  </div>
+                </div>
+              )}
+
+              {chatHistory.map((msg, idx) => (
+                <div key={idx} className={`chat-message ${msg.type}`}>
+                  <div className="message-avatar">
+                    {msg.type === 'question' ? '👤' : '🤖'}
+                  </div>
+                  <div className="message-content">
+                    <div className="message-header">
+                      <span className="message-sender">
+                        {msg.type === 'question' ? 'You' : 'Security AI'}
+                      </span>
+                      {msg.computer && (
+                        <span className="message-context">
+                          Computer: {msg.computer}
+                        </span>
+                      )}
+                    </div>
+                    <div className="message-text">
+                      {msg.content}
+                    </div>
+                    <div className="message-time">
+                      {new Date(msg.timestamp).toLocaleTimeString([], {
+                        hour: '2-digit',
+                        minute: '2-digit'
+                      })}
+                    </div>
+                  </div>
+                </div>
+              ))}
+
+              {chatLoading && (
+                <div className="chat-message answer">
+                  <div className="message-avatar">🤖</div>
+                  <div className="message-content">
+                    <div className="typing-indicator">
+                      <div></div>
+                      <div></div>
+                      <div></div>
+                    </div>
+                  </div>
+                </div>
+              )}
+            </div>
+
+            <div className="chat-input-area">
+              <select
+                value={chatComputer}
+                onChange={(e) => setChatComputer(e.target.value)}
+                className="chat-select"
+              >
+                <option value="">🌐 All Network</option>
+                {computers.map((c) => (
+                  <option key={c.name} value={c.name}>
+                    {c.name}
+                  </option>
+                ))}
+              </select>
+
+              <div className="input-wrapper">
+                <input
+                  type="text"
+                  placeholder="Ask about security events..."
+                  value={chatQuestion}
+                  onChange={(e) => setChatQuestion(e.target.value)}
+                  onKeyDown={(e) => e.key === 'Enter' && askAssistant()}
+                  className="chat-input"
+                />
+                <button
+                  onClick={askAssistant}
+                  disabled={chatLoading || !chatQuestion.trim()}
+                  className="send-btn"
+                >
+                  {chatLoading ? '...' : '➤'}
+                </button>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      {/* Enhanced Computer Details Modal */}
+      {selectedComputer && (
+        <div className="modal-overlay" onClick={closeModal}>
+          <div className="modal large" onClick={(e) => e.stopPropagation()}>
+            <div className="modal-header">
+              <div className="modal-title">
+                <h2>{selectedComputer}</h2>
+                <div className="modal-subtitle">
+                  <span className="ip-badge">{computerDetails?.computer?.ip}</span>
+                  <span className="os-badge">{computerDetails?.computer?.os}</span>
+                  <span className={`status-badge ${computerDetails?.computer?.status}`}>
+                    {computerDetails?.computer?.status}
+                  </span>
+                </div>
+              </div>
+              <div className="modal-actions">
+                <button className="modal-btn">
+                  <span className="btn-icon">📊</span>
+                  Report
+                </button>
+                <button className="modal-close" onClick={closeModal}>
+                  ✕
+                </button>
+              </div>
+            </div>
+
+            <div className="modal-content">
+              {loadingDetails ? (
+                <div className="loading-state">
+                  <div className="loader"></div>
+                  <p>Loading device details...</p>
+                </div>
+              ) : (
+                <>
+                  {/* Quick Stats */}
+                  <div className="quick-stats">
+                    <div className="quick-stat">
+                      <div className="stat-icon">⚡</div>
+                      <div className="stat-info">
+                        <div className="stat-value">
+                          {computerDetails?.current_metrics?.cpu_usage || 0}%
+                        </div>
+                        <div className="stat-label">CPU</div>
+                      </div>
+                    </div>
+                    <div className="quick-stat">
+                      <div className="stat-icon">💾</div>
+                      <div className="stat-info">
+                        <div className="stat-value">
+                          {computerDetails?.current_metrics?.ram_usage || 0}%
+                        </div>
+                        <div className="stat-label">RAM</div>
+                      </div>
+                    </div>
+                    <div className="quick-stat">
+                      <div className="stat-icon">📡</div>
+                      <div className="stat-info">
+                        <div className="stat-value">
+                          {computerDetails?.current_metrics?.network_usage || 0}%
+                        </div>
+                        <div className="stat-label">Network</div>
+                      </div>
+                    </div>
+                    <div className="quick-stat">
+                      <div className="stat-icon">🛡️</div>
+                      <div className="stat-info">
+                        <div className="stat-value">
+                          {computerDetails?.recent_sysmon || 0}
+                        </div>
+                        <div className="stat-label">Sysmon Events</div>
+                      </div>
+                    </div>
+                  </div>
+
+                  {/* Tabs */}
+                  <div className="modal-tabs">
+                    <div className="tabs-header">
+                      <button
+                        className={`tab ${activeTab === 'security' ? 'active' : ''}`}
+                        onClick={() => setActiveTab('security')}
+                      >
+                        Security Events
+                      </button>
+                      <button
+                        className={`tab ${activeTab === 'sysmon' ? 'active' : ''}`}
+                        onClick={() => {
+                          setActiveTab('sysmon');
+                          if (!sysmonData) loadSysmonProcesses();
+                        }}
+                      >
+                        Sysmon Processes
+                      </button>
+                      <button
+                        className={`tab ${activeTab === 'network' ? 'active' : ''}`}
+                        onClick={() => {
+                          setActiveTab('network');
+                          if (!networkData) loadNetworkConnections();
+                        }}
+                      >
+                        Network Connections
+                      </button>
+                      <button
+                        className={`tab ${activeTab === 'details' ? 'active' : ''}`}
+                        onClick={() => setActiveTab('details')}
+                      >
+                        System Details
+                      </button>
+                    </div>
+
+                    <div className="tabs-content">
+                      {/* Security Events Tab */}
+                      {activeTab === 'security' && (
+                        <div className="tab-pane active">
+                          <div className="data-table-container">
+                            <table className="data-table">
+                              <thead>
+                                <tr>
+                                  <th>Time</th>
+                                  <th>Event ID</th>
+                                  <th>Type</th>
+                                  <th>Description</th>
+                                </tr>
+                              </thead>
+                              <tbody>
+                                {(computerDetails?.sysmon_events || []).map((ev, idx) => (
+                                  <tr key={idx}>
+                                    <td>{formatTime(ev.timestamp)}</td>
+                                    <td>
+                                      <span className="event-id">#{ev.event_id}</span>
+                                    </td>
+                                    <td>
+                                      <span className={`event-type ${ev.event_type?.toLowerCase()}`}>
+                                        {ev.event_type}
+                                      </span>
+                                    </td>
+                                    <td className="event-desc">
+                                      {ev.message?.slice(0, 100) || 'No description'}
+                                    </td>
+                                  </tr>
+                                ))}
+                              </tbody>
+                            </table>
+                          </div>
+                        </div>
+                      )}
+
+                      {/* Sysmon Processes Tab */}
+                      {activeTab === 'sysmon' && (
+                        <div className="tab-pane active">
+                          <div className="pane-header">
+                            <h3>Sysmon Processes</h3>
+                            <button
+                              onClick={loadSysmonProcesses}
+                              className="refresh-btn-small"
+                              disabled={loadingSysmon}
+                            >
+                              {loadingSysmon ? 'Loading...' : '↻ Refresh'}
+                            </button>
+                          </div>
+
+                          {loadingSysmon ? (
+                            <div className="loading-state">
+                              <div className="loader"></div>
+                              <p>Loading Sysmon processes...</p>
                             </div>
-
-                            {/* FILTERS */}
-                            <div className="panel" style={{marginBottom: 14}}>
-                                <div className="panel-head">
-                                    <div className="panel-title">Filters</div>
-                                    <div style={{color: "var(--dim)", fontSize: 12}}>
-                                        {loading ? "Refreshing…" : `${filtered.length} shown`}
+                          ) : sysmonData ? (
+                            <div className="data-grid">
+                              <div className="grid-container">
+                                {sysmonData.processes && sysmonData.processes.length > 0 ? (
+                                  <>
+                                    <div className="grid-sidebar">
+                                      <div className="process-list">
+                                        {sysmonData.processes.map((proc, idx) => (
+                                          <div
+                                            key={idx}
+                                            className={`process-item ${selectedSysmonProcess === idx ? 'active' : ''}`}
+                                            onClick={() => setSelectedSysmonProcess(idx)}
+                                          >
+                                            <div className="process-name">
+                                              <strong>{proc.process_name || 'Unknown'}</strong>
+                                            </div>
+                                            <div className="process-pid">
+                                              PID: {proc.process_id}
+                                            </div>
+                                            <div className="process-time">
+                                              {formatTime(proc.timestamp)}
+                                            </div>
+                                          </div>
+                                        ))}
+                                      </div>
                                     </div>
-                                </div>
-                                <div className="panel-body">
-                                    <div className="filters">
-                                        <input
-                                            className="input"
-                                            value={query}
-                                            onChange={(e) => setQuery(e.target.value)}
-                                            placeholder="Search by name / user / ip / os…"
-                                        />
-
-                                        <select
-                                            className="select"
-                                            value={status}
-                                            onChange={(e) => setStatus(e.target.value)}
-                                        >
-                                            <option value="">All status</option>
-                                            <option value="online">Online</option>
-                                            <option value="idle">Idle</option>
-                                            <option value="offline">Offline</option>
-                                        </select>
-
-                                        <button className="btn primary" onClick={refresh}>
-                                            Refresh
-                                        </button>
+                                    <div className="grid-content">
+                                      {selectedSysmonProcess !== null ? (
+                                        <div className="json-viewer">
+                                          <div className="json-header">
+                                            <h4>Process Details (JSON)</h4>
+                                            <button
+                                              className="copy-json"
+                                              onClick={() => navigator.clipboard.writeText(
+                                                JSON.stringify(sysmonData.processes[selectedSysmonProcess], null, 2)
+                                              )}
+                                            >
+                                              📋 Copy JSON
+                                            </button>
+                                          </div>
+                                          <pre className="json-content">
+                                            {JSON.stringify(sysmonData.processes[selectedSysmonProcess], null, 2)}
+                                          </pre>
+                                        </div>
+                                      ) : (
+                                        <div className="select-prompt">
+                                          <div className="prompt-icon">👈</div>
+                                          <p>Select a process from the list to view its JSON data</p>
+                                        </div>
+                                      )}
                                     </div>
-                                </div>
+                                  </>
+                                ) : (
+                                  <div className="empty-data">
+                                    <div className="empty-icon">📄</div>
+                                    <p>No Sysmon processes found</p>
+                                  </div>
+                                )}
+                              </div>
                             </div>
-
-                            {/* COMPUTERS */}
-                            <div className="panel">
-                                <div className="panel-head">
-                                    <div className="panel-title">Connected Computers</div>
-                                    <div style={{display: "flex", gap: 10, alignItems: "center"}}>
-                                        <span className="pill">{filtered.length} computers</span>
-                                        <button
-                                            className="btn"
-                                            onClick={() => {
-                                                setQuery("");
-                                                setStatus("");
-                                            }}
-                                        >
-                                            Reset
-                                        </button>
+                          ) : (
+                            <div className="empty-data">
+                              <p>Click refresh to load Sysmon processes</p>
+                              <button onClick={loadSysmonProcesses} className="load-btn">
+                                Load Sysmon Data
+                              </button>
+                            </div>
+                          )}
+                        </div>
+                      )}
+
+                      {/* Network Connections Tab */}
+                      {activeTab === 'network' && (
+                        <div className="tab-pane active">
+                          <div className="pane-header">
+                            <h3>Network Connections</h3>
+                            <button
+                              onClick={loadNetworkConnections}
+                              className="refresh-btn-small"
+                              disabled={loadingNetwork}
+                            >
+                              {loadingNetwork ? 'Loading...' : '↻ Refresh'}
+                            </button>
+                          </div>
+
+                          {loadingNetwork ? (
+                            <div className="loading-state">
+                              <div className="loader"></div>
+                              <p>Loading network connections...</p>
+                            </div>
+                          ) : networkData ? (
+                            <div className="data-grid">
+                              <div className="grid-container">
+                                {networkData.connections && networkData.connections.length > 0 ? (
+                                  <>
+                                    <div className="grid-sidebar">
+                                      <div className="network-list">
+                                        {networkData.connections.map((conn, idx) => (
+                                          <div
+                                            key={idx}
+                                            className={`network-item ${selectedNetworkItem === idx ? 'active' : ''}`}
+                                            onClick={() => setSelectedNetworkItem(idx)}
+                                          >
+                                            <div className="network-address">
+                                              <strong>{conn.local_address}:{conn.local_port}</strong>
+                                              <div className="arrow">→</div>
+                                              <strong>{conn.remote_address}:{conn.remote_port}</strong>
+                                            </div>
+                                            <div className="network-protocol">
+                                              {conn.protocol} • {conn.state}
+                                            </div>
+                                          </div>
+                                        ))}
+                                      </div>
                                     </div>
-                                </div>
-                                <div className="panel-body">
-                                    <div className="computers-grid">
-                                        {filtered.map((c) => (
-                                            <ComputerCard
-                                                key={c.name}
-                                                c={c}
-                                                onOpen={(pc) => {
-                                                    setSelectedComputer(pc);
-                                                    setAiScope(pc.name);
-                                                }}
-                                            />
-                                        ))}
-                                        {filtered.length === 0 && (
-                                            <div style={{color: "var(--dim)"}}>
-                                                No computers match filter.
-                                            </div>
-                                        )}
+                                    <div className="grid-content">
+                                      {selectedNetworkItem !== null ? (
+                                        <div className="json-viewer">
+                                          <div className="json-header">
+                                            <h4>Connection Details (JSON)</h4>
+                                            <button
+                                              className="copy-json"
+                                              onClick={() => navigator.clipboard.writeText(
+                                                JSON.stringify(networkData.connections[selectedNetworkItem], null, 2)
+                                              )}
+                                            >
+                                              📋 Copy JSON
+                                            </button>
+                                          </div>
+                                          <pre className="json-content">
+                                            {JSON.stringify(networkData.connections[selectedNetworkItem], null, 2)}
+                                          </pre>
+                                        </div>
+                                      ) : (
+                                        <div className="select-prompt">
+                                          <div className="prompt-icon">👈</div>
+                                          <p>Select a connection to view its JSON data</p>
+                                        </div>
+                                      )}
                                     </div>
-                                </div>
+                                  </>
+                                ) : (
+                                  <div className="empty-data">
+                                    <div className="empty-icon">🌐</div>
+                                    <p>No network connections found</p>
+                                  </div>
+                                )}
+                              </div>
                             </div>
-                        </>
-                    )}
-
-                    {activeView === "visualizations" && (
-                        <>
-                            <div className="panel" style={{marginBottom: 14}}>
-                                <div className="panel-head">
-                                    <div className="panel-title">Visualizations</div>
-                                    <div style={{color: "var(--dim)", fontSize: 12}}>
-                                        Network graphs / charts / tables
-                                    </div>
-                                </div>
-                                <div className="panel-body">
-                                    <NetIntelViz baseUrl="http://localhost:5555"/>
-                                </div>
+                          ) : (
+                            <div className="empty-data">
+                              <p>Click refresh to load network connections</p>
+                              <button onClick={loadNetworkConnections} className="load-btn">
+                                Load Network Data
+                              </button>
                             </div>
-                        </>
-                    )}
-
-                    {activeView === "account" && (
-                        <div className="panel">
-                            <div className="panel-head">
-                                <div className="panel-title">Account</div>
-                                <div style={{color: "var(--dim)", fontSize: 12}}>
-                                    Profile info
-                                </div>
+                          )}
+                        </div>
+                      )}
+
+                      {/* System Details Tab */}
+                      {activeTab === 'details' && computerDetails && (
+                        <div className="tab-pane active">
+                          <div className="details-grid">
+                            <div className="detail-group">
+                              <h4>System Information</h4>
+                              <div className="detail-item">
+                                <span className="detail-label">Hostname:</span>
+                                <span className="detail-value">{computerDetails.computer.name}</span>
+                              </div>
+                              <div className="detail-item">
+                                <span className="detail-label">IP Address:</span>
+                                <span className="detail-value">{computerDetails.computer.ip}</span>
+                              </div>
+                              <div className="detail-item">
+                                <span className="detail-label">OS Version:</span>
+                                <span className="detail-value">{computerDetails.computer.os}</span>
+                              </div>
+                              <div className="detail-item">
+                                <span className="detail-label">Last Seen:</span>
+                                <span className="detail-value">{formatTime(computerDetails.computer.last_seen)}</span>
+                              </div>
                             </div>
-                            <div className="panel-body">
-                                <div style={{display: "grid", gap: 10}}>
-                                    <div className="pill">Email: {me.email}</div>
-                                    <div className="pill">Role: {me.role}</div>
-                                    <div className="pill">Tenant ID: {me.tenant_id}</div>
-                                    <div className="pill">Name: {me.name || "—"}</div>
-                                </div>
+
+                            <div className="detail-group">
+                              <h4>Current Metrics</h4>
+                              <div className="detail-item">
+                                <span className="detail-label">CPU Usage:</span>
+                                <span className="detail-value">{computerDetails.current_metrics?.cpu_usage || 0}%</span>
+                              </div>
+                              <div className="detail-item">
+                                <span className="detail-label">Memory Usage:</span>
+                                <span className="detail-value">{computerDetails.current_metrics?.ram_usage || 0}%</span>
+                              </div>
+                              <div className="detail-item">
+                                <span className="detail-label">Disk Usage:</span>
+                                <span className="detail-value">{computerDetails.current_metrics?.disk_usage || 0}%</span>
+                              </div>
                             </div>
+                          </div>
                         </div>
-                    )}
-                </div>
-
-                {/* DETAILS MODAL */}
-                <ComputerDetailsModal
-                    open={!!selectedComputer}
-                    computer={selectedComputer}
-                    onClose={() => setSelectedComputer(null)}
-                    onAskAI={(name) => {
-                        setAiScope(name);
-                        setAiOpen(true);
-                    }}
-                />
-
-                {/* AI DRAWER */}
-                <AIAssistantDrawer
-                    open={aiOpen}
-                    onClose={() => setAiOpen(false)}
-                    computers={computers}
-                    scope={aiScope}
-                    setScope={setAiScope}
-                    apiBase={import.meta.env.VITE_API_BASE}
-                />
-            </div>
+                      )}
+                    </div>
+                  </div>
+                </>
+              )}
+            </div>
+          </div>
         </div>
-    );
+      )}
+    </div>
+  );
 }
+
+export default App;
Index: lan-frontend/src/components/AIAssistantDrawer.jsx
===================================================================
--- lan-frontend/src/components/AIAssistantDrawer.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,177 +1,0 @@
-import React, {useEffect, useRef, useState} from "react";
-
-export default function AIAssistantDrawer({
-                                              open,
-                                              onClose,
-                                              computers,
-                                              scope,
-                                              setScope,
-                                              apiBase = "", // ако сакаш можеш да пратиш VITE_API_BASE тука
-                                          }) {
-    const [messages, setMessages] = useState([
-        {
-            role: "assistant",
-            text: "Постави прашање за Sysmon, процеси, мрежа. Можеш да избереш компјутер (scope) горе.",
-        },
-    ]);
-    const [input, setInput] = useState("");
-    const [sending, setSending] = useState(false);
-    const bottomRef = useRef(null);
-
-    useEffect(() => {
-        if (open) {
-            // затвори со Escape
-            const onKey = (e) => {
-                if (e.key === "Escape") onClose?.();
-            };
-            window.addEventListener("keydown", onKey);
-            return () => window.removeEventListener("keydown", onKey);
-        }
-    }, [open, onClose]);
-
-    useEffect(() => {
-        if (bottomRef.current) bottomRef.current.scrollIntoView({behavior: "smooth"});
-    }, [messages, open]);
-
-    async function send() {
-        const q = input.trim();
-        if (!q || sending) return;
-
-        setInput("");
-        setMessages((m) => [...m, {role: "user", text: q}]);
-        setSending(true);
-
-        try {
-            const base = (apiBase || "").replace(/\/$/, ""); // тргни trailing /
-            const url = `${base}/api/chat`;
-
-            const r = await fetch(url, {
-                method: "POST",
-                headers: {"Content-Type": "application/json"},
-                credentials: "include",
-                body: JSON.stringify({question: q, computer_name: scope || null}),
-            });
-
-
-            const data = await r.json().catch(() => ({}));
-            // if (!r.ok) {
-            //     setMessages((m) => [...m, {
-            //         role: "assistant",
-            //         text: `Error ${r.status}: ${data.error || "Request failed"}`
-            //     }]);
-            // } else {
-            //     setMessages((m) => [...m, {role: "assistant", text: data.answer || "(no answer)"}]);
-            // }
-            // setMessages((m) => [
-            //     ...m,
-            //     {role: "assistant", text: data.answer || "(no answer)"},
-            // ]);
-            if (!r.ok) {
-                setMessages((m) => [
-                    ...m,
-                    {role: "assistant", text: `Error ${r.status}: ${data.error || "Request failed"}`},
-                ]);
-            } else {
-                const qs = Array.isArray(data.queries) ? data.queries : [];
-                const notes = data.notes || "";
-
-                const text =
-                    qs.length
-                        ? `SQL queries:\n\n${qs.map((q, i) => `${i + 1}) ${q}`).join("\n\n")}\n\nNotes: ${notes}`
-                        : `Notes: ${notes || "No queries returned."}`;
-
-                setMessages((m) => [...m, {role: "assistant", text}]);
-            }
-
-
-        } catch (e) {
-            setMessages((m) => [
-                ...m,
-                {role: "assistant", text: `Грешка: ${String(e?.message || e)}`},
-            ]);
-        } finally {
-            setSending(false);
-        }
-    }
-
-    if (!open) return null;
-
-    return (
-        <>
-            <div className="aiDrawer-backdrop" onMouseDown={onClose}/>
-
-            <aside
-                className="aiDrawer"
-                role="dialog"
-                aria-modal="true"
-                onMouseDown={(e) => e.stopPropagation()}
-            >
-                <header className="aiDrawer-head">
-                    <div>
-                        <div className="aiDrawer-title">🤖 AI Assistant</div>
-                        <div className="aiDrawer-sub">RAG over your logs</div>
-                    </div>
-
-                    <button className="aiBtn" onClick={onClose} aria-label="Close">
-                        ✕
-                    </button>
-                </header>
-
-                <div className="aiDrawer-toolbar">
-                    <select
-                        className="aiSelect"
-                        value={scope || ""}
-                        onChange={(e) => setScope(e.target.value || null)}
-                    >
-                        <option value="">All computers</option>
-                        {(computers || []).map((c) => (
-                            <option key={c.name} value={c.name}>
-                                {c.name}
-                            </option>
-                        ))}
-                    </select>
-
-                    <div className="aiHint">
-                        Tip: “Top процеси по RAM” / “сомнителни sysmon events”
-                    </div>
-                </div>
-
-                <main className="aiDrawer-body">
-                    {messages.map((m, i) => (
-                        <div
-                            key={i}
-                            className={`aiMsg ${m.role === "user" ? "user" : "assistant"}`}
-                        >
-                            <div className="aiMsg-meta">{m.role === "user" ? "You" : "Assistant"}</div>
-                            <div className="aiMsg-text">{m.text}</div>
-                        </div>
-                    ))}
-                    <div ref={bottomRef}/>
-                </main>
-
-                <footer className="aiDrawer-foot">
-          <textarea
-              className="aiTextarea"
-              rows={2}
-              value={input}
-              placeholder="Постави прашање… (Enter = send, Shift+Enter нов ред)"
-              onChange={(e) => setInput(e.target.value)}
-              onKeyDown={(e) => {
-                  if (e.key === "Enter" && !e.shiftKey) {
-                      e.preventDefault();
-                      send();
-                  }
-              }}
-          />
-                    <button
-                        className="aiBtn aiBtnPrimary"
-                        onClick={send}
-                        disabled={sending || !input.trim()}
-                    >
-                        {sending ? "Sending…" : "Send"}
-                    </button>
-                </footer>
-            </aside>
-        </>
-    );
-}
Index: lan-frontend/src/components/ComputerCard.jsx
===================================================================
--- lan-frontend/src/components/ComputerCard.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,42 +1,0 @@
-import React from "react";
-
-function dotClass(status) {
-  if (status === "online") return "dot online";
-  if (status === "idle") return "dot idle";
-  return "dot offline";
-}
-
-export default function ComputerCard({ c, onOpen }) {
-  return (
-    <button className="card" onClick={() => onOpen?.(c)}>
-      <div className="card-head">
-        <div className="card-title">{c.name}</div>
-        <div className={dotClass(c.status)} />
-      </div>
-
-      <div className="meta">
-        <div><span className="dim">🏷 Env:</span> {c.env_name || "default"}</div>
-        <div><span className="dim">👤 User:</span> {c.user || "—"}</div>
-        <div><span className="dim">🌐 IP:</span> {c.ip || "—"}</div>
-        <div><span className="dim">💻 OS:</span> {(c.os || "—").slice(0, 60)}</div>
-        <div><span className="dim">⏰ Last:</span> {c.last_seen ? new Date(c.last_seen).toLocaleTimeString() : "—"}</div>
-        <div><span className="dim">🛡 Sysmon:</span> {c.sysmon_available ? "Yes" : "No"} • {c.recent_sysmon ?? 0} events</div>
-      </div>
-
-      <div className="kpis">
-        <div className="kpi">
-          <div className="k">CPU avg</div>
-          <div className="v">{c.avg_cpu ?? 0}%</div>
-        </div>
-        <div className="kpi">
-          <div className="k">RAM avg</div>
-          <div className="v">{c.avg_ram ?? 0}%</div>
-        </div>
-        <div className="kpi">
-          <div className="k">Logs 24h</div>
-          <div className="v">{c.recent_logs ?? 0}</div>
-        </div>
-      </div>
-    </button>
-  );
-}
Index: lan-frontend/src/components/ComputerDetailsModal.jsx
===================================================================
--- lan-frontend/src/components/ComputerDetailsModal.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,587 +1,0 @@
-import React, { useEffect, useMemo, useState } from "react";
-
-function severityForEventId(eventId) {
-  const id = Number.parseInt(String(eventId ?? ""), 10);
-  const high = new Set([1, 3, 8, 10]);
-  const med = new Set([5, 6, 7, 11, 22]);
-  if (high.has(id)) return "high";
-  if (med.has(id)) return "medium";
-  return "low";
-}
-
-function fmtTime(ts) {
-  if (!ts) return "—";
-  try {
-    return new Date(ts).toLocaleTimeString();
-  } catch {
-    return String(ts);
-  }
-}
-
-export default function ComputerDetailsModal({ open, computer, onClose, onAskAI }) {
-  const [tab, setTab] = useState("overview");
-  const [loading, setLoading] = useState(false);
-  const [details, setDetails] = useState(null);
-  const [error, setError] = useState("");
-  const [search, setSearch] = useState("");
-
-  // JSON viewer
-  const [jsonOpen, setJsonOpen] = useState(false);
-  const [jsonTitle, setJsonTitle] = useState("");
-  const [jsonData, setJsonData] = useState(null);
-
-  // reset when opening / switching pc
-  useEffect(() => {
-    if (!open) return;
-    setTab("overview");
-    setSearch("");
-    setError("");
-    setDetails(null);
-    setJsonOpen(false);
-    setJsonTitle("");
-    setJsonData(null);
-  }, [open, computer?.name]);
-
-  // close on ESC
-  useEffect(() => {
-    if (!open) return;
-    const onKey = (e) => {
-      if (e.key === "Escape") onClose?.();
-    };
-    window.addEventListener("keydown", onKey);
-    return () => window.removeEventListener("keydown", onKey);
-  }, [open, onClose]);
-
-  // load details
-  useEffect(() => {
-    if (!open || !computer?.name) return;
-    let cancelled = false;
-
-    async function load() {
-      setLoading(true);
-      setError("");
-      try {
-        const r = await fetch(`/api/computer/${encodeURIComponent(computer.name)}`, {
-          credentials: "include",
-        });
-        const d = await r.json().catch(() => ({}));
-        if (!r.ok) throw new Error(d?.error || `HTTP ${r.status}`);
-        if (!cancelled) setDetails(d);
-      } catch (e) {
-        if (!cancelled) {
-          setError(String(e?.message || e));
-          setDetails(null);
-        }
-      } finally {
-        if (!cancelled) setLoading(false);
-      }
-    }
-
-    load();
-    return () => {
-      cancelled = true;
-    };
-  }, [open, computer?.name]);
-
-  const filteredSysmon = useMemo(() => {
-    const list = details?.sysmon_events || [];
-    const q = search.trim().toLowerCase();
-    if (!q) return list;
-    return list.filter((ev) => {
-      const msg = (ev.message || "").toLowerCase();
-      const type = (ev.event_type || "").toLowerCase();
-      const id = String(ev.event_id ?? "").toLowerCase();
-      return msg.includes(q) || type.includes(q) || id.includes(q);
-    });
-  }, [details, search]);
-
-  function openJson(ev) {
-    setJsonTitle(`Sysmon Event ${ev?.event_id ?? ""} • ${ev?.event_type ?? ""}`);
-    setJsonData(ev?.details ?? ev);
-    setJsonOpen(true);
-  }
-
-  function closeJson() {
-    setJsonOpen(false);
-    setJsonTitle("");
-    setJsonData(null);
-  }
-
-  if (!open) return null;
-
-  return (
-    <>
-      <style>{`
-        .cdm-backdrop{
-          position: fixed; inset: 0; z-index: 60;
-          background: rgba(2,6,23,0.70);
-          backdrop-filter: blur(6px);
-          display:flex; align-items:center; justify-content:center;
-          padding: 18px;
-        }
-        .cdm-modal{
-          width: min(1100px, 96vw);
-          height: min(86vh, 860px);
-          display:flex; flex-direction:column;
-          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.20);
-          box-shadow: 0 30px 80px rgba(0,0,0,0.45);
-          overflow: hidden;
-        }
-        .cdm-head{
-          padding: 14px 16px;
-          border-bottom: 1px solid rgba(96,165,250,0.16);
-          display:flex; align-items:center; justify-content:space-between; gap:12px;
-        }
-        .cdm-title{ font-size: 16px; font-weight: 900; color: rgba(255,255,255,0.95); }
-        .cdm-sub{ margin-top: 2px; font-size: 12px; color: rgba(191,219,254,0.80); }
-        .cdm-right{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; justify-content:flex-end; }
-        .cdm-tabs{
-          display:flex; gap:8px; padding: 6px;
-          background: rgba(15,23,42,0.45);
-          border: 1px solid rgba(96,165,250,0.18);
-          border-radius: 999px;
-        }
-        .cdm-tab{
-          appearance:none; border: 0;
-          padding: 8px 12px;
-          border-radius: 999px;
-          background: transparent;
-          color: rgba(226,232,240,0.88);
-          font-weight: 900;
-          font-size: 12px;
-          cursor:pointer;
-          transition: background .12s ease, transform .12s ease, color .12s ease;
-        }
-        .cdm-tab:hover{ transform: translateY(-1px); background: rgba(30,41,59,0.55); }
-        .cdm-tab.active{
-          background: linear-gradient(135deg, rgba(37,99,235,0.95), rgba(56,189,248,0.65));
-          color: rgba(5,12,24,0.95);
-        }
-        .cdm-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.92);
-          padding: 9px 12px;
-          border-radius: 12px;
-          font-weight: 900;
-          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);
-        }
-        .cdm-btn:hover{ transform: translateY(-1px); background: rgba(30,41,59,0.65); border-color: rgba(96,165,250,0.38); }
-        .cdm-btn:disabled{ opacity: .6; cursor: not-allowed; transform:none; }
-        .cdm-btn.primary{
-          background: linear-gradient(135deg, rgba(37,99,235,0.95), rgba(56,189,248,0.65));
-          border-color: rgba(147,197,253,0.35);
-          color: rgba(5,12,24,0.95);
-        }
-
-        /* body scroll zone */
-        .cdm-body{
-          flex: 1;
-          display:flex;
-          flex-direction:column;
-          min-height: 0; /* important for scroll */
-        }
-        .cdm-content{
-          flex: 1;
-          min-height: 0;
-          overflow: auto;
-          padding: 14px 16px 18px;
-        }
-
-        .cdm-panel{
-          border-radius: 16px;
-          background: rgba(15,23,42,0.55);
-          border: 1px solid rgba(96,165,250,0.16);
-          overflow: hidden;
-        }
-        .cdm-panel-head{
-          padding: 12px 12px;
-          display:flex; align-items:center; justify-content:space-between; gap:10px;
-          border-bottom: 1px solid rgba(96,165,250,0.14);
-        }
-        .cdm-panel-title{ color: rgba(255,255,255,0.92); font-weight: 900; }
-        .cdm-panel-body{ padding: 12px; }
-
-        /* table area scroll fix */
-        .cdm-table-wrap{
-          border-radius: 14px;
-          border: 1px solid rgba(148,163,184,0.18);
-          background: rgba(2,6,23,0.28);
-          overflow: hidden;
-        }
-        .cdm-table-scroll{
-          max-height: 52vh;
-          overflow: auto;
-        }
-        table{ width: 100%; border-collapse: collapse; }
-        thead th{
-          position: sticky; top: 0;
-          background: rgba(10,16,32,0.95);
-          backdrop-filter: blur(8px);
-          text-align:left;
-          font-size: 12px;
-          color: rgba(191,219,254,0.88);
-          padding: 10px 10px;
-          border-bottom: 1px solid rgba(96,165,250,0.16);
-          z-index: 1;
-        }
-        tbody td{
-          padding: 10px 10px;
-          border-bottom: 1px solid rgba(148,163,184,0.12);
-          color: rgba(226,232,240,0.92);
-          font-size: 13px;
-          vertical-align: top;
-        }
-        tbody tr:hover td{ background: rgba(30,41,59,0.35); }
-
-        .cdm-pill{
-          display:inline-flex; align-items:center; gap:8px;
-          padding: 6px 10px;
-          border-radius: 999px;
-          background: rgba(30,41,59,0.55);
-          border: 1px solid rgba(96,165,250,0.18);
-          color: rgba(255,255,255,0.9);
-          font-weight: 900;
-          font-size: 12px;
-        }
-        .cdm-input{
-          width: min(420px, 45vw);
-          padding: 9px 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: 800;
-        }
-        .cdm-input::placeholder{ color: rgba(148,163,184,0.75); }
-
-        .cdm-badge{
-          display:inline-flex; align-items:center; justify-content:center;
-          padding: 5px 10px;
-          border-radius: 999px;
-          font-weight: 1000;
-          font-size: 11px;
-          letter-spacing: .3px;
-          border: 1px solid rgba(148,163,184,0.18);
-          background: rgba(15,23,42,0.5);
-          color: rgba(226,232,240,0.95);
-        }
-        .cdm-badge.high{ border-color: rgba(239,68,68,0.35); color: rgba(254,202,202,0.95); }
-        .cdm-badge.medium{ border-color: rgba(251,191,36,0.35); color: rgba(254,243,199,0.95); }
-        .cdm-badge.low{ border-color: rgba(34,197,94,0.25); color: rgba(220,252,231,0.95); }
-
-        /* JSON modal */
-        .cdm-json-modal{
-          width: min(980px, 96vw);
-          height: min(82vh, 860px);
-          display:flex; flex-direction:column;
-          border-radius: 18px;
-          background: rgba(10,16,32,0.94);
-          border: 1px solid rgba(96,165,250,0.20);
-          overflow:hidden;
-        }
-        .cdm-json-body{
-          flex:1; min-height:0; overflow:auto; padding: 14px 16px 18px;
-        }
-        .cdm-pre{
-          margin: 0;
-          padding: 14px;
-          border-radius: 12px;
-          border: 1px solid rgba(148,163,184,0.22);
-          background: rgba(2,6,23,0.35);
-          color: rgba(226,232,240,0.95);
-          font-size: 12px;
-          line-height: 1.5;
-          white-space: pre-wrap;
-          word-break: break-word;
-        }
-      `}</style>
-
-      <div className="cdm-backdrop" onMouseDown={onClose}>
-        <div className="cdm-modal" onMouseDown={(e) => e.stopPropagation()}>
-          <div className="cdm-head">
-            <div>
-              <div className="cdm-title">{computer?.name || "Computer"}</div>
-              <div className="cdm-sub">
-                {computer?.ip || "—"} • {computer?.user || "—"} • {computer?.status || "—"}
-              </div>
-            </div>
-
-            <div className="cdm-right">
-              <button className="cdm-btn primary" onClick={() => onAskAI?.(computer?.name)} disabled={!computer?.name}>
-                Ask AI
-              </button>
-
-              <div className="cdm-tabs">
-                <button className={`cdm-tab ${tab === "overview" ? "active" : ""}`} onClick={() => setTab("overview")}>
-                  Overview
-                </button>
-                <button className={`cdm-tab ${tab === "sysmon" ? "active" : ""}`} onClick={() => setTab("sysmon")}>
-                  Sysmon
-                </button>
-                <button className={`cdm-tab ${tab === "processes" ? "active" : ""}`} onClick={() => setTab("processes")}>
-                  Processes
-                </button>
-                <button className={`cdm-tab ${tab === "network" ? "active" : ""}`} onClick={() => setTab("network")}>
-                  Network
-                </button>
-              </div>
-
-              <button className="cdm-btn" onClick={onClose}>
-                Close
-              </button>
-            </div>
-          </div>
-
-          <div className="cdm-body">
-            <div className="cdm-content">
-              {loading && <div style={{ color: "rgba(148,163,184,0.95)" }}>Loading…</div>}
-
-              {!loading && error && (
-                <div className="cdm-panel" style={{ borderColor: "rgba(239,68,68,0.35)" }}>
-                  <div className="cdm-panel-head">
-                    <div className="cdm-panel-title">Error</div>
-                  </div>
-                  <div className="cdm-panel-body" style={{ color: "rgba(254,202,202,0.95)" }}>
-                    {error}
-                  </div>
-                </div>
-              )}
-
-              {!loading && !error && details && tab === "overview" && (
-                <div className="cdm-panel">
-                  <div className="cdm-panel-head">
-                    <div className="cdm-panel-title">System Overview</div>
-                    <span className="cdm-pill">Env: {details.computer?.env_name || "default"}</span>
-                  </div>
-                  <div className="cdm-panel-body" style={{ display: "grid", gap: 10 }}>
-                    <div><b>User:</b> {details.computer?.user || "—"}</div>
-                    <div><b>IP:</b> {details.computer?.ip || "—"}</div>
-                    <div><b>OS:</b> {details.computer?.os || "—"}</div>
-                    <div><b>First seen:</b> {details.computer?.first_seen || "—"}</div>
-                    <div><b>Last seen:</b> {details.computer?.last_seen || "—"}</div>
-                    <div><b>Total logs:</b> {details.computer?.total_logs ?? "—"}</div>
-                  </div>
-                </div>
-              )}
-
-              {!loading && !error && details && tab === "sysmon" && (
-                <div className="cdm-panel">
-                  <div className="cdm-panel-head">
-                    <div className="cdm-panel-title">Sysmon Events</div>
-                    <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
-                      <span className="cdm-pill">{filteredSysmon.length} events</span>
-                      <input
-                        className="cdm-input"
-                        placeholder="Search by id / type / message…"
-                        value={search}
-                        onChange={(e) => setSearch(e.target.value)}
-                      />
-                    </div>
-                  </div>
-
-                  <div className="cdm-panel-body">
-                    <div className="cdm-table-wrap">
-                      <div className="cdm-table-scroll">
-                        <table>
-                          <thead>
-                            <tr>
-                              <th style={{ width: 90 }}>Time</th>
-                              <th style={{ width: 70 }}>ID</th>
-                              <th style={{ width: 190 }}>Type</th>
-                              <th style={{ width: 110 }}>Severity</th>
-                              <th>Message</th>
-                              <th style={{ width: 90 }}>View</th>
-                            </tr>
-                          </thead>
-                          <tbody>
-                            {filteredSysmon.slice(0, 400).map((ev, idx) => {
-                              const sev = severityForEventId(ev.event_id);
-                              return (
-                                <tr key={idx}>
-                                  <td>{fmtTime(ev.timestamp)}</td>
-                                  <td><b>{ev.event_id ?? "—"}</b></td>
-                                  <td>{ev.event_type || "—"}</td>
-                                  <td><span className={`cdm-badge ${sev}`}>{sev.toUpperCase()}</span></td>
-                                  <td style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }}>
-                                    {(ev.message || "").slice(0, 220)}
-                                  </td>
-                                  <td>
-                                    <button className="cdm-btn" onClick={() => openJson(ev)}>
-                                      View
-                                    </button>
-                                  </td>
-                                </tr>
-                              );
-                            })}
-
-                            {filteredSysmon.length === 0 && (
-                              <tr>
-                                <td colSpan="6" style={{ color: "rgba(148,163,184,0.95)" }}>
-                                  No sysmon events.
-                                </td>
-                              </tr>
-                            )}
-                          </tbody>
-                        </table>
-                      </div>
-                    </div>
-                    {filteredSysmon.length > 400 && (
-                      <div style={{ marginTop: 10, color: "rgba(148,163,184,0.95)", fontSize: 12 }}>
-                        Showing first 400 (for speed).
-                      </div>
-                    )}
-                  </div>
-                </div>
-              )}
-
-              {!loading && !error && details && tab === "processes" && (
-                <div className="cdm-panel">
-                  <div className="cdm-panel-head">
-                    <div className="cdm-panel-title">Recent Processes</div>
-                    <span className="cdm-pill">{(details.recent_processes || []).length} rows</span>
-                  </div>
-
-                  <div className="cdm-panel-body">
-                    <div className="cdm-table-wrap">
-                      <div className="cdm-table-scroll">
-                        <table>
-                          <thead>
-                            <tr>
-                              <th style={{ width: 90 }}>Time</th>
-                              <th style={{ width: 80 }}>PID</th>
-                              <th style={{ width: 200 }}>Name</th>
-                              <th style={{ width: 140 }}>User</th>
-                              <th style={{ width: 90 }}>CPU%</th>
-                              <th style={{ width: 110 }}>RAM MB</th>
-                              <th>Cmdline</th>
-                            </tr>
-                          </thead>
-                          <tbody>
-                            {(details.recent_processes || []).slice(0, 400).map((p, idx) => (
-                              <tr key={idx}>
-                                <td>{fmtTime(p.timestamp)}</td>
-                                <td><b>{p.pid ?? "—"}</b></td>
-                                <td>{p.name || "—"}</td>
-                                <td>{p.username || "—"}</td>
-                                <td>{p.cpu_percent ?? 0}</td>
-                                <td>{p.memory_mb ?? 0}</td>
-                                <td style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }}>
-                                  {(p.cmdline || "").slice(0, 220)}
-                                </td>
-                              </tr>
-                            ))}
-                            {(details.recent_processes || []).length === 0 && (
-                              <tr>
-                                <td colSpan="7" style={{ color: "rgba(148,163,184,0.95)" }}>
-                                  No process data.
-                                </td>
-                              </tr>
-                            )}
-                          </tbody>
-                        </table>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              )}
-
-              {!loading && !error && details && tab === "network" && (
-                <div className="cdm-panel">
-                  <div className="cdm-panel-head">
-                    <div className="cdm-panel-title">Network Connections</div>
-                    <span className="cdm-pill">{(details.network_connections || []).length} rows</span>
-                  </div>
-
-                  <div className="cdm-panel-body">
-                    <div className="cdm-table-wrap">
-                      <div className="cdm-table-scroll">
-                        <table>
-                          <thead>
-                            <tr>
-                              <th style={{ width: 90 }}>Time</th>
-                              <th style={{ width: 80 }}>PID</th>
-                              <th style={{ width: 180 }}>Process</th>
-                              <th style={{ width: 220 }}>Local</th>
-                              <th style={{ width: 220 }}>Remote</th>
-                              <th style={{ width: 140 }}>Status</th>
-                            </tr>
-                          </thead>
-                          <tbody>
-                            {(details.network_connections || []).slice(0, 400).map((n, idx) => (
-                              <tr key={idx}>
-                                <td>{fmtTime(n.timestamp)}</td>
-                                <td><b>{n.pid ?? "—"}</b></td>
-                                <td>{n.process_name || "—"}</td>
-                                <td style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }}>
-                                  {n.local_address || "—"}
-                                </td>
-                                <td style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }}>
-                                  {n.remote_address || "—"}
-                                </td>
-                                <td>{n.status || "—"}</td>
-                              </tr>
-                            ))}
-                            {(details.network_connections || []).length === 0 && (
-                              <tr>
-                                <td colSpan="6" style={{ color: "rgba(148,163,184,0.95)" }}>
-                                  No network data.
-                                </td>
-                              </tr>
-                            )}
-                          </tbody>
-                        </table>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              )}
-
-              {!loading && !error && !details && (
-                <div style={{ color: "rgba(148,163,184,0.95)" }}>No details.</div>
-              )}
-            </div>
-          </div>
-        </div>
-      </div>
-
-      {/* JSON VIEWER */}
-      {jsonOpen && (
-        <div className="cdm-backdrop" onMouseDown={closeJson}>
-          <div className="cdm-json-modal" onMouseDown={(e) => e.stopPropagation()}>
-            <div className="cdm-head">
-              <div>
-                <div className="cdm-title">View JSON</div>
-                <div className="cdm-sub">{jsonTitle}</div>
-              </div>
-              <div className="cdm-right">
-                <button
-                  className="cdm-btn"
-                  onClick={() => navigator.clipboard.writeText(JSON.stringify(jsonData ?? {}, null, 2))}
-                >
-                  Copy
-                </button>
-                <button className="cdm-btn" onClick={closeJson}>
-                  Close
-                </button>
-              </div>
-            </div>
-
-            <div className="cdm-json-body">
-              <pre className="cdm-pre">{JSON.stringify(jsonData ?? {}, null, 2)}</pre>
-            </div>
-          </div>
-        </div>
-      )}
-    </>
-  );
-}
Index: lan-frontend/src/components/Dropdown.jsx
===================================================================
--- lan-frontend/src/components/Dropdown.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,45 +1,0 @@
-import { useState } from "react";
-import SankeyPanel from "../components/SankeyPanel";
-
-export default function VisualizationsPage() {
-  const [env, setEnv] = useState("office1");
-  const [computers, setComputers] = useState(["All"]);
-  const [ips, setIps] = useState(["All"]);
-
-  return (
-    <div style={{ padding: 16 }}>
-      <h2>Network Sankey</h2>
-
-      {/* Пример UI (ти можеш да ставиш multi-select компонента) */}
-      <div style={{ display: "flex", gap: 12, marginBottom: 12 }}>
-        <select value={env} onChange={(e) => setEnv(e.target.value)}>
-          <option value="office1">office1</option>
-          <option value="default">default</option>
-        </select>
-
-        <select
-          value={computers[0]}
-          onChange={(e) => setComputers([e.target.value])}
-        >
-          <option value="All">All</option>
-          <option value="Ilina-laptop">Ilina-laptop</option>
-          <option value="Irena">Irena</option>
-        </select>
-
-        <select value={ips[0]} onChange={(e) => setIps([e.target.value])}>
-          <option value="All">All</option>
-          <option value="127.0.0.1:443">127.0.0.1:443</option>
-        </select>
-      </div>
-
-      <SankeyPanel
-        dashboardUid="adt9v8k"   // <- стави го твојот UID од Grafana URL
-        env={env}
-        computers={computers}
-        ips={ips}
-        from="now-24h"
-        to="now"
-      />
-    </div>
-  );
-}
Index: lan-frontend/src/components/Login.jsx
===================================================================
--- lan-frontend/src/components/Login.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,29 +1,0 @@
-import React from "react";
-import { GoogleLogin } from "@react-oauth/google";
-
-export default function Login({ onDone }) {
-  return (
-    <div style={{ display: "grid", placeItems: "center", minHeight: "100vh" }}>
-      <div className="panel" style={{ maxWidth: 460, width: "92%" }}>
-        <div className="panel-head">
-          <div className="panel-title">Sign in to netIntel</div>
-        </div>
-        <div className="panel-body">
-          <GoogleLogin
-            onSuccess={async (cred) => {
-              const r = await fetch("/api/auth/google", {
-                method: "POST",
-                headers: { "Content-Type": "application/json" },
-                credentials: "include",
-                body: JSON.stringify({ credential: cred.credential }),
-              });
-              if (r.ok) onDone?.();
-              else console.error(await r.json());
-            }}
-            onError={() => console.error("Google login failed")}
-          />
-        </div>
-      </div>
-    </div>
-  );
-}
Index: lan-frontend/src/components/NetIntelViz.jsx
===================================================================
--- lan-frontend/src/components/NetIntelViz.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,653 +1,0 @@
-import React, { useEffect, useMemo, useState } from "react";
-import {
-  LineChart,
-  Line,
-  XAxis,
-  YAxis,
-  Tooltip,
-  Legend,
-  ResponsiveContainer,
-  CartesianGrid,
-} from "recharts";
-
-const VIZ = {
-  STATS: "stats",
-  COMPUTERS: "computers",
-  COMPUTER_DETAILS: "computer_details",
-  NET_GRAPH: "net_graph",
-};
-
-function fmtTs(ts) {
-  if (!ts) return "";
-  return String(ts).replace("T", " ").replace("Z", "");
-}
-
-async function fetchJson(url, { method = "GET", headers = {}, body } = {}) {
-  const res = await fetch(url, {
-    method,
-    credentials: "include",
-    headers: {
-      "Content-Type": "application/json",
-      ...headers,
-    },
-    body: body ? JSON.stringify(body) : undefined,
-  });
-
-  const text = await res.text();
-  let data;
-  try {
-    data = text ? JSON.parse(text) : null;
-  } catch {
-    data = { raw: text };
-  }
-  if (!res.ok) {
-    const msg = data?.error || data?.message || res.statusText;
-    throw new Error(msg);
-  }
-  return data;
-}
-
-/** ✅ Use existing theme.css look: panel / btn / select / input / pill */
-function Panel({ title, right, children, subtitle }) {
-  return (
-    <div className="panel" style={{ marginBottom: 14 }}>
-      <div className="panel-head">
-        <div>
-          <div className="panel-title">{title}</div>
-          {subtitle ? (
-            <div style={{ color: "var(--dim)", fontSize: 12, marginTop: 4 }}>{subtitle}</div>
-          ) : null}
-        </div>
-        {right}
-      </div>
-      <div className="panel-body">{children}</div>
-    </div>
-  );
-}
-
-function Select({ label, value, onChange, options, disabled }) {
-  return (
-    <label style={{ display: "grid", gap: 6 }}>
-      <div style={{ fontSize: 12, color: "var(--dim)" }}>{label}</div>
-      <select
-        className="select"
-        value={value}
-        disabled={disabled}
-        onChange={(e) => onChange(e.target.value)}
-      >
-        {options.map((o) => (
-          <option key={o.value} value={o.value}>
-            {o.label}
-          </option>
-        ))}
-      </select>
-    </label>
-  );
-}
-
-function Pill({ children }) {
-  return <span className="pill">{children}</span>;
-}
-
-function Table({ columns, rows, emptyText = "Нема податоци." }) {
-  return (
-    <div style={{ overflow: "auto" }}>
-      <table className="table">
-        <thead>
-          <tr>
-            {columns.map((c) => (
-              <th key={c.key}>{c.title}</th>
-            ))}
-          </tr>
-        </thead>
-        <tbody>
-          {rows.length === 0 ? (
-            <tr>
-              <td colSpan={columns.length} style={{ color: "var(--dim)", padding: 12 }}>
-                {emptyText}
-              </td>
-            </tr>
-          ) : (
-            rows.map((r, idx) => (
-              <tr key={idx}>
-                {columns.map((c) => (
-                  <td key={c.key}>{c.render ? c.render(r) : String(r[c.key] ?? "")}</td>
-                ))}
-              </tr>
-            ))
-          )}
-        </tbody>
-      </table>
-    </div>
-  );
-}
-
-export default function NetIntelViz({ baseUrl = "" }) {
-  const [loading, setLoading] = useState(false);
-  const [err, setErr] = useState("");
-
-  const [me, setMe] = useState(null);
-  const [envs, setEnvs] = useState(["default"]);
-  const [env, setEnv] = useState("default");
-
-  const [viz, setViz] = useState(VIZ.NET_GRAPH);
-
-  const [stats, setStats] = useState(null);
-  const [computers, setComputers] = useState([]);
-  const [computerName, setComputerName] = useState("");
-  const [computerDetails, setComputerDetails] = useState(null);
-
-  const [sinceHours, setSinceHours] = useState("24");
-  const [graph, setGraph] = useState(null);
-
-  const api = useMemo(() => (baseUrl || "").replace(/\/+$/, ""), [baseUrl]);
-
-  const envOptions = useMemo(() => {
-    const uniq = [...new Set(envs)];
-    return uniq.map((e) => ({ value: e, label: e }));
-  }, [envs]);
-
-  const computerOptions = useMemo(() => {
-    const opts = computers.map((c) => ({ value: c.name, label: c.name }));
-    return [{ value: "", label: "all" }, ...opts];
-  }, [computers]);
-
-  const vizOptions = [
-    { value: VIZ.STATS, label: "Stats (24h)" },
-    { value: VIZ.COMPUTERS, label: "Computers" },
-    { value: VIZ.COMPUTER_DETAILS, label: "Computer details" },
-    { value: VIZ.NET_GRAPH, label: "Network graph" },
-  ];
-
-  async function loadMe() {
-    const data = await fetchJson(`${api}/api/me`);
-    setMe(data?.user || null);
-  }
-
-  async function loadEnvs() {
-    try {
-      const data = await fetchJson(`${api}/api/admin/environments`);
-      const list = data?.environments?.length ? data.environments : ["default"];
-      setEnvs(list);
-      if (!list.includes(env)) setEnv(list[0] || "default");
-    } catch {
-      setEnvs(["default"]);
-      setEnv("default");
-    }
-  }
-
-  async function loadStats(selectedEnv) {
-    const data = await fetchJson(`${api}/api/stats`, {
-      headers: { "X-Env": selectedEnv || env || "default" },
-    });
-    setStats(data);
-  }
-
-  async function loadComputers(selectedEnv) {
-    const data = await fetchJson(`${api}/api/computers`, {
-      headers: { "X-Env": selectedEnv || env || "default" },
-    });
-    setComputers(Array.isArray(data) ? data : []);
-  }
-
-  async function loadComputerDetails(name) {
-    if (!name) {
-      setComputerDetails(null);
-      return;
-    }
-    const data = await fetchJson(`${api}/api/computer/${encodeURIComponent(name)}`, {
-      headers: { "X-Env": env || "default" },
-    });
-    setComputerDetails(data);
-  }
-
-  async function loadNetGraph() {
-    const qs = new URLSearchParams();
-    qs.set("env", env || "default");
-    qs.set("since_hours", String(parseInt(sinceHours || "24", 10) || 24));
-    if (computerName) qs.set("computer", computerName);
-
-    const data = await fetchJson(`${api}/api/graph/net?${qs.toString()}`, {
-      headers: { "X-Env": env || "default" },
-    });
-    setGraph(data);
-  }
-  function MiniBipartiteGraph({ nodes = [], edges = [] }) {
-  const comps = nodes.filter((n) => (n.subTitle || "").toLowerCase().includes("computer"));
-  const ips = nodes.filter((n) => (n.subTitle || "").toLowerCase().includes("remote"));
-
-  const width = 1100;
-  const height = 520;
-  const pad = 40;
-
-  const compPos = comps.map((n, i) => ({
-    ...n,
-    x: pad,
-    y: pad + (i * (height - 2 * pad)) / Math.max(1, comps.length - 1),
-  }));
-
-  const ipPos = ips.map((n, i) => ({
-    ...n,
-    x: width - pad,
-    y: pad + (i * (height - 2 * pad)) / Math.max(1, ips.length - 1),
-  }));
-
-  const pos = new Map();
-  [...compPos, ...ipPos].forEach((n) => pos.set(n.id, n));
-
-  const shownEdges = edges.slice(0, 120);
-
-  return (
-    <div style={{ overflow: "auto" }}>
-      <svg width={width} height={height} style={{ borderRadius: 16, background: "rgba(255,255,255,0.03)" }}>
-        {shownEdges.map((e, idx) => {
-          const s = pos.get(e.source);
-          const t = pos.get(e.target);
-          if (!s || !t) return null;
-          const v = Number(e.mainStat || 1);
-          const strokeWidth = Math.max(1, Math.min(6, Math.log2(v + 1)));
-          return (
-            <line
-              key={idx}
-              x1={s.x}
-              y1={s.y}
-              x2={t.x}
-              y2={t.y}
-              stroke="rgba(120,180,255,0.25)"
-              strokeWidth={strokeWidth}
-            />
-          );
-        })}
-
-        {compPos.map((n) => (
-          <g key={n.id}>
-            <circle cx={n.x} cy={n.y} r={6} fill="rgba(255,255,255,0.9)" />
-            <text x={n.x + 12} y={n.y + 4} fontSize="11" fill="rgba(255,255,255,0.85)">
-              {n.title}
-            </text>
-          </g>
-        ))}
-
-        {ipPos.map((n) => (
-          <g key={n.id}>
-            <circle cx={n.x} cy={n.y} r={6} fill="rgba(80,160,255,0.95)" />
-            <text x={n.x - 12} y={n.y + 4} textAnchor="end" fontSize="11" fill="rgba(255,255,255,0.85)">
-              {n.title}
-            </text>
-          </g>
-        ))}
-      </svg>
-
-      <div style={{ color: "var(--dim)", fontSize: 12, marginTop: 10 }}>
-        Tip: ако има премногу линии, намали Hours или избери конкретен Computer.
-      </div>
-    </div>
-  );
-}
-
-
-  async function refreshAll(nextViz = viz, nextEnv = env) {
-    setErr("");
-    setLoading(true);
-    try {
-      if (nextViz === VIZ.STATS) {
-        await loadStats(nextEnv);
-      } else if (nextViz === VIZ.COMPUTERS) {
-        await loadComputers(nextEnv);
-      } else if (nextViz === VIZ.COMPUTER_DETAILS) {
-        await loadComputers(nextEnv);
-        if (computerName) await loadComputerDetails(computerName);
-      } else if (nextViz === VIZ.NET_GRAPH) {
-        await loadComputers(nextEnv);
-        await loadNetGraph();
-      }
-    } catch (e) {
-      setErr(e.message || "Грешка при вчитување.");
-    } finally {
-      setLoading(false);
-    }
-  }
-
-  useEffect(() => {
-    (async () => {
-      setLoading(true);
-      setErr("");
-      try {
-        await loadMe();
-        await loadEnvs();
-        await refreshAll(VIZ.NET_GRAPH, env);
-      } catch (e) {
-        setErr(e.message || "Неуспешно поврзување.");
-      } finally {
-        setLoading(false);
-      }
-    })();
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [api]);
-
-  const historySeries = useMemo(() => {
-    const hist = computerDetails?.history || [];
-    const rows = [...hist].reverse().slice(-80);
-    return rows.map((r) => ({
-      t: fmtTs(r.timestamp),
-      cpu: Number(r.cpu_usage ?? 0),
-      ram: Number(r.ram_usage ?? 0),
-      disk: Number(r.disk_usage ?? 0),
-      sent: Number(r.network_sent_mb ?? 0),
-      recv: Number(r.network_recv_mb ?? 0),
-    }));
-  }, [computerDetails]);
-
-  const nodes = graph?.nodes || [];
-  const edges = graph?.edges || [];
-  const meta = graph?.meta || {};
-
-  return (
-    <div>
-      {/* HEADER / CONTROLS */}
-      <Panel
-        title="Visualizations"
-        subtitle={me ? `Logged: ${me.email} • tenant: ${me.tenant_id} • role: ${me.role}` : "Not logged"}
-        right={
-          <div style={{ display: "flex", gap: 10, alignItems: "end", flexWrap: "wrap" }}>
-            <Select
-              label="Env"
-              value={env}
-              disabled={loading}
-              onChange={(v) => {
-                setEnv(v);
-                refreshAll(viz, v);
-              }}
-              options={envOptions}
-            />
-
-            <Select
-              label="View"
-              value={viz}
-              disabled={loading}
-              onChange={(v) => {
-                setViz(v);
-                refreshAll(v, env);
-              }}
-              options={vizOptions}
-            />
-
-            {(viz === VIZ.COMPUTER_DETAILS || viz === VIZ.NET_GRAPH) && (
-              <Select
-                label="Computer"
-                value={computerName}
-                disabled={loading}
-                onChange={(v) => {
-                  setComputerName(v);
-                  if (viz === VIZ.COMPUTER_DETAILS) loadComputerDetails(v).catch((e) => setErr(e.message));
-                  if (viz === VIZ.NET_GRAPH) loadNetGraph().catch((e) => setErr(e.message));
-                }}
-                options={computerOptions}
-              />
-            )}
-
-            {viz === VIZ.NET_GRAPH && (
-              <Select
-                label="Hours"
-                value={sinceHours}
-                disabled={loading}
-                onChange={(v) => setSinceHours(v)}
-                options={[
-                  { value: "1", label: "1" },
-                  { value: "6", label: "6" },
-                  { value: "12", label: "12" },
-                  { value: "24", label: "24" },
-                  { value: "48", label: "48" },
-                  { value: "72", label: "72" },
-                ]}
-              />
-            )}
-
-            <button className="btn primary" disabled={loading} onClick={() => refreshAll(viz, env)}>
-              {loading ? "Loading…" : "Refresh"}
-            </button>
-          </div>
-        }
-      >
-        {err ? (
-          <div style={{ color: "#ffb4b4", background: "rgba(255,0,0,0.08)", padding: 10, borderRadius: 12 }}>
-            {err}
-          </div>
-        ) : (
-          <div style={{ color: "var(--dim)", fontSize: 12 }}>
-            Избери “View” за да смениш што се прикажува.
-          </div>
-        )}
-      </Panel>
-      <Panel title="Graph (SVG)" subtitle="Computers left, IPs right, lines = connections">
-  <MiniBipartiteGraph nodes={nodes} edges={edges} />
-</Panel>
-
-
-      {/* STATS */}
-      {viz === VIZ.STATS && (
-        <Panel title="Stats (24h)" subtitle="From /api/stats">
-          <div className="stats-grid" style={{ marginTop: 0 }}>
-            <div className="stat-card">
-              <div className="label">Sysmon Events</div>
-              <div className="value">{stats?.total_sysmon_events ?? "—"}</div>
-              <div className="sub">Last 24 hours</div>
-            </div>
-            <div className="stat-card">
-              <div className="label">Network Connections</div>
-              <div className="value">{stats?.total_network_connections ?? "—"}</div>
-              <div className="sub">Last 24 hours</div>
-            </div>
-            <div className="stat-card">
-              <div className="label">Server Time</div>
-              <div className="value" style={{ fontSize: 18 }}>
-                {fmtTs(stats?.server_time) || "—"}
-              </div>
-              <div className="sub">Now</div>
-            </div>
-          </div>
-        </Panel>
-      )}
-
-      {/* COMPUTERS */}
-      {viz === VIZ.COMPUTERS && (
-        <Panel title="Computers" subtitle="From /api/computers">
-          <Table
-            columns={[
-              { key: "name", title: "Name" },
-              { key: "user", title: "User" },
-              { key: "ip", title: "IP" },
-              { key: "os", title: "OS" },
-              { key: "status", title: "Status" },
-              { key: "avg_cpu", title: "Avg CPU" },
-              { key: "avg_ram", title: "Avg RAM" },
-              { key: "recent_logs", title: "Logs(24h)" },
-              { key: "recent_sysmon", title: "Sysmon(24h)" },
-              { key: "last_seen", title: "Last seen", render: (r) => fmtTs(r.last_seen) },
-            ]}
-            rows={computers}
-            emptyText="No computers for this env."
-          />
-        </Panel>
-      )}
-
-      {/* COMPUTER DETAILS */}
-      {viz === VIZ.COMPUTER_DETAILS && (
-        <>
-          <Panel
-            title="Computer details"
-            subtitle="From /api/computer/<name>"
-            right={
-              <button
-                className="btn"
-                disabled={loading || !computerName}
-                onClick={() => loadComputerDetails(computerName).catch((e) => setErr(e.message))}
-              >
-                Reload
-              </button>
-            }
-          >
-            {!computerName ? (
-              <div style={{ color: "var(--dim)" }}>Select a computer.</div>
-            ) : !computerDetails ? (
-              <div style={{ color: "var(--dim)" }}>Loading…</div>
-            ) : (
-              <div className="stats-grid" style={{ marginTop: 0 }}>
-                <div className="stat-card">
-                  <div className="label">Name</div>
-                  <div className="value">{computerDetails.computer?.name}</div>
-                  <div className="sub">{computerDetails.computer?.ip} • {computerDetails.computer?.os}</div>
-                </div>
-                <div className="stat-card">
-                  <div className="label">User</div>
-                  <div className="value">{computerDetails.computer?.user || "—"}</div>
-                  <div className="sub">
-                    {fmtTs(computerDetails.computer?.first_seen)} → {fmtTs(computerDetails.computer?.last_seen)}
-                  </div>
-                </div>
-                <div className="stat-card">
-                  <div className="label">Total logs</div>
-                  <div className="value">{computerDetails.computer?.total_logs ?? "—"}</div>
-                  <div className="sub">Env: {computerDetails.computer?.env_name || env}</div>
-                </div>
-              </div>
-            )}
-          </Panel>
-
-          <Panel title="Performance charts" subtitle="Last ~80 history rows">
-            {historySeries.length === 0 ? (
-              <div style={{ color: "var(--dim)" }}>No history data.</div>
-            ) : (
-              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
-                <div style={{ height: 280 }}>
-                  <div style={{ marginBottom: 8, color: "var(--dim)", fontSize: 12 }}>CPU / RAM / Disk</div>
-                  <ResponsiveContainer width="100%" height="100%">
-                    <LineChart data={historySeries}>
-                      <CartesianGrid strokeDasharray="3 3" />
-                      <XAxis dataKey="t" hide />
-                      <YAxis />
-                      <Tooltip />
-                      <Legend />
-                      <Line type="monotone" dataKey="cpu" dot={false} />
-                      <Line type="monotone" dataKey="ram" dot={false} />
-                      <Line type="monotone" dataKey="disk" dot={false} />
-                    </LineChart>
-                  </ResponsiveContainer>
-                </div>
-
-                <div style={{ height: 280 }}>
-                  <div style={{ marginBottom: 8, color: "var(--dim)", fontSize: 12 }}>Network MB sent/recv</div>
-                  <ResponsiveContainer width="100%" height="100%">
-                    <LineChart data={historySeries}>
-                      <CartesianGrid strokeDasharray="3 3" />
-                      <XAxis dataKey="t" hide />
-                      <YAxis />
-                      <Tooltip />
-                      <Legend />
-                      <Line type="monotone" dataKey="sent" dot={false} />
-                      <Line type="monotone" dataKey="recv" dot={false} />
-                    </LineChart>
-                  </ResponsiveContainer>
-                </div>
-              </div>
-            )}
-          </Panel>
-
-          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
-            <Panel title="Processes (current)" subtitle="computer_processes_current (limit 200)">
-              <Table
-                columns={[
-                  { key: "pid", title: "PID" },
-                  { key: "name", title: "Name" },
-                  { key: "username", title: "User" },
-                  { key: "cpu_percent", title: "CPU%" },
-                  { key: "memory_mb", title: "MemMB" },
-                  { key: "timestamp", title: "Time", render: (r) => fmtTs(r.timestamp) },
-                ]}
-                rows={computerDetails?.recent_processes || []}
-              />
-            </Panel>
-
-            <Panel title="Sysmon events" subtitle="sysmon_events (limit 200)">
-              <Table
-                columns={[
-                  { key: "event_id", title: "Event" },
-                  { key: "event_type", title: "Type" },
-                  {
-                    key: "message",
-                    title: "Message",
-                    render: (r) =>
-                      (r.message || "").slice(0, 70) + ((r.message || "").length > 70 ? "…" : ""),
-                  },
-                  { key: "timestamp", title: "Time", render: (r) => fmtTs(r.timestamp) },
-                ]}
-                rows={computerDetails?.sysmon_events || []}
-              />
-            </Panel>
-          </div>
-
-          <Panel title="Network connections" subtitle="network_connections (limit 100)">
-            <Table
-              columns={[
-                { key: "pid", title: "PID" },
-                { key: "process_name", title: "Process" },
-                { key: "local_address", title: "Local" },
-                { key: "remote_address", title: "Remote" },
-                { key: "status", title: "Status" },
-                { key: "timestamp", title: "Time", render: (r) => fmtTs(r.timestamp) },
-              ]}
-              rows={computerDetails?.network_connections || []}
-            />
-          </Panel>
-        </>
-      )}
-
-      {/* NET GRAPH */}
-      {viz === VIZ.NET_GRAPH && (
-        <>
-          <Panel
-            title="Network graph (computer → remote IP)"
-            subtitle="From /api/graph/net"
-            right={
-              <button className="btn" disabled={loading} onClick={() => loadNetGraph().catch((e) => setErr(e.message))}>
-                Reload graph
-              </button>
-            }
-          >
-            <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
-              <Pill>env: {meta?.env || env}</Pill>
-              <Pill>hours: {meta?.since_hours ?? sinceHours}</Pill>
-              <Pill>computer: {meta?.computer || "all"}</Pill>
-              <Pill>nodes: {nodes.length}</Pill>
-              <Pill>edges: {edges.length}</Pill>
-            </div>
-          </Panel>
-
-          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
-            <Panel title="Top edges" subtitle="Each row = number of connections">
-              <Table
-                columns={[
-                  { key: "source", title: "Source" },
-                  { key: "target", title: "Target" },
-                  { key: "mainStat", title: "Count" },
-                ]}
-                rows={edges.slice(0, 80)}
-                emptyText="No edges (maybe no traffic in that period)."
-              />
-            </Panel>
-
-            <Panel title="Nodes" subtitle="Computers + remote IP">
-              <Table
-                columns={[
-                  { key: "title", title: "Title" },
-                  { key: "subTitle", title: "Type" },
-                ]}
-                rows={nodes.slice(0, 120)}
-              />
-            </Panel>
-          </div>
-        </>
-      )}
-    </div>
-  );
-}
Index: lan-frontend/src/components/SankeyPanel.jsx
===================================================================
--- lan-frontend/src/components/SankeyPanel.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,45 +1,0 @@
-// src/components/SankeyPanel.jsx
-import { useMemo } from "react";
-
-export default function SankeyPanel({
-  env = "office1",
-  computers = [],   // ✅ празно значи All
-  ips = [],         // ✅ празно значи All
-  from = "now-24h",
-  to = "now",
-}) {
-  const dashboardUid = "adt9v8k";
-  const grafanaBase = "http://localhost:3000";
-
-  const src = useMemo(() => {
-    const params = new URLSearchParams();
-    params.set("orgId", "1");
-    params.set("kiosk", "tv");
-    params.set("from", from);
-    params.set("to", to);
-
-    params.set("var-env", env);
-
-    // ✅ НЕ праќај "All"
-    const cleanComputers = (computers || []).filter((c) => c && c !== "All");
-    const cleanIps = (ips || []).filter((ip) => ip && ip !== "All");
-
-    // ✅ додавај само ако има конкретни вредности
-    cleanComputers.forEach((c) => params.append("var-computers", c));
-    cleanIps.forEach((ip) => params.append("var-ips", ip));
-
-    return `${grafanaBase}/d/${dashboardUid}/sankey-panel?${params.toString()}`;
-  }, [env, computers, ips, from, to]);
-
-  return (
-    <div style={{ width: "100%", height: "85vh", overflow: "hidden", borderRadius: 12 }}>
-      <iframe
-        src={src}
-        title="Network Sankey"
-        width="100%"
-        height="100%"
-        frameBorder="0"
-      />
-    </div>
-  );
-}
Index: lan-frontend/src/components/SankeyViz.jsx
===================================================================
--- lan-frontend/src/components/SankeyViz.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,119 +1,0 @@
-import React, { useEffect, useMemo, useRef } from "react";
-import * as d3 from "d3";
-import { sankey, sankeyLinkHorizontal } from "d3-sankey";
-
-/**
- * dataRows: [{ source: "PC1", target: "1.2.3.4:443", value: 12 }, ...]
- */
-export default function SankeyViz({ dataRows, height = 520 }) {
-  const ref = useRef(null);
-
-  const graph = useMemo(() => {
-    const rows = Array.isArray(dataRows) ? dataRows : [];
-    const nodesMap = new Map();
-    const nodes = [];
-    const links = [];
-
-    function getNode(name) {
-      if (!nodesMap.has(name)) {
-        const id = nodes.length;
-        nodesMap.set(name, id);
-        nodes.push({ name });
-      }
-      return nodesMap.get(name);
-    }
-
-    for (const r of rows) {
-      if (!r?.source || !r?.target) continue;
-      const s = getNode(String(r.source));
-      const t = getNode(String(r.target));
-      const v = Number(r.value ?? 1);
-      links.push({ source: s, target: t, value: isFinite(v) ? v : 1 });
-    }
-
-    return { nodes, links };
-  }, [dataRows]);
-
-  useEffect(() => {
-    const el = ref.current;
-    if (!el) return;
-
-    el.innerHTML = "";
-
-    const width = el.clientWidth || 900;
-    const svg = d3
-      .select(el)
-      .append("svg")
-      .attr("width", "100%")
-      .attr("viewBox", `0 0 ${width} ${height}`)
-      .style("background", "rgba(255,255,255,0.02)")
-      .style("border", "1px solid rgba(255,255,255,0.08)")
-      .style("borderRadius", "14px");
-
-    if (!graph.nodes.length || !graph.links.length) {
-      svg
-        .append("text")
-        .attr("x", width / 2)
-        .attr("y", height / 2)
-        .attr("text-anchor", "middle")
-        .style("fill", "rgba(255,255,255,0.7)")
-        .style("font-size", "14px")
-        .text("No data for this selection.");
-      return;
-    }
-
-    const sk = sankey()
-      .nodeWidth(18)
-      .nodePadding(14)
-      .extent([
-        [20, 20],
-        [width - 20, height - 20],
-      ]);
-
-    const { nodes, links } = sk({
-      nodes: graph.nodes.map((d) => ({ ...d })),
-      links: graph.links.map((d) => ({ ...d })),
-    });
-
-    // Links
-    svg
-      .append("g")
-      .attr("fill", "none")
-      .selectAll("path")
-      .data(links)
-      .join("path")
-      .attr("d", sankeyLinkHorizontal())
-      .attr("stroke", "rgba(80,160,255,0.55)")
-      .attr("stroke-width", (d) => Math.max(1, d.width))
-      .attr("opacity", 0.9);
-
-    // Nodes
-    const node = svg
-      .append("g")
-      .selectAll("g")
-      .data(nodes)
-      .join("g");
-
-    node
-      .append("rect")
-      .attr("x", (d) => d.x0)
-      .attr("y", (d) => d.y0)
-      .attr("height", (d) => Math.max(1, d.y1 - d.y0))
-      .attr("width", (d) => d.x1 - d.x0)
-      .attr("rx", 6)
-      .attr("fill", "rgba(255,255,255,0.18)")
-      .attr("stroke", "rgba(255,255,255,0.15)");
-
-    node
-      .append("text")
-      .attr("x", (d) => (d.x0 < width / 2 ? d.x1 + 8 : d.x0 - 8))
-      .attr("y", (d) => (d.y0 + d.y1) / 2)
-      .attr("dy", "0.35em")
-      .attr("text-anchor", (d) => (d.x0 < width / 2 ? "start" : "end"))
-      .style("fill", "rgba(255,255,255,0.85)")
-      .style("font-size", "12px")
-      .text((d) => d.name);
-  }, [graph, height]);
-
-  return <div ref={ref} style={{ width: "100%" }} />;
-}
Index: lan-frontend/src/components/SideMenu.jsx
===================================================================
--- lan-frontend/src/components/SideMenu.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,101 +1,0 @@
-import React, {useEffect, useRef, useState} from "react";
-
-function Item({active, icon, label, onClick, collapsed}) {
-    return (
-        <button
-            onClick={onClick}
-            className={`side-item ${active ? "active" : ""}`}
-            title={label}
-            type="button"
-        >
-            <span className="side-icon">{icon}</span>
-            {!collapsed && <span className="side-label">{label}</span>}
-        </button>
-    );
-}
-
-export default function SideMenu({activeView, setActiveView}) {
-    const [open, setOpen] = useState(false);
-    const rootRef = useRef(null);
-
-    // close on click outside (ако е open преку click)
-    useEffect(() => {
-        function onDocClick(e) {
-            if (!open) return;
-            if (!rootRef.current) return;
-            if (!rootRef.current.contains(e.target)) setOpen(false);
-        }
-
-        document.addEventListener("mousedown", onDocClick);
-        return () => document.removeEventListener("mousedown", onDocClick);
-    }, [open]);
-
-    const collapsed = !open;
-
-    return (
-        <aside
-            ref={rootRef}
-            className={`side ${open ? "open" : "collapsed"}`}
-            onMouseEnter={() => setOpen(true)}   // hover open
-            onMouseLeave={() => setOpen(false)}  // hover close
-        >
-            {/* мал “handle” што се гледа секогаш */}
-            <button
-                className="side-handle"
-                type="button"
-                onClick={() => setOpen((v) => !v)}  // click toggle (ако не сакаш click, избриши ова)
-                aria-label="Toggle menu"
-                title="Menu"
-            >
-                {open ? "◀" : "▶"}
-            </button>
-
-            <div className="side-inner">
-                <div className="side-brand">
-                    <div className="side-brand-dot"/>
-                    {!collapsed && (
-                        <div className="side-brand-text">
-                            <div className="side-brand-title">netIntel</div>
-                            <div className="side-brand-sub">tenant dashboard</div>
-                        </div>
-                    )}
-                </div>
-
-
-                <div className="side-section">
-                    {/*<div className="side-section-title">MAIN</div>*/}
-
-                    <Item
-                        collapsed={collapsed}
-                        active={activeView === "dashboard"}
-                        icon="🖥️"
-                        label="Dashboard"
-                        onClick={() => setActiveView("dashboard")}
-                    />
-
-                    <Item
-                        collapsed={collapsed}
-                        active={activeView === "visualizations"}
-                        icon="📈"
-                        label="Visualizations"
-                        onClick={() => setActiveView("visualizations")}
-                    />
-
-                    <Item
-                        collapsed={collapsed}
-                        active={activeView === "account"}
-                        icon="👤"
-                        label="Account"
-                        onClick={() => setActiveView("account")}
-                    />
-                </div>
-
-                {!collapsed && (
-                    <div className="side-footer">
-                        <div className="side-footer-hint">Tip: користи Visualizations за графови</div>
-                    </div>
-                )}
-            </div>
-        </aside>
-    );
-}
Index: lan-frontend/src/components/TopBar.jsx
===================================================================
--- lan-frontend/src/components/TopBar.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,401 +1,0 @@
-import React, { useEffect, useMemo, useState } from "react";
-import logo from "../assets/logo_image.png";
-
-export default function TopBar({
-  onOpenAI,
-  onRefresh,
-  lastUpdated,
-  onEnvChange,
-  selectedEnv: selectedEnvProp, // optional (ако сакаш да го контролира App)
-  me,
-  onLoggedOut, // optional callback (App loadMe)
-
-  // NEW: process history switch (controlled by App)
-  saveProcHistory = false,
-  onToggleProcHistory, // (enabled:boolean) => void
-}) {
-  const [open, setOpen] = useState(false);
-
-  // env + tokens
-  const [envs, setEnvs] = useState(["default"]);
-  const [selectedEnv, setSelectedEnv] = useState(selectedEnvProp || "default");
-  const [newEnvName, setNewEnvName] = useState("");
-  const [token, setToken] = useState("");
-
-  const [busy, setBusy] = useState(false);
-  const [error, setError] = useState("");
-
-  const isAdmin = (me?.role || "").toLowerCase() === "admin";
-
-  // keep in sync ако App праќа selectedEnv prop
-  useEffect(() => {
-    if (selectedEnvProp && selectedEnvProp !== selectedEnv) {
-      setSelectedEnv(selectedEnvProp);
-    }
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [selectedEnvProp]);
-
-  async function loadEnvironments() {
-    const r = await fetch("/api/admin/environments", {
-      credentials: "include",
-    });
-    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 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" },
-        credentials: "include",
-        body: JSON.stringify({ name: newEnvName.trim() }),
-      });
-
-      const data = await r.json();
-      if (!r.ok) throw new Error(data?.error || `HTTP ${r.status}`);
-
-      await loadEnvironments();
-      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" },
-        credentials: "include",
-        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);
-  }
-
-  async function logout() {
-    try {
-      await fetch("/api/auth/logout", { method: "POST", credentials: "include" });
-    } catch {}
-    onLoggedOut?.();
-    // reset local admin modal state
-    setOpen(false);
-    setToken("");
-    setError("");
-  }
-
-  const prettyTime = useMemo(() => {
-    if (!lastUpdated) return "Loading…";
-    try {
-      return `Updated ${new Date(lastUpdated).toLocaleTimeString()}`;
-    } catch {
-      return "Updated";
-    }
-  }, [lastUpdated]);
-
-  return (
-    <>
-      <style>{`
-        .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-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)); }
-
-        .tb-switch{ display:flex; align-items:center; gap:10px; padding: 8px 12px; border-radius: 999px;
-          background: rgba(15,23,42,0.55); border: 1px solid rgba(96,165,250,0.22);
-          color: rgba(255,255,255,0.9); font-weight: 800; font-size: 12px; }
-        .tb-switch .label{ color: rgba(191,219,254,0.9); }
-        .tb-toggle{ width: 46px; height: 26px; border-radius: 999px; position: relative; border: 1px solid rgba(96,165,250,0.25);
-          background: rgba(30,41,59,0.65); cursor: pointer; transition: background .12s ease, opacity .12s ease; }
-        .tb-toggle.disabled{ opacity: .55; cursor: not-allowed; }
-        .tb-knob{ width: 22px; height: 22px; border-radius: 999px; position: absolute; top: 1px; left: 1px;
-          background: rgba(226,232,240,0.95); transition: transform .14s ease; box-shadow: 0 6px 14px rgba(0,0,0,0.22); }
-        .tb-toggle.on{ background: rgba(37,99,235,0.85); border-color: rgba(147,197,253,0.45); }
-        .tb-toggle.on .tb-knob{ transform: translateX(20px); }
-
-        .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; gap: 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>
-
-      <div className="tb-wrap">
-        <div className="tb-inner">
-          <div>
-            <img
-              src={logo}
-              alt="NETIntel logo"
-              className="netintel-logo"
-              width={150}
-              style={{ marginTop: 10 }}
-            />
-            <div style={{ marginTop: 4, fontSize: 12, color: "rgba(191,219,254,0.8)", fontWeight: 800 }}>
-              <span style={{ color: "rgba(96,165,250,0.95)" }}>{prettyTime}</span>
-              {me?.email ? (
-                <span style={{ marginLeft: 10 }}>
-                  • {me.email}
-                  {me?.role ? ` • ${me.role}` : ""}
-                </span>
-              ) : null}
-            </div>
-          </div>
-
-          <div className="tb-actions">
-            <span className="tb-pill" title="Selected environment">
-              <span className="dot" />
-              Env: {selectedEnv}
-            </span>
-
-            {/* NEW: Save process history switch */}
-            <div className="tb-switch" title={isAdmin ? "Toggle saving process history for this environment" : "Only admin can change this"}>
-              <span className="label">Proc history</span>
-              <div
-                className={[
-                  "tb-toggle",
-                  saveProcHistory ? "on" : "",
-                  !isAdmin ? "disabled" : "",
-                ].join(" ")}
-                role="switch"
-                aria-checked={saveProcHistory ? "true" : "false"}
-                tabIndex={0}
-                onClick={() => {
-                  if (!isAdmin) return;
-                  onToggleProcHistory?.(!saveProcHistory);
-                }}
-                onKeyDown={(e) => {
-                  if (!isAdmin) return;
-                  if (e.key === "Enter" || e.key === " ") {
-                    e.preventDefault();
-                    onToggleProcHistory?.(!saveProcHistory);
-                  }
-                }}
-              >
-                <div className="tb-knob" />
-              </div>
-            </div>
-
-            <button className="tb-btn" onClick={onRefresh} disabled={busy}>
-              Refresh
-            </button>
-
-            <button
-              className="tb-btn"
-              onClick={async () => {
-                setOpen(true);
-                setError("");
-                setToken("");
-                try {
-                  await loadEnvironments();
-                } catch (e) {
-                  setError(String(e.message || e));
-                }
-              }}
-            >
-              Admin
-            </button>
-
-            <button className="tb-btn primary" onClick={onOpenAI}>
-              AI Assistant
-            </button>
-          </div>
-        </div>
-      </div>
-
-      {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">Tenant Admin</div>
-                <div className="tb-modal-sub">
-                  Environments ➜ Generate token (clients send X-Env-Token to /receive)
-                </div>
-              </div>
-              <div style={{ display: "flex", gap: 10 }}>
-                <button className="tb-btn" onClick={logout}>
-                  Logout
-                </button>
-                <button className="tb-btn" onClick={() => setOpen(false)}>
-                  Close
-                </button>
-              </div>
-            </div>
-
-            <div className="tb-grid">
-              <div className="tb-card">
-                <div className="tb-card-head">
-                  <div className="tb-card-title">Choose environment</div>
-                </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.
-                    <br />
-                    <b>Proc history</b> switch горе важи за тековниот env.
-                  </div>
-                </div>
-              </div>
-
-              <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>
-
-              <div className="tb-card">
-                <div className="tb-card-head">
-                  <div className="tb-card-title">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>
-      )}
-    </>
-  );
-}
Index: lan-frontend/src/components/Visualisations.jsx
===================================================================
--- lan-frontend/src/components/Visualisations.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,290 +1,0 @@
-// // src/pages/Visualizations.jsx
-// import { useState } from "react";
-// import SankeyPanel from "../components/SankeyPanel";
-//
-// export default function Visualizations() {
-//   const [env, setEnv] = useState("office1");
-//
-//   // ✅ All = празни низи
-//   const [computers, setComputers] = useState([]);
-//   const [ips, setIps] = useState([]);
-//
-//   return (
-//     <div>
-//       <h2>Network Sankey Visualization</h2>
-//
-//       <SankeyPanel
-//         env={env}
-//         computers={computers}
-//         ips={ips}
-//         from="now-2d"
-//         to="now"
-//       />
-//     </div>
-//   );
-// }
-
-
-import React, { useEffect, useMemo, useState } from "react";
-import SankeyViz from "../components/SankeyViz";
-
-function MultiSelect({ options, value, onChange, label }) {
-  return (
-    <div style={{ display: "grid", gap: 6 }}>
-      <div style={{ fontSize: 12, color: "rgba(255,255,255,0.7)" }}>{label}</div>
-      <select
-        multiple
-        value={value}
-        onChange={(e) => {
-          const vals = Array.from(e.target.selectedOptions).map((o) => o.value);
-          onChange(vals);
-        }}
-        className="select"
-        style={{ minHeight: 90 }}
-      >
-        {options.map((o) => (
-          <option key={o} value={o}>
-            {o}
-          </option>
-        ))}
-      </select>
-      <div style={{ fontSize: 11, color: "rgba(255,255,255,0.5)" }}>
-        (држи Ctrl за multi-select)
-      </div>
-    </div>
-  );
-}
-
-export default function Visualizations() {
-  const [env, setEnv] = useState("office1");
-
-  // 👇 ако сакаш env листа динамички, додај endpoint.
-  const envOptions = ["default", "office1"];
-
-  const [computerOptions, setComputerOptions] = useState(["All"]);
-  const [ipOptions, setIpOptions] = useState(["All"]);
-
-  const [computers, setComputers] = useState(["All"]);
-  const [ips, setIps] = useState(["All"]);
-
-  const [hours, setHours] = useState(48);
-  const [limit, setLimit] = useState(200);
-
-  const [vizType, setVizType] = useState("sankey"); // sankey | table | topips
-  const [rows, setRows] = useState([]);
-  const [loading, setLoading] = useState(false);
-
-  // ✅ 1) load computers for env (користи твојот постоечки /api/computers)
-  async function loadComputers() {
-    const r = await fetch("/api/computers", {
-      credentials: "include",
-      headers: { "X-Env": env },
-    });
-    const data = await r.json();
-    const names = (Array.isArray(data) ? data : []).map((c) => c.name).filter(Boolean);
-    setComputerOptions(["All", ...names]);
-  }
-
-  // ✅ 2) load ips list (ОВА треба да го имаш на backend како /api/ips)
-  async function loadIps() {
-    const r = await fetch(`/api/ips?env=${encodeURIComponent(env)}&hours=${hours}&limit=${limit}`, {
-      credentials: "include",
-    });
-    const data = await r.json();
-    const list = Array.isArray(data?.ips) ? data.ips : Array.isArray(data) ? data : [];
-    setIpOptions(["All", ...list]);
-  }
-
-  // ✅ 3) load sankey rows (ОВА треба да го имаш на backend како /api/sankey)
-  async function loadSankey() {
-    setLoading(true);
-    try {
-      const params = new URLSearchParams();
-      params.set("env", env);
-      params.set("hours", String(hours));
-      params.set("limit", String(limit));
-      params.set("computers", (computers?.length ? computers : ["All"]).join(","));
-      params.set("ips", (ips?.length ? ips : ["All"]).join(","));
-
-      const r = await fetch(`/api/sankey?${params.toString()}`, { credentials: "include" });
-      const data = await r.json();
-      setRows(Array.isArray(data) ? data : []);
-    } finally {
-      setLoading(false);
-    }
-  }
-
-  useEffect(() => {
-    loadComputers();
-    loadIps();
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [env, hours, limit]);
-
-  useEffect(() => {
-    loadSankey();
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [env, hours, limit, computers, ips]);
-
-  // Table view
-  const tableRows = useMemo(() => rows.slice(0, 200), [rows]);
-
-  // Top IPs view
-  const topIps = useMemo(() => {
-    const map = new Map();
-    for (const r of rows) {
-      map.set(r.target, (map.get(r.target) || 0) + Number(r.value || 0));
-    }
-    return Array.from(map.entries())
-      .sort((a, b) => b[1] - a[1])
-      .slice(0, 20);
-  }, [rows]);
-
-  return (
-    <div className="panel" style={{ marginTop: 14 }}>
-      <div className="panel-head">
-        <div className="panel-title">Visualizations</div>
-        <div style={{ color: "var(--dim)", fontSize: 12 }}>
-          {loading ? "Loading…" : `${rows.length} links`}
-        </div>
-      </div>
-
-      <div className="panel-body" style={{ display: "grid", gap: 14 }}>
-        {/* Controls */}
-        <div
-          style={{
-            display: "grid",
-            gridTemplateColumns: "1fr 1fr 1fr",
-            gap: 12,
-            alignItems: "start",
-          }}
-        >
-          <div style={{ display: "grid", gap: 8 }}>
-            <div style={{ fontSize: 12, color: "rgba(255,255,255,0.7)" }}>Environment</div>
-            <select className="select" value={env} onChange={(e) => setEnv(e.target.value)}>
-              {envOptions.map((x) => (
-                <option key={x} value={x}>
-                  {x}
-                </option>
-              ))}
-            </select>
-
-            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
-              <div>
-                <div style={{ fontSize: 12, color: "rgba(255,255,255,0.7)" }}>Hours</div>
-                <input
-                  className="input"
-                  type="number"
-                  value={hours}
-                  min={1}
-                  max={720}
-                  onChange={(e) => setHours(Number(e.target.value))}
-                />
-              </div>
-              <div>
-                <div style={{ fontSize: 12, color: "rgba(255,255,255,0.7)" }}>Limit</div>
-                <input
-                  className="input"
-                  type="number"
-                  value={limit}
-                  min={10}
-                  max={2000}
-                  onChange={(e) => setLimit(Number(e.target.value))}
-                />
-              </div>
-            </div>
-
-            <div style={{ display: "grid", gap: 6 }}>
-              <div style={{ fontSize: 12, color: "rgba(255,255,255,0.7)" }}>Visualization</div>
-              <select className="select" value={vizType} onChange={(e) => setVizType(e.target.value)}>
-                <option value="sankey">Sankey</option>
-                <option value="table">Table</option>
-                <option value="topips">Top IPs</option>
-              </select>
-            </div>
-
-            <button
-              className="btn"
-              onClick={() => {
-                setComputers(["All"]);
-                setIps(["All"]);
-              }}
-            >
-              Reset filters
-            </button>
-          </div>
-
-          <MultiSelect
-            label="Computers"
-            options={computerOptions}
-            value={computers}
-            onChange={(vals) => setComputers(vals.length ? vals : ["All"])}
-          />
-
-          <MultiSelect
-            label="IPs"
-            options={ipOptions}
-            value={ips}
-            onChange={(vals) => setIps(vals.length ? vals : ["All"])}
-          />
-        </div>
-
-        {/* Viz */}
-        {vizType === "sankey" && <SankeyViz dataRows={rows} height={520} />}
-
-        {vizType === "table" && (
-          <div style={{ overflow: "auto", border: "1px solid rgba(255,255,255,0.08)", borderRadius: 12 }}>
-            <table style={{ width: "100%", borderCollapse: "collapse" }}>
-              <thead>
-                <tr>
-                  <th style={{ textAlign: "left", padding: 10, color: "rgba(255,255,255,0.8)" }}>Source</th>
-                  <th style={{ textAlign: "left", padding: 10, color: "rgba(255,255,255,0.8)" }}>Target</th>
-                  <th style={{ textAlign: "right", padding: 10, color: "rgba(255,255,255,0.8)" }}>Value</th>
-                </tr>
-              </thead>
-              <tbody>
-                {tableRows.map((r, idx) => (
-                  <tr key={idx} style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }}>
-                    <td style={{ padding: 10, color: "rgba(255,255,255,0.85)" }}>{r.source}</td>
-                    <td style={{ padding: 10, color: "rgba(255,255,255,0.85)" }}>{r.target}</td>
-                    <td style={{ padding: 10, textAlign: "right", color: "rgba(255,255,255,0.85)" }}>{r.value}</td>
-                  </tr>
-                ))}
-              </tbody>
-            </table>
-          </div>
-        )}
-
-        {vizType === "topips" && (
-          <div style={{ display: "grid", gap: 8 }}>
-            {topIps.map(([ip, v]) => (
-              <div
-                key={ip}
-                style={{
-                  display: "grid",
-                  gridTemplateColumns: "220px 1fr 80px",
-                  gap: 10,
-                  alignItems: "center",
-                  padding: "8px 10px",
-                  border: "1px solid rgba(255,255,255,0.08)",
-                  borderRadius: 12,
-                }}
-              >
-                <div style={{ color: "rgba(255,255,255,0.85)" }}>{ip}</div>
-                <div style={{ height: 10, background: "rgba(255,255,255,0.08)", borderRadius: 99, overflow: "hidden" }}>
-                  <div
-                    style={{
-                      height: "100%",
-                      width: `${Math.min(100, (v / (topIps[0]?.[1] || 1)) * 100)}%`,
-                      background: "rgba(80,160,255,0.7)",
-                    }}
-                  />
-                </div>
-                <div style={{ textAlign: "right", color: "rgba(255,255,255,0.8)" }}>{v}</div>
-              </div>
-            ))}
-          </div>
-        )}
-      </div>
-    </div>
-  );
-}
Index: lan-frontend/src/index.css
===================================================================
--- lan-frontend/src/index.css	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ lan-frontend/src/index.css	(revision 640ed89213ad0086dc8379de00b8d102057ead1f)
@@ -1563,149 +1563,2 @@
   background: var(--secondary-color);
 }
-
-.aiDrawer-backdrop{
-  position: fixed;
-  inset: 0;
-  background: rgba(0,0,0,.55);
-  backdrop-filter: blur(2px);
-  z-index: 999;
-}
-
-/* десен drawer */
-.aiDrawer{
-  position: fixed;
-  top: 0;
-  right: 0;
-  height: 100vh;
-  width: min(520px, 92vw);
-  background: #0b1220;            /* темна */
-  color: #e7eefc;
-  border-left: 1px solid rgba(255,255,255,.08);
-  z-index: 1000;
-  display: grid;
-  grid-template-rows: auto auto 1fr auto;
-
-  /* slide in */
-  transform: translateX(0);
-  animation: aiSlideIn .18s ease-out;
-}
-
-@keyframes aiSlideIn{
-  from { transform: translateX(24px); opacity: .7; }
-  to   { transform: translateX(0); opacity: 1; }
-}
-
-.aiDrawer-head{
-  padding: 14px 16px;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  border-bottom: 1px solid rgba(255,255,255,.08);
-}
-
-.aiDrawer-title{
-  font-size: 16px;
-  font-weight: 700;
-}
-
-.aiDrawer-sub{
-  font-size: 12px;
-  opacity: .75;
-  margin-top: 2px;
-}
-
-.aiDrawer-toolbar{
-  padding: 12px 16px;
-  border-bottom: 1px solid rgba(255,255,255,.08);
-  display: grid;
-  gap: 8px;
-}
-
-.aiSelect{
-  width: 100%;
-  background: rgba(255,255,255,.06);
-  border: 1px solid rgba(255,255,255,.10);
-  color: #e7eefc;
-  padding: 10px 12px;
-  border-radius: 10px;
-  outline: none;
-}
-
-.aiHint{
-  font-size: 12px;
-  opacity: .75;
-}
-
-.aiDrawer-body{
-  padding: 14px 16px;
-  overflow: auto;
-  display: grid;
-  gap: 10px;
-}
-
-/* chat bubbles */
-.aiMsg{
-  max-width: 92%;
-  border: 1px solid rgba(255,255,255,.08);
-  border-radius: 14px;
-  padding: 10px 12px;
-  background: rgba(255,255,255,.04);
-}
-
-.aiMsg.user{
-  margin-left: auto;
-  background: rgba(88, 167, 255, .12);
-  border-color: rgba(88, 167, 255, .20);
-}
-
-.aiMsg-meta{
-  font-size: 11px;
-  opacity: .7;
-  margin-bottom: 6px;
-}
-
-.aiMsg-text{
-  white-space: pre-wrap;
-  line-height: 1.35;
-  font-size: 14px;
-}
-
-.aiDrawer-foot{
-  padding: 12px 16px;
-  border-top: 1px solid rgba(255,255,255,.08);
-  display: grid;
-  grid-template-columns: 1fr auto;
-  gap: 10px;
-  align-items: end;
-  background: rgba(255,255,255,.02);
-}
-
-.aiTextarea{
-  width: 100%;
-  resize: none;
-  background: rgba(255,255,255,.06);
-  border: 1px solid rgba(255,255,255,.10);
-  color: #e7eefc;
-  padding: 10px 12px;
-  border-radius: 12px;
-  outline: none;
-}
-
-.aiBtn{
-  background: rgba(255,255,255,.06);
-  border: 1px solid rgba(255,255,255,.10);
-  color: #e7eefc;
-  padding: 10px 12px;
-  border-radius: 12px;
-  cursor: pointer;
-}
-
-.aiBtn:disabled{
-  opacity: .5;
-  cursor: not-allowed;
-}
-
-.aiBtnPrimary{
-  background: rgba(88, 167, 255, .22);
-  border-color: rgba(88, 167, 255, .35);
-}
Index: lan-frontend/src/main.jsx
===================================================================
--- lan-frontend/src/main.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ lan-frontend/src/main.jsx	(revision 640ed89213ad0086dc8379de00b8d102057ead1f)
@@ -1,13 +1,10 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-import { GoogleOAuthProvider } from "@react-oauth/google";
-import App from "./App.jsx";
-import "./styles/theme.css";
+import React from 'react';                    // ⬅ додадено
+import ReactDOM from 'react-dom/client';
+import App from './App.jsx';
+import './index.css';
 
-ReactDOM.createRoot(document.getElementById("root")).render(
+ReactDOM.createRoot(document.getElementById('root')).render(
   <React.StrictMode>
-    <GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>
-      <App />
-    </GoogleOAuthProvider>
+    <App />
   </React.StrictMode>
 );
Index: lan-frontend/src/styles/netinel.css
===================================================================
--- lan-frontend/src/styles/netinel.css	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,125 +1,0 @@
-/* netintel.css - basic styling without Tailwind */
-
-.netintel-wrap {
-  max-width: 1100px;
-  margin: 0 auto;
-  padding: 16px;
-  color: #e5e7eb;
-  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
-}
-
-.netintel-title {
-  font-size: 24px;
-  font-weight: 700;
-  margin-bottom: 6px;
-}
-
-.netintel-sub {
-  font-size: 13px;
-  opacity: 0.85;
-  margin-bottom: 14px;
-}
-
-.netintel-toolbar {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 10px;
-  align-items: end;
-  margin-bottom: 14px;
-}
-
-.netintel-field {
-  display: flex;
-  flex-direction: column;
-  gap: 4px;
-}
-
-.netintel-label {
-  font-size: 11px;
-  opacity: 0.85;
-}
-
-.netintel-select,
-.netintel-btn {
-  background: rgba(255,255,255,0.06);
-  border: 1px solid rgba(255,255,255,0.15);
-  color: #e5e7eb;
-  border-radius: 10px;
-  padding: 8px 10px;
-  font-size: 14px;
-  outline: none;
-}
-
-.netintel-btn {
-  cursor: pointer;
-}
-
-.netintel-btn:hover {
-  background: rgba(255,255,255,0.10);
-}
-
-.netintel-card {
-  background: rgba(255,255,255,0.06);
-  border: 1px solid rgba(255,255,255,0.12);
-  border-radius: 16px;
-  padding: 14px;
-  margin-bottom: 14px;
-}
-
-.netintel-card h3 {
-  margin: 0 0 6px;
-  font-size: 16px;
-}
-
-.netintel-card p {
-  margin: 0 0 10px;
-  font-size: 13px;
-  opacity: 0.85;
-}
-
-.netintel-pill {
-  display: inline-flex;
-  align-items: center;
-  padding: 2px 8px;
-  border-radius: 999px;
-  font-size: 12px;
-  background: rgba(255,255,255,0.08);
-  border: 1px solid rgba(255,255,255,0.12);
-  margin-right: 6px;
-  margin-bottom: 6px;
-}
-
-.netintel-tablewrap {
-  overflow: auto;
-  border-radius: 12px;
-  border: 1px solid rgba(255,255,255,0.12);
-}
-
-.netintel-table {
-  width: 100%;
-  border-collapse: collapse;
-  min-width: 700px;
-}
-
-.netintel-table th {
-  text-align: left;
-  font-weight: 600;
-  font-size: 13px;
-  background: rgba(0,0,0,0.25);
-  padding: 10px;
-}
-
-.netintel-table td {
-  padding: 10px;
-  border-top: 1px solid rgba(255,255,255,0.08);
-  font-size: 13px;
-}
-
-.netintel-error {
-  background: rgba(239, 68, 68, 0.12);
-  border: 1px solid rgba(239, 68, 68, 0.25);
-  color: #fecaca;
-  padding: 10px 12px;
-  border-radius: 12px;
-  margin-bottom: 12px;
-}
Index: lan-frontend/src/styles/theme.css
===================================================================
--- lan-frontend/src/styles/theme.css	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,814 +1,0 @@
-/* =========================
-   LAN Security Monitor Theme
-   Dark Navy + Light Blue
-   ========================= */
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-:root{
-  --bg0: #070b16;
-  --bg1: #0a1020;
-  --bg2: #0e1730;
-
-  --panel: rgba(15,23,42,0.62);
-  --panel2: rgba(2,6,23,0.35);
-
-  --border: rgba(96,165,250,0.18);
-  --border2: rgba(147,197,253,0.28);
-
-  --text: rgba(255,255,255,0.92);
-  --muted: rgba(191,219,254,0.82);
-  --dim: rgba(148,163,184,0.85);
-
-  --accent: rgba(96,165,250,0.95);   /* light blue */
-  --accent2: rgba(34,211,238,0.80);  /* cyan */
-  --danger: rgba(239,68,68,0.95);
-  --warn: rgba(245,158,11,0.95);
-  --ok: rgba(34,197,94,0.95);
-
-  --shadow: 0 18px 45px rgba(0,0,0,0.32);
-  --shadow2: 0 10px 25px rgba(0,0,0,0.22);
-
-  --r12: 12px;
-  --r16: 16px;
-  --r18: 18px;
-}
-
-*{ box-sizing: border-box; }
-html, body { height: 100%; }
-body{
-  margin:0;
-  color: var(--text);
-  background:
-    radial-gradient(900px 460px at 20% -10%, rgba(96,165,250,0.18), transparent 60%),
-    radial-gradient(800px 420px at 80% 0%, rgba(34,211,238,0.10), transparent 55%),
-    radial-gradient(1000px 700px at 50% 120%, rgba(30,64,175,0.16), transparent 60%),
-    linear-gradient(180deg, var(--bg0), var(--bg1) 45%, #060913 100%);
-  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
-}
-
-/* Layout */
-.app-wrap{
-  max-width: 1200px;
-  margin: 0 auto;
-  padding: 16px 18px 40px;
-}
-
-/* =========================
-   Panels
-   ========================= */
-.panel{
-  background: var(--panel);
-  border: 1px solid var(--border);
-  border-radius: var(--r18);
-  box-shadow: var(--shadow2);
-  overflow: hidden;
-  backdrop-filter: blur(10px);
-}
-.panel-head{
-  display:flex;
-  align-items:center;
-  justify-content: space-between;
-  gap: 12px;
-  padding: 14px 16px;
-  border-bottom: 1px solid rgba(96,165,250,0.14);
-  background: linear-gradient(180deg, rgba(2,6,23,0.32), rgba(2,6,23,0.12));
-}
-.panel-title{
-  font-weight: 900;
-  letter-spacing: 0.3px;
-  color: rgba(255,255,255,0.95);
-}
-.panel-body{
-  padding: 16px;
-}
-
-/* small helpers */
-.pill{
-  display:inline-flex;
-  align-items:center;
-  gap: 10px;
-  padding: 8px 12px;
-  border-radius: 999px;
-  background: rgba(30,41,59,0.60);
-  border: 1px solid rgba(96,165,250,0.22);
-  color: rgba(255,255,255,0.92);
-  font-weight: 800;
-  font-size: 12px;
-  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
-}
-.small{
-  font-size: 12px;
-  color: var(--dim);
-  line-height: 1.35;
-}
-.dim{ color: var(--dim); font-weight: 700; }
-
-/* =========================
-   Inputs / Selects / Buttons
-   ========================= */
-.input, .select{
-  width: 100%;
-  padding: 10px 12px;
-  border-radius: var(--r12);
-  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;
-  transition: border-color .12s ease, background .12s ease, transform .12s ease;
-}
-.input::placeholder{ color: rgba(148,163,184,0.75); }
-.input:focus, .select:focus{
-  border-color: rgba(96,165,250,0.42);
-  background: rgba(2,6,23,0.46);
-}
-
-.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.92);
-  padding: 9px 12px;
-  border-radius: var(--r12);
-  font-weight: 900;
-  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);
-}
-.btn:hover{
-  transform: translateY(-1px);
-  background: rgba(30,41,59,0.65);
-  border-color: rgba(96,165,250,0.38);
-}
-.btn:disabled{
-  opacity: .6;
-  cursor: not-allowed;
-  transform: none;
-}
-.btn.primary{
-  background: linear-gradient(135deg, rgba(37,99,235,0.95), rgba(56,189,248,0.70));
-  border-color: rgba(147,197,253,0.35);
-  color: rgba(5,12,24,0.95);
-}
-.btn.primary:hover{
-  background: linear-gradient(135deg, rgba(59,130,246,0.95), rgba(34,211,238,0.75));
-}
-
-/* Filters row */
-.filters{
-  display:grid;
-  grid-template-columns: 1.6fr 0.6fr auto;
-  gap: 10px;
-}
-@media (max-width: 820px){
-  .filters{ grid-template-columns: 1fr; }
-}
-
-/* =========================
-   Stats grid
-   ========================= */
-.stats-grid{
-  display: grid;
-  grid-template-columns: repeat(3, minmax(0, 1fr));
-  gap: 12px;
-  margin: 14px 0;
-}
-@media (max-width: 900px){
-  .stats-grid{ grid-template-columns: 1fr; }
-}
-
-.stat-card{
-  background:
-    radial-gradient(500px 180px at 20% 0%, rgba(96,165,250,0.16), transparent 55%),
-    rgba(15,23,42,0.58);
-  border: 1px solid rgba(96,165,250,0.18);
-  border-radius: var(--r18);
-  padding: 14px 16px;
-  box-shadow: var(--shadow2);
-  backdrop-filter: blur(10px);
-}
-.stat-card .label{
-  color: var(--dim);
-  font-size: 12px;
-  font-weight: 900;
-  letter-spacing: .8px;
-  text-transform: uppercase;
-}
-.stat-card .value{
-  font-size: 28px;
-  font-weight: 1000;
-  margin: 8px 0 2px;
-  color: rgba(255,255,255,0.95);
-}
-.stat-card .sub{
-  color: var(--muted);
-  font-size: 12px;
-}
-
-/* =========================
-   Computers grid + Card
-   ========================= */
-.computers-grid{
-  display:grid;
-  grid-template-columns: repeat(3, minmax(0, 1fr));
-  gap: 12px;
-}
-@media (max-width: 980px){
-  .computers-grid{ grid-template-columns: 1fr; }
-}
-
-.card{
-  width:100%;
-  text-align:left;
-  border-radius: var(--r18);
-  border: 1px solid rgba(96,165,250,0.18);
-  background:
-    radial-gradient(520px 220px at 20% 0%, rgba(96,165,250,0.14), transparent 55%),
-    rgba(15,23,42,0.54);
-  box-shadow: var(--shadow2);
-  padding: 14px;
-  cursor: pointer;
-  transition: transform .14s ease, border-color .14s ease, background .14s ease;
-  backdrop-filter: blur(10px);
-}
-.card:hover{
-  transform: translateY(-3px);
-  border-color: rgba(96,165,250,0.38);
-  background:
-    radial-gradient(520px 220px at 20% 0%, rgba(34,211,238,0.12), transparent 55%),
-    rgba(15,23,42,0.62);
-}
-.card:active{ transform: translateY(-1px); }
-
-.card-head{
-  display:flex;
-  justify-content: space-between;
-  align-items:center;
-  gap: 10px;
-  margin-bottom: 10px;
-}
-.card-title{
-  font-weight: 1000;
-  color: rgba(255,255,255,0.95);
-  letter-spacing: .2px;
-}
-.dot{
-  width: 10px;
-  height: 10px;
-  border-radius: 999px;
-  box-shadow: 0 0 0 4px rgba(255,255,255,0.04);
-}
-.dot.online{ background: var(--ok); box-shadow: 0 0 0 4px rgba(34,197,94,0.12); }
-.dot.idle{ background: var(--warn); box-shadow: 0 0 0 4px rgba(245,158,11,0.12); }
-.dot.offline{ background: rgba(148,163,184,0.8); box-shadow: 0 0 0 4px rgba(148,163,184,0.10); }
-
-.meta{
-  display:grid;
-  gap: 6px;
-  color: rgba(226,232,240,0.92);
-  font-size: 13px;
-  line-height: 1.25;
-}
-
-.kpis{
-  display:grid;
-  grid-template-columns: repeat(3, minmax(0, 1fr));
-  gap: 10px;
-  margin-top: 12px;
-  padding-top: 12px;
-  border-top: 1px solid rgba(96,165,250,0.14);
-}
-.kpi{
-  border-radius: var(--r16);
-  border: 1px solid rgba(96,165,250,0.14);
-  background: rgba(2,6,23,0.30);
-  padding: 10px 10px;
-}
-.kpi .k{
-  font-size: 11px;
-  color: var(--dim);
-  font-weight: 900;
-  letter-spacing: .6px;
-  text-transform: uppercase;
-}
-.kpi .v{
-  margin-top: 6px;
-  font-size: 18px;
-  font-weight: 1000;
-  color: rgba(255,255,255,0.95);
-}
-
-/* =========================
-   Modal (shared classes)
-   ========================= */
-.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: 60;
-}
-.modal{
-  width: min(920px, 96vw);
-  border-radius: var(--r18);
-  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;
-}
-.modal-head{
-  padding: 14px 16px;
-  display:flex;
-  align-items:center;
-  justify-content: space-between;
-  gap: 10px;
-  border-bottom: 1px solid rgba(96,165,250,0.18);
-}
-.modal-title{
-  font-weight: 1000;
-  color: rgba(255,255,255,0.95);
-  letter-spacing: .2px;
-}
-.modal-sub{
-  margin-top: 2px;
-  font-size: 12px;
-  color: var(--muted);
-}
-
-/* If you have drawer classes */
-.ai-drawer{
-  position: fixed;
-  top: 0;
-  right: 0;
-  height: 100vh;
-  width: min(420px, 92vw);
-  background:
-    radial-gradient(700px 400px at 20% 0%, rgba(96,165,250,0.18), transparent 55%),
-    rgba(10,16,32,0.94);
-  border-left: 1px solid rgba(96,165,250,0.20);
-  box-shadow: -20px 0 60px rgba(0,0,0,0.35);
-  z-index: 70;
-  padding: 14px;
-}
-.ai-header{
-  font-weight: 1000;
-  color: rgba(255,255,255,0.95);
-  margin-bottom: 10px;
-}
-.ai-messages{
-  height: calc(100vh - 160px);
-  overflow: auto;
-  border-radius: var(--r16);
-  border: 1px solid rgba(96,165,250,0.14);
-  background: rgba(2,6,23,0.28);
-  padding: 10px;
-}
-.ai-msg{
-  color: rgba(226,232,240,0.92);
-  font-size: 13px;
-  line-height: 1.35;
-  padding: 10px;
-  border-radius: 12px;
-  border: 1px solid rgba(96,165,250,0.12);
-  background: rgba(15,23,42,0.45);
-  margin-bottom: 10px;
-}
-.ai-input{
-  width: 100%;
-  margin-top: 10px;
-  padding: 10px 12px;
-  border-radius: var(--r12);
-  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;
-}
-.ai-input::placeholder{ color: rgba(148,163,184,0.75); }
-
-/* Scrollbars (nice) */
-*::-webkit-scrollbar{ width: 10px; height: 10px; }
-*::-webkit-scrollbar-thumb{
-  background: rgba(96,165,250,0.20);
-  border: 2px solid rgba(2,6,23,0.35);
-  border-radius: 999px;
-}
-*::-webkit-scrollbar-track{ background: rgba(2,6,23,0.25); }
-.aiDrawer-backdrop{
-  position: fixed;
-  inset: 0;
-  background: rgba(0,0,0,.55);
-  backdrop-filter: blur(2px);
-  z-index: 999;
-}
-
-/* десен drawer */
-.aiDrawer{
-  position: fixed;
-  top: 0;
-  right: 0;
-  height: 100vh;
-  width: min(520px, 92vw);
-  background: #0b1220;            /* темна */
-  color: #e7eefc;
-  border-left: 1px solid rgba(255,255,255,.08);
-  z-index: 1000;
-  display: grid;
-  grid-template-rows: auto auto 1fr auto;
-
-  /* slide in */
-  transform: translateX(0);
-  animation: aiSlideIn .18s ease-out;
-}
-
-@keyframes aiSlideIn{
-  from { transform: translateX(24px); opacity: .7; }
-  to   { transform: translateX(0); opacity: 1; }
-}
-
-.aiDrawer-head{
-  padding: 14px 16px;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  border-bottom: 1px solid rgba(255,255,255,.08);
-}
-
-.aiDrawer-title{
-  font-size: 16px;
-  font-weight: 700;
-}
-
-.aiDrawer-sub{
-  font-size: 12px;
-  opacity: .75;
-  margin-top: 2px;
-}
-
-.aiDrawer-toolbar{
-  padding: 12px 16px;
-  border-bottom: 1px solid rgba(255,255,255,.08);
-  display: grid;
-  gap: 8px;
-}
-
-.aiSelect{
-  width: 100%;
-  background: rgba(255,255,255,.06);
-  border: 1px solid rgba(255,255,255,.10);
-  color: #e7eefc;
-  padding: 10px 12px;
-  border-radius: 10px;
-  outline: none;
-}
-
-.aiHint{
-  font-size: 12px;
-  opacity: .75;
-}
-
-.aiDrawer-body{
-  padding: 14px 16px;
-  overflow: auto;
-  display: grid;
-  gap: 10px;
-}
-
-/* chat bubbles */
-.aiMsg{
-  max-width: 92%;
-  border: 1px solid rgba(255,255,255,.08);
-  border-radius: 14px;
-  padding: 10px 12px;
-  background: rgba(255,255,255,.04);
-}
-
-.aiMsg.user{
-  margin-left: auto;
-  background: rgba(88, 167, 255, .12);
-  border-color: rgba(88, 167, 255, .20);
-}
-
-.aiMsg-meta{
-  font-size: 11px;
-  opacity: .7;
-  margin-bottom: 6px;
-}
-
-.aiMsg-text{
-  white-space: pre-wrap;
-  line-height: 1.35;
-  font-size: 14px;
-}
-
-.aiDrawer-foot{
-  padding: 12px 16px;
-  border-top: 1px solid rgba(255,255,255,.08);
-  display: grid;
-  grid-template-columns: 1fr auto;
-  gap: 10px;
-  align-items: end;
-  background: rgba(255,255,255,.02);
-}
-
-.aiTextarea{
-  width: 100%;
-  resize: none;
-  background: rgba(255,255,255,.06);
-  border: 1px solid rgba(255,255,255,.10);
-  color: #e7eefc;
-  padding: 10px 12px;
-  border-radius: 12px;
-  outline: none;
-}
-
-.aiBtn{
-  background: rgba(255,255,255,.06);
-  border: 1px solid rgba(255,255,255,.10);
-  color: #e7eefc;
-  padding: 10px 12px;
-  border-radius: 12px;
-  cursor: pointer;
-}
-
-.aiBtn:disabled{
-  opacity: .5;
-  cursor: not-allowed;
-}
-
-.aiBtnPrimary{
-  background: rgba(88, 167, 255, .22);
-  border-color: rgba(88, 167, 255, .35);
-}
-
-/* ====== LAYOUT with LEFT SIDEBAR ====== */
-.app-shell {
-  display: grid;
-  grid-template-columns: 240px 1fr;
-  min-height: 100vh;
-}
-
-.app-main {
-  padding: 14px;
-}
-
-/* Sidebar */
-.side {
-  background: rgba(255,255,255,0.04);
-  border-right: 1px solid rgba(255,255,255,0.10);
-  padding: 14px;
-  position: sticky;
-  top: 0;
-  height: 100vh;
-}
-
-.side-brand {
-  display: flex;
-  gap: 10px;
-  align-items: center;
-  padding: 10px;
-  border: 1px solid rgba(255,255,255,0.10);
-  border-radius: 14px;
-  background: rgba(0,0,0,0.12);
-  margin-bottom: 14px;
-}
-
-.side-brand-dot {
-  width: 10px;
-  height: 10px;
-  border-radius: 999px;
-  background: #7dd3fc;
-  box-shadow: 0 0 0 4px rgba(125,211,252,0.15);
-}
-
-.side-brand-title {
-  font-weight: 700;
-  letter-spacing: 0.2px;
-}
-
-.side-brand-sub {
-  font-size: 12px;
-  color: var(--dim);
-}
-
-.side-section-title {
-  font-size: 11px;
-  color: var(--dim);
-  margin: 12px 8px 8px;
-  letter-spacing: 0.12em;
-}
-
-.side-item {
-  width: 100%;
-  display: flex;
-  align-items: center;
-  gap: 10px;
-  padding: 10px 12px;
-  border-radius: 12px;
-  border: 1px solid transparent;
-  background: transparent;
-  color: inherit;
-  cursor: pointer;
-  text-align: left;
-}
-
-.side-item:hover {
-  background: rgba(255,255,255,0.06);
-  border-color: rgba(255,255,255,0.08);
-}
-
-.side-item.active {
-  background: rgba(125,211,252,0.12);
-  border-color: rgba(125,211,252,0.25);
-}
-
-.side-icon {
-  width: 26px;
-  display: grid;
-  place-items: center;
-  font-size: 18px;
-}
-
-.side-label {
-  font-size: 14px;
-  font-weight: 600;
-}
-
-.side-footer {
-  position: absolute;
-  bottom: 12px;
-  left: 14px;
-  right: 14px;
-  padding: 10px;
-  border-radius: 12px;
-  border: 1px solid rgba(255,255,255,0.10);
-  background: rgba(0,0,0,0.12);
-}
-
-.side-footer-hint {
-  font-size: 12px;
-  color: var(--dim);
-}
-/* ==== Collapsible Side Menu ==== */
-.side {
-  position: sticky;
-  top: 0;
-  height: 100vh;
-  z-index: 20;
-
-  width: 260px;
-  transition: width 180ms ease;
-  border-right: 1px solid rgba(255,255,255,0.08);
-  background: rgba(6, 12, 24, 0.72);
-  backdrop-filter: blur(10px);
-}
-
-.side.collapsed {
-  width: 54px; /* само тенка лента */
-}
-
-.side-inner {
-  height: 100%;
-  padding: 14px 10px;
-  display: flex;
-  flex-direction: column;
-}
-
-.side-handle {
-  position: absolute;
-  left: 6px;
-  top: 10px;
-  width: 42px;
-  height: 34px;
-  border-radius: 12px;
-  border: 1px solid rgba(255,255,255,0.12);
-  background: rgba(255,255,255,0.06);
-  color: rgba(255,255,255,0.9);
-  cursor: pointer;
-}
-
-.side-brand {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-  padding: 10px;
-  margin-top: 38px; /* за да не се судри со handle */
-  border-radius: 16px;
-  background: rgba(255,255,255,0.03);
-  border: 1px solid rgba(255,255,255,0.06);
-}
-
-.side-brand-dot {
-  width: 12px;
-  height: 12px;
-  border-radius: 999px;
-  background: rgba(80, 160, 255, 0.95);
-  box-shadow: 0 0 18px rgba(80,160,255,0.45);
-}
-
-.side-brand-text { min-width: 0; }
-.side-brand-title { font-weight: 800; color: rgba(255,255,255,0.92); }
-.side-brand-sub { font-size: 12px; color: rgba(255,255,255,0.55); }
-
-.side-section { margin-top: 14px; }
-.side-section-title {
-  font-size: 11px;
-  letter-spacing: 0.12em;
-  color: rgba(255,255,255,0.45);
-  padding: 10px;
-}
-
-.side-item {
-  width: 100%;
-  display: flex;
-  align-items: center;
-  gap: 10px;
-  padding: 10px 12px;
-  margin: 6px 0;
-  border-radius: 14px;
-  border: 1px solid transparent;
-  background: transparent;
-  color: rgba(255,255,255,0.82);
-  cursor: pointer;
-  text-align: left;
-}
-
-.side-item:hover {
-  background: rgba(255,255,255,0.06);
-  border-color: rgba(255,255,255,0.08);
-}
-
-.side-item.active {
-  background: rgba(80,160,255,0.16);
-  border-color: rgba(80,160,255,0.25);
-}
-
-.side-icon {
-  width: 28px;
-  display: grid;
-  place-items: center;
-  font-size: 18px;
-}
-
-.side-label {
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-.side-footer {
-  margin-top: auto;
-  padding: 10px;
-  color: rgba(255,255,255,0.55);
-  font-size: 12px;
-}
-.layout { display: flex; }
-.content { flex: 1; min-width: 0; }
-/* collapsed = центрирање иконите + тргање празни простори */
-.side.collapsed .side-inner {
-  padding: 10px 6px;
-  align-items: center;
-}
-
-.side.collapsed .side-brand {
-  width: 42px;
-  padding: 10px 0;
-  justify-content: center;
-  margin-top: 48px;
-}
-
-.side.collapsed .side-item {
-  justify-content: center;
-  padding: 10px 0;
-}
-
-.side.collapsed .side-icon {
-  width: 42px;
-}
-
-/* handle во средина (поубаво) */
-.side-handle {
-  left: 6px;
-  top: 10px;
-  width: 42px;
-  height: 34px;
-}
-
-/* кога е open да си е нормално */
-.side.open .side-inner {
-  align-items: stretch;
-}
-
-.cont{
-  margin-right: 50px;
-}
Index: lan-frontend/tailwind.config.js
===================================================================
--- lan-frontend/tailwind.config.js	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ 	(revision )
@@ -1,11 +1,0 @@
-/** @type {import('tailwindcss').Config} */
-export default {
-  content: [
-    "./index.html",
-    "./src/**/*.{js,ts,jsx,tsx}",
-  ],
-  theme: {
-    extend: {},
-  },
-  plugins: [],
-}
Index: lan-frontend/vite.config.js
===================================================================
--- lan-frontend/vite.config.js	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ lan-frontend/vite.config.js	(revision 640ed89213ad0086dc8379de00b8d102057ead1f)
@@ -1,33 +1,11 @@
-// import { defineConfig } from "vite";
-// import react from "@vitejs/plugin-react";
-//
-// export default defineConfig({
-//   plugins: [react()],
-//   server: {
-//     proxy: {
-//       "/api": { target: "http://localhost:5555", changeOrigin: true },
-//       "/receive": { target: "http://localhost:5555", changeOrigin: true },
-//       "/ping": { target: "http://localhost:5555", changeOrigin: true },
-//     },
-//   },
-// });
-import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
 
-export default defineConfig({
-  plugins: [react()],
+// https://vite.dev/config/
+export default {
   server: {
     proxy: {
-      "/api": {
-        target: "http://localhost:5555",
-        changeOrigin: true,
-        secure: false,
-      },
-      "/receive": {
-        target: "http://localhost:5555",
-        changeOrigin: true,
-        secure: false,
-      },
+      "/api": "http://localhost:5555",
     },
   },
-});
+};
