{"vulnerability": "cve-2024-23225", "sightings": [{"uuid": "2a1d4941-11eb-4b79-a1e5-d6e3636e9564", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "MISP/3c19819c-1dac-4ef2-bfed-be5efa7e0123", "content": "", "creation_timestamp": "2024-03-06T18:10:03.000000Z"}, {"uuid": "97a69ee3-daa0-4b87-8801-dfb5936b0d71", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "MISP/3c19819c-1dac-4ef2-bfed-be5efa7e0123", "content": "", "creation_timestamp": "2025-02-23T02:10:53.000000Z"}, {"uuid": "9047a4a2-9760-49a1-94b7-f524c76b9aea", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "86ecb4e1-bb32-44d5-9f39-8a4673af8385", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://www.govcert.gov.hk/en/alerts_detail.php?id=1239", "content": "", "creation_timestamp": "2024-03-06T04:00:00.000000Z"}, {"uuid": "0cb27dc8-4a5d-4efa-84e0-e34f17f5b78d", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "af0120d0-3dac-4a6a-974b-a9f33d2a9846", "vulnerability": "CVE-2024-23225", "type": "exploited", "source": "https://vulnerability.circl.lu/known-exploited-vulnerabilities-catalog/56989578-546c-4e7a-a0a7-8e519876753e", "content": "", "creation_timestamp": "2026-02-02T12:26:40.239191Z"}, {"uuid": "7771e265-ee40-4ee1-a44d-d014dcfdb63d", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "exploited", "source": "Telegram/Znrun3cJvmmTZztqyXAOifqHTaymKMbuq4371VKXVg_P-g", "content": "", "creation_timestamp": "2024-03-06T09:06:41.000000Z"}, {"uuid": "ea208207-b207-4fce-ab39-f9daf792eb20", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "exploited", "source": "https://t.me/arpsyndicate/4124", "content": "#ExploitObserverAlert\n\nCVE-2024-23225\n\nDESCRIPTION: Exploit Observer has 3 entries in 1 file formats related to CVE-2024-23225. A memory corruption issue was addressed with improved validation. This issue is fixed in iOS 16.7.6 and iPadOS 16.7.6, iOS 17.4 and iPadOS 17.4. An attacker with arbitrary kernel read and write capability may be able to bypass kernel memory protections. Apple is aware of a report that this issue may have been exploited.", "creation_timestamp": "2024-03-06T16:26:56.000000Z"}, {"uuid": "94ac3c10-1f43-4eb9-bf46-31f3ef7c6f13", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "Telegram/L8wMfvEtFuov0vQZa6a5kvrLN1U3VeODaVHdB5RXvGt9ow", "content": "", "creation_timestamp": "2024-03-06T10:04:37.000000Z"}, {"uuid": "2b083ea7-90f8-4797-9182-6f596e7f4d3e", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://t.me/KomunitiSiber/1589", "content": "Urgent: Apple Issues Critical Updates for Actively Exploited Zero-Day Flaws\nhttps://thehackernews.com/2024/03/urgent-apple-issues-critical-updates.html\n\nApple has released security updates to address several security flaws, including two vulnerabilities that it said have been actively exploited in the wild.\nThe shortcomings are listed below -\n\nCVE-2024-23225\u00a0- A memory corruption issue in Kernel that an attacker with arbitrary kernel read and write capability can exploit to bypass kernel memory protections\n\n\nCVE-2024-23296\u00a0- A memory", "creation_timestamp": "2024-03-06T06:58:32.000000Z"}, {"uuid": "425e8cd4-6558-4668-b09d-0b64232857c3", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://t.me/true_secator/6472", "content": "\u0418\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0438\u0437 \u043a\u043e\u043c\u0430\u043d\u0434\u044b GReAT \u041b\u0430\u0431\u043e\u0440\u0430\u0442\u043e\u0440\u0438\u0438 \u041a\u0430\u0441\u043f\u0435\u0440\u0441\u043a\u043e\u0433\u043e \u043f\u043e\u0434\u044b\u0442\u043e\u0436\u0438\u043b\u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0441\u0432\u043e\u0435\u0439 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438 \u043f\u043e \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430\u043c \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043b\u0430\u043d\u0434\u0448\u0430\u0444\u0442\u0430 \u0443\u0433\u0440\u043e\u0437, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c\u044e APT \u0432 2024 \u0433\u043e\u0434\u0443.\n\n\u0412 \u043f\u043e\u043b\u0435 \u0437\u0440\u0435\u043d\u0438\u044f \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043d\u0430\u0445\u043e\u0434\u0438\u043b\u043e\u0441\u044c \u0431\u043e\u043b\u0435\u0435 900 \u0433\u0440\u0443\u043f\u043f. \u041f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0441\u0435 \u0432\u0435\u0440\u0434\u0438\u043a\u0442\u044b \u043f\u043e \u0437\u0430\u044f\u0432\u043b\u0435\u043d\u043d\u044b\u043c \u0432 \u043a\u043e\u043d\u0446\u0435 \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e \u0433\u043e\u0434\u0430 \u0442\u0440\u0435\u043d\u0434\u0430\u043c - \u0432 \u043f\u043e\u043b\u044c\u0437\u0443 \u041b\u041a: \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u044b \u0432 \u0446\u0435\u043b\u043e\u043c \u0441\u0431\u044b\u043b\u0438\u0441\u044c.\n\n\u0415\u0441\u043b\u0438 \u0432\u043a\u0440\u0430\u0442\u0446\u0435, \u0442\u043e \u044d\u043a\u0441\u043f\u0435\u0440\u0442\u0430\u043c \u041b\u0430\u0431\u043e\u0440\u0430\u0442\u043e\u0440\u0438\u0438 \u041a\u0430\u0441\u043f\u0435\u0440\u0441\u043a\u043e\u0433\u043e \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u0435\u0434\u0443\u0433\u0430\u0434\u0430\u0442\u044c, \u0447\u0442\u043e:\n\n1. \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u043b\u043e\u0441\u044c \u0447\u0438\u0441\u043b\u043e \u0438\u0437\u043e\u0431\u0440\u0435\u0442\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0430\u0442\u0430\u043a \u043d\u0430 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0435, \u043d\u043e\u0441\u0438\u043c\u044b\u0435 \u0438 \u0443\u043c\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u0412\u0441\u043b\u0435\u0434 \u0437\u0430 \u041e\u043f\u0435\u0440\u0430\u0446\u0438\u0435\u0439 \u0422\u0440\u0438\u0430\u043d\u0433\u0443\u043b\u044f\u0446\u0438\u0435\u0439 \u0430\u0442\u0430\u043a\u0438 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u044d\u043a\u0441\u043f\u043b\u043e\u0439\u0442\u043e\u0432 \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 Apple \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043b\u0438\u0441\u044c \u0438 \u0432 2024\u00a0\u0433\u043e\u0434\u0443.\n\n\u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0432 \u044f\u043d\u0432\u0430\u0440\u0435 \u0432 \u043a\u0438\u0431\u0435\u0440\u0430\u0442\u0430\u043a\u0430\u0445 \u043c\u043e\u0433\u043b\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u044c\u00a0CVE-2024-23222\u00a0\u0432 \u0434\u0432\u0438\u0436\u043a\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 Safari, \u0430 \u043e\u0441\u0435\u043d\u044c\u044e \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e\u00a0\u0434\u0432\u0443\u0445 \u043d\u043e\u0432\u044b\u0445 \u044d\u043a\u0441\u043f\u043b\u043e\u0439\u0442\u0430\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435, \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0432 \u0434\u0438\u043a\u043e\u0439 \u043f\u0440\u0438\u0440\u043e\u0434\u0435: CVE-2024-23225 \u0432 \u044f\u0434\u0440\u0435 XNU \u0438 CVE-2024-23296 \u0432 RTKit.\n\n\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0430 \u0431\u0430\u0437\u0435 Android \u0442\u0430\u043a\u0436\u0435 \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0432 \u0444\u043e\u043a\u0443\u0441\u0435 APT-\u0433\u0440\u0443\u043f\u043f.\u00a0\u0412 \u043d\u043e\u044f\u0431\u0440\u0435 Google \u0441\u043e\u043e\u0431\u0449\u0438\u043b\u0430 \u043e \u0434\u0432\u0443\u0445 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u044f\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u043b\u0438 \u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u0446\u0435\u043b\u0435\u0432\u044b\u0445 \u0430\u0442\u0430\u043a\u0430\u0445:\u00a0CVE-2024-43093 \u0438 CVE-2024-43047.\u00a0\n\n2. \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0440\u0435\u0439\u0434\u044b \u043f\u043e \u043d\u0435\u0439\u0442\u0440\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u04212 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u043f\u0440\u0438\u0432\u0435\u043b\u0438 \u043a \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 APT-\u0433\u0440\u0443\u043f\u043f\u044b \u043d\u0430\u0447\u0430\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0431\u043e\u0442\u043d\u0435\u0442\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043a\u0438\u0431\u0435\u0440\u0430\u0442\u0430\u043a.\n\n\u0412 2024\u00a0\u0433\u043e\u0434\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0438\u0442\u0430\u0435\u044f\u0437\u044b\u0447\u043d\u044b\u0445 \u0433\u0440\u0443\u043f\u043f \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u043b\u0438 \u0431\u043e\u0442\u043d\u0435\u0442\u044b \u0434\u043b\u044f \u0446\u0435\u043b\u0435\u0432\u044b\u0445 \u0430\u0442\u0430\u043a. \u041e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445 -\u00a0Quad7 \u043d\u0430 \u0431\u0430\u0437\u0435 \u0441\u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0440\u043e\u0443\u0442\u0435\u0440\u043e\u0432, \u0434\u0440\u0443\u0433\u043e\u0439 -\u00a0KV-Botnet, \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044b\u0439 \u043d\u0430 \u0443\u044f\u0437\u0432\u0438\u043c\u044b\u0445 \u043c\u0435\u0436\u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u044d\u043a\u0440\u0430\u043d\u0430\u0445, \u0440\u043e\u0443\u0442\u0435\u0440\u0430\u0445 \u0438 IP-\u043a\u0430\u043c\u0435\u0440\u0430\u0445.\n\n3. \u0421\u0442\u0430\u043b\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u0430\u0442\u0430\u043a \u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435\u043c \u043a\u043e\u0434\u0430 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u044f\u0434\u0440\u0430 (\u0440\u0443\u0442\u043a\u0438\u0442\u044b \u0440\u0435\u0436\u0438\u043c\u0430 \u044f\u0434\u0440\u0430 \u0441\u043d\u043e\u0432\u0430 \u0432 \u0434\u0435\u043b\u0435). \u0412 2024\u00a0\u0433\u043e\u0434\u0443 \u0442\u0435\u0445\u043d\u0438\u043a\u0430 BYOVD, \u043e\u0441\u0442\u0430\u0432\u0430\u044f\u0441\u044c \u0441\u0430\u043c\u044b\u043c \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u044f\u0434\u0440\u0443, \u0438 \u0441\u0442\u0430\u043b\u0430 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u0434\u0430\u0436\u0435 \u0447\u0430\u0449\u0435 (\u0440\u043e\u0441\u0442 \u043d\u0430 23%). \n\n4. \u0421 \u043a\u0430\u0436\u0434\u044b\u043c \u0433\u043e\u0434\u043e\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0430\u0442\u0430\u043a, \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u043c\u044b\u0445 \u043e\u043f\u044b\u0442\u043d\u044b\u043c\u0438 \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u0430\u043c\u0438, \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u0442 \u0440\u0430\u0441\u0442\u0438, \u0438 2024\u00a0\u0433\u043e\u0434 \u043d\u0435 \u0441\u0442\u0430\u043b \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c. \u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0435 \u044d\u0442\u043e\u0433\u043e \u0433\u043e\u0434\u0430\u00a0\u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u00a0\u0440\u043e\u0441\u0442 \u043d\u0430 25% \u0447\u0438\u0441\u043b\u0430 APT-\u0430\u0442\u0430\u043a.\n\n5. \u041a\u0430\u043a \u043c\u044b \u0438 \u043e\u0436\u0438\u0434\u0430\u043b\u043e\u0441\u044c, \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u043b\u0441\u044f \u0440\u043e\u0441\u0442 \u0447\u0438\u0441\u043b\u0430 \u0430\u0442\u0430\u043a \u0445\u0430\u043a\u0442\u0438\u0432\u0438\u0441\u0442\u0441\u043a\u0438\u0445 \u0433\u0440\u0443\u043f\u043f \u043d\u0430 \u0444\u043e\u043d\u0435 \u0433\u0435\u043e\u043f\u043e\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u043e\u0432. \u041e\u0442\u043c\u0435\u0442\u0438\u043b\u0438\u0441\u044c:\u00a0Twelve, Head Mare \u0438 Crypt Ghouls, BlackMeta.\n\n6. \u0421 \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0418\u0418 \u043c\u043d\u043e\u0433\u0438\u0435 \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u0438, \u043a\u0430\u043a \u0444\u0438\u043d\u0430\u043d\u0441\u043e\u0432\u043e \u043c\u043e\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435, \u0442\u0430\u043a \u0438 \u0441\u043f\u043e\u043d\u0441\u0438\u0440\u0443\u0435\u043c\u044b\u0435 \u0433\u043e\u0441\u0443\u0434\u0430\u0440\u0441\u0442\u0432\u0430\u043c\u0438, \u043d\u0430\u0447\u0430\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u0443 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044e \u0434\u043b\u044f \u043f\u043e\u0432\u044b\u0448\u0435\u043d\u0438\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438 \u0441\u0432\u043e\u0438\u0445 \u0430\u0442\u0430\u043a. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443 -\u00a0\u043d\u0435\u0443\u0434\u0430\u0447\u043d\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0430\u0446\u0438\u0438 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 KnowBe4.\n\n7. \u041d\u043e\u0432\u044b\u0445 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u043d\u044b\u0445 \u0430\u0442\u0430\u043a \u0447\u0435\u0440\u0435\u0437 MFT-\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043d\u0435 \u0437\u0430\u043c\u0435\u0447\u0435\u043d\u043e, \u043d\u043e \u044d\u043a\u0441\u043f\u0435\u0440\u0442\u044b \u0442\u0430\u043a\u0436\u0435 \u0432\u044b\u044f\u0432\u0438\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u0445 MFT: CVE-2024-0204 \u0441 \u043e\u0431\u0445\u043e\u0434\u043e\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432 GoAnywhere MFT \u0438 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u0430\u044f CVE-2024-5806 \u0432 MOVEit Transfer.\n\n\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u044b \u043f\u043e \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u043c \u0443\u0433\u0440\u043e\u0437\u0430\u043c \u043d\u0430 2025\u00a0\u0433\u043e\u0434 \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0442:\n\n- \u0410\u043b\u044c\u044f\u043d\u0441\u044b \u0445\u0430\u043a\u0442\u0438\u0432\u0438\u0441\u0442\u043e\u0432 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442 \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0442\u044c\u0441\u044f\n- APT-\u0433\u0440\u0443\u043f\u043f\u044b \u0431\u0443\u0434\u0443\u0442 \u0447\u0430\u0449\u0435 \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u0442\u044c \u0430\u0442\u0430\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 IoT\n- \u0420\u043e\u0441\u0442 \u0447\u0438\u0441\u043b\u0430 \u0430\u0442\u0430\u043a \u043d\u0430 \u0446\u0435\u043f\u043e\u0447\u043a\u0443 \u043f\u043e\u0441\u0442\u0430\u0432\u043e\u043a \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0441 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c\n- \u041f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u043b\u043e\u0432\u0440\u0435\u0434\u043e\u0432 \u043d\u0430 \u044f\u0437\u044b\u043a\u0430\u0445 C++ \u0438 Go \u0434\u043b\u044f \u0430\u0434\u0430\u043f\u0442\u0430\u0446\u0438\u0438 \u043a \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c \u0441 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c\n- \u0411\u043e\u043b\u0435\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0418\u0418 \u0433\u0440\u0443\u043f\u043f\u0430\u043c\u0438, \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u043c\u0438 \u043f\u0440\u0438 \u0433\u043e\u0441\u0443\u0434\u0430\u0440\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0435\n- APT-\u0433\u0440\u0443\u043f\u043f\u044b \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u0438\u043f\u0444\u0435\u0439\u043a\u0430\u043c\u0438\n- \u0411\u044d\u043a\u0434\u043e\u0440\u044b \u0432 \u043c\u043e\u0434\u0435\u043b\u044f\u0445 \u0418\u0418\n- \u0420\u043e\u0441\u0442 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u043e\u0441\u0442\u0438 \u044d\u043a\u0441\u043f\u043b\u043e\u0439\u0442\u043e\u0432 \u0442\u0438\u043f\u0430 BYOVD \u0432 APT-\u043a\u0430\u043c\u043f\u0430\u043d\u0438\u044f\u0445\n\n\u0411\u0443\u0434\u0435\u043c \u0441\u043b\u0435\u0434\u0438\u0442\u044c.", "creation_timestamp": "2024-11-26T11:40:16.000000Z"}, {"uuid": "e9f44b78-d396-4f41-b559-7cb025c2d0b5", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://t.me/true_secator/5492", "content": "\u041f\u043e\u0434\u043a\u0430\u0442\u0438\u043b\u043e \u044d\u043a\u0441\u0442\u0440\u0435\u043d\u043d\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442 Apple \u0434\u043b\u044f iOS \u0441 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043c\u0438 \u0434\u0432\u0443\u0445 0-day, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0432 \u0446\u0435\u043b\u0435\u0432\u044b\u0445 \u0430\u0442\u0430\u043a\u0430\u0445 \u043d\u0430 iPhone, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445, \u043f\u043e \u0432\u0441\u0435\u0439 \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438, \u0441\u043e spyware.\n\n\u0427\u0438\u0441\u043b\u043e \u043d\u0443\u043b\u0435\u0439 \u0432 \u043f\u043e\u0441\u043b\u0443\u0436\u043d\u043e\u043c \u0441\u043f\u0438\u0441\u043a\u0435 \u0437\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0433\u043e\u0434 \u0443 Apple \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u043f\u043e\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f, \u043f\u043e\u043a\u0430, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u0434\u043e \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e\u0434\u043d\u0435\u0433\u043e \u0440\u0435\u043a\u043e\u0440\u0434\u0430 \u0432 20 \u0435\u0449\u0435 \u0434\u0430\u043b\u0435\u043a\u043e, \u043d\u043e \u0442\u0435\u043c\u043f \u0437\u0430\u0434\u0430\u043d.\n\n\u041d\u043e\u0432\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u0431\u044b\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0432 \u044f\u0434\u0440\u0435 iOS (CVE-2024-23225) \u0438 RTKit (CVE-2024-23296) \u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u0430\u043c \u0441 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u043c\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0447\u0442\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u043f\u0438\u0441\u0438 \u044f\u0434\u0440\u0430 \u043e\u0431\u043e\u0439\u0442\u0438 \u0437\u0430\u0449\u0438\u0442\u0443 \u043f\u0430\u043c\u044f\u0442\u0438 \u044f\u0434\u0440\u0430.\n\n\u041a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u0437\u0430\u044f\u0432\u043b\u044f\u0435\u0442, \u0447\u0442\u043e \u0443\u0441\u0442\u0440\u0430\u043d\u0438\u043b\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u043f\u043e\u0434 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c iOS 17.4, iPadOS 17.4, iOS 16.76 \u0438 iPad 16.7.6 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u043d\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0432\u0432\u043e\u0434\u0430.\n\n\u0421\u043f\u0438\u0441\u043e\u043a \u0437\u0430\u0442\u0440\u043e\u043d\u0443\u0442\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 Apple \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0442\u0430\u043a\u0438 \u043e\u0431\u0448\u0438\u0440\u0435\u043d \u0438 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u0432\u0441\u0435 \u043b\u0438\u043d\u0435\u0439\u043a\u0438 iPhone XS, iPhone 8, iPhone X, iPad 5-\u0433\u043e \u043f\u043e\u043a\u043e\u043b\u0435\u043d\u0438\u044f, iPad Pro 9,7 \u0434\u044e\u0439\u043c\u0430 \u0438 iPad Pro 12,9 \u0434\u044e\u0439\u043c\u0430 1-\u0433\u043e \u0438 2-\u0433\u043e \u043f\u043e\u043a\u043e\u043b\u0435\u043d\u0438\u0439, iPad Pro 10,5 \u0434\u044e\u0439\u043c\u0430, iPad Pro 11 \u0434\u044e\u0439\u043c\u043e\u0432 1-\u0433\u043e \u043f\u043e\u043a\u043e\u043b\u0435\u043d\u0438\u044f, iPad Air 3-\u0433\u043e \u043f\u043e\u043a\u043e\u043b\u0435\u043d\u0438\u044f, iPad 6-\u0433\u043e \u043f\u043e\u043a\u043e\u043b\u0435\u043d\u0438\u044f, iPad mini 5-\u0433\u043e \u043f\u043e\u043a\u043e\u043b\u0435\u043d\u0438\u044f (\u0438 \u043d\u043e\u0432\u0435\u0435).\n\nApple \u0432 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u043c\u0430\u043d\u0435\u0440\u0435 \u043d\u0435 \u0441\u043e\u043e\u0431\u0449\u0438\u043b\u0430, \u043a\u0442\u043e \u0441\u0442\u043e\u0438\u0442 \u0437\u0430 \u0440\u0430\u0441\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0438 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u043e\u0434\u043e\u0432\u0430\u043b\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0435 \u0438 \u043e\u0431\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u0430\u0445 \u0430\u0442\u0430\u043a \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c 0-day.\n\n\u041d\u043e \u043d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u044d\u0442\u043e \u043d\u0430\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0441\u043a\u043e\u0440\u0435\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f.", "creation_timestamp": "2024-03-06T12:35:01.000000Z"}, {"uuid": "4331068a-53cd-46e3-be26-48ac5f946c5d", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://t.me/ctinow/212663", "content": "https://ift.tt/Wtzd5mq\nCVE-2024-23225 Exploitation", "creation_timestamp": "2024-03-24T21:16:58.000000Z"}, {"uuid": "dd05968a-2b92-4595-a100-a80bce2428ad", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://t.me/ctinow/200713", "content": "https://ift.tt/UL10s3A\nCVE-2024-23225", "creation_timestamp": "2024-03-05T21:26:45.000000Z"}, {"uuid": "ca96001c-74b3-4803-a02a-c2ef82a92ad2", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "exploited", "source": "https://t.me/ctinow/201195", "content": "https://ift.tt/xC7bHAk\nApple fixes two actively exploited iOS zero-days (CVE-2024-23225, CVE-2024-23296) - Help Net Security", "creation_timestamp": "2024-03-06T10:41:44.000000Z"}, {"uuid": "8ad80a25-f65e-4c9b-b317-23b481bd688f", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "exploited", "source": "https://t.me/ctinow/201219", "content": "https://ift.tt/xC7bHAk\nApple fixes two actively exploited iOS zero-days (CVE-2024-23225, CVE-2024-23296)", "creation_timestamp": "2024-03-06T11:36:21.000000Z"}, {"uuid": "5e1c5247-0e1a-4f86-8de5-eebd9f145257", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://t.me/ctinow/200704", "content": "https://ift.tt/UL10s3A\nCVE-2024-23225", "creation_timestamp": "2024-03-05T21:26:33.000000Z"}, {"uuid": "6e6d0308-f662-48dd-b388-eb8a80f19259", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "exploited", "source": "https://t.me/thehackernews/4639", "content": "\ud83d\udea8 Heads up, Apple users! \n \nApple rolls out crucial updates for iOS &amp; iPadOS to patch actively exploited vulnerabilities CVE-2024-23225 &amp; CVE-2024-23296, enhancing kernel memory protection. \n \nEnsure your devices are updated: https://thehackernews.com/2024/03/urgent-apple-issues-critical-updates.html", "creation_timestamp": "2024-03-06T06:58:06.000000Z"}, {"uuid": "892ff8e3-7ef9-4d7f-a330-d4774c2a60c3", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "Telegram/hA0yHiuiSqmH5OBoqITU5btkktLLTBMBRHKgmBiIklUFlxDMSQ", "content": "", "creation_timestamp": "2024-08-09T10:14:11.000000Z"}, {"uuid": "2a5b3203-41d8-4c54-9f22-4768bb57f51f", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://t.me/CyberSachok/1700", "content": "Apple \u0432\u044b\u043f\u0443\u0441\u0442\u0438\u043b\u0430 \u0441\u0440\u043e\u0447\u043d\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0443\u0436\u0435 \u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0445\u0430\u043a\u0435\u0440\u0430\u043c\u0438. \n\n\u0414\u0432\u0435 zero-day \u0432 iOS \u0434\u043e \u043d\u043e\u0432\u0435\u0439\u0448\u0438\u0445 \u043f\u0430\u0442\u0447\u0435\u0439 \u0443\u0441\u043f\u0435\u043b\u0438 \u043f\u043e\u043f\u0430\u0441\u0442\u044c \u0432 \u043e\u0431\u0438\u0445\u043e\u0434 \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u043e\u0432 \u0432 \u0430\u0442\u0430\u043a\u0430\u0445 \u043d\u0430 iPhone. \n\n\u0420\u0435\u0447\u044c \u043e\u0431 CVE-2024-23225 \u0432 \u044f\u0434\u0440\u0435 iOS \u0438 CVE-2024-23296 \u0432 RTKit(\u0434\u0430\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0445\u0430\u043a\u0435\u0440\u0430\u043c \u0441 root-\u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043e\u0431\u043e\u0439\u0442\u0438 \u0437\u0430\u0449\u0438\u0442\u0443 \u043f\u0430\u043c\u044f\u0442\u0438 \u044f\u0434\u0440\u0430). \n\n\u041e\u0431\u0435 \u043e\u043d\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0432 \u0448\u043f\u0438\u043e\u043d\u0441\u043a\u0438\u0445 \u041f\u041e. \u0412 \u0437\u043e\u043d\u0435 \u0440\u0438\u0441\u043a\u0430 iPhoneXS, iPhone 8, iPhone X \u0438 \u0446\u0435\u043b\u0430\u044f \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f \u0430\u0439\u043f\u0430\u0434\u043e\u0432. \n\n\u0412\u0441\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0440\u043e\u0447\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u044f.\n\n@cybersachok", "creation_timestamp": "2024-03-06T17:42:31.000000Z"}, {"uuid": "f7a55ae7-c090-49d7-b8ce-a7db218d700a", "vulnerability_lookup_origin": "caeb2787-0d58-4236-9039-7c86c3e566f3", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "exploited", "source": "https://vulnerability.circl.lu/known-exploited-vulnerabilities-catalog/e6a9ed78-349f-4c6a-b703-f085b793ff5a", "content": "", "creation_timestamp": "2026-06-19T12:46:41.658065Z"}, {"uuid": "ddbfb494-5130-44be-a08c-4ed65b55d0eb", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://gist.github.com/ShaiOnionGod/bb378bb47a2626f2a0b85bb402724549", "content": "\n\n  \n    \n    \n    Axonius \u2014 Dashboard prototype\n    \n    \n      {\n        \"imports\": {\n          \"react\": \"https://esm.sh/react@18.3.1\",\n          \"react-dom\": \"https://esm.sh/react-dom@18.3.1\",\n          \"react-dom/client\": \"https://esm.sh/react-dom@18.3.1/client\",\n          \"lucide-react\": \"https://esm.sh/lucide-react@0.456.0?deps=react@18.3.1\"\n        }\n      }\n    \n    \n    \n      html, body { margin: 0; height: 100%; background: #191A23; font-family: 'Hanken Grotesk', -apple-system, BlinkMacSystemFont, sans-serif; }\n      #root { height: 100%; }\n      .sb-tag { position: fixed; right: 14px; bottom: 12px; z-index: 99999; font: 700 10px/1 ui-monospace, monospace; letter-spacing: .14em; color: #0D5ED7; background: rgba(255,255,255,.9); border: 1px solid #E0E4EC; border-radius: 999px; padding: 6px 10px; box-shadow: 0 6px 18px rgba(27,32,70,.12); pointer-events: none; }\n    \n  \n  \n    \n\n    \nSANDBOX\n\n    \nimport React, { useState, useRef, useCallback, useEffect } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { createPortal } from \"react-dom\";\nimport {\n  Search, Star, MoreHorizontal, Info, ListFilter, ArrowUpDown, Columns3,\n  Plus, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, ChevronDown,\n  ArrowUpRight, ArrowDownRight, LayoutGrid, Server, ShieldCheck, Boxes,\n  Workflow, Cable, Bell, CircleHelp, Clock, Play, Grip, PanelLeftClose,\n  PanelLeftOpen, Download, GripVertical, X, Settings, ShieldAlert, Crosshair,\n  Pin, PinOff, RotateCw, Filter, Table2, ChevronsUpDown, Pencil, Check, Trash2,\n  Users, Layers, RotateCcw, Save, Copy, Calendar, Menu, Zap, Link, BarChart3, Sun, Moon,\n  Globe, Lock, Folder, FolderOpen\n} from \"lucide-react\";\n\n/**\n * Axonius \u2014 Dashboard + Assets table views\n * Collapsible side panel \u00b7 resizable + reorderable dashboard tiles\n * Real Axonius header \u00b7 Twenty-style table \u00b7 tokens from Figma source\n */\n\nfunction AxoniusLogo() {\n  return `.replaceAll('fill=\"black\"', `fill=\"${T.ink}\"`) }} /&gt;;\n}\n\n// Data / highlight palette \u2014 bright fills used in both themes (light-mode text variants applied where colored text is needed)\nconst VIZ = { green: \"#5BC4BF\", greenDeep: \"#247D78\", mint: \"#8ED0FF\", lilac: \"#AD85FF\", lilacPale: \"#CDB4FF\", orange: \"#FF8C66\", amber: \"#FFB286\" };\nconst FONT = \"'Hanken Grotesk', -apple-system, sans-serif\", DISPLAY = \"'Schibsted Grotesk', -apple-system, sans-serif\";\nconst THEMES = {\n  dark: {\n    ink: \"#E4E8F0\", body: \"#AEB7CC\", muted: \"#8494B5\", faint: \"#69738C\",\n    line: \"rgba(132,148,181,0.20)\", lineSoft: \"rgba(132,148,181,0.12)\", hair: \"rgba(132,148,181,0.09)\", lineStrong: \"rgba(132,148,181,0.42)\",\n    bg100: \"#AEB7CC\", bg90: \"#9AA3BC\", bg80: \"#8494B5\", bg40: \"#8494B5\",\n    blue: \"#5C7CFF\", blueDeep: \"#4E6CF5\", green: \"#5BC4BF\", red: \"#FF8C66\",\n    shell: \"#191A23\", headerBg: \"#191A23\", canvas: \"#191A23\", surface: \"#222431\", surface2: \"#2A2D3C\", control: \"#262838\",\n    white: \"#FFFFFF\", onAccent: \"#191A23\", isLight: false, coin: \"#2A2D3C\", accentSoft: \"rgba(92,124,255,0.16)\", accentText: \"#5C7CFF\", accentBorder: \"rgba(92,124,255,0.50)\",\n    viz: VIZ, shadow: \"0 1px 2px rgba(0,0,0,0.4), 0 14px 34px rgba(0,0,0,0.5)\", font: FONT, display: DISPLAY,\n  },\n  light: {\n    ink: \"#2E3850\", body: \"#465472\", muted: \"#6B7894\", faint: \"#8494B5\",\n    line: \"rgba(132,148,181,0.30)\", lineSoft: \"rgba(132,148,181,0.18)\", hair: \"rgba(132,148,181,0.12)\", lineStrong: \"rgba(132,148,181,0.55)\",\n    bg100: \"#465472\", bg90: \"#5A6889\", bg80: \"#8494B5\", bg40: \"#8494B5\",\n    blue: \"#4361EE\", blueDeep: \"#3A55D6\", green: \"#247D78\", red: \"#BF4B26\",\n    shell: \"#F9FAFB\", headerBg: \"#F9FAFB\", canvas: \"#FFFFFF\", surface: \"#FFFFFF\", surface2: \"#F1F2F6\", control: \"#FFFFFF\",\n    white: \"#FFFFFF\", onAccent: \"#FFFFFF\", isLight: true, coin: \"#FFFFFF\", accentSoft: \"rgba(67,97,238,0.10)\", accentText: \"#4361EE\", accentBorder: \"rgba(67,97,238,0.45)\",\n    viz: VIZ, shadow: \"0 1px 2px rgba(70,84,114,0.06), 0 10px 26px rgba(70,84,114,0.08)\", font: FONT, display: DISPLAY,\n  },\n};\nlet T = THEMES.dark;\n// Severity scale stays semantic (red \u2192 orange \u2192 yellow \u2192 green, neutral for informational) in both themes\nconst SEVC = { critical: \"#E5484D\", high: \"#F0743E\", medium: \"#F5C28C\", low: \"#4F8FE3\", info: \"#CFC4F2\" };\nconst fmt = (n) =&gt; n.toLocaleString(\"en-US\");\nconst tnum = { fontFeatureSettings: '\"tnum\" 1', fontVariantNumeric: \"tabular-nums\" };\n\n/* ============ vendor logos ============ */\nfunction LogoMark({ brand, size = 32 }) {\n  const glyph = {\n    aws: aws,\n    microsoft: ,\n    azure: ,\n    googlecloud: ,\n    oracle: ,\n    vmware: vm,\n    okta: ,\n    crowdstrike: ,\n    active_directory: ,\n    sentinelone: ,\n    service_now: ,\n    cisco: cisco,\n    cisco_meraki: ,\n    cisco_ise: cisco,\n    checkpoint: ,\n    chef: ,\n    claroty: ,\n    cylance: ,\n    epo: ,\n    paloalto: ,\n    tanium: ,\n    tenable: ,\n    forescout: ,\n    zoom: ,\n    miro: ,\n    dropbox: ,\n    salesforce: ,\n    slack: ,\n    office365: ,\n    google_workspace: ,\n  }[brand] || {(brand || \"?\").slice(0, 2).toUpperCase()};\n  return \n{glyph};\n}\n\n/* ============ chips ============ */\n// Tags use the dashboard data palette (turquoise / purple / baby-blue / orange) \u2014 dark text variant in light mode, bright in dark\nconst TAGV = [{ b: \"#5BC4BF\", d: \"#247D78\" }, { b: \"#AD85FF\", d: \"#683CB5\" }, { b: \"#8ED0FF\", d: \"#1E75B3\" }, { b: \"#FF8C66\", d: \"#BF4B26\" }];\nfunction Tag({ label }) {\n  let n = 0; for (let i = 0; i &lt; label.length; i++) n = (n * 31 + label.charCodeAt(i)) &gt;&gt;&gt; 0;\n  const t = TAGV[n % TAGV.length];\n  return {label};\n}\n// Cursor-following tooltip (like sandbox 2) \u2014 surface card with readable dark text\nfunction FloatTip({ x, y, children }) {\n  return createPortal(\n{children}, document.body);\n}\nfunction UserAvatar({ name, size = 24 }) {\n  const s = name || \"?\"; let n = 0; for (let i = 0; i &lt; s.length; i++) n = (n * 31 + s.charCodeAt(i)) &gt;&gt;&gt; 0;\n  const t = TAGV[n % TAGV.length];\n  return {s.trim()[0] || \"?\"};\n}\nconst APP_LOGO_MAP = {\n  Zoom: \"zoom\", \"Zoom One Pro\": \"zoom\",\n  Miro: \"miro\",\n  Dropbox: \"dropbox\",\n  Salesforce: \"salesforce\",\n  Slack: \"slack\",\n  Google: \"googlecloud\", \"Google WS Ent. Starter\": \"google_workspace\", \"Google WS Ent. Standard\": \"google_workspace\", \"Google WS Business\": \"google_workspace\",\n  Office365: \"office365\", \"Microsoft 365 G3 GCC\": \"office365\", \"Microsoft 365 E5\": \"office365\", \"Microsoft Power Aut\u2026\": \"office365\",\n  Microsoft: \"microsoft\",\n};\nfunction AppAvatar({ name, size = 22 }) {\n  const brand = APP_LOGO_MAP[name];\n  if (brand) return ;\n  const s = name || \"?\"; let n = 0; for (let i = 0; i &lt; s.length; i++) n = (n * 31 + s.charCodeAt(i)) &gt;&gt;&gt; 0;\n  const t = TAGV[n % TAGV.length];\n  return {s.trim()[0] || \"?\"};\n}\nconst STATUS = { Active: T.green, Warning: T.viz.amber, Inactive: T.faint, Error: T.red };\nfunction StatusChip({ status }) {\n  return {status};\n}\nconst RISK = { Critical: SEVC.critical, High: SEVC.high, Medium: SEVC.medium, Low: SEVC.low };\nfunction RiskChip({ level }) {\n  const c = RISK[level];\n  return {level};\n}\nfunction Delta({ up, children }) {\n  const color = up ? T.green : T.red;\n  return {up ?  : }{children};\n}\nfunction IconBtn({ children, onClick }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ width: 26, height: 26, borderRadius: 6, border: \"none\", cursor: \"pointer\", background: h ? T.surface2 : \"transparent\", color: h ? T.ink : T.faint, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}&gt;{children};\n}\n\n/* ============ KPIs ============ */\nconst KPIS = [\n  { label: \"Total Assets\", value: 84355, delta: \"12.4%\", up: true },\n  { label: \"Cloud Coverage\", value: 96.2, suffix: \"%\", delta: \"2.1%\", up: true },\n  { label: \"Open Vulnerabilities\", value: 382, delta: \"8.0%\", up: false },\n  { label: \"Unmanaged Devices\", value: 1204, delta: \"3.2%\", up: false },\n];\nfunction useCountUp(target, dur = 950) {\n  const [v, setV] = useState(0);\n  useEffect(() =&gt; {\n    let raf; const start = performance.now();\n    const tick = (now) =&gt; { const p = Math.min(1, (now - start) / dur); setV(target * (1 - Math.pow(1 - p, 3))); if (p &lt; 1) raf = requestAnimationFrame(tick); };\n    raf = requestAnimationFrame(tick); return () =&gt; cancelAnimationFrame(raf);\n  }, [target]);\n  return v;\n}\nfunction KpiValue({ value, suffix }) {\n  const v = useCountUp(value);\n  const text = suffix === \"%\" ? v.toFixed(1) : fmt(Math.round(v));\n  return {text}{suffix || \"\"};\n}\nfunction KpiRow() {\n  return (\n    \n\n      {KPIS.map((k, i) =&gt; (\n        \n\n          \n{k.label}\n          \n\n            \n            {k.delta}\n          \n        \n      ))}\n    \n  );\n}\n\n/* ============ chart bodies ============ */\nconst PIE = [\n  { c: \"green\", label: \"Windows Assets\", val: 1236 }, { c: \"greenDeep\", label: \"Active Directory\", val: 411 },\n  { c: \"mint\", label: \"SCCM Managed\", val: 198 }, { c: \"lilac\", label: \"Intune Managed\", val: 89 }, { c: \"lilacPale\", label: \"Unmanaged\", val: 43 },\n];\nfunction DonutBody() {\n  const [hi, setHi] = useState(null);\n  const [tip, setTip] = useState(null);\n  const [off, setOff] = useState(() =&gt; new Set());\n  const [drawn, setDrawn] = useState(false);\n  useEffect(() =&gt; { const id = setTimeout(() =&gt; setDrawn(true), 60); return () =&gt; clearTimeout(id); }, []);\n  const r = 46, sw = 14, C = 2 * Math.PI * r, gap = 5;\n  const toggle = (i) =&gt; setOff(s =&gt; { const n = new Set(s); n.has(i) ? n.delete(i) : n.add(i); return n; });\n  const visTotal = PIE.reduce((a, p, i) =&gt; a + (off.has(i) ? 0 : p.val), 0) || 1;\n  let acc = 0;\n  const segs = PIE.map((p, i) =&gt; {\n    if (off.has(i)) return null;\n    const len = (p.val / visTotal) * C;\n    const seg = Math.max(1, len - gap);\n    const active = hi === i, dim = hi !== null &amp;&amp; !off.has(hi) &amp;&amp; !active;\n    const node = (\n       { setHi(i); setTip({ i, x: e.clientX, y: e.clientY }); }} onMouseMove={(e) =&gt; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; { setHi(null); setTip(null); }} onClick={() =&gt; toggle(i)}\n        style={{ opacity: dim ? 0.35 : 1, cursor: \"pointer\", transition: \"stroke-dasharray .8s cubic-bezier(.22,1,.36,1), stroke-width .18s ease, opacity .18s ease\" }} /&gt;\n    );\n    acc += len; return node;\n  });\n  const liveHi = hi !== null &amp;&amp; !off.has(hi);\n  const center = liveHi ? { big: ((PIE[hi].val / visTotal) * 100).toFixed(1) + \"%\", small: PIE[hi].label } : { big: fmt(visTotal), small: \"Total Assets\" };\n  return (\n    \n\n      \n\n        \n          \n          {segs}\n        \n        \n\n          {center.big}\n          {center.small}\n        \n      \n      \n\n        {PIE.map((p, i) =&gt; {\n          const isOff = off.has(i);\n          return (\n            \n { setHi(i); if (!isOff) setTip({ i, x: e.clientX, y: e.clientY }); }} onMouseMove={(e) =&gt; !isOff &amp;&amp; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; { setHi(null); setTip(null); }} onClick={() =&gt; toggle(i)}\n              title={isOff ? \"Click to show\" : \"Click to hide\"}\n              style={{ display: \"flex\", alignItems: \"center\", gap: 9, padding: \"5px 6px\", borderRadius: 6, cursor: \"pointer\", background: hi === i &amp;&amp; !isOff ? T.canvas : \"transparent\", transition: \"background .12s\" }}&gt;\n              \n              {p.label}\n              {isOff ? \"Hidden\" : (hi === i ? fmt(p.val) : ((p.val / visTotal) * 100).toFixed(1) + \"%\")}\n            \n          );\n        })}\n      \n      {tip !== null &amp;&amp; !off.has(tip.i) &amp;&amp; \n        \n{PIE[tip.i].label}\n        \n{fmt(PIE[tip.i].val)}\n        \n{(PIE[tip.i].val / visTotal * 100).toFixed(1)}% of assets\n      }\n    \n  );\n}\nconst LINE_LABELS = [\"W1\", \"W2\", \"W3\", \"W4\", \"W5\", \"W6\", \"W7\", \"W8\", \"W9\", \"W10\", \"W11\", \"W12\"];\nconst LINE_SERIES = [\n  { key: \"all\", label: \"All Devices\", color: T.viz.green, pts: [38000, 41200, 44500, 47800, 51000, 55300, 59800, 63200, 68500, 72100, 78400, 84355] },\n  { key: \"managed\", label: \"Managed\", color: T.viz.lilac, pts: [29000, 31000, 33500, 35800, 38000, 41000, 44500, 47200, 50500, 54100, 58400, 61200] },\n  { key: \"cloud\", label: \"Cloud\", color: T.viz.orange, pts: [8000, 9200, 10500, 11800, 13000, 14300, 15800, 17200, 18500, 20100, 21400, 22097] },\n  { key: \"unmanaged\", label: \"Unmanaged\", color: T.viz.greenDeep, pts: [3200, 3100, 3300, 3000, 3500, 3300, 3100, 2900, 2700, 2500, 2300, 2100] },\n];\nfunction LineBody() {\n  const [off, setOff] = useState(() =&gt; new Set());\n  const [hov, setHov] = useState(null);\n  const w = 680, h = 196, pad = 12, n = LINE_LABELS.length, max = 90000;\n  const X = (i) =&gt; pad + (i / (n - 1)) * (w - pad * 2);\n  const Y = (v) =&gt; h - pad - (v / max) * (h - pad * 2);\n  const smooth = (pts) =&gt; { let d = `M ${X(0)} ${Y(pts[0])}`; for (let i = 1; i &lt; n; i++) { const cx = (X(i - 1) + X(i)) / 2; d += ` C ${cx} ${Y(pts[i - 1])}, ${cx} ${Y(pts[i])}, ${X(i)} ${Y(pts[i])}`; } return d; };\n  const toggle = (k) =&gt; setOff(s =&gt; { const x = new Set(s); x.has(k) ? x.delete(k) : x.add(k); return x; });\n  const visible = LINE_SERIES.filter(s =&gt; !off.has(s.key));\n  const xPct = (i) =&gt; (X(i) / w) * 100, yPct = (v) =&gt; (Y(v) / h) * 100;\n  return (\n    \n\n      \n setHov(null)}&gt;\n        \n          {[0, 1, 2, 3].map(i =&gt; )}\n          {hov &amp;&amp; }\n          {visible.map(s =&gt; )}\n        \n        {/* a hoverable dot on every point \u2014 hovering one shows just that series */}\n        {visible.map(s =&gt; s.pts.map((v, i) =&gt; {\n          const isHov = hov &amp;&amp; hov.s.key === s.key &amp;&amp; hov.i === i, dim = hov &amp;&amp; !isHov;\n          return (\n            \n setHov({ s, i })}\n              style={{ position: \"absolute\", left: `${xPct(i)}%`, top: `${yPct(v)}%`, transform: \"translate(-50%, -50%)\", width: 20, height: 20, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", cursor: \"pointer\", zIndex: isHov ? 4 : 2 }}&gt;\n              \n            \n          );\n        }))}\n        {hov &amp;&amp; (\n          \n n - 4 ? \"calc(-100% - 12px)\" : hov.i &lt; 3 ? \"12px\" : \"-50%\"}, calc(-100% - 14px))`, background: T.surface, border: `1px solid ${T.line}`, borderRadius: 10, boxShadow: T.shadow, padding: \"9px 12px\", whiteSpace: \"nowrap\", pointerEvents: \"none\", zIndex: 6 }}&gt;\n            \n{hov.s.label}\n            \n{fmt(hov.s.pts[hov.i])}\n            {hov.i &gt; 0 &amp;&amp; (() =&gt; { const pct = (hov.s.pts[hov.i] - hov.s.pts[hov.i - 1]) / hov.s.pts[hov.i - 1] * 100; const up = pct &gt;= 0; return \n{up ?  : }{up ? \"+\" : \"\"}{pct.toFixed(1)}% from previous; })()}\n            \n{LINE_LABELS[hov.i]}\n          \n        )}\n      \n      \n\n        {LINE_SERIES.map(s =&gt; {\n          const isOff = off.has(s.key);\n          return (\n             toggle(s.key)} title={isOff ? \"Show series\" : \"Hide series\"} className=\"ax-press\"\n              style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, padding: \"3px 9px\", borderRadius: 999, border: `1px solid ${isOff ? T.lineSoft : T.line}`, background: isOff ? \"transparent\" : T.canvas, cursor: \"pointer\", fontFamily: T.font, fontSize: 11.5, fontWeight: 500, color: isOff ? T.faint : T.body, transition: \"all .14s\" }}&gt;\n              \n              {s.label}\n            \n          );\n        })}\n      \n    \n  );\n}\nconst STACK = [[\"Jan\", 40, 25, 20], [\"Feb\", 35, 30, 15], [\"Mar\", 52, 22, 26], [\"Apr\", 30, 35, 18], [\"May\", 48, 28, 24], [\"Jun\", 58, 32, 28]];\nconst STACK_KEYS = [[\"Cloud\", T.viz.green], [\"On-prem\", T.viz.mint], [\"SaaS\", T.viz.lilac]];\nfunction StackedBody() {\n  const [hi, setHi] = useState(null);\n  const [tip, setTip] = useState(null);\n  const max = 130;\n  return (\n    \n\n      {tip &amp;&amp; \n{STACK[tip.i][0]}\n{fmt((STACK[tip.i][1] + STACK[tip.i][2] + STACK[tip.i][3]) * 86)}\ntotal assets}\n      \n\n        {[0, 1, 2, 3].map(i =&gt; \n)}\n        {STACK.map((s, i) =&gt; {\n          const total = s[1] + s[2] + s[3];\n          const active = hi === i, dim = hi !== null &amp;&amp; !active;\n          return (\n            \n { setHi(i); setTip({ i, x: e.clientX, y: e.clientY }); }} onMouseMove={(e) =&gt; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; { setHi(null); setTip(null); }}\n              style={{ flex: 1, display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 8, height: \"100%\", justifyContent: \"flex-end\", zIndex: 1, cursor: \"pointer\", position: \"relative\" }}&gt;\n              \n\n                \n\n                \n\n\n              \n              {s[0]}\n            \n          );\n        })}\n      \n      \n\n        {STACK_KEYS.map(([label, color], i) =&gt; (\n          {label}\n        ))}\n      \n    \n  );\n}\nconst SEV = [[\"Critical\", 142, 100, SEVC.critical], [\"High\", 98, 70, SEVC.high], [\"Medium\", 76, 54, SEVC.medium], [\"Low\", 45, 32, SEVC.low], [\"Info\", 21, 15, SEVC.info]];\nconst SEV_ASSETS = {\n  Critical: [[\"PC-CURTIS-WILLIAMS\", \"CVE-2024-21412 \u00b7 SmartScreen bypass\"], [\"srv-db-fin-04\", \"CVE-2024-3094 \u00b7 xz-utils backdoor\"], [\"esx-infranginx-5567897\", \"CVE-2023-46604 \u00b7 ActiveMQ RCE\"], [\"WIN-RUTHD\", \"CVE-2024-21413 \u00b7 Outlook RCE\"]],\n  High: [[\"macbook-pro-jdoe\", \"CVE-2024-23222 \u00b7 WebKit type confusion\"], [\"azure-infra9274676\", \"CVE-2024-21401 \u00b7 Entra ID elevation\"], [\"lablb-2918146-beta\", \"CVE-2024-1709 \u00b7 ScreenConnect auth bypass\"]],\n  Medium: [[\"iphone-asmith\", \"CVE-2024-23225 \u00b7 kernel memory\"], [\"sepio-external3026786\", \"CVE-2024-0519 \u00b7 V8 out-of-bounds\"]],\n  Low: [[\"android-pixel-7\", \"CVE-2024-0039 \u00b7 System component\"]],\n  Info: [[\"win-marychasse\", \"Informational \u00b7 TLS 1.0 enabled\"]],\n};\nfunction SeverityDrawer({ sev, close }) {\n  const list = SEV_ASSETS[sev[0]] || [];\n  return createPortal(&lt;&gt;\n    \n\n    \n\n      \n\n        \n        \n\n          \n{sev[0]} severity\n          \n{sev[1]} open vulnerabilities \u00b7 {list.length} assets shown\n        \n        \n      \n      \n\n        {list.map(([host, cve], i) =&gt; (\n          \n\n            \n\n              {host}\n              {sev[0]}\n            \n            {cve}\n          \n        ))}\n      \n      \n\n        View all {sev[1]} in Inventory\n      \n    \n  , document.body);\n}\nfunction SeverityBody() {\n  const [hi, setHi] = useState(null);\n  const [drill, setDrill] = useState(null);\n  const [tip, setTip] = useState(null);\n  const sevTotal = SEV.reduce((a, b) =&gt; a + b[1], 0);\n  return (&lt;&gt;\n    \n\n      {SEV.map((b, i) =&gt; {\n        const active = hi === i, dim = hi !== null &amp;&amp; !active;\n        return (\n          \n setDrill(b)} onMouseEnter={(e) =&gt; { setHi(i); setTip({ i, x: e.clientX, y: e.clientY }); }} onMouseMove={(e) =&gt; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; { setHi(null); setTip(null); }}\n            style={{ display: \"flex\", alignItems: \"center\", gap: 12, opacity: dim ? 0.5 : 1, transition: \"opacity .15s\", cursor: \"pointer\" }}&gt;\n            {b[0]}\n            \n\n\n            {active ? b[1] + \" open\" : b[1]}\n          \n        );\n      })}\n    \n    {tip &amp;&amp; \n{SEV[tip.i][0]}\n{fmt(SEV[tip.i][1])}\n{Math.round(SEV[tip.i][1] / sevTotal * 100)}% of open findings}\n    {drill &amp;&amp;  setDrill(null)} /&gt;}\n  );\n}\nconst ST_COLOR = { ok: T.viz.green, warn: T.viz.amber, err: T.red };\n\n/* ===== System Lifecycle (discovery cycle) ===== */\nconst LIFECYCLE = [\n  { name: \"Fetch\", pct: 100 }, { name: \"Clean\", pct: 100 }, { name: \"Correlate\", pct: 100 },\n  { name: \"Enrich\", pct: 78 }, { name: \"Calculate\", pct: 0 },\n];\nfunction LifecycleBody() {\n  const [tip, setTip] = useState(null);\n  const done = LIFECYCLE.filter(s =&gt; s.pct === 100).length, total = LIFECYCLE.length;\n  const overall = Math.round(LIFECYCLE.reduce((a, s) =&gt; a + s.pct, 0) / total);\n  const r = 42, sw = 11, C = 2 * Math.PI * r, dash = (overall / 100) * C;\n  return (\n    \n\n      {tip &amp;&amp; \n{LIFECYCLE[tip.i].name} stage\n{LIFECYCLE[tip.i].pct &gt; 0 ? LIFECYCLE[tip.i].pct + \"% complete\" : \"Not started\"}}\n      \n\n        \n\n          \n            \n            \n          \n          \n\n            {done}/{total}\n            Stages\n          \n        \n        \n\n          {LIFECYCLE.map((s, i) =&gt; {\n            const c = s.pct === 100 ? T.viz.green : s.pct &gt; 0 ? T.blue : T.bg40;\n            return (\n              \n setTip({ i, x: e.clientX, y: e.clientY })} onMouseMove={(e) =&gt; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; setTip(null)} style={{ display: \"flex\", alignItems: \"center\", gap: 10, cursor: \"default\" }}&gt;\n                {s.name}\n                \n\n                  \n\n                \n                 0 ? T.blue : T.bg40, ...tnum }}&gt;{s.pct &gt; 0 ? s.pct + \"%\" : \"\u2014\"}\n              \n            );\n          })}\n        \n      \n      \n\n        {[[\"Cycle started\", \"09:00:04\"], [\"Duration\", \"00:42:18\"], [\"Next cycle\", \"13:17:42\"]].map(([k, v], i) =&gt; (\n          \n\n            \n{k}\n            \n{v}\n          \n        ))}\n      \n    \n  );\n}\n\n/* ===== Discovery Log ===== */\nconst DISC_PHASES = [[\"Fetch\", T.viz.green], [\"Clean\", T.viz.mint], [\"Correlate\", T.viz.lilac], [\"Enrich\", T.viz.lilacPale], [\"Calculate\", T.viz.amber], [\"Save\", T.viz.orange]];\nconst DISCOVERY = [\n  { started: \"Jun 15, 09:00\", completed: \"Jun 15, 09:35\", duration: \"35min 22sec\", ph: [40, 12, 18, 10, 12, 8] },\n  { started: \"Jun 14, 09:00\", completed: \"Jun 14, 09:35\", duration: \"35min 59sec\", ph: [38, 14, 17, 11, 12, 8] },\n  { started: \"Jun 13, 09:00\", completed: \"Jun 13, 09:35\", duration: \"35min 29sec\", ph: [41, 11, 18, 10, 12, 8] },\n  { started: \"Jun 12, 09:00\", completed: \"Jun 12, 09:35\", duration: \"35min 11sec\", ph: [39, 13, 17, 11, 12, 8] },\n  { started: \"Jun 11, 09:00\", completed: \"Jun 11, 09:35\", duration: \"35min 58sec\", ph: [44, 12, 16, 9, 11, 8] },\n  { started: \"Jun 10, 09:00\", completed: \"Jun 10, 09:30\", duration: \"30min 54sec\", ph: [36, 12, 18, 12, 14, 8] },\n  { started: \"Jun 9, 09:00\", completed: \"Jun 9, 09:34\", duration: \"34min 02sec\", ph: [37, 13, 17, 11, 13, 9] },\n  { started: \"Jun 8, 09:00\", completed: \"Jun 8, 09:31\", duration: \"31min 38sec\", ph: [40, 12, 17, 10, 13, 8] },\n];\nfunction DiscoveryBody() {\n  const [hi, setHi] = useState(null);\n  const [tip, setTip] = useState(null);\n  const cols = \"minmax(70px, 1.6fr) 104px 92px\";\n  const durToSec = (s) =&gt; { let t = 0; const m = s.match(/(\\d+)\\s*min/), sec = s.match(/(\\d+)\\s*sec/); if (m) t += +m[1] * 60; if (sec) t += +sec[1]; return t; };\n  const fmtDur = (x) =&gt; { x = Math.round(x); const m = Math.floor(x / 60), s = x % 60; return m &gt; 0 ? `${m}min ${s}sec` : `${s}sec`; };\n  return (\n    \n\n      \n\n        {[\"Latest Discoveries\", \"Started\", \"Duration\"].map((c, i) =&gt; (\n          {c}\n        ))}\n      \n      \n\n        {DISCOVERY.map((d, i) =&gt; (\n          \n setHi(i)} onMouseLeave={() =&gt; { setHi(null); setTip(null); }}\n            style={{ display: \"grid\", gridTemplateColumns: cols, gap: 12, alignItems: \"center\", padding: \"7px 6px\", borderBottom: i &lt; DISCOVERY.length - 1 ? `1px solid ${T.hair}` : \"none\", background: hi === i ? T.canvas : \"transparent\", borderRadius: 6, transition: \"background .12s\" }}&gt;\n            \n\n              {d.ph.map((w, j) =&gt;  setTip({ d, j, x: e.clientX, y: e.clientY })} onMouseMove={(e) =&gt; setTip({ d, j, x: e.clientX, y: e.clientY })}\n                style={{ width: `${w}%`, height: \"100%\", background: DISC_PHASES[j][1], cursor: \"pointer\" }} /&gt;)}\n            \n            {d.started}\n            {d.duration}\n          \n        ))}\n      \n      {tip &amp;&amp; \n        \n{DISC_PHASES[tip.j][0]}\n        \n{fmtDur(durToSec(tip.d.duration) * tip.d.ph[tip.j] / 100)}\n        \n{tip.d.ph[tip.j]}% of cycle\n      }\n    \n  );\n}\n\n/* ===== Adapter Connections (status + logo coin + preview) ===== */\nconst ADAPTER_CONN = [\n  { brand: \"crowdstrike\", name: \"CrowdStrike\", st: \"ok\", desc: \"9,120 devices \u00b7 synced 4m ago\" },\n  { brand: \"aws\", name: \"Amazon Web Services\", st: \"ok\", desc: \"12,400 assets \u00b7 synced 6m ago\" },\n  { brand: \"azure\", name: \"Microsoft Azure\", st: \"ok\", desc: \"22,097 assets \u00b7 synced 5m ago\" },\n  { brand: \"okta\", name: \"Okta\", st: \"ok\", desc: \"9,870 users \u00b7 synced 8m ago\" },\n  { short: \"Jm\", color: \"#3B3B3B\", name: \"Jamf Pro\", st: \"err\", desc: \"Connection timeout \u2014 check credentials\" },\n  { short: \"now\", color: \"#2E8B57\", name: \"ServiceNow\", st: \"warn\", desc: \"Rate limited \u00b7 retrying\" },\n  { brand: \"googlecloud\", name: \"Google Cloud\", st: \"ok\", desc: \"9,055 assets \u00b7 synced 7m ago\" },\n  { brand: \"vmware\", name: \"VMware vCenter\", st: \"ok\", desc: \"4,310 VMs \u00b7 synced 11m ago\" },\n];\nconst ST_LABEL = { ok: \"Connected\", warn: \"Degraded\", err: \"Error\" };\nfunction AdapterCoin({ a }) {\n  if (a.brand) return ;\n  return {a.short};\n}\nfunction AdaptersBody() {\n  const connected = ADAPTER_CONN.filter(a =&gt; a.st === \"ok\").length;\n  const attention = ADAPTER_CONN.length - connected;\n  return (\n    \n\n      \n\n        {connected}\n        connected sources\n        {attention &gt; 0 &amp;&amp; {attention} need attention}\n      \n      \n\n        {ADAPTER_CONN.map((a, i) =&gt; {\n          const ring = ST_COLOR[a.st];\n          return (\n            \n\n              \n              \n\n                \n{a.name}\n                \n\n                  \n                  {a.desc}\n                \n              \n            \n          );\n        })}\n      \n    \n  );\n}\nconst HEALTH_OK_BRANDS = [\"crowdstrike\", \"aws\", \"azure\", \"okta\", \"googlecloud\", \"vmware\"];\nfunction HealthOkBody() {\n  return (\n    \n\n      \n\n        33\n        adapters syncing successfully\n      \n      \n\n        {HEALTH_OK_BRANDS.map((b, i) =&gt; )}\n        +27\n      \n      \n\n         100% healthy \u00b7 last full sync 4m ago\n      \n    \n  );\n}\nconst HEALTH_FAIL = [\n  { short: \"Jm\", color: \"#3B3B3B\", name: \"Jamf Pro\", reason: \"Connection timeout \u2014 check credentials\" },\n  { short: \"now\", color: \"#2E8B57\", name: \"ServiceNow\", reason: \"Rate limited \u00b7 retrying\" },\n];\nfunction HealthFailBody() {\n  return (\n    \n\n      \n\n        {HEALTH_FAIL.length}\n        adapters need attention\n      \n      \n\n        {HEALTH_FAIL.map((a, i) =&gt; (\n          \n\n            {a.short}\n            \n\n              \n{a.name}\n              \n{a.reason}\n            \n            Fix\n          \n        ))}\n      \n    \n  );\n}\n/* ===== Cost Optimization dashboard ===== */\nconst fmtMoney = (n) =&gt; n.toLocaleString(\"en-US\");\nfunction CostKPI({ value, unit }) {\n  return (\n    \n\n      {value}\n      {unit &amp;&amp; {unit}}\n    \n  );\n}\nfunction HBarList({ rows, accent, prefix, avatar, appLogo }) {\n  const max = Math.max(...rows.map(r =&gt; r[1]), 1);\n  const totalVal = rows.reduce((a, r) =&gt; a + r[1], 0) || 1;\n  const [tip, setTip] = useState(null);\n  return (\n    \n\n      {rows.map(([label, val], i) =&gt; {\n        const pct = Math.max(4, (val / max) * 100), inside = pct &gt; 24;\n        return (\n          \n setTip({ i, x: e.clientX, y: e.clientY })} onMouseMove={(e) =&gt; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; setTip(null)}\n            style={{ display: \"flex\", alignItems: \"center\", gap: 12, cursor: \"default\" }}&gt;\n            {avatar &amp;&amp; }{appLogo &amp;&amp; }{label}\n            \n\n              \n\n                {inside &amp;&amp; {prefix || \"\"}{fmtMoney(val)}}\n              \n              {!inside &amp;&amp; {prefix || \"\"}{fmtMoney(val)}}\n            \n          \n        );\n      })}\n      {tip &amp;&amp; \n        \n{rows[tip.i][0]}\n        \n{prefix || \"\"}{fmtMoney(rows[tip.i][1])}\n        \n{(rows[tip.i][1] / totalVal * 100).toFixed(1)}% of shown total\n      }\n    \n  );\n}\nconst COST_CATEGORY = [[\"Productivity\", 141993], [\"Video Conference\", 55928], [\"File Sharing\", 52943], [\"Authentication\", 11440], [\"CRM\", 9870]];\nconst COST_APP = [[\"Zoom\", 55928], [\"Miro\", 52885], [\"Dropbox\", 50431], [\"Salesforce\", 43176], [\"Slack\", 38420]];\nconst COST_TOPLIC = [[\"Zoom One Pro\", 70800], [\"Google WS Ent. Starter\", 27450], [\"Google WS Ent. Standard\", 12985], [\"Google WS Business\", 8640], [\"Microsoft 365 G3 GCC\", 1930]];\nconst COST_USAGE = [[\"aaron.church@demo.local\", 29], [\"aaron.daniels@demo.local\", 29], [\"ada.pires@demo.local\", 29], [\"adelaide.gercak@demo.local\", 29], [\"adrian.williams@demo.local\", 29]];\nconst COST_RENEWALS = [\n  { app: \"Office365\", name: \"Microsoft 365 E5\", date: \"2026-07-06\", cost: \"3,175.50\", term: \"Yearly\", unit: \"54.75\" },\n  { app: \"Office365\", name: \"Microsoft Power Aut\u2026\", date: \"2026-08-07\", cost: \"6,518.75\", term: \"Yearly\", unit: \"37.25\" },\n];\nfunction CostExpAllBody() { return ; }\nfunction CostLicAllBody() { return ; }\nfunction CostExp2024Body() { return ; }\nfunction CostLicTotalBody() { return ; }\nfunction CostCategoryBody() { return ; }\nfunction CostAppBody() { return ; }\nfunction CostTopLicBody() { return ; }\nfunction CostUsageBody() { return ; }\nfunction CostRenewalsBody() {\n  const cols = [\n    { label: \"Application\", render: r =&gt; {r.app} },\n    { label: \"Name\", render: r =&gt; {r.name} },\n    { label: \"Renewal Date\", render: r =&gt; {r.date} },\n    { label: \"Total Cost\", align: \"right\", render: r =&gt; ${r.cost} },\n    { label: \"Term\", render: r =&gt; {r.term} },\n    { label: \"Unit\", align: \"right\", render: r =&gt; ${r.unit} },\n  ];\n  return (\n    \n\n      \n\n        \n          \n            {cols.map((c, i) =&gt; {c.label})}\n          \n        \n        \n          {COST_RENEWALS.map((r, ri) =&gt; (\n            \n              {cols.map((c, i) =&gt; {c.render(r)})}\n            \n          ))}\n        \n      \n    \n  );\n}\nfunction ResetViewBtn({ disabled, onReset }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)}\n      title={disabled ? \"Layout is at its default\" : \"Restore the default tile layout\"}\n      style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, height: 30, padding: \"0 13px\", borderRadius: 999, fontFamily: T.font, fontSize: 13, fontWeight: 600, cursor: disabled ? \"default\" : \"pointer\", border: `1px solid ${disabled ? T.lineSoft : h ? T.lineStrong : T.line}`, background: disabled ? \"transparent\" : h ? T.surface2 : T.control, color: disabled ? T.faint : T.body, transition: \"all .14s\" }}&gt;\n       Reset view\n    \n  );\n}\n\nconst TILE_META = {\n  line: { eyebrow: \"Activity\", title: \"Device Discovery Over Time\", Body: LineBody },\n  donut: { eyebrow: \"Composition\", title: \"Windows Distribution\", Body: DonutBody },\n  lifecycle: { eyebrow: \"Pipeline\", title: \"System Lifecycle\", Body: LifecycleBody },\n  discovery: { eyebrow: \"Activity\", title: \"Discovery Log\", Body: DiscoveryBody },\n  adapters: { eyebrow: \"Connections\", title: \"Adapter Connections\", Body: AdaptersBody },\n  stacked: { eyebrow: \"Trend\", title: \"Asset Growth by Category\", Body: StackedBody, footer: 12.4% },\n  severity: { eyebrow: \"Risk\", title: \"Vulnerabilities by Severity\", Body: SeverityBody, footer: 18 resolved },\n  healthOk: { eyebrow: \"Adapters\", title: \"Adapter Health \u00b7 Successful\", Body: HealthOkBody },\n  healthFail: { eyebrow: \"Adapters\", title: \"Adapter Health \u00b7 Attention\", Body: HealthFailBody },\n  costExpAll: { eyebrow: \"Spend\", title: \"Total Expenses (All Time)\", Body: CostExpAllBody },\n  costExp2024: { eyebrow: \"Spend\", title: \"Total Expenses (2024)\", Body: CostExp2024Body },\n  costLicAll: { eyebrow: \"Licenses\", title: \"Total License Cost (All Time)\", Body: CostLicAllBody },\n  costLicTotal: { eyebrow: \"Licenses\", title: \"Total License Cost\", Body: CostLicTotalBody },\n  costRenewals: { eyebrow: \"Renewals\", title: \"Upcoming Renewals (next 90 days)\", Body: CostRenewalsBody, footer: View all results, noPad: true },\n  costCategory: { eyebrow: \"Spend\", title: \"Expenses by Category (2024)\", Body: CostCategoryBody },\n  costApp: { eyebrow: \"Spend\", title: \"Expenses by App (2024)\", Body: CostAppBody },\n  costTopLic: { eyebrow: \"Licenses\", title: \"Most Expensive Licenses (2024)\", Body: CostTopLicBody },\n  costUsage: { eyebrow: \"Usage\", title: \"Azure AD Logons \u00b7 last 30 days\", Body: CostUsageBody },\n};\n\n/* ============ resizable grid (corner drag, neighbors reflow) ============ */\nconst GRID_COLS = 12, ROW_UNIT = 40, GAP = 16;\nconst INITIAL_TILES = [\n  { id: \"line\", w: 8, h: 7 }, { id: \"donut\", w: 4, h: 7 },\n  { id: \"lifecycle\", w: 5, h: 7 }, { id: \"discovery\", w: 7, h: 7 },\n  { id: \"adapters\", w: 7, h: 6 }, { id: \"severity\", w: 5, h: 6 },\n  { id: \"healthOk\", w: 6, h: 5 }, { id: \"healthFail\", w: 6, h: 5 },\n  { id: \"stacked\", w: 12, h: 5 },\n];\nconst COST_TILES = [\n  { id: \"costExpAll\", w: 3, h: 4 }, { id: \"costExp2024\", w: 3, h: 4 }, { id: \"costLicAll\", w: 3, h: 4 }, { id: \"costLicTotal\", w: 3, h: 4 },\n  { id: \"costRenewals\", w: 12, h: 6 },\n  { id: \"costCategory\", w: 6, h: 6 }, { id: \"costApp\", w: 6, h: 6 },\n  { id: \"costTopLic\", w: 6, h: 6 }, { id: \"costUsage\", w: 6, h: 6 },\n];\nconst DASH_TILESETS = { \"Cost Optimization\": COST_TILES };\nfunction DashboardGrid({ tileSet = INITIAL_TILES }) {\n  const [tiles, setTiles] = useState(tileSet);\n  const [hoverId, setHoverId] = useState(null);\n  const [resizingId, setResizingId] = useState(null);\n  const [drag, setDrag] = useState(null);\n  const dragId = drag ? drag.id : null;\n  const ref = useRef(null);\n  const tileEls = useRef({});\n  const justDragged = useRef(false);\n\n  const startDrag = (e, id, immediate) =&gt; {\n    if (e.button || (e.target.closest &amp;&amp; (e.target.closest(\"[data-resize]\") || e.target.closest(\"button\")))) return;\n    const startX = e.clientX, startY = e.clientY;\n    let dragging = false;\n    const begin = (cx, cy) =&gt; {\n      const el = tileEls.current[id]; if (!el) return;\n      const rect = el.getBoundingClientRect();\n      dragging = true;\n      setDrag({ id, x: cx, y: cy, ox: cx - rect.left, oy: cy - rect.top, w: rect.width, h: rect.height });\n      document.body.style.userSelect = \"none\";\n    };\n    const timer = immediate ? null : setTimeout(() =&gt; begin(startX, startY), 300);\n    const onMove = (ev) =&gt; {\n      if (!dragging) {\n        const moved = Math.abs(ev.clientX - startX) &gt; (immediate ? 4 : 8) || Math.abs(ev.clientY - startY) &gt; (immediate ? 4 : 8);\n        if (!moved) return;\n        if (immediate) begin(ev.clientX, ev.clientY); else { end(); return; }\n        if (!dragging) return;\n      }\n      setDrag(d =&gt; d ? { ...d, x: ev.clientX, y: ev.clientY } : d);\n      for (const tid in tileEls.current) {\n        const el = tileEls.current[tid];\n        if (!el || tid === id) continue;\n        const r = el.getBoundingClientRect();\n        if (ev.clientX &gt;= r.left &amp;&amp; ev.clientX &lt;= r.right &amp;&amp; ev.clientY &gt;= r.top &amp;&amp; ev.clientY &lt;= r.bottom) {\n          setTiles(ts =&gt; { const from = ts.findIndex(x =&gt; x.id === id), to = ts.findIndex(x =&gt; x.id === tid); if (from &lt; 0 || to &lt; 0 || from === to) return ts; const n = ts.slice(); const [mv] = n.splice(from, 1); n.splice(to, 0, mv); return n; });\n          break;\n        }\n      }\n    };\n    const end = () =&gt; { clearTimeout(timer); if (dragging) { justDragged.current = true; setTimeout(() =&gt; { justDragged.current = false; }, 60); } dragging = false; setDrag(null); document.body.style.userSelect = \"\"; window.removeEventListener(\"pointermove\", onMove); window.removeEventListener(\"pointerup\", end); };\n    window.addEventListener(\"pointermove\", onMove); window.addEventListener(\"pointerup\", end);\n  };\n\n  const startResize = (e, id) =&gt; {\n    e.preventDefault(); e.stopPropagation();\n    setResizingId(id);\n    const rect = ref.current.getBoundingClientRect();\n    const colW = (rect.width - GAP * (GRID_COLS - 1)) / GRID_COLS;\n    const t = tiles.find(x =&gt; x.id === id);\n    const startWpx = t.w * colW + (t.w - 1) * GAP;\n    const startHpx = t.h * ROW_UNIT + (t.h - 1) * GAP;\n    const sx = e.clientX, sy = e.clientY;\n    const onMove = (ev) =&gt; {\n      let w = Math.round((startWpx + (ev.clientX - sx) + GAP) / (colW + GAP));\n      let h = Math.round((startHpx + (ev.clientY - sy) + GAP) / (ROW_UNIT + GAP));\n      w = Math.max(3, Math.min(GRID_COLS, w));\n      h = Math.max(4, Math.min(14, h));\n      setTiles(ts =&gt; ts.map(x =&gt; x.id === id ? { ...x, w, h } : x));\n    };\n    const onUp = () =&gt; { setResizingId(null); document.body.style.cursor = \"\"; window.removeEventListener(\"mousemove\", onMove); window.removeEventListener(\"mouseup\", onUp); };\n    document.body.style.cursor = \"nwse-resize\";\n    window.addEventListener(\"mousemove\", onMove); window.addEventListener(\"mouseup\", onUp);\n  };\n\n  const isDefault = JSON.stringify(tiles) === JSON.stringify(tileSet);\n  return (\n    &lt;&gt;\n      \n\n         setTiles(tileSet)} /&gt;\n      \n    \n\n      {tiles.map((t, ti) =&gt; {\n        const m = TILE_META[t.id];\n        const isHover = hoverId === t.id, isResizing = resizingId === t.id, isDragging = dragId === t.id, anyDrag = dragId !== null;\n        return (\n          \n (tileEls.current[t.id] = el)}\n            onPointerDown={(e) =&gt; startDrag(e, t.id)}\n            onClickCapture={(e) =&gt; { if (justDragged.current) { e.stopPropagation(); justDragged.current = false; } }}\n            onMouseEnter={() =&gt; setHoverId(t.id)} onMouseLeave={() =&gt; setHoverId(null)}\n            style={{ gridColumn: `span ${t.w}`, gridRow: `span ${t.h}`, position: \"relative\", minWidth: 0, zIndex: 1 }}&gt;\n            {isDragging ? (\n              \n\n            ) : (&lt;&gt;\n            \n\n              \n { e.stopPropagation(); startDrag(e, t.id, true); }} title=\"Drag to move\" style={{ padding: \"13px 20px 12px\", borderBottom: `1px solid ${T.hair}`, display: \"flex\", alignItems: \"center\", gap: 8, cursor: \"grab\" }}&gt;\n                \n\n                  \n{m.eyebrow}\n                  \n{m.title}\n                \n                \n              \n              \n\n              {m.footer &amp;&amp; \n{m.footer}}\n            \n            \n startResize(e, t.id)} data-resize title=\"Drag to resize\"\n              style={{ position: \"absolute\", right: 0, bottom: 0, width: 20, height: 20, cursor: \"nwse-resize\", display: \"flex\", alignItems: \"flex-end\", justifyContent: \"flex-end\", padding: 4, opacity: isHover || isResizing ? 1 : 0, transition: \"opacity .15s\" }}&gt;\n              \n            \n            )}\n          \n        );\n      })}\n    \n    {drag &amp;&amp; (() =&gt; { const m = TILE_META[drag.id]; return createPortal(\n      \n\n        \n\n          \n\n            \n\n              \n{m.eyebrow}\n              \n{m.title}\n            \n            \n          \n          \nDrop to reorder\n        \n      , document.body); })()}\n    \n  );\n}\n\n/* ============ Assets table view (real device schema) ============ */\nconst ADAPTERS = {\n  aws_adapter: { brand: \"aws\" }, active_directory_adapter: { brand: \"active_directory\" },\n  crowd_strike_adapter: { brand: \"crowdstrike\" }, google_mdm_adapter: { brand: \"googlecloud\" },\n  esx_adapter: { brand: \"vmware\" }, sentinelone_adapter: { brand: \"sentinelone\" },\n  service_now_adapter: { brand: \"service_now\" }, epo_adapter: { brand: \"epo\" },\n  paloalto_panorama_adapter: { brand: \"paloalto\" }, cisco_meraki_adapter: { brand: \"cisco_meraki\" },\n  cisco_ise_adapter: { brand: \"cisco_ise\" }, cisco_adapter: { brand: \"cisco\" },\n  tanium_adapter: { brand: \"tanium\" }, tanium_asset_adapter: { brand: \"tanium\" },\n  claroty_adapter: { brand: \"claroty\" }, counter_act_adapter: { brand: \"forescout\" },\n  tenable_security_center_adapter: { brand: \"tenable\" }, zoom_adapter: { brand: \"zoom\" },\n  checkpoint_r80_adapter: { brand: \"checkpoint\" }, cylance_adapter: { brand: \"cylance\" },\n  chef_adapter: { brand: \"chef\" }, axonius_network_inspector_adapter: { short: \"NP\", color: \"#1C1D1F\" },\n};\nfunction AdapterAvatar({ k, size = 32 }) {\n  const a = ADAPTERS[k] || { short: k.slice(0, 2).toUpperCase(), color: \"#939598\" };\n  if (a.brand) return ;\n  return \n 28 ? 11 : 9, fontWeight: 700, color: a.color }}&gt;{a.short};\n}\nfunction AdapterStack({ list }) {\n  const shown = list.slice(0, 3), extra = list.length - shown.length;\n  return (\n    \n      \n      {shown.map((k, i) =&gt; )}\n      {extra &gt; 0 &amp;&amp; +{extra}}\n    \n  );\n}\n/* OS category icons */\nfunction OsIcon({ os }) {\n  const c = { Windows: \"#0078D4\", Linux: \"#1C1D1F\", \"OS X\": \"#555\", iOS: \"#111\", Android: \"#3DDC84\", VMWare: \"#607078\" }[os] || T.faint;\n  const mc = T.isLight ? \"#1B2030\" : \"#D6DAE6\"; // monochrome marks adapt to theme\n  const g = {\n    Windows: ,\n    \"OS X\": ,\n    iOS: ,\n    Android: ,\n    Linux: ,\n    VMWare: vm,\n  }[os];\n  if (!os) return \u2014;\n  return {g || }{os};\n}\nconst DEVICES = [\n  { name: \"PC-CURTIS-WILLIAMS\", host: \"PC-CURTIS-WILLIAMS.demo.local\", os: \"Windows\", ip: \"10.0.49.148\", mac: \"88:53:2E:12:45:C4\", seen: \"2026-06-08 09:34\", tags: [\"Corporate\", \"Managed\"], adapters: [\"active_directory_adapter\", \"crowd_strike_adapter\", \"epo_adapter\", \"google_mdm_adapter\", \"tanium_adapter\", \"sentinelone_adapter\", \"cisco_ise_adapter\"] },\n  { name: \"infranginx-5567897-stg\", host: \"esx-infranginx-5567897-stg.demo.local\", os: \"Linux\", ip: \"10.0.63.107\", mac: \"00:0C:29:12:55:38\", seen: \"2026-06-08 11:43\", tags: [\"Staging\", \"Cloud\"], adapters: [\"cisco_ise_adapter\", \"claroty_adapter\", \"counter_act_adapter\", \"epo_adapter\", \"tanium_adapter\", \"esx_adapter\", \"aws_adapter\"] },\n  { name: \"WIN-RUTHD\", host: \"WIN-RUTHD.demo.local\", os: \"Windows\", ip: \"10.0.48.107\", mac: \"88:53:2E:12:44:9B\", seen: \"2026-06-08 13:18\", tags: [\"Corporate\"], adapters: [\"active_directory_adapter\", \"cisco_adapter\", \"crowd_strike_adapter\", \"cylance_adapter\", \"epo_adapter\", \"tanium_adapter\"] },\n  { name: \"external3026786-prd\", host: \"sepio-external3026786-prd.demo.local\", os: \"Linux\", ip: \"10.0.64.36\", mac: \"88:53:2E:12:56:12\", seen: \"2026-06-08 05:54\", tags: [\"Production\", \"Internet-facing\"], adapters: [\"checkpoint_r80_adapter\", \"chef_adapter\", \"epo_adapter\", \"paloalto_panorama_adapter\", \"axonius_network_inspector_adapter\", \"claroty_adapter\", \"tenable_security_center_adapter\"] },\n  { name: \"macbook-pro-jdoe\", host: \"macbook-pro-jdoe.demo.local\", os: \"OS X\", ip: \"10.0.51.22\", mac: \"A4:83:E7:9C:11:04\", seen: \"2026-06-08 12:02\", tags: [\"Endpoint\", \"BYOD\"], adapters: [\"google_mdm_adapter\", \"crowd_strike_adapter\", \"sentinelone_adapter\"] },\n  { name: \"lablb-2918146-beta\", host: \"esx-lablb-2918146-beta.manufacturing.com\", os: \"Linux\", ip: \"10.0.56.90\", mac: \"00:0C:29:12:4C:BF\", seen: \"2026-06-08 11:30\", tags: [\"Lab\"], adapters: [\"cisco_meraki_adapter\", \"claroty_adapter\", \"epo_adapter\", \"esx_adapter\", \"tanium_asset_adapter\", \"counter_act_adapter\", \"service_now_adapter\"] },\n  { name: \"iphone-asmith\", host: \"\u2014\", os: \"iOS\", ip: \"10.0.77.51\", mac: \"F0:18:98:22:7D:AA\", seen: \"2026-06-08 11:58\", tags: [\"Mobile\", \"BYOD\"], adapters: [\"google_mdm_adapter\", \"zoom_adapter\"] },\n  { name: \"srv-db-fin-04\", host: \"srv-db-fin-04.healthcare-subsidiary.com\", os: \"Linux\", ip: \"10.0.40.12\", mac: \"00:50:56:9A:3C:11\", seen: \"2026-06-08 07:12\", tags: [\"Production\", \"Database\", \"PCI\"], adapters: [\"service_now_adapter\", \"tenable_security_center_adapter\", \"tanium_adapter\", \"epo_adapter\", \"aws_adapter\", \"claroty_adapter\"] },\n  { name: \"azure-infra9274676-prd\", host: \"azure-infra9274676-prd.demo.local\", os: \"Windows\", ip: \"10.0.61.5\", mac: \"00:0D:3A:1F:8E:22\", seen: \"2026-06-08 10:47\", tags: [\"Production\", \"Cloud\"], adapters: [\"active_directory_adapter\", \"aws_adapter\", \"sentinelone_adapter\", \"service_now_adapter\"] },\n  { name: \"android-pixel-7\", host: \"\u2014\", os: \"Android\", ip: \"10.0.78.130\", mac: \"3C:28:6D:55:01:9F\", seen: \"2026-06-08 09:05\", tags: [\"Mobile\"], adapters: [\"google_mdm_adapter\"] },\n  { name: \"Win-MaryChasse\", host: \"WIN-MARYCHASSE.demo.local\", os: \"Windows\", ip: \"10.0.49.201\", mac: \"88:53:2E:12:48:7E\", seen: \"2026-06-07 22:41\", tags: [\"Corporate\"], adapters: [\"active_directory_adapter\", \"epo_adapter\", \"cisco_meraki_adapter\"] },\n  { name: \"esx-mail-2231-prd\", host: \"esx-mail-2231-prd.manufacturing.com\", os: \"VMWare\", ip: \"10.0.62.18\", mac: \"00:0C:29:44:1A:E0\", seen: \"2026-06-08 06:33\", tags: [\"Production\"], adapters: [\"esx_adapter\", \"tenable_security_center_adapter\", \"paloalto_panorama_adapter\"] },\n  { name: \"PC-Doris9920\", host: \"PC-DORIS9920.demo.local\", os: \"Windows\", ip: \"10.0.50.66\", mac: \"88:53:2E:12:51:C9\", seen: \"2026-06-08 08:59\", tags: [\"Corporate\", \"Managed\"], adapters: [\"active_directory_adapter\", \"crowd_strike_adapter\", \"tanium_adapter\", \"google_mdm_adapter\"] },\n  { name: \"kiosk-lobby-03\", host: \"kiosk-lobby-03.demo.local\", os: \"Linux\", ip: \"10.0.44.7\", mac: \"B8:27:EB:0C:5A:31\", seen: \"2026-06-07 19:20\", tags: [\"IoT\", \"Unmanaged\"], adapters: [\"counter_act_adapter\", \"claroty_adapter\"] },\n];\n\nconst INIT_COLS = [\n  { key: \"adapters\", label: \"Adapter Connections\", w: 230, pinned: false },\n  { key: \"name\", label: \"Asset Name\", w: 230, pinned: false },\n  { key: \"host\", label: \"Host Name\", w: 300, pinned: false },\n  { key: \"os\", label: \"OS Type\", w: 140, pinned: false },\n  { key: \"ip\", label: \"IP Address\", w: 140, pinned: false },\n  { key: \"mac\", label: \"MAC Address\", w: 160, pinned: false },\n  { key: \"seen\", label: \"Last Seen (UTC)\", w: 170, pinned: false },\n  { key: \"tags\", label: \"Tags\", w: 220, pinned: false },\n];\nconst CB_W = 46;\nconst mono = { fontFamily: \"ui-monospace, 'SF Mono', monospace\" };\nfunction cellContent(key, r) {\n  switch (key) {\n    case \"adapters\": return ;\n    case \"name\": return {r.name};\n    case \"host\": return {r.host};\n    case \"os\": return ;\n    case \"ip\": return {r.ip};\n    case \"mac\": return {r.mac};\n    case \"seen\": return {r.seen};\n    case \"tags\": return {r.tags.slice(0, 2).map((t, j) =&gt; )}{r.tags.length &gt; 2 &amp;&amp; +{r.tags.length - 2}};\n    default: return null;\n  }\n}\n\nfunction HeaderCell({ col, left, isLastPinned, onResize, onPin, onDragStart, regRef, dragging }) {\n  const [h, setH] = useState(false);\n  const sticky = col.pinned;\n  return (\n    \n regRef(col.key, el)} onPointerDown={(e) =&gt; onDragStart(e, col.key)} onMouseEnter={() =&gt; setH(true)} onMouseLeave={() =&gt; setH(false)}\n      style={{ width: col.w, flexShrink: 0, position: sticky ? \"sticky\" : \"relative\", left: sticky ? left : undefined, zIndex: sticky ? 4 : 1, background: T.canvas, height: 40, display: \"flex\", alignItems: \"center\", gap: 6, padding: \"0 12px\", boxShadow: isLastPinned ? \"2px 0 5px rgba(27,32,70,0.06)\" : \"none\", cursor: \"grab\", opacity: dragging ? 0.4 : 1 }}&gt;\n      {col.label}\n      {(h || col.pinned) &amp;&amp; (\n         onPin(col.key)} title={col.pinned ? \"Unpin column\" : \"Pin column\"}\n          style={{ width: 24, height: 24, borderRadius: 6, border: \"none\", cursor: \"pointer\", background: \"transparent\", color: col.pinned ? T.blue : T.faint, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", transform: col.pinned ? \"none\" : \"rotate(45deg)\", flexShrink: 0 }}&gt;\n          \n        \n      )}\n      \n onResize(e, col.key)} data-colresize title=\"Drag to resize\"\n        style={{ position: \"absolute\", right: -3, top: 8, width: 6, height: 24, cursor: \"col-resize\", display: \"flex\", justifyContent: \"center\", zIndex: 5 }}&gt;\n        \n      \n    \n  );\n}\n\nfunction AssetsTable() {\n  const [columns, setColumns] = useState(INIT_COLS);\n  const [hoverRow, setHoverRow] = useState(null);\n  const [perPage, setPerPage] = useState(20);\n  const [dragCol, setDragCol] = useState(null);\n  const colEls = useRef({});\n  const regRef = (k, el) =&gt; { colEls.current[k] = el; };\n\n  const startColDrag = (e, key) =&gt; {\n    if (e.button || (e.target.closest &amp;&amp; (e.target.closest(\"[data-colresize]\") || e.target.closest(\"button\")))) return;\n    const startX = e.clientX; let dragging = false;\n    const onMove = (ev) =&gt; {\n      if (!dragging) { if (Math.abs(ev.clientX - startX) &gt; 6) { dragging = true; setDragCol(key); document.body.style.userSelect = \"none\"; } else return; }\n      for (const k in colEls.current) {\n        const el = colEls.current[k]; if (!el || k === key) continue;\n        const r = el.getBoundingClientRect();\n        if (ev.clientX &gt;= r.left &amp;&amp; ev.clientX &lt;= r.right) {\n          setColumns(cs =&gt; { const from = cs.findIndex(c =&gt; c.key === key), to = cs.findIndex(c =&gt; c.key === k); if (from &lt; 0 || to &lt; 0 || from === to) return cs; const n = cs.slice(); const [m] = n.splice(from, 1); n.splice(to, 0, m); return n; });\n          break;\n        }\n      }\n    };\n    const onUp = () =&gt; { dragging = false; setDragCol(null); document.body.style.userSelect = \"\"; window.removeEventListener(\"pointermove\", onMove); window.removeEventListener(\"pointerup\", onUp); };\n    window.addEventListener(\"pointermove\", onMove); window.addEventListener(\"pointerup\", onUp);\n  };\n\n  const startColResize = (e, key) =&gt; {\n    e.preventDefault(); e.stopPropagation();\n    const sx = e.clientX, startW = columns.find(c =&gt; c.key === key).w;\n    const onMove = (ev) =&gt; { const w = Math.max(90, startW + (ev.clientX - sx)); setColumns(cs =&gt; cs.map(c =&gt; c.key === key ? { ...c, w } : c)); };\n    const onUp = () =&gt; { document.body.style.cursor = \"\"; window.removeEventListener(\"mousemove\", onMove); window.removeEventListener(\"mouseup\", onUp); };\n    document.body.style.cursor = \"col-resize\";\n    window.addEventListener(\"mousemove\", onMove); window.addEventListener(\"mouseup\", onUp);\n  };\n  const togglePin = (key) =&gt; setColumns(cs =&gt; cs.map(c =&gt; c.key === key ? { ...c, pinned: !c.pinned } : c));\n\n  const pinned = columns.filter(c =&gt; c.pinned), unpinned = columns.filter(c =&gt; !c.pinned);\n  const ordered = [...pinned, ...unpinned];\n  const leftMap = {}; let acc = CB_W;\n  pinned.forEach(c =&gt; { leftMap[c.key] = acc; acc += c.w; });\n  const lastPinned = pinned.length ? pinned[pinned.length - 1].key : null;\n  const totalW = CB_W + columns.reduce((a, c) =&gt; a + c.w, 0);\n\n  return (\n    \n\n      \n\n        \n\n          {/* header */}\n          \n\n            \n\n              \n            \n            {ordered.map(c =&gt; )}\n            \n\n          \n          {/* rows */}\n          {DEVICES.map((r, i) =&gt; {\n            const rowBg = hoverRow === i ? T.surface2 : T.surface;\n            return (\n              \n setHoverRow(i)} onMouseLeave={() =&gt; setHoverRow(null)}\n                style={{ display: \"flex\", height: 52, borderBottom: i &lt; DEVICES.length - 1 ? `1px solid ${T.hair}` : \"none\", background: rowBg }}&gt;\n                \n\n                  {hoverRow === i ?  : {i + 1}}\n                \n                {ordered.map(c =&gt; {\n                  const sticky = c.pinned;\n                  return (\n                    \n\n                      {cellContent(c.key, r)}\n                    \n                  );\n                })}\n                \n\n              \n            );\n          })}\n        \n      \n      {/* footer */}\n      \n\n        \n\n          Results per page:\n          \n\n            {[20, 50, 100].map(n =&gt;  setPerPage(n)} style={{ minWidth: 30, height: 28, borderRadius: 8, border: \"none\", cursor: \"pointer\", fontFamily: T.font, fontSize: 13, fontWeight: 500, ...tnum, background: perPage === n ? T.blue : \"transparent\", color: perPage === n ? T.white : T.body }}&gt;{n})}\n          \n        \n        \n\n          1\u2013{perPage} of 12,409\n          \n\n            \n            1234\n            \u2026621\n            \n          \n        \n      \n    \n  );\n}\n\nfunction QueryPill({ children, primary, active, onClick }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, height: 32, padding: \"0 14px\", borderRadius: 999, cursor: \"pointer\", fontFamily: T.font, fontSize: 13.5, fontWeight: 600, border: primary ? \"none\" : `1px solid ${active || h ? T.lineStrong : T.line}`, background: primary ? T.blue : active || h ? T.surface2 : T.control, color: primary ? T.onAccent : T.body }}&gt;{children};\n}\nfunction ToolLink({ icon: Icon, children }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, height: 30, padding: \"0 10px\", border: \"none\", borderRadius: 999, background: h ? T.surface2 : \"transparent\", color: T.body, fontSize: 13.5, fontWeight: 600, cursor: \"pointer\", fontFamily: T.font }}&gt;{Icon &amp;&amp; }{children};\n}\nfunction SegTool({ icon: Icon, title, first }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)}&gt;\n      \n      {h &amp;&amp; {title}}\n    \n  );\n}\nfunction IconTool({ icon: Icon, title }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)}&gt;\n      \n      {h &amp;&amp; {title}}\n    \n  );\n}\nfunction Segmented({ options, value, onChange }) {\n  const idx = Math.max(0, options.indexOf(value));\n  const wpct = 100 / options.length;\n  return (\n    \n\n      \n      {options.map(o =&gt; (\n         onChange(o)} style={{ position: \"relative\", zIndex: 1, minWidth: 62, padding: \"0 12px\", border: \"none\", background: \"transparent\", color: value === o ? T.onAccent : T.body, fontSize: 13.5, fontWeight: value === o ? 600 : 500, cursor: \"pointer\", fontFamily: T.font, transition: \"color .2s\" }}&gt;{o}\n      ))}\n    \n  );\n}\n\nfunction QueriesPanel({ close }) {\n  const recent = [\n    { nm: \"FH: 1.4 Asset Context.Business Importance\", sub: \"Shared Queries\" },\n    { nm: \"low Risk Score_2025-12-24T11-53\", sub: \"Public Queries / Predefined Queries\" },\n    { nm: \"Unmanaged endpoints seen &lt; 7d\", sub: \"Shared Queries\" },\n    { nm: \"Critical CVEs on internet-facing\", sub: \"Public Queries / Predefined Queries\" },\n  ];\n  return createPortal(&lt;&gt;\n    \n\n    \n\n      \n\n        Queries\n        \n        \n        \n      \n      \n\n        \n\n          \n        \n      \n      \n\n        \nFavorites\n        \nRecently Used Saved Queries\n        {recent.map((q, i) =&gt; (\n           (e.currentTarget.style.background = T.surface2)} onMouseLeave={(e) =&gt; (e.currentTarget.style.background = \"transparent\")}\n            style={{ display: \"flex\", alignItems: \"center\", gap: 11, width: \"100%\", border: \"none\", background: \"transparent\", borderRadius: 10, cursor: \"pointer\", padding: \"9px\", textAlign: \"left\", fontFamily: T.font }}&gt;\n            \n            \n              {q.nm}\n              {q.sub}\n            \n          \n        ))}\n      \n    \n  , document.body);\n}\nfunction AssetsView() {\n  const [mode, setMode] = useState(\"Wizard\");\n  const [enrichOpen, setEnrichOpen] = useState(false);\n  const [queriesOpen, setQueriesOpen] = useState(false);\n  return (\n    \n\n      \n\n      {/* breadcrumb + title + module actions */}\n      \n\n        \n\n          \nInventory / Assets\n          \nDevices\n        \n        \n\n          \n\n             setEnrichOpen(o =&gt; !o)}&gt;Enrichment &amp; Investigation \n            {enrichOpen &amp;&amp; (&lt;&gt;\n              \n setEnrichOpen(false)} style={{ position: \"fixed\", inset: 0, zIndex: 40 }} /&gt;\n              \n\n                {[\"Business Data Enrichment\", \"Device Inventory Classification\", \"Asset Investigation\"].map(o =&gt; (\n                   setEnrichOpen(false)} onMouseEnter={(e) =&gt; (e.currentTarget.style.background = T.surface2)} onMouseLeave={(e) =&gt; (e.currentTarget.style.background = \"transparent\")}\n                    style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", gap: 10, width: \"100%\", border: \"none\", background: \"transparent\", borderRadius: 8, cursor: \"pointer\", padding: \"9px 11px\", fontSize: 13, fontWeight: 500, fontFamily: T.font, color: T.body, textAlign: \"left\" }}&gt;\n                    {o} \n                  \n                ))}\n              \n            )}\n          \n          \n        \n      \n      {/* query toolbar */}\n      \n\n        New Query\n        Save As\n        Reset\n        Copy Query\n        \n\n        Display by Date\n        \n\n         setQueriesOpen(o =&gt; !o)}&gt;{queriesOpen ?  : &lt;&gt; Queries}\n        \n      \n      {/* search + wizard / basic filters */}\n      {mode === \"Wizard\" ? (\n        \n\n          \n\n            \n            \n          \n          Query Wizard\n        \n      ) : (\n        \n\n          \n          \n          \nLast Seen\n          \nTags\n           Filter\n        \n      )}\n      {/* stats toolbar */}\n      \n\n        Total 12,409\n         Last updated: 2026-06-08 17:07:59\n        \n\n        \n\n          \n\n            \n            \n            \n          \n           New Asset\n        \n      \n      {/* end flexShrink header */}\n      \n\n        \n      \n      {queriesOpen &amp;&amp;  setQueriesOpen(false)} /&gt;}\n    \n  );\n}\nfunction PageBtn({ children, active, disabled, wide }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ minWidth: wide ? \"auto\" : 30, height: 30, padding: wide ? \"0 10px\" : 0, borderRadius: 999, border: active ? \"none\" : `1px solid ${h &amp;&amp; !disabled ? T.lineStrong : \"transparent\"}`, background: active ? T.blue : h &amp;&amp; !disabled ? T.surface2 : \"transparent\", color: active ? T.onAccent : disabled ? T.faint : T.body, fontSize: 13, fontWeight: 500, cursor: disabled ? \"default\" : \"pointer\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: T.font, ...tnum }}&gt;{children};\n}\n\n/* ============ Inventory tree panel (assets view) ============ */\nconst INV_GROUPS_INIT = [\n  { name: \"Compute\", icon: Server, open: true, items: [\n    { icon: Server, label: \"Corporate Devices\", count: \"8,204\", fav: true },\n    { icon: ShieldAlert, label: \"Internet-facing Assets\", count: \"312\", fav: true },\n    { folder: \"Endpoint\", open: true, items: [\n      { icon: Server, label: \"Devices\", count: \"12,409\", active: true },\n      { icon: Workflow, label: \"Processes\", count: \"0\" },\n    ]},\n    { folder: \"Infrastructure\", open: false, items: [\n      { icon: Boxes, label: \"Compute Services\", count: \"14\" },\n      { icon: Cable, label: \"Databases\", count: \"36\" },\n      { icon: Boxes, label: \"Containers\", count: \"258\" },\n      { icon: Cable, label: \"Serverless Functions\", count: \"103\" },\n      { icon: Boxes, label: \"Compute Images\", count: \"25\" },\n    ]},\n    { icon: Settings, label: \"Configurations\", count: \"0\" },\n    { icon: Clock, label: \"Latest Devices\", count: \"12,409\" },\n  ] },\n  { name: \"Identity\", icon: Users, open: false, items: [\n    { folder: \"Directory\", open: false, items: [\n      { icon: Users, label: \"Users\", count: \"4,821\" },\n      { icon: Users, label: \"Groups\", count: \"312\" },\n    ]},\n    { icon: ShieldAlert, label: \"Service Accounts\", count: \"87\" },\n  ] },\n  { name: \"Applications\", icon: Layers, open: false, items: [\n    { icon: Layers, label: \"SaaS Apps\", count: \"143\" },\n    { icon: Layers, label: \"Installed Software\", count: \"2,904\" },\n  ] },\n  { name: \"Tickets\", icon: Bell, open: false, items: [\n    { icon: Bell, label: \"Open Tickets\", count: \"48\" },\n    { icon: Bell, label: \"Resolved\", count: \"203\" },\n  ] },\n];\nfunction InventoryPanel({ collapsed, setCollapsed }) {\n  const [groups, setGroups] = useState(INV_GROUPS_INIT);\n  const [favOpen, setFavOpen] = useState(true);\n\n  const collectItems = (items) =&gt; items.flatMap(it =&gt; it.folder ? collectItems(it.items) : [it]);\n  const favItems = groups.flatMap(g =&gt; collectItems(g.items)).filter(it =&gt; it.fav);\n\n  const toggleFavInItems = (items, label) =&gt; items.map(it =&gt;\n    it.folder ? { ...it, items: toggleFavInItems(it.items, label) }\n              : it.label === label ? { ...it, fav: !it.fav } : it\n  );\n  const toggleFav = (label) =&gt; setGroups(gs =&gt; gs.map(g =&gt; ({ ...g, items: toggleFavInItems(g.items, label) })));\n  if (collapsed) {\n    const compute = groups.find(g =&gt; g.name === \"Compute\");\n    return (\n      \n\n         setCollapsed(false)} title=\"Expand inventory\" className=\"ax-edge-collapse\"\n          style={{ position: \"absolute\", top: 16, right: -14, zIndex: 6, width: 28, height: 28, borderRadius: \"50%\", border: `1px solid ${T.line}`, background: T.surface, color: T.muted, cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", boxShadow: T.shadow }}&gt;\n          \n        \n        \n\n          \n        \n        \n\n        {(compute ? compute.items : []).map((it, i) =&gt; {\n          const Icon = it.icon;\n          return (\n            \n\n              \n            \n          );\n        })}\n      \n    );\n  }\n  return (\n    \n\n       setCollapsed(true)} title=\"Collapse panel\" className=\"ax-edge-collapse\"\n        style={{ position: \"absolute\", top: 16, right: -14, zIndex: 6, width: 28, height: 28, borderRadius: \"50%\", border: `1px solid ${T.line}`, background: T.surface, color: T.muted, cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", boxShadow: T.shadow }}&gt;\n        \n      \n      \n\n        \n\n          \n        \n      \n      \n\n        \n\n          \n setFavOpen(o =&gt; !o)} style={{ display: \"flex\", alignItems: \"center\", gap: 9, padding: \"9px 8px\", cursor: \"pointer\", borderRadius: 8, color: T.body }}&gt;\n            \n            \n            Favorites\n            {favItems.length}\n          \n          {favOpen &amp;&amp; favItems.map((it, ii) =&gt; )}\n        \n        {groups.map((g, gi) =&gt; { const Icon = g.icon; return (\n          \n\n            \n setGroups(gs =&gt; gs.map((x, i) =&gt; i === gi ? { ...x, open: !x.open } : x))}\n              style={{ display: \"flex\", alignItems: \"center\", gap: 9, padding: \"9px 8px\", cursor: \"pointer\", borderRadius: 8, color: g.open ? T.accentText : T.body }}&gt;\n              \n              \n              {g.name}\n            \n            {g.open &amp;&amp; g.items.map((it, ii) =&gt; it.folder ?  : )}\n          \n        ); })}\n      \n    \n  );\n}\nfunction InvFolder({ folder, toggleFav }) {\n  const [open, setOpen] = useState(folder.open);\n  const [h, setH] = useState(false);\n  const FIcon = open ? FolderOpen : Folder;\n  return (\n    \n\n      \n setH(true)} onMouseLeave={() =&gt; setH(false)} onClick={() =&gt; setOpen(o =&gt; !o)}\n        style={{ display: \"flex\", alignItems: \"center\", gap: 8, height: 34, padding: \"0 8px 0 22px\", borderRadius: 8, cursor: \"pointer\", background: h ? T.surface2 : \"transparent\", transition: \"background .12s\" }}&gt;\n        \n        \n        {folder.folder}\n      \n      {open &amp;&amp; folder.items.map((it, i) =&gt; )}\n    \n  );\n}\nfunction InvRow({ icon: Icon, label, count, active, fav, indent, toggleFav }) {\n  const [h, setH] = useState(false);\n  const left = indent || 28;\n  return (\n    \n setH(true)} onMouseLeave={() =&gt; setH(false)}\n      style={{ position: \"relative\", display: \"flex\", alignItems: \"center\", gap: 11, height: 36, margin: \"1px 0\", padding: `0 8px 0 ${left}px`, borderRadius: 8, cursor: \"pointer\", background: active ? T.accentSoft : h ? T.surface2 : \"transparent\", transition: \"background .14s\" }}&gt;\n      \n      {label}\n      {(h || fav) &amp;&amp; toggleFav &amp;&amp; (\n         { e.stopPropagation(); toggleFav(label); }} title={fav ? \"Remove from favorites\" : \"Add to favorites\"} className=\"ax-star\"\n          style={{ width: 22, height: 22, borderRadius: 6, border: \"none\", background: \"transparent\", cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", padding: 0, flexShrink: 0 }}&gt;\n          \n        \n      )}\n      {!h &amp;&amp; !fav &amp;&amp; {count}}\n    \n  );\n}\n\n/* ============ chrome ============ */\nfunction AppHeader({ theme, setTheme }) {\n  return (\n    \n\n      \n\n        \n      \n      \n\n        \n\n          Search Axonius\n          \u2318K\n        \n      \n      \n\n        New navigation BETA\n         setTheme(theme === \"dark\" ? \"light\" : \"dark\")} title=\"Toggle light / dark\"&gt;{theme === \"dark\" ?  : }\n        \n        \n        \nSB\n      \n    \n  );\n}\nfunction HBtn({ children, onClick, title }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ width: 32, height: 32, borderRadius: 8, border: \"none\", cursor: \"pointer\", background: h ? T.surface2 : \"transparent\", color: h ? T.ink : T.bg80, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}&gt;{children};\n}\n\nconst HICONS = {\n  dashboard: '',\n  inventory: '',\n  shield: '',\n  bolt: '',\n  link: '',\n  chart: '',\n  cog: '',\n};\nfunction HIcon({ name, size = 20, color = \"currentColor\", sw = 1.6, className }) {\n  return ${HICONS[name] || \"\"}` }} /&gt;;\n}\nconst RAIL = [\n  { key: \"dashboard\", hi: \"dashboard\", label: \"Dashboard\" }, { key: \"assets\", hi: \"inventory\", label: \"Inventory\" },\n  { key: \"exposures\", hi: \"shield\", label: \"Exposures\" }, { key: \"action\", hi: \"bolt\", label: \"Action Center\" },\n  { key: \"adapters\", hi: \"link\", label: \"Adapters\" }, { key: \"analysis\", hi: \"chart\", label: \"Analysis\" },\n];\nfunction IconRail({ view, setView }) {\n  return (\n    \n\n      {RAIL.map(r =&gt;  setView(r.key)} /&gt;)}\n      \n\n       {}} /&gt;\n      \n\n    \n  );\n}\nfunction RailGlyph({ hi, label, active, onClick }) {\n  const [h, setH] = useState(false);\n  return (\n    \n setH(true)} onMouseLeave={() =&gt; setH(false)} title={label} className=\"ax-rail ax-press\"\n      style={{ position: \"relative\", width: 44, height: 44, borderRadius: 14, cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", background: active ? T.accentSoft : h ? T.surface2 : \"transparent\", transition: \"background .15s\" }}&gt;\n      \n    \n  );\n}\n\nconst DASH_TREE_INIT = [\n  { g: \"Favorites\", open: true, items: [\n    { nm: \"My Dashboard\", fav: true }, { nm: \"Asset Profile - Copy_2024-06-09\", fav: true },\n    { nm: \"Axonius Dashboard\", fav: true, active: true, def: true }, { nm: \"Cost Optimization\", fav: true }, { nm: \"Cloud Compliance\", fav: true },\n  ] },\n  { g: \"Public\", open: false, items: [\n    { folder: \"Security\", open: false, items: [{ nm: \"SecOps Overview\" }, { nm: \"Threat Intelligence\" }] },\n    { folder: \"Executive\", open: false, items: [{ nm: \"Executive Summary\" }, { nm: \"Board Report\" }] },\n    { nm: \"Asset Inventory V 1.0\" }, { nm: \"Active Directory Insights\" },\n  ] },\n  { g: \"Shared\", open: false, items: [\n    { folder: \"Team SOC\", open: false, items: [{ nm: \"Vulnerability Posture\" }, { nm: \"Incident Response\" }] },\n    { nm: \"Cloud Inventory\" }, { nm: \"Compliance Overview\" },\n  ] },\n  { g: \"Private\", open: false, items: [{ nm: \"My Drafts\" }, { nm: \"Work in Progress\" }] },\n  { g: \"Managed by Axonius\", open: false, items: [{ nm: \"Device Overview\" }, { nm: \"User Overview\" }, { nm: \"Adapters Health\" }, { nm: \"Coverage &amp; Gaps\" }] },\n];\nfunction PanelBtn({ primary, filled, icon: Icon, children, onClick }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)} className={filled ? \"ax-primary\" : \"ax-press\"}\n      style={{ height: 38, borderRadius: 999, border: filled ? \"none\" : primary ? `1.5px solid ${T.accentText}` : `1px solid ${h ? T.lineStrong : T.line}`, cursor: \"pointer\", width: \"100%\", background: filled ? (h ? T.blueDeep : T.blue) : primary ? (h ? T.accentSoft : \"transparent\") : h ? T.surface2 : \"transparent\", color: filled ? T.onAccent : primary ? T.accentText : T.body, fontSize: 13, fontWeight: 600, fontFamily: T.font, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", gap: 7, boxShadow: \"none\", transition: \"background .14s ease, border-color .14s ease\" }}&gt;\n       {children}\n    \n  );\n}\nfunction CreateDashModal({ onCreate, close }) {\n  const [name, setName] = useState(\"\");\n  const [tpl, setTpl] = useState(\"blank\");\n  const tpls = [{ k: \"blank\", t: \"Blank dashboard\", d: \"Start from an empty canvas\" }, { k: \"template\", t: \"From a template\", d: \"Prebuilt charts to tweak\" }];\n  return createPortal(\n    \n\n      \n e.stopPropagation()} style={{ width: 480, maxWidth: \"92vw\", background: T.canvas, border: `1px solid ${T.line}`, borderRadius: 14, boxShadow: \"0 30px 80px rgba(0,0,0,.55)\", overflow: \"hidden\", animation: \"axRise .26s cubic-bezier(.22,1,.36,1) both\" }}&gt;\n        \n\n          Create dashboard\n          \n        \n        \n\n          \n\n            Dashboard name\n             setName(e.target.value)} placeholder=\"e.g. Security Posture\" style={{ width: \"100%\", height: 40, padding: \"0 14px\", border: `1px solid ${T.line}`, borderRadius: 8, background: T.control, color: T.ink, fontFamily: T.font, fontSize: 14, outline: \"none\", boxSizing: \"border-box\" }} /&gt;\n          \n          \n\n            Starting point\n            \n\n              {tpls.map(o =&gt; (\n                 setTpl(o.k)} style={{ flex: 1, textAlign: \"left\", padding: \"12px 13px\", borderRadius: 10, border: `1.5px solid ${tpl === o.k ? T.accentText : T.line}`, background: tpl === o.k ? T.accentSoft : \"transparent\", cursor: \"pointer\", fontFamily: T.font }}&gt;\n                  \n{o.t}\n                  \n{o.d}\n                \n              ))}\n            \n          \n        \n        \n\n          Cancel\n           name.trim() &amp;&amp; onCreate(name.trim())} className=\"ax-press\" style={{ height: 38, padding: \"0 18px\", borderRadius: 999, border: \"none\", background: name.trim() ? T.blue : T.surface2, color: name.trim() ? T.onAccent : T.faint, fontSize: 13.5, fontWeight: 600, cursor: name.trim() ? \"pointer\" : \"default\", fontFamily: T.font }}&gt;Create dashboard\n        \n      \n    , document.body);\n}\nfunction SidePanel({ collapsed, setCollapsed, activeDash, setActiveDash }) {\n  const [tree, setTree] = useState(DASH_TREE_INIT);\n  const [menu, setMenu] = useState(null);\n  const [editing, setEditing] = useState(null);\n  const [createOpen, setCreateOpen] = useState(false);\n\n  const mut = (fn) =&gt; setTree(t =&gt; fn(t.map(g =&gt; ({ ...g, items: g.items.map(it =&gt; ({ ...it })) }))));\n  const toggleGroup = (gi) =&gt; setTree(t =&gt; t.map((g, i) =&gt; i === gi ? { ...g, open: !g.open } : g));\n  const setActive = (gi, ii) =&gt; { mut(t =&gt; { t.forEach(g =&gt; g.items.forEach(it =&gt; it.active = false)); t[gi].items[ii].active = true; return t; }); if (setActiveDash) setActiveDash(tree[gi].items[ii].nm); };\n  const toggleFav = (gi, ii) =&gt; mut(t =&gt; { t[gi].items[ii].fav = !t[gi].items[ii].fav; return t; });\n  const setDefault = (gi, ii) =&gt; mut(t =&gt; { t.forEach(g =&gt; g.items.forEach(it =&gt; it.def = false)); t[gi].items[ii].def = true; return t; });\n  const duplicate = (gi, ii) =&gt; mut(t =&gt; { t[gi].items.splice(ii + 1, 0, { nm: t[gi].items[ii].nm + \" (copy)\" }); return t; });\n  const rename = (gi, ii, nm) =&gt; mut(t =&gt; { t[gi].items[ii].nm = nm || t[gi].items[ii].nm; return t; });\n  const remove = (gi, ii) =&gt; mut(t =&gt; { t[gi].items.splice(ii, 1); return t; });\n  const createDash = (nm) =&gt; mut(t =&gt; { const p = t.find(g =&gt; g.g === \"Private\"); p.open = true; p.items.push({ nm: nm || \"Untitled dashboard\" }); return t; });\n  const openMenu = (gi, ii, e) =&gt; { const r = e.currentTarget.getBoundingClientRect(); setMenu({ gi, ii, top: r.bottom + 6, left: Math.max(8, r.right - 196) }); };\n\n  if (collapsed) {\n    const favItems = tree.flatMap(g =&gt; g.items).filter(it =&gt; it.fav || it.active);\n    return (\n      \n\n         setCollapsed(false)} title=\"Expand dashboards\" className=\"ax-edge-collapse\"\n          style={{ position: \"absolute\", top: 16, right: -14, zIndex: 6, width: 28, height: 28, borderRadius: \"50%\", border: `1px solid ${T.line}`, background: T.surface, color: T.muted, cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", boxShadow: T.shadow }}&gt;\n          \n        \n         setCreateOpen(true)} style={{ width: 38, height: 38, borderRadius: 8, border: `1.5px solid ${T.accentText}`, background: \"transparent\", cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", color: T.accentText }}&gt;\n          \n        \n        \n\n        {favItems.map((s, i) =&gt; (\n          \n\n            \n            {s.fav &amp;&amp; }\n          \n        ))}\n        {createOpen &amp;&amp;  { createDash(nm); setCreateOpen(false); }} close={() =&gt; setCreateOpen(false)} /&gt;}\n      \n    );\n  }\n  return (\n    \n\n       setCollapsed(true)} title=\"Collapse panel\" className=\"ax-edge-collapse\"\n        style={{ position: \"absolute\", top: 16, right: -14, zIndex: 6, width: 28, height: 28, borderRadius: \"50%\", border: `1px solid ${T.line}`, background: T.surface, color: T.muted, cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", boxShadow: T.shadow }}&gt;\n        \n      \n      \n\n         setCreateOpen(true)}&gt;Create Dashboard\n        Manage Dashboards\n      \n      \n\n        \n\n          \n        \n      \n      \n\n        {tree.map((g, gi) =&gt; (\n          \n        ))}\n      \n      {menu &amp;&amp;  setMenu(null)}\n        actions={{ rename: () =&gt; setEditing({ gi: menu.gi, ii: menu.ii }), duplicate, toggleFav, setDefault, remove }} /&gt;}\n      {createOpen &amp;&amp;  { createDash(nm); setCreateOpen(false); }} close={() =&gt; setCreateOpen(false)} /&gt;}\n    \n  );\n}\nconst DASH_CAT_ICON = { Favorites: Star, Public: Globe, Shared: Users, Private: Lock, \"Managed by Axonius\": ShieldCheck };\nfunction DashGroup({ group, gi, toggleGroup, setActive, toggleFav, openMenu, editing, setEditing, rename }) {\n  const Icon = DASH_CAT_ICON[group.g] || LayoutGrid;\n  return (\n    \n\n       toggleGroup(gi)} className=\"ax-catrow\"\n        style={{ display: \"flex\", alignItems: \"center\", gap: 9, width: \"100%\", border: \"none\", background: \"transparent\", cursor: \"pointer\", padding: \"8px 8px\", borderRadius: 8, fontFamily: T.font }}&gt;\n        \n        \n        {group.g}\n        {group.g === \"Favorites\" ? group.items.filter(it =&gt; it.fav !== false).length : group.items.length}\n      \n      {group.open &amp;&amp; group.items\n        .filter(it =&gt; group.g !== \"Favorites\" || it.fav !== false)\n        .map((it, ii) =&gt;\n          it.folder\n            ? \n            : \n        )}\n    \n  );\n}\nfunction DashFolder({ folder, indent, onToggle }) {\n  const [h, setH] = useState(false);\n  const [open, setOpen] = useState(folder.open);\n  const toggle = () =&gt; { setOpen(o =&gt; !o); if (onToggle) onToggle(!open); };\n  const FIcon = open ? FolderOpen : Folder;\n  return (\n    \n\n      \n setH(true)} onMouseLeave={() =&gt; setH(false)} onClick={toggle}\n        style={{ display: \"flex\", alignItems: \"center\", gap: 8, height: 34, padding: `0 8px 0 ${indent || 22}px`, borderRadius: 8, cursor: \"pointer\", background: h ? T.surface2 : \"transparent\", transition: \"background .12s\" }}&gt;\n        \n        \n        {folder.folder}\n      \n      {open &amp;&amp; folder.items.map((it, i) =&gt; (\n        \n e.currentTarget.style.background = T.surface2} onMouseLeave={(e) =&gt; e.currentTarget.style.background = \"transparent\"}&gt;\n          \n          {it.nm}\n        \n      ))}\n    \n  );\n}\nfunction DashItem({ item, gi, ii, setActive, toggleFav, openMenu, editing, setEditing, rename }) {\n  const [h, setH] = useState(false);\n  return (\n    \n setH(true)} onMouseLeave={() =&gt; setH(false)} onClick={() =&gt; !editing &amp;&amp; setActive(gi, ii)} className=\"ax-press\"\n      style={{ position: \"relative\", display: \"flex\", alignItems: \"center\", gap: 9, height: 36, padding: \"0 8px 0 28px\", margin: \"1px 0\", cursor: \"pointer\", borderRadius: 8, border: \"none\", background: item.active ? T.accentSoft : h ? T.surface2 : \"transparent\", transition: \"background .14s\" }}&gt;\n      \n      {editing ? (\n         e.stopPropagation()}\n          onBlur={(e) =&gt; { rename(gi, ii, e.target.value.trim()); setEditing(null); }}\n          onKeyDown={(e) =&gt; { if (e.key === \"Enter\") { rename(gi, ii, e.target.value.trim()); setEditing(null); } if (e.key === \"Escape\") setEditing(null); }}\n          style={{ flex: 1, minWidth: 0, border: `1px solid ${T.blue}`, borderRadius: 6, padding: \"2px 6px\", fontSize: 13.5, fontFamily: T.font, color: T.ink, outline: \"none\", background: T.surface }} /&gt;\n      ) : (\n        {item.nm}\n      )}\n      {(h || item.fav) &amp;&amp; !editing &amp;&amp; (\n         { e.stopPropagation(); toggleFav(gi, ii); }} title={item.fav ? \"Remove from favorites\" : \"Add to favorites\"} className=\"ax-star\"\n          style={{ width: 22, height: 22, borderRadius: 6, border: \"none\", background: \"transparent\", cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", padding: 0, flexShrink: 0 }}&gt;\n          \n        \n      )}\n      {h &amp;&amp; !editing &amp;&amp; (\n         { e.stopPropagation(); openMenu(gi, ii, e); }} title=\"More actions\"\n          style={{ width: 22, height: 22, borderRadius: 6, border: \"none\", background: \"transparent\", cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", padding: 0, flexShrink: 0, color: T.muted }}&gt;\n          \n        \n      )}\n    \n  );\n}\nfunction RowMenu({ menu, tree, close, actions }) {\n  const it = tree[menu.gi]?.items?.[menu.ii] || {};\n  const rows = [\n    { ic: Pencil, l: \"Rename\", fn: () =&gt; actions.rename() },\n    { ic: Copy, l: \"Duplicate\", fn: () =&gt; actions.duplicate(menu.gi, menu.ii) },\n    { ic: Star, l: it.fav ? \"Remove from favorites\" : \"Add to favorites\", fn: () =&gt; actions.toggleFav(menu.gi, menu.ii) },\n    { ic: Check, l: it.def ? \"Default dashboard\" : \"Set as default\", fn: () =&gt; actions.setDefault(menu.gi, menu.ii) },\n    { ic: Download, l: \"Export\", fn: () =&gt; {} },\n    { sep: true },\n    { ic: Trash2, l: \"Delete\", danger: true, fn: () =&gt; actions.remove(menu.gi, menu.ii) },\n  ];\n  return (\n    &lt;&gt;\n      \n\n      \n\n        {rows.map((a, i) =&gt; a.sep\n          ? \n\n          :  { a.fn(); close(); }}&gt;{a.l})}\n      \n    \n  );\n}\nfunction MenuRow({ icon: Icon, danger, children, onClick }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)}\n      style={{ display: \"flex\", alignItems: \"center\", gap: 9, width: \"100%\", border: \"none\", borderRadius: 8, cursor: \"pointer\", padding: \"7px 9px\", fontSize: 13, fontFamily: T.font, fontWeight: 500, textAlign: \"left\", color: danger ? T.red : T.body, background: h ? (danger ? `${T.red}10` : T.canvas) : \"transparent\" }}&gt;\n       {children}\n    \n  );\n}\n\nfunction ModuleHeader({ view, activeDash }) {\n  const isDash = view === \"dashboard\";\n  return (\n    \n\n      \n{isDash ? \"Dashboards / Favorites\" : \"Assets / All Devices\"}\n      \n\n        \n\n          \n{isDash ? activeDash : \"Assets\"}\n          {isDash &amp;&amp; }\n        \n        \n\n          {isDash ? &lt;&gt;\n             Updated 2m ago\n            Add Chart\n           : &lt;&gt;\n            Export\n            New Query\n          }\n          \n        \n      \n    \n  );\n}\nfunction HdrBtn({ icon: Icon, children, primary, filled }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ display: \"flex\", alignItems: \"center\", gap: 6, height: 34, padding: \"0 14px\", borderRadius: 999, cursor: \"pointer\", fontFamily: T.font, fontSize: 13.5, fontWeight: 600, border: filled ? \"none\" : (primary ? `1.5px solid ${T.accentText}` : `1px solid ${h ? T.lineStrong : T.line}`), background: filled ? (h ? T.blueDeep : T.blue) : (primary ? (h ? T.accentSoft : \"transparent\") : (h ? T.surface2 : T.control)), color: filled ? T.onAccent : (primary ? T.accentText : T.body) }}&gt;{children};\n}\n\nfunction AxoniusApp() {\n  const [view, setView] = useState(\"dashboard\");\n  const [collapsed, setCollapsed] = useState(false);\n  const [invCollapsed, setInvCollapsed] = useState(false);\n  const [theme, setTheme] = useState(\"dark\");\n  const [activeDash, setActiveDash] = useState(\"Axonius Dashboard\");\n  T = THEMES[theme];\n  useEffect(() =&gt; {\n    document.body.style.background = THEMES[theme].shell;\n    const el = document.documentElement;\n    el.classList.add(\"theming\");\n    const id = setTimeout(() =&gt; el.classList.remove(\"theming\"), 450);\n    return () =&gt; clearTimeout(id);\n  }, [theme]);\n  return (\n    \n\n      {`@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&amp;family=Schibsted+Grotesk:wght@500;600;700&amp;display=swap'); * { box-sizing: border-box; } .theming, .theming * { transition: background-color .4s ease, border-color .4s ease, color .35s ease, fill .3s ease, stroke .3s ease !important; } *::-webkit-scrollbar { width: 9px; height: 9px; } *::-webkit-scrollbar-thumb { background: ${T.bg40}; border-radius: 5px; } *::-webkit-scrollbar-track { background: transparent; }\n        .ax-tile *::-webkit-scrollbar-thumb { background: transparent; transition: background .2s ease; }\n        .ax-tile:hover *::-webkit-scrollbar-thumb { background: ${T.bg40}; }\n        .ax-tbl::-webkit-scrollbar-thumb { background: transparent; } .ax-tbl:hover::-webkit-scrollbar-thumb { background: ${T.bg40}; } .ax-tbl::-webkit-scrollbar-corner { background: transparent; }\n        input::placeholder { color: ${T.muted}; opacity: 1; }\n        .ax-catrow:hover { background: ${T.surface2}; }\n        .ax-catrow:hover .ax-favbtn { opacity: 1 !important; }\n        .ax-edge-collapse { opacity: 0; transform: scale(.8); transition: opacity .15s ease, transform .15s ease; }\n        .ax-sidepanel:hover .ax-edge-collapse { opacity: 1; transform: scale(1); }\n        button, [role=\"button\"] { transition: transform .14s cubic-bezier(.34,1.56,.64,1), background-color .15s ease, box-shadow .18s ease, border-color .15s ease, color .12s ease; }\n        button:not(:disabled):active { transform: scale(.92); }\n        .ax-press { transition: transform .14s cubic-bezier(.34,1.56,.64,1), background-color .14s ease; }\n        .ax-press:active { transform: scale(.95); }\n        .ax-card { transition: transform .2s cubic-bezier(.34,1.56,.64,1), box-shadow .2s ease, border-color .15s ease; }\n        .ax-card:hover { border-color: ${T.lineStrong} !important; box-shadow: ${T.shadow} !important; }\n        .ax-primary:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(37,99,235,.4) !important; }\n        .ax-primary:active { transform: translateY(0) scale(.96); }\n        @keyframes axRise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }\n        .ax-rise { animation: axRise .45s cubic-bezier(.22,1,.36,1) both; }\n        @keyframes axJiggle { 0%, 100% { transform: rotate(-0.5deg); } 50% { transform: rotate(0.5deg); } }\n        .ax-jiggle { animation: axJiggle .32s ease-in-out infinite; }\n        @keyframes axPop { 0% { transform: scale(1); } 45% { transform: scale(1.25) rotate(-8deg); } 100% { transform: scale(1); } }\n        .ax-star:hover { animation: axPop .4s ease; }\n        @keyframes axSpin { to { transform: rotate(360deg); } }\n        .ax-spin:hover { animation: axSpin .6s ease; }\n        button:not(:disabled):hover { transform: translateY(-1px); }\n        @keyframes axGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }\n        @keyframes axGrowY { from { transform: scaleY(0); } to { transform: scaleY(1); } }\n        @keyframes axDraw { to { stroke-dashoffset: 0; } }\n        @keyframes axFade { from { opacity: 0; } to { opacity: 1; } }\n        @keyframes axSlideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }\n        @keyframes axBounce { 0% { transform: translateY(0) scale(1); } 35% { transform: translateY(-4px) scale(1.14); } 70% { transform: translateY(0) scale(1); } 100% { transform: translateY(0) scale(1); } }\n        .ax-rail:hover .ax-railicon { animation: axBounce .5s ease; }\n        .ax-flat:hover { transform: none !important; }`}\n      \n      \n\n        \n        \n\n          {view === \"dashboard\" &amp;&amp; }\n          {view === \"assets\" &amp;&amp; }\n          \n\n            {view === \"dashboard\" &amp;&amp; }\n            {view === \"dashboard\" ? (\n              \n\n                {DASH_TILESETS[activeDash]\n                  ? \n                  : &lt;&gt;}\n              \n            ) : view === \"assets\" ? (\n              \n            ) : (\n              \n\n                {RAIL.find(r =&gt; r.key === view)?.label} view \u2014 coming soon\n              \n            )}\n          \n        \n      \n    \n  );\n}\n\ncreateRoot(document.getElementById(\"root\")).render();\n    \n  \n    fetch('/.inspector/overlay.js')\n      .then(r =&gt; r.text())\n      .then(code =&gt; { const s = document.createElement('script'); s.textContent = code; document.head.appendChild(s); })\n      .catch(() =&gt; {});\n  \n  \n\n", "creation_timestamp": "2026-06-23T09:25:09.000000Z"}, {"uuid": "b35dc297-37e5-4af5-9dce-a956d5fce1e5", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "seen", "source": "https://gist.github.com/ShaiOnionGod/a2470d5624f426a312bf6d47e9b8a91c", "content": "\n\n  \n    \n    \n    Axonius \u2014 Dashboard prototype\n    \n    \n      {\n        \"imports\": {\n          \"react\": \"https://esm.sh/react@18.3.1\",\n          \"react-dom\": \"https://esm.sh/react-dom@18.3.1\",\n          \"react-dom/client\": \"https://esm.sh/react-dom@18.3.1/client\",\n          \"lucide-react\": \"https://esm.sh/lucide-react@0.456.0?deps=react@18.3.1\"\n        }\n      }\n    \n    \n    \n      html, body { margin: 0; height: 100%; background: #191A23; font-family: 'Hanken Grotesk', -apple-system, BlinkMacSystemFont, sans-serif; }\n      #root { height: 100%; }\n      .sb-tag { position: fixed; right: 14px; bottom: 12px; z-index: 99999; font: 700 10px/1 ui-monospace, monospace; letter-spacing: .14em; color: #0D5ED7; background: rgba(255,255,255,.9); border: 1px solid #E0E4EC; border-radius: 999px; padding: 6px 10px; box-shadow: 0 6px 18px rgba(27,32,70,.12); pointer-events: none; }\n    \n  \n  \n    \n\n    \nSANDBOX\n\n    \nimport React, { useState, useRef, useCallback, useEffect } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { createPortal } from \"react-dom\";\nimport {\n  Search, Star, MoreHorizontal, Info, ListFilter, ArrowUpDown, Columns3,\n  Plus, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, ChevronDown,\n  ArrowUpRight, ArrowDownRight, LayoutGrid, Server, ShieldCheck, Boxes,\n  Workflow, Cable, Bell, CircleHelp, Clock, Play, Grip, PanelLeftClose,\n  PanelLeftOpen, Download, GripVertical, X, Settings, ShieldAlert, Crosshair,\n  Pin, PinOff, RotateCw, Filter, Table2, ChevronsUpDown, Pencil, Check, Trash2,\n  Users, Layers, RotateCcw, Save, Copy, Calendar, Menu, Zap, Link, BarChart3, Sun, Moon,\n  Globe, Lock, Folder, FolderOpen\n} from \"lucide-react\";\n\n/**\n * Axonius \u2014 Dashboard + Assets table views\n * Collapsible side panel \u00b7 resizable + reorderable dashboard tiles\n * Real Axonius header \u00b7 Twenty-style table \u00b7 tokens from Figma source\n */\n\nfunction AxoniusLogo() {\n  return `.replaceAll('fill=\"black\"', `fill=\"${T.ink}\"`) }} /&gt;;\n}\n\n// Data / highlight palette \u2014 bright fills used in both themes (light-mode text variants applied where colored text is needed)\nconst VIZ = { green: \"#5BC4BF\", greenDeep: \"#247D78\", mint: \"#8ED0FF\", lilac: \"#AD85FF\", lilacPale: \"#CDB4FF\", orange: \"#FF8C66\", amber: \"#FFB286\" };\nconst FONT = \"'Hanken Grotesk', -apple-system, sans-serif\", DISPLAY = \"'Schibsted Grotesk', -apple-system, sans-serif\";\nconst THEMES = {\n  dark: {\n    ink: \"#E4E8F0\", body: \"#AEB7CC\", muted: \"#8494B5\", faint: \"#69738C\",\n    line: \"rgba(132,148,181,0.20)\", lineSoft: \"rgba(132,148,181,0.12)\", hair: \"rgba(132,148,181,0.09)\", lineStrong: \"rgba(132,148,181,0.42)\",\n    bg100: \"#AEB7CC\", bg90: \"#9AA3BC\", bg80: \"#8494B5\", bg40: \"#8494B5\",\n    blue: \"#5C7CFF\", blueDeep: \"#4E6CF5\", green: \"#5BC4BF\", red: \"#FF8C66\",\n    shell: \"#191A23\", headerBg: \"#191A23\", canvas: \"#191A23\", surface: \"#222431\", surface2: \"#2A2D3C\", control: \"#262838\",\n    white: \"#FFFFFF\", onAccent: \"#191A23\", isLight: false, coin: \"#2A2D3C\", accentSoft: \"rgba(92,124,255,0.16)\", accentText: \"#5C7CFF\", accentBorder: \"rgba(92,124,255,0.50)\",\n    viz: VIZ, shadow: \"0 1px 2px rgba(0,0,0,0.4), 0 14px 34px rgba(0,0,0,0.5)\", font: FONT, display: DISPLAY,\n  },\n  light: {\n    ink: \"#2E3850\", body: \"#465472\", muted: \"#6B7894\", faint: \"#8494B5\",\n    line: \"rgba(132,148,181,0.30)\", lineSoft: \"rgba(132,148,181,0.18)\", hair: \"rgba(132,148,181,0.12)\", lineStrong: \"rgba(132,148,181,0.55)\",\n    bg100: \"#465472\", bg90: \"#5A6889\", bg80: \"#8494B5\", bg40: \"#8494B5\",\n    blue: \"#4361EE\", blueDeep: \"#3A55D6\", green: \"#247D78\", red: \"#BF4B26\",\n    shell: \"#F9FAFB\", headerBg: \"#F9FAFB\", canvas: \"#FFFFFF\", surface: \"#FFFFFF\", surface2: \"#F1F2F6\", control: \"#FFFFFF\",\n    white: \"#FFFFFF\", onAccent: \"#FFFFFF\", isLight: true, coin: \"#FFFFFF\", accentSoft: \"rgba(67,97,238,0.10)\", accentText: \"#4361EE\", accentBorder: \"rgba(67,97,238,0.45)\",\n    viz: VIZ, shadow: \"0 1px 2px rgba(70,84,114,0.06), 0 10px 26px rgba(70,84,114,0.08)\", font: FONT, display: DISPLAY,\n  },\n};\nlet T = THEMES.dark;\n// Severity scale stays semantic (red \u2192 orange \u2192 yellow \u2192 green, neutral for informational) in both themes\nconst SEVC = { critical: \"#E5484D\", high: \"#F0743E\", medium: \"#F5C28C\", low: \"#4F8FE3\", info: \"#CFC4F2\" };\nconst fmt = (n) =&gt; n.toLocaleString(\"en-US\");\nconst tnum = { fontFeatureSettings: '\"tnum\" 1', fontVariantNumeric: \"tabular-nums\" };\n\n/* ============ vendor logos ============ */\nfunction LogoMark({ brand, size = 32 }) {\n  const glyph = {\n    aws: aws,\n    microsoft: ,\n    azure: ,\n    googlecloud: ,\n    oracle: ,\n    vmware: vm,\n    okta: ,\n    crowdstrike: ,\n    active_directory: ,\n    sentinelone: ,\n    service_now: ,\n    cisco: cisco,\n    cisco_meraki: ,\n    cisco_ise: cisco,\n    checkpoint: ,\n    chef: ,\n    claroty: ,\n    cylance: ,\n    epo: ,\n    paloalto: ,\n    tanium: ,\n    tenable: ,\n    forescout: ,\n    zoom: ,\n    miro: ,\n    dropbox: ,\n    salesforce: ,\n    slack: ,\n    office365: ,\n    google_workspace: ,\n  }[brand] || {(brand || \"?\").slice(0, 2).toUpperCase()};\n  return \n{glyph};\n}\n\n/* ============ chips ============ */\n// Tags use the dashboard data palette (turquoise / purple / baby-blue / orange) \u2014 dark text variant in light mode, bright in dark\nconst TAGV = [{ b: \"#5BC4BF\", d: \"#247D78\" }, { b: \"#AD85FF\", d: \"#683CB5\" }, { b: \"#8ED0FF\", d: \"#1E75B3\" }, { b: \"#FF8C66\", d: \"#BF4B26\" }];\nfunction Tag({ label }) {\n  let n = 0; for (let i = 0; i &lt; label.length; i++) n = (n * 31 + label.charCodeAt(i)) &gt;&gt;&gt; 0;\n  const t = TAGV[n % TAGV.length];\n  return {label};\n}\n// Cursor-following tooltip (like sandbox 2) \u2014 surface card with readable dark text\nfunction FloatTip({ x, y, children }) {\n  return createPortal(\n{children}, document.body);\n}\nfunction UserAvatar({ name, size = 24 }) {\n  const s = name || \"?\"; let n = 0; for (let i = 0; i &lt; s.length; i++) n = (n * 31 + s.charCodeAt(i)) &gt;&gt;&gt; 0;\n  const t = TAGV[n % TAGV.length];\n  return {s.trim()[0] || \"?\"};\n}\nconst APP_LOGO_MAP = {\n  Zoom: \"zoom\", \"Zoom One Pro\": \"zoom\",\n  Miro: \"miro\",\n  Dropbox: \"dropbox\",\n  Salesforce: \"salesforce\",\n  Slack: \"slack\",\n  Google: \"googlecloud\", \"Google WS Ent. Starter\": \"google_workspace\", \"Google WS Ent. Standard\": \"google_workspace\", \"Google WS Business\": \"google_workspace\",\n  Office365: \"office365\", \"Microsoft 365 G3 GCC\": \"office365\", \"Microsoft 365 E5\": \"office365\", \"Microsoft Power Aut\u2026\": \"office365\",\n  Microsoft: \"microsoft\",\n};\nfunction AppAvatar({ name, size = 22 }) {\n  const brand = APP_LOGO_MAP[name];\n  if (brand) return ;\n  const s = name || \"?\"; let n = 0; for (let i = 0; i &lt; s.length; i++) n = (n * 31 + s.charCodeAt(i)) &gt;&gt;&gt; 0;\n  const t = TAGV[n % TAGV.length];\n  return {s.trim()[0] || \"?\"};\n}\nconst STATUS = { Active: T.green, Warning: T.viz.amber, Inactive: T.faint, Error: T.red };\nfunction StatusChip({ status }) {\n  return {status};\n}\nconst RISK = { Critical: SEVC.critical, High: SEVC.high, Medium: SEVC.medium, Low: SEVC.low };\nfunction RiskChip({ level }) {\n  const c = RISK[level];\n  return {level};\n}\nfunction Delta({ up, children }) {\n  const color = up ? T.green : T.red;\n  return {up ?  : }{children};\n}\nfunction IconBtn({ children, onClick }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ width: 26, height: 26, borderRadius: 6, border: \"none\", cursor: \"pointer\", background: h ? T.surface2 : \"transparent\", color: h ? T.ink : T.faint, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}&gt;{children};\n}\n\n/* ============ KPIs ============ */\nconst KPIS = [\n  { label: \"Total Assets\", value: 84355, delta: \"12.4%\", up: true },\n  { label: \"Cloud Coverage\", value: 96.2, suffix: \"%\", delta: \"2.1%\", up: true },\n  { label: \"Open Vulnerabilities\", value: 382, delta: \"8.0%\", up: false },\n  { label: \"Unmanaged Devices\", value: 1204, delta: \"3.2%\", up: false },\n];\nfunction useCountUp(target, dur = 950) {\n  const [v, setV] = useState(0);\n  useEffect(() =&gt; {\n    let raf; const start = performance.now();\n    const tick = (now) =&gt; { const p = Math.min(1, (now - start) / dur); setV(target * (1 - Math.pow(1 - p, 3))); if (p &lt; 1) raf = requestAnimationFrame(tick); };\n    raf = requestAnimationFrame(tick); return () =&gt; cancelAnimationFrame(raf);\n  }, [target]);\n  return v;\n}\nfunction KpiValue({ value, suffix }) {\n  const v = useCountUp(value);\n  const text = suffix === \"%\" ? v.toFixed(1) : fmt(Math.round(v));\n  return {text}{suffix || \"\"};\n}\nfunction KpiRow() {\n  return (\n    \n\n      {KPIS.map((k, i) =&gt; (\n        \n\n          \n{k.label}\n          \n\n            \n            {k.delta}\n          \n        \n      ))}\n    \n  );\n}\n\n/* ============ chart bodies ============ */\nconst PIE = [\n  { c: \"green\", label: \"Windows Assets\", val: 1236 }, { c: \"greenDeep\", label: \"Active Directory\", val: 411 },\n  { c: \"mint\", label: \"SCCM Managed\", val: 198 }, { c: \"lilac\", label: \"Intune Managed\", val: 89 }, { c: \"lilacPale\", label: \"Unmanaged\", val: 43 },\n];\nfunction DonutBody() {\n  const [hi, setHi] = useState(null);\n  const [tip, setTip] = useState(null);\n  const [off, setOff] = useState(() =&gt; new Set());\n  const [drawn, setDrawn] = useState(false);\n  useEffect(() =&gt; { const id = setTimeout(() =&gt; setDrawn(true), 60); return () =&gt; clearTimeout(id); }, []);\n  const r = 46, sw = 14, C = 2 * Math.PI * r, gap = 5;\n  const toggle = (i) =&gt; setOff(s =&gt; { const n = new Set(s); n.has(i) ? n.delete(i) : n.add(i); return n; });\n  const visTotal = PIE.reduce((a, p, i) =&gt; a + (off.has(i) ? 0 : p.val), 0) || 1;\n  let acc = 0;\n  const segs = PIE.map((p, i) =&gt; {\n    if (off.has(i)) return null;\n    const len = (p.val / visTotal) * C;\n    const seg = Math.max(1, len - gap);\n    const active = hi === i, dim = hi !== null &amp;&amp; !off.has(hi) &amp;&amp; !active;\n    const node = (\n       { setHi(i); setTip({ i, x: e.clientX, y: e.clientY }); }} onMouseMove={(e) =&gt; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; { setHi(null); setTip(null); }} onClick={() =&gt; toggle(i)}\n        style={{ opacity: dim ? 0.35 : 1, cursor: \"pointer\", transition: \"stroke-dasharray .8s cubic-bezier(.22,1,.36,1), stroke-width .18s ease, opacity .18s ease\" }} /&gt;\n    );\n    acc += len; return node;\n  });\n  const liveHi = hi !== null &amp;&amp; !off.has(hi);\n  const center = liveHi ? { big: ((PIE[hi].val / visTotal) * 100).toFixed(1) + \"%\", small: PIE[hi].label } : { big: fmt(visTotal), small: \"Total Assets\" };\n  return (\n    \n\n      \n\n        \n          \n          {segs}\n        \n        \n\n          {center.big}\n          {center.small}\n        \n      \n      \n\n        {PIE.map((p, i) =&gt; {\n          const isOff = off.has(i);\n          return (\n            \n { setHi(i); if (!isOff) setTip({ i, x: e.clientX, y: e.clientY }); }} onMouseMove={(e) =&gt; !isOff &amp;&amp; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; { setHi(null); setTip(null); }} onClick={() =&gt; toggle(i)}\n              title={isOff ? \"Click to show\" : \"Click to hide\"}\n              style={{ display: \"flex\", alignItems: \"center\", gap: 9, padding: \"5px 6px\", borderRadius: 6, cursor: \"pointer\", background: hi === i &amp;&amp; !isOff ? T.canvas : \"transparent\", transition: \"background .12s\" }}&gt;\n              \n              {p.label}\n              {isOff ? \"Hidden\" : (hi === i ? fmt(p.val) : ((p.val / visTotal) * 100).toFixed(1) + \"%\")}\n            \n          );\n        })}\n      \n      {tip !== null &amp;&amp; !off.has(tip.i) &amp;&amp; \n        \n{PIE[tip.i].label}\n        \n{fmt(PIE[tip.i].val)}\n        \n{(PIE[tip.i].val / visTotal * 100).toFixed(1)}% of assets\n      }\n    \n  );\n}\nconst LINE_LABELS = [\"W1\", \"W2\", \"W3\", \"W4\", \"W5\", \"W6\", \"W7\", \"W8\", \"W9\", \"W10\", \"W11\", \"W12\"];\nconst LINE_SERIES = [\n  { key: \"all\", label: \"All Devices\", color: T.viz.green, pts: [38000, 41200, 44500, 47800, 51000, 55300, 59800, 63200, 68500, 72100, 78400, 84355] },\n  { key: \"managed\", label: \"Managed\", color: T.viz.lilac, pts: [29000, 31000, 33500, 35800, 38000, 41000, 44500, 47200, 50500, 54100, 58400, 61200] },\n  { key: \"cloud\", label: \"Cloud\", color: T.viz.orange, pts: [8000, 9200, 10500, 11800, 13000, 14300, 15800, 17200, 18500, 20100, 21400, 22097] },\n  { key: \"unmanaged\", label: \"Unmanaged\", color: T.viz.greenDeep, pts: [3200, 3100, 3300, 3000, 3500, 3300, 3100, 2900, 2700, 2500, 2300, 2100] },\n];\nfunction LineBody() {\n  const [off, setOff] = useState(() =&gt; new Set());\n  const [hov, setHov] = useState(null);\n  const w = 680, h = 196, padT = 12, padB = 12, padL = 8, padR = 8, n = LINE_LABELS.length, max = 90000;\n  const yAxisW = 40;\n  const X = (i) =&gt; padL + (i / (n - 1)) * (w - padL - padR);\n  const Y = (v) =&gt; padT + (1 - v / max) * (h - padT - padB);\n  const smooth = (pts) =&gt; { let d = `M ${X(0)} ${Y(pts[0])}`; for (let i = 1; i &lt; n; i++) { const cx = (X(i - 1) + X(i)) / 2; d += ` C ${cx} ${Y(pts[i - 1])}, ${cx} ${Y(pts[i])}, ${X(i)} ${Y(pts[i])}`; } return d; };\n  const toggle = (k) =&gt; setOff(s =&gt; { const x = new Set(s); x.has(k) ? x.delete(k) : x.add(k); return x; });\n  const visible = LINE_SERIES.filter(s =&gt; !off.has(s.key));\n  const xPct = (i) =&gt; (X(i) / w) * 100, yPct = (v) =&gt; (Y(v) / h) * 100;\n  const yTicks = [90000, 60000, 30000, 0];\n  return (\n    \n\n      \n\n        {/* Y-axis labels */}\n        \n\n          {yTicks.map((v, i) =&gt; (\n            {v === 0 ? \"0\" : (v / 1000) + \"K\"}\n          ))}\n        \n        {/* Chart area */}\n        \n setHov(null)}&gt;\n          \n            {yTicks.map((v, i) =&gt; )}\n            {hov &amp;&amp; }\n            {visible.map(s =&gt; )}\n          \n          {visible.map(s =&gt; s.pts.map((v, i) =&gt; {\n            const isHov = hov &amp;&amp; hov.s.key === s.key &amp;&amp; hov.i === i, dim = hov &amp;&amp; !isHov;\n            return (\n              \n setHov({ s, i })}\n                style={{ position: \"absolute\", left: `${xPct(i)}%`, top: `${yPct(v)}%`, transform: \"translate(-50%, -50%)\", width: 20, height: 20, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", cursor: \"pointer\", zIndex: isHov ? 4 : 2 }}&gt;\n                \n              \n            );\n          }))}\n          {hov &amp;&amp; (\n            \n n - 4 ? \"calc(-100% - 12px)\" : hov.i &lt; 3 ? \"12px\" : \"-50%\"}, calc(-100% - 14px))`, background: T.surface, border: `1px solid ${T.line}`, borderRadius: 10, boxShadow: T.shadow, padding: \"9px 12px\", whiteSpace: \"nowrap\", pointerEvents: \"none\", zIndex: 6 }}&gt;\n              \n{hov.s.label}\n              \n{fmt(hov.s.pts[hov.i])}\n              {hov.i &gt; 0 &amp;&amp; (() =&gt; { const pct = (hov.s.pts[hov.i] - hov.s.pts[hov.i - 1]) / hov.s.pts[hov.i - 1] * 100; const up = pct &gt;= 0; return \n{up ?  : }{up ? \"+\" : \"\"}{pct.toFixed(1)}% from prev; })()}\n              \n{LINE_LABELS[hov.i]}\n            \n          )}\n        \n      \n      {/* X-axis labels */}\n      \n\n        {LINE_LABELS.map((label, i) =&gt; (\n          \n{i % 3 === 0 ? label : \"\"}\n        ))}\n      \n      \n\n        {LINE_SERIES.map(s =&gt; {\n          const isOff = off.has(s.key);\n          return (\n             toggle(s.key)} title={isOff ? \"Show series\" : \"Hide series\"} className=\"ax-press\"\n              style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, padding: \"3px 9px\", borderRadius: 999, border: `1px solid ${isOff ? T.lineSoft : T.line}`, background: isOff ? \"transparent\" : T.canvas, cursor: \"pointer\", fontFamily: T.font, fontSize: 11.5, fontWeight: 500, color: isOff ? T.faint : T.body, transition: \"all .14s\" }}&gt;\n              \n              {s.label}\n            \n          );\n        })}\n      \n    \n  );\n}\nconst STACK_DATA = [[\"Jan\", 40, 25, 20], [\"Feb\", 35, 30, 15], [\"Mar\", 52, 22, 26], [\"Apr\", 30, 35, 18], [\"May\", 48, 28, 24], [\"Jun\", 58, 32, 28]];\nconst STACK_SERIES = [\n  { key: \"cloud\", label: \"Cloud\", color: T.viz.green },\n  { key: \"onprem\", label: \"On-prem\", color: T.viz.mint },\n  { key: \"saas\", label: \"SaaS\", color: T.viz.lilac },\n];\nfunction StackedBody() {\n  const [off, setOff] = useState(() =&gt; new Set());\n  const [segHov, setSegHov] = useState(null);\n  const [tip, setTip] = useState(null);\n  const toggle = (key) =&gt; setOff(s =&gt; { const n = new Set(s); n.has(key) ? n.delete(key) : n.add(key); return n; });\n  const visSeries = STACK_SERIES.filter(s =&gt; !off.has(s.key));\n  const yAxisW = 34;\n  const allTotals = STACK_DATA.map(row =&gt; visSeries.reduce((sum, s) =&gt; sum + row[STACK_SERIES.findIndex(s2 =&gt; s2.key === s.key) + 1], 0));\n  const rawMax = Math.max(...allTotals, 1);\n  const yMax = Math.ceil(rawMax / 20) * 20;\n  const yTicks = [yMax, yMax * 0.75, yMax * 0.5, yMax * 0.25, 0];\n  const tipContent = tip &amp;&amp; segHov ? (() =&gt; {\n    const row = STACK_DATA[segHov.col];\n    const s = STACK_SERIES.find(s =&gt; s.key === segHov.key);\n    const idx = STACK_SERIES.findIndex(s2 =&gt; s2.key === segHov.key);\n    const val = row[idx + 1] * 86;\n    const totalVis = visSeries.reduce((sum, ss) =&gt; sum + row[STACK_SERIES.findIndex(s2 =&gt; s2.key === ss.key) + 1], 0);\n    const pct = (row[idx + 1] / (totalVis || 1) * 100).toFixed(1);\n    return { label: s.label, color: s.color, val, pct, month: row[0] };\n  })() : null;\n  return (\n    \n\n      {tip &amp;&amp; tipContent &amp;&amp; \n        \n{tipContent.month}\n        \n{tipContent.label}\n        \n{fmt(tipContent.val)}\n        \n{tipContent.pct}% of total\n      }\n      \n\n        {/* Y-axis */}\n        \n\n          {yTicks.map((v, i) =&gt; (\n            {v === 0 ? \"0\" : v + \"K\"}\n          ))}\n        \n        {/* Bars */}\n        \n\n          \n\n            {yTicks.map((v, i) =&gt; (\n              \n\n            ))}\n            {STACK_DATA.map((row, ci) =&gt; {\n              const total = visSeries.reduce((sum, s) =&gt; sum + row[STACK_SERIES.findIndex(s2 =&gt; s2.key === s.key) + 1], 0);\n              const colHov = segHov?.col === ci;\n              const dim = segHov &amp;&amp; !colHov;\n              return (\n                \n\n                  \n\n                    {visSeries.map((s, si) =&gt; {\n                      const val = row[STACK_SERIES.findIndex(s2 =&gt; s2.key === s.key) + 1];\n                      const isFirst = si === 0, isLast = si === visSeries.length - 1;\n                      const isHov = segHov?.col === ci &amp;&amp; segHov?.key === s.key;\n                      return (\n                        \n { setSegHov({ col: ci, key: s.key }); setTip({ x: e.clientX, y: e.clientY }); }}\n                          onMouseMove={(e) =&gt; setTip({ x: e.clientX, y: e.clientY })}\n                          onMouseLeave={() =&gt; { setSegHov(null); setTip(null); }}\n                          style={{ flex: val, background: s.color, borderRadius: isFirst ? \"6px 6px 0 0\" : isLast ? \"0 0 6px 6px\" : 0, filter: isHov ? \"brightness(1.18)\" : \"none\", transition: \"filter .15s\", cursor: \"pointer\" }} /&gt;\n                      );\n                    })}\n                  \n                  {row[0]}\n                \n              );\n            })}\n          \n        \n      \n      {/* Clickable legend */}\n      \n\n        {STACK_SERIES.map(s =&gt; {\n          const isOff = off.has(s.key);\n          return (\n             toggle(s.key)} className=\"ax-press\"\n              style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, padding: \"3px 9px\", borderRadius: 999, border: `1px solid ${isOff ? T.lineSoft : T.line}`, background: isOff ? \"transparent\" : T.canvas, cursor: \"pointer\", fontFamily: T.font, fontSize: 11.5, fontWeight: 500, color: isOff ? T.faint : T.body, transition: \"all .14s\" }}&gt;\n              \n              {s.label}\n            \n          );\n        })}\n      \n    \n  );\n}\nconst SEV = [[\"Critical\", 142, 100, SEVC.critical], [\"High\", 98, 70, SEVC.high], [\"Medium\", 76, 54, SEVC.medium], [\"Low\", 45, 32, SEVC.low], [\"Info\", 21, 15, SEVC.info]];\nconst SEV_ASSETS = {\n  Critical: [[\"PC-CURTIS-WILLIAMS\", \"CVE-2024-21412 \u00b7 SmartScreen bypass\"], [\"srv-db-fin-04\", \"CVE-2024-3094 \u00b7 xz-utils backdoor\"], [\"esx-infranginx-5567897\", \"CVE-2023-46604 \u00b7 ActiveMQ RCE\"], [\"WIN-RUTHD\", \"CVE-2024-21413 \u00b7 Outlook RCE\"]],\n  High: [[\"macbook-pro-jdoe\", \"CVE-2024-23222 \u00b7 WebKit type confusion\"], [\"azure-infra9274676\", \"CVE-2024-21401 \u00b7 Entra ID elevation\"], [\"lablb-2918146-beta\", \"CVE-2024-1709 \u00b7 ScreenConnect auth bypass\"]],\n  Medium: [[\"iphone-asmith\", \"CVE-2024-23225 \u00b7 kernel memory\"], [\"sepio-external3026786\", \"CVE-2024-0519 \u00b7 V8 out-of-bounds\"]],\n  Low: [[\"android-pixel-7\", \"CVE-2024-0039 \u00b7 System component\"]],\n  Info: [[\"win-marychasse\", \"Informational \u00b7 TLS 1.0 enabled\"]],\n};\nfunction SeverityDrawer({ sev, close }) {\n  const list = SEV_ASSETS[sev[0]] || [];\n  return createPortal(&lt;&gt;\n    \n\n    \n\n      \n\n        \n        \n\n          \n{sev[0]} severity\n          \n{sev[1]} open vulnerabilities \u00b7 {list.length} assets shown\n        \n        \n      \n      \n\n        {list.map(([host, cve], i) =&gt; (\n          \n\n            \n\n              {host}\n              {sev[0]}\n            \n            {cve}\n          \n        ))}\n      \n      \n\n        View all {sev[1]} in Inventory\n      \n    \n  , document.body);\n}\nfunction SeverityBody() {\n  const [hi, setHi] = useState(null);\n  const [drill, setDrill] = useState(null);\n  const [tip, setTip] = useState(null);\n  const sevTotal = SEV.reduce((a, b) =&gt; a + b[1], 0);\n  return (&lt;&gt;\n    \n\n      {SEV.map((b, i) =&gt; {\n        const active = hi === i, dim = hi !== null &amp;&amp; !active;\n        return (\n          \n setDrill(b)} onMouseEnter={(e) =&gt; { setHi(i); setTip({ i, x: e.clientX, y: e.clientY }); }} onMouseMove={(e) =&gt; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; { setHi(null); setTip(null); }}\n            style={{ display: \"flex\", alignItems: \"center\", gap: 12, opacity: dim ? 0.5 : 1, transition: \"opacity .15s\", cursor: \"pointer\" }}&gt;\n            {b[0]}\n            \n\n\n            {active ? b[1] + \" open\" : b[1]}\n          \n        );\n      })}\n    \n    {tip &amp;&amp; \n{SEV[tip.i][0]}\n{fmt(SEV[tip.i][1])}\n{Math.round(SEV[tip.i][1] / sevTotal * 100)}% of open findings}\n    {drill &amp;&amp;  setDrill(null)} /&gt;}\n  );\n}\nconst ST_COLOR = { ok: T.viz.green, warn: T.viz.amber, err: T.red };\n\n/* ===== System Lifecycle (discovery cycle) ===== */\nconst LIFECYCLE = [\n  { name: \"Fetch\", pct: 100 }, { name: \"Clean\", pct: 100 }, { name: \"Correlate\", pct: 100 },\n  { name: \"Enrich\", pct: 78 }, { name: \"Calculate\", pct: 0 },\n];\nfunction LifecycleBody() {\n  const [tip, setTip] = useState(null);\n  const done = LIFECYCLE.filter(s =&gt; s.pct === 100).length, total = LIFECYCLE.length;\n  const overall = Math.round(LIFECYCLE.reduce((a, s) =&gt; a + s.pct, 0) / total);\n  const r = 42, sw = 11, C = 2 * Math.PI * r, dash = (overall / 100) * C;\n  return (\n    \n\n      {tip &amp;&amp; \n{LIFECYCLE[tip.i].name} stage\n{LIFECYCLE[tip.i].pct &gt; 0 ? LIFECYCLE[tip.i].pct + \"% complete\" : \"Not started\"}}\n      \n\n        \n\n          \n            \n            \n          \n          \n\n            {done}/{total}\n            Stages\n          \n        \n        \n\n          {LIFECYCLE.map((s, i) =&gt; {\n            const c = s.pct === 100 ? T.viz.green : s.pct &gt; 0 ? T.blue : T.bg40;\n            return (\n              \n setTip({ i, x: e.clientX, y: e.clientY })} onMouseMove={(e) =&gt; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; setTip(null)} style={{ display: \"flex\", alignItems: \"center\", gap: 10, cursor: \"default\" }}&gt;\n                {s.name}\n                \n\n                  \n\n                \n                 0 ? T.blue : T.bg40, ...tnum }}&gt;{s.pct &gt; 0 ? s.pct + \"%\" : \"\u2014\"}\n              \n            );\n          })}\n        \n      \n      \n\n        {[[\"Cycle started\", \"09:00:04\"], [\"Duration\", \"00:42:18\"], [\"Next cycle\", \"13:17:42\"]].map(([k, v], i) =&gt; (\n          \n\n            \n{k}\n            \n{v}\n          \n        ))}\n      \n    \n  );\n}\n\n/* ===== Discovery Log ===== */\nconst DISC_PHASES = [[\"Fetch\", T.viz.green], [\"Clean\", T.viz.mint], [\"Correlate\", T.viz.lilac], [\"Enrich\", T.viz.lilacPale], [\"Calculate\", T.viz.amber], [\"Save\", T.viz.orange]];\nconst DISCOVERY = [\n  { started: \"Jun 15, 09:00\", completed: \"Jun 15, 09:35\", duration: \"35min 22sec\", ph: [40, 12, 18, 10, 12, 8] },\n  { started: \"Jun 14, 09:00\", completed: \"Jun 14, 09:35\", duration: \"35min 59sec\", ph: [38, 14, 17, 11, 12, 8] },\n  { started: \"Jun 13, 09:00\", completed: \"Jun 13, 09:35\", duration: \"35min 29sec\", ph: [41, 11, 18, 10, 12, 8] },\n  { started: \"Jun 12, 09:00\", completed: \"Jun 12, 09:35\", duration: \"35min 11sec\", ph: [39, 13, 17, 11, 12, 8] },\n  { started: \"Jun 11, 09:00\", completed: \"Jun 11, 09:35\", duration: \"35min 58sec\", ph: [44, 12, 16, 9, 11, 8] },\n  { started: \"Jun 10, 09:00\", completed: \"Jun 10, 09:30\", duration: \"30min 54sec\", ph: [36, 12, 18, 12, 14, 8] },\n  { started: \"Jun 9, 09:00\", completed: \"Jun 9, 09:34\", duration: \"34min 02sec\", ph: [37, 13, 17, 11, 13, 9] },\n  { started: \"Jun 8, 09:00\", completed: \"Jun 8, 09:31\", duration: \"31min 38sec\", ph: [40, 12, 17, 10, 13, 8] },\n];\nfunction DiscoveryBody() {\n  const [hi, setHi] = useState(null);\n  const [tip, setTip] = useState(null);\n  const cols = \"minmax(70px, 1.6fr) 104px 92px\";\n  const durToSec = (s) =&gt; { let t = 0; const m = s.match(/(\\d+)\\s*min/), sec = s.match(/(\\d+)\\s*sec/); if (m) t += +m[1] * 60; if (sec) t += +sec[1]; return t; };\n  const fmtDur = (x) =&gt; { x = Math.round(x); const m = Math.floor(x / 60), s = x % 60; return m &gt; 0 ? `${m}min ${s}sec` : `${s}sec`; };\n  return (\n    \n\n      \n\n        {[\"Latest Discoveries\", \"Started\", \"Duration\"].map((c, i) =&gt; (\n          {c}\n        ))}\n      \n      \n\n        {DISCOVERY.map((d, i) =&gt; (\n          \n setHi(i)} onMouseLeave={() =&gt; { setHi(null); setTip(null); }}\n            style={{ display: \"grid\", gridTemplateColumns: cols, gap: 12, alignItems: \"center\", padding: \"7px 6px\", borderBottom: i &lt; DISCOVERY.length - 1 ? `1px solid ${T.hair}` : \"none\", background: hi === i ? T.canvas : \"transparent\", borderRadius: 6, transition: \"background .12s\" }}&gt;\n            \n\n              {d.ph.map((w, j) =&gt;  setTip({ d, j, x: e.clientX, y: e.clientY })} onMouseMove={(e) =&gt; setTip({ d, j, x: e.clientX, y: e.clientY })}\n                style={{ width: `${w}%`, height: \"100%\", background: DISC_PHASES[j][1], cursor: \"pointer\" }} /&gt;)}\n            \n            {d.started}\n            {d.duration}\n          \n        ))}\n      \n      {tip &amp;&amp; \n        \n{DISC_PHASES[tip.j][0]}\n        \n{fmtDur(durToSec(tip.d.duration) * tip.d.ph[tip.j] / 100)}\n        \n{tip.d.ph[tip.j]}% of cycle\n      }\n    \n  );\n}\n\n/* ===== Adapter Connections (status + logo coin + preview) ===== */\nconst ADAPTER_CONN = [\n  { brand: \"crowdstrike\", name: \"CrowdStrike\", st: \"ok\", desc: \"9,120 devices \u00b7 synced 4m ago\" },\n  { brand: \"aws\", name: \"Amazon Web Services\", st: \"ok\", desc: \"12,400 assets \u00b7 synced 6m ago\" },\n  { brand: \"azure\", name: \"Microsoft Azure\", st: \"ok\", desc: \"22,097 assets \u00b7 synced 5m ago\" },\n  { brand: \"okta\", name: \"Okta\", st: \"ok\", desc: \"9,870 users \u00b7 synced 8m ago\" },\n  { short: \"Jm\", color: \"#3B3B3B\", name: \"Jamf Pro\", st: \"err\", desc: \"Connection timeout \u2014 check credentials\" },\n  { short: \"now\", color: \"#2E8B57\", name: \"ServiceNow\", st: \"warn\", desc: \"Rate limited \u00b7 retrying\" },\n  { brand: \"googlecloud\", name: \"Google Cloud\", st: \"ok\", desc: \"9,055 assets \u00b7 synced 7m ago\" },\n  { brand: \"vmware\", name: \"VMware vCenter\", st: \"ok\", desc: \"4,310 VMs \u00b7 synced 11m ago\" },\n];\nconst ST_LABEL = { ok: \"Connected\", warn: \"Degraded\", err: \"Error\" };\nfunction AdapterCoin({ a }) {\n  if (a.brand) return ;\n  return {a.short};\n}\nfunction AdaptersBody() {\n  const connected = ADAPTER_CONN.filter(a =&gt; a.st === \"ok\").length;\n  const attention = ADAPTER_CONN.length - connected;\n  return (\n    \n\n      \n\n        {connected}\n        connected sources\n        {attention &gt; 0 &amp;&amp; {attention} need attention}\n      \n      \n\n        {ADAPTER_CONN.map((a, i) =&gt; {\n          const ring = ST_COLOR[a.st];\n          return (\n            \n\n              \n              \n\n                \n{a.name}\n                \n\n                  \n                  {a.desc}\n                \n              \n            \n          );\n        })}\n      \n    \n  );\n}\nconst HEALTH_OK_BRANDS = [\"crowdstrike\", \"aws\", \"azure\", \"okta\", \"googlecloud\", \"vmware\"];\nfunction HealthOkBody() {\n  return (\n    \n\n      \n\n        33\n        adapters syncing successfully\n      \n      \n\n        {HEALTH_OK_BRANDS.map((b, i) =&gt; )}\n        +27\n      \n      \n\n         100% healthy \u00b7 last full sync 4m ago\n      \n    \n  );\n}\nconst HEALTH_FAIL = [\n  { short: \"Jm\", color: \"#3B3B3B\", name: \"Jamf Pro\", reason: \"Connection timeout \u2014 check credentials\" },\n  { short: \"now\", color: \"#2E8B57\", name: \"ServiceNow\", reason: \"Rate limited \u00b7 retrying\" },\n];\nfunction HealthFailBody() {\n  return (\n    \n\n      \n\n        {HEALTH_FAIL.length}\n        adapters need attention\n      \n      \n\n        {HEALTH_FAIL.map((a, i) =&gt; (\n          \n\n            {a.short}\n            \n\n              \n{a.name}\n              \n{a.reason}\n            \n            Fix\n          \n        ))}\n      \n    \n  );\n}\n/* ===== Cost Optimization dashboard ===== */\nconst fmtMoney = (n) =&gt; n.toLocaleString(\"en-US\");\nfunction CostKPI({ value, unit }) {\n  return (\n    \n\n      {value}\n      {unit &amp;&amp; {unit}}\n    \n  );\n}\nfunction HBarList({ rows, accent, prefix, avatar, appLogo }) {\n  const max = Math.max(...rows.map(r =&gt; r[1]), 1);\n  const totalVal = rows.reduce((a, r) =&gt; a + r[1], 0) || 1;\n  const [tip, setTip] = useState(null);\n  return (\n    \n\n      {rows.map(([label, val], i) =&gt; {\n        const pct = Math.max(4, (val / max) * 100), inside = pct &gt; 24;\n        return (\n          \n setTip({ i, x: e.clientX, y: e.clientY })} onMouseMove={(e) =&gt; setTip({ i, x: e.clientX, y: e.clientY })} onMouseLeave={() =&gt; setTip(null)}\n            style={{ display: \"flex\", alignItems: \"center\", gap: 12, cursor: \"default\" }}&gt;\n            {avatar &amp;&amp; }{appLogo &amp;&amp; }{label}\n            \n\n              \n\n                {inside &amp;&amp; {prefix || \"\"}{fmtMoney(val)}}\n              \n              {!inside &amp;&amp; {prefix || \"\"}{fmtMoney(val)}}\n            \n          \n        );\n      })}\n      {tip &amp;&amp; \n        \n{rows[tip.i][0]}\n        \n{prefix || \"\"}{fmtMoney(rows[tip.i][1])}\n        \n{(rows[tip.i][1] / totalVal * 100).toFixed(1)}% of shown total\n      }\n    \n  );\n}\nconst COST_CATEGORY = [[\"Productivity\", 141993], [\"Video Conference\", 55928], [\"File Sharing\", 52943], [\"Authentication\", 11440], [\"CRM\", 9870]];\nconst COST_APP = [[\"Zoom\", 55928], [\"Miro\", 52885], [\"Dropbox\", 50431], [\"Salesforce\", 43176], [\"Slack\", 38420]];\nconst COST_TOPLIC = [[\"Zoom One Pro\", 70800], [\"Google WS Ent. Starter\", 27450], [\"Google WS Ent. Standard\", 12985], [\"Google WS Business\", 8640], [\"Microsoft 365 G3 GCC\", 1930]];\nconst COST_USAGE = [[\"aaron.church@demo.local\", 29], [\"aaron.daniels@demo.local\", 29], [\"ada.pires@demo.local\", 29], [\"adelaide.gercak@demo.local\", 29], [\"adrian.williams@demo.local\", 29]];\nconst COST_RENEWALS = [\n  { app: \"Office365\", name: \"Microsoft 365 E5\", date: \"2026-07-06\", cost: \"3,175.50\", term: \"Yearly\", unit: \"54.75\" },\n  { app: \"Office365\", name: \"Microsoft Power Aut\u2026\", date: \"2026-08-07\", cost: \"6,518.75\", term: \"Yearly\", unit: \"37.25\" },\n];\nfunction CostExpAllBody() { return ; }\nfunction CostLicAllBody() { return ; }\nfunction CostExp2024Body() { return ; }\nfunction CostLicTotalBody() { return ; }\nfunction CostCategoryBody() { return ; }\nfunction CostAppBody() { return ; }\nfunction CostTopLicBody() { return ; }\nfunction CostUsageBody() { return ; }\nfunction CostRenewalsBody() {\n  const cols = [\n    { label: \"Application\", render: r =&gt; {r.app} },\n    { label: \"Name\", render: r =&gt; {r.name} },\n    { label: \"Renewal Date\", render: r =&gt; {r.date} },\n    { label: \"Total Cost\", align: \"right\", render: r =&gt; ${r.cost} },\n    { label: \"Term\", render: r =&gt; {r.term} },\n    { label: \"Unit\", align: \"right\", render: r =&gt; ${r.unit} },\n  ];\n  return (\n    \n\n      \n\n        \n          \n            {cols.map((c, i) =&gt; {c.label})}\n          \n        \n        \n          {COST_RENEWALS.map((r, ri) =&gt; (\n            \n              {cols.map((c, i) =&gt; {c.render(r)})}\n            \n          ))}\n        \n      \n    \n  );\n}\nfunction ResetViewBtn({ disabled, onReset }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)}\n      title={disabled ? \"Layout is at its default\" : \"Restore the default tile layout\"}\n      style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, height: 30, padding: \"0 13px\", borderRadius: 999, fontFamily: T.font, fontSize: 13, fontWeight: 600, cursor: disabled ? \"default\" : \"pointer\", border: `1px solid ${disabled ? T.lineSoft : h ? T.lineStrong : T.line}`, background: disabled ? \"transparent\" : h ? T.surface2 : T.control, color: disabled ? T.faint : T.body, transition: \"all .14s\" }}&gt;\n       Reset view\n    \n  );\n}\n\nconst TILE_META = {\n  line: { eyebrow: \"Activity\", title: \"Device Discovery Over Time\", Body: LineBody },\n  donut: { eyebrow: \"Composition\", title: \"Windows Distribution\", Body: DonutBody },\n  lifecycle: { eyebrow: \"Pipeline\", title: \"System Lifecycle\", Body: LifecycleBody },\n  discovery: { eyebrow: \"Activity\", title: \"Discovery Log\", Body: DiscoveryBody },\n  adapters: { eyebrow: \"Connections\", title: \"Adapter Connections\", Body: AdaptersBody },\n  stacked: { eyebrow: \"Trend\", title: \"Asset Growth by Category\", Body: StackedBody, footer: 12.4% },\n  severity: { eyebrow: \"Risk\", title: \"Vulnerabilities by Severity\", Body: SeverityBody, footer: 18 resolved },\n  healthOk: { eyebrow: \"Adapters\", title: \"Adapter Health \u00b7 Successful\", Body: HealthOkBody },\n  healthFail: { eyebrow: \"Adapters\", title: \"Adapter Health \u00b7 Attention\", Body: HealthFailBody },\n  costExpAll: { eyebrow: \"Spend\", title: \"Total Expenses (All Time)\", Body: CostExpAllBody },\n  costExp2024: { eyebrow: \"Spend\", title: \"Total Expenses (2024)\", Body: CostExp2024Body },\n  costLicAll: { eyebrow: \"Licenses\", title: \"Total License Cost (All Time)\", Body: CostLicAllBody },\n  costLicTotal: { eyebrow: \"Licenses\", title: \"Total License Cost\", Body: CostLicTotalBody },\n  costRenewals: { eyebrow: \"Renewals\", title: \"Upcoming Renewals (next 90 days)\", Body: CostRenewalsBody, footer: View all results, noPad: true },\n  costCategory: { eyebrow: \"Spend\", title: \"Expenses by Category (2024)\", Body: CostCategoryBody },\n  costApp: { eyebrow: \"Spend\", title: \"Expenses by App (2024)\", Body: CostAppBody },\n  costTopLic: { eyebrow: \"Licenses\", title: \"Most Expensive Licenses (2024)\", Body: CostTopLicBody },\n  costUsage: { eyebrow: \"Usage\", title: \"Azure AD Logons \u00b7 last 30 days\", Body: CostUsageBody },\n};\n\n/* ============ resizable grid (corner drag, neighbors reflow) ============ */\nconst GRID_COLS = 12, ROW_UNIT = 40, GAP = 16;\nconst INITIAL_TILES = [\n  { id: \"line\", w: 8, h: 7 }, { id: \"donut\", w: 4, h: 7 },\n  { id: \"lifecycle\", w: 5, h: 7 }, { id: \"discovery\", w: 7, h: 7 },\n  { id: \"adapters\", w: 7, h: 6 }, { id: \"severity\", w: 5, h: 6 },\n  { id: \"healthOk\", w: 6, h: 5 }, { id: \"healthFail\", w: 6, h: 5 },\n  { id: \"stacked\", w: 12, h: 5 },\n];\nconst COST_TILES = [\n  { id: \"costExpAll\", w: 3, h: 4 }, { id: \"costExp2024\", w: 3, h: 4 }, { id: \"costLicAll\", w: 3, h: 4 }, { id: \"costLicTotal\", w: 3, h: 4 },\n  { id: \"costRenewals\", w: 12, h: 6 },\n  { id: \"costCategory\", w: 6, h: 6 }, { id: \"costApp\", w: 6, h: 6 },\n  { id: \"costTopLic\", w: 6, h: 6 }, { id: \"costUsage\", w: 6, h: 6 },\n];\nconst DASH_TILESETS = { \"Cost Optimization\": COST_TILES };\nfunction DashboardGrid({ tileSet = INITIAL_TILES }) {\n  const [tiles, setTiles] = useState(tileSet);\n  const [hoverId, setHoverId] = useState(null);\n  const [resizingId, setResizingId] = useState(null);\n  const [drag, setDrag] = useState(null);\n  const dragId = drag ? drag.id : null;\n  const ref = useRef(null);\n  const tileEls = useRef({});\n  const justDragged = useRef(false);\n\n  const startDrag = (e, id, immediate) =&gt; {\n    if (e.button || (e.target.closest &amp;&amp; (e.target.closest(\"[data-resize]\") || e.target.closest(\"button\")))) return;\n    const startX = e.clientX, startY = e.clientY;\n    let dragging = false;\n    const begin = (cx, cy) =&gt; {\n      const el = tileEls.current[id]; if (!el) return;\n      const rect = el.getBoundingClientRect();\n      dragging = true;\n      setDrag({ id, x: cx, y: cy, ox: cx - rect.left, oy: cy - rect.top, w: rect.width, h: rect.height });\n      document.body.style.userSelect = \"none\";\n    };\n    const timer = immediate ? null : setTimeout(() =&gt; begin(startX, startY), 300);\n    const onMove = (ev) =&gt; {\n      if (!dragging) {\n        const moved = Math.abs(ev.clientX - startX) &gt; (immediate ? 4 : 8) || Math.abs(ev.clientY - startY) &gt; (immediate ? 4 : 8);\n        if (!moved) return;\n        if (immediate) begin(ev.clientX, ev.clientY); else { end(); return; }\n        if (!dragging) return;\n      }\n      setDrag(d =&gt; d ? { ...d, x: ev.clientX, y: ev.clientY } : d);\n      for (const tid in tileEls.current) {\n        const el = tileEls.current[tid];\n        if (!el || tid === id) continue;\n        const r = el.getBoundingClientRect();\n        if (ev.clientX &gt;= r.left &amp;&amp; ev.clientX &lt;= r.right &amp;&amp; ev.clientY &gt;= r.top &amp;&amp; ev.clientY &lt;= r.bottom) {\n          setTiles(ts =&gt; { const from = ts.findIndex(x =&gt; x.id === id), to = ts.findIndex(x =&gt; x.id === tid); if (from &lt; 0 || to &lt; 0 || from === to) return ts; const n = ts.slice(); const [mv] = n.splice(from, 1); n.splice(to, 0, mv); return n; });\n          break;\n        }\n      }\n    };\n    const end = () =&gt; { clearTimeout(timer); if (dragging) { justDragged.current = true; setTimeout(() =&gt; { justDragged.current = false; }, 60); } dragging = false; setDrag(null); document.body.style.userSelect = \"\"; window.removeEventListener(\"pointermove\", onMove); window.removeEventListener(\"pointerup\", end); };\n    window.addEventListener(\"pointermove\", onMove); window.addEventListener(\"pointerup\", end);\n  };\n\n  const startResize = (e, id) =&gt; {\n    e.preventDefault(); e.stopPropagation();\n    setResizingId(id);\n    const rect = ref.current.getBoundingClientRect();\n    const colW = (rect.width - GAP * (GRID_COLS - 1)) / GRID_COLS;\n    const t = tiles.find(x =&gt; x.id === id);\n    const startWpx = t.w * colW + (t.w - 1) * GAP;\n    const startHpx = t.h * ROW_UNIT + (t.h - 1) * GAP;\n    const sx = e.clientX, sy = e.clientY;\n    const onMove = (ev) =&gt; {\n      let w = Math.round((startWpx + (ev.clientX - sx) + GAP) / (colW + GAP));\n      let h = Math.round((startHpx + (ev.clientY - sy) + GAP) / (ROW_UNIT + GAP));\n      w = Math.max(3, Math.min(GRID_COLS, w));\n      h = Math.max(4, Math.min(14, h));\n      setTiles(ts =&gt; ts.map(x =&gt; x.id === id ? { ...x, w, h } : x));\n    };\n    const onUp = () =&gt; { setResizingId(null); document.body.style.cursor = \"\"; window.removeEventListener(\"mousemove\", onMove); window.removeEventListener(\"mouseup\", onUp); };\n    document.body.style.cursor = \"nwse-resize\";\n    window.addEventListener(\"mousemove\", onMove); window.addEventListener(\"mouseup\", onUp);\n  };\n\n  const isDefault = JSON.stringify(tiles) === JSON.stringify(tileSet);\n  return (\n    &lt;&gt;\n      \n\n         setTiles(tileSet)} /&gt;\n      \n    \n\n      {tiles.map((t, ti) =&gt; {\n        const m = TILE_META[t.id];\n        const isHover = hoverId === t.id, isResizing = resizingId === t.id, isDragging = dragId === t.id, anyDrag = dragId !== null;\n        return (\n          \n (tileEls.current[t.id] = el)}\n            onPointerDown={(e) =&gt; startDrag(e, t.id)}\n            onClickCapture={(e) =&gt; { if (justDragged.current) { e.stopPropagation(); justDragged.current = false; } }}\n            onMouseEnter={() =&gt; setHoverId(t.id)} onMouseLeave={() =&gt; setHoverId(null)}\n            style={{ gridColumn: `span ${t.w}`, gridRow: `span ${t.h}`, position: \"relative\", minWidth: 0, zIndex: 1 }}&gt;\n            {isDragging ? (\n              \n\n            ) : (&lt;&gt;\n            \n\n              \n { e.stopPropagation(); startDrag(e, t.id, true); }} title=\"Drag to move\" style={{ padding: \"13px 20px 12px\", borderBottom: `1px solid ${T.hair}`, display: \"flex\", alignItems: \"center\", gap: 8, cursor: \"grab\" }}&gt;\n                \n\n                  \n{m.eyebrow}\n                  \n{m.title}\n                \n                \n              \n              \n\n              {m.footer &amp;&amp; \n{m.footer}}\n            \n            \n startResize(e, t.id)} data-resize title=\"Drag to resize\"\n              style={{ position: \"absolute\", right: 0, bottom: 0, width: 20, height: 20, cursor: \"nwse-resize\", display: \"flex\", alignItems: \"flex-end\", justifyContent: \"flex-end\", padding: 4, opacity: isHover || isResizing ? 1 : 0, transition: \"opacity .15s\" }}&gt;\n              \n            \n            )}\n          \n        );\n      })}\n    \n    {drag &amp;&amp; (() =&gt; { const m = TILE_META[drag.id]; return createPortal(\n      \n\n        \n\n          \n\n            \n\n              \n{m.eyebrow}\n              \n{m.title}\n            \n            \n          \n          \nDrop to reorder\n        \n      , document.body); })()}\n    \n  );\n}\n\n/* ============ Assets table view (real device schema) ============ */\nconst ADAPTERS = {\n  aws_adapter: { brand: \"aws\" }, active_directory_adapter: { brand: \"active_directory\" },\n  crowd_strike_adapter: { brand: \"crowdstrike\" }, google_mdm_adapter: { brand: \"googlecloud\" },\n  esx_adapter: { brand: \"vmware\" }, sentinelone_adapter: { brand: \"sentinelone\" },\n  service_now_adapter: { brand: \"service_now\" }, epo_adapter: { brand: \"epo\" },\n  paloalto_panorama_adapter: { brand: \"paloalto\" }, cisco_meraki_adapter: { brand: \"cisco_meraki\" },\n  cisco_ise_adapter: { brand: \"cisco_ise\" }, cisco_adapter: { brand: \"cisco\" },\n  tanium_adapter: { brand: \"tanium\" }, tanium_asset_adapter: { brand: \"tanium\" },\n  claroty_adapter: { brand: \"claroty\" }, counter_act_adapter: { brand: \"forescout\" },\n  tenable_security_center_adapter: { brand: \"tenable\" }, zoom_adapter: { brand: \"zoom\" },\n  checkpoint_r80_adapter: { brand: \"checkpoint\" }, cylance_adapter: { brand: \"cylance\" },\n  chef_adapter: { brand: \"chef\" }, axonius_network_inspector_adapter: { short: \"NP\", color: \"#1C1D1F\" },\n};\nfunction AdapterAvatar({ k, size = 32 }) {\n  const a = ADAPTERS[k] || { short: k.slice(0, 2).toUpperCase(), color: \"#939598\" };\n  if (a.brand) return ;\n  return \n 28 ? 11 : 9, fontWeight: 700, color: a.color }}&gt;{a.short};\n}\nfunction AdapterStack({ list }) {\n  const shown = list.slice(0, 3), extra = list.length - shown.length;\n  return (\n    \n      \n      {shown.map((k, i) =&gt; )}\n      {extra &gt; 0 &amp;&amp; +{extra}}\n    \n  );\n}\n/* OS category icons */\nfunction OsIcon({ os }) {\n  const c = { Windows: \"#0078D4\", Linux: \"#1C1D1F\", \"OS X\": \"#555\", iOS: \"#111\", Android: \"#3DDC84\", VMWare: \"#607078\" }[os] || T.faint;\n  const mc = T.isLight ? \"#1B2030\" : \"#D6DAE6\"; // monochrome marks adapt to theme\n  const g = {\n    Windows: ,\n    \"OS X\": ,\n    iOS: ,\n    Android: ,\n    Linux: ,\n    VMWare: vm,\n  }[os];\n  if (!os) return \u2014;\n  return {g || }{os};\n}\nconst DEVICES = [\n  { name: \"PC-CURTIS-WILLIAMS\", host: \"PC-CURTIS-WILLIAMS.demo.local\", os: \"Windows\", ip: \"10.0.49.148\", mac: \"88:53:2E:12:45:C4\", seen: \"2026-06-08 09:34\", tags: [\"Corporate\", \"Managed\"], adapters: [\"active_directory_adapter\", \"crowd_strike_adapter\", \"epo_adapter\", \"google_mdm_adapter\", \"tanium_adapter\", \"sentinelone_adapter\", \"cisco_ise_adapter\"] },\n  { name: \"infranginx-5567897-stg\", host: \"esx-infranginx-5567897-stg.demo.local\", os: \"Linux\", ip: \"10.0.63.107\", mac: \"00:0C:29:12:55:38\", seen: \"2026-06-08 11:43\", tags: [\"Staging\", \"Cloud\"], adapters: [\"cisco_ise_adapter\", \"claroty_adapter\", \"counter_act_adapter\", \"epo_adapter\", \"tanium_adapter\", \"esx_adapter\", \"aws_adapter\"] },\n  { name: \"WIN-RUTHD\", host: \"WIN-RUTHD.demo.local\", os: \"Windows\", ip: \"10.0.48.107\", mac: \"88:53:2E:12:44:9B\", seen: \"2026-06-08 13:18\", tags: [\"Corporate\"], adapters: [\"active_directory_adapter\", \"cisco_adapter\", \"crowd_strike_adapter\", \"cylance_adapter\", \"epo_adapter\", \"tanium_adapter\"] },\n  { name: \"external3026786-prd\", host: \"sepio-external3026786-prd.demo.local\", os: \"Linux\", ip: \"10.0.64.36\", mac: \"88:53:2E:12:56:12\", seen: \"2026-06-08 05:54\", tags: [\"Production\", \"Internet-facing\"], adapters: [\"checkpoint_r80_adapter\", \"chef_adapter\", \"epo_adapter\", \"paloalto_panorama_adapter\", \"axonius_network_inspector_adapter\", \"claroty_adapter\", \"tenable_security_center_adapter\"] },\n  { name: \"macbook-pro-jdoe\", host: \"macbook-pro-jdoe.demo.local\", os: \"OS X\", ip: \"10.0.51.22\", mac: \"A4:83:E7:9C:11:04\", seen: \"2026-06-08 12:02\", tags: [\"Endpoint\", \"BYOD\"], adapters: [\"google_mdm_adapter\", \"crowd_strike_adapter\", \"sentinelone_adapter\"] },\n  { name: \"lablb-2918146-beta\", host: \"esx-lablb-2918146-beta.manufacturing.com\", os: \"Linux\", ip: \"10.0.56.90\", mac: \"00:0C:29:12:4C:BF\", seen: \"2026-06-08 11:30\", tags: [\"Lab\"], adapters: [\"cisco_meraki_adapter\", \"claroty_adapter\", \"epo_adapter\", \"esx_adapter\", \"tanium_asset_adapter\", \"counter_act_adapter\", \"service_now_adapter\"] },\n  { name: \"iphone-asmith\", host: \"\u2014\", os: \"iOS\", ip: \"10.0.77.51\", mac: \"F0:18:98:22:7D:AA\", seen: \"2026-06-08 11:58\", tags: [\"Mobile\", \"BYOD\"], adapters: [\"google_mdm_adapter\", \"zoom_adapter\"] },\n  { name: \"srv-db-fin-04\", host: \"srv-db-fin-04.healthcare-subsidiary.com\", os: \"Linux\", ip: \"10.0.40.12\", mac: \"00:50:56:9A:3C:11\", seen: \"2026-06-08 07:12\", tags: [\"Production\", \"Database\", \"PCI\"], adapters: [\"service_now_adapter\", \"tenable_security_center_adapter\", \"tanium_adapter\", \"epo_adapter\", \"aws_adapter\", \"claroty_adapter\"] },\n  { name: \"azure-infra9274676-prd\", host: \"azure-infra9274676-prd.demo.local\", os: \"Windows\", ip: \"10.0.61.5\", mac: \"00:0D:3A:1F:8E:22\", seen: \"2026-06-08 10:47\", tags: [\"Production\", \"Cloud\"], adapters: [\"active_directory_adapter\", \"aws_adapter\", \"sentinelone_adapter\", \"service_now_adapter\"] },\n  { name: \"android-pixel-7\", host: \"\u2014\", os: \"Android\", ip: \"10.0.78.130\", mac: \"3C:28:6D:55:01:9F\", seen: \"2026-06-08 09:05\", tags: [\"Mobile\"], adapters: [\"google_mdm_adapter\"] },\n  { name: \"Win-MaryChasse\", host: \"WIN-MARYCHASSE.demo.local\", os: \"Windows\", ip: \"10.0.49.201\", mac: \"88:53:2E:12:48:7E\", seen: \"2026-06-07 22:41\", tags: [\"Corporate\"], adapters: [\"active_directory_adapter\", \"epo_adapter\", \"cisco_meraki_adapter\"] },\n  { name: \"esx-mail-2231-prd\", host: \"esx-mail-2231-prd.manufacturing.com\", os: \"VMWare\", ip: \"10.0.62.18\", mac: \"00:0C:29:44:1A:E0\", seen: \"2026-06-08 06:33\", tags: [\"Production\"], adapters: [\"esx_adapter\", \"tenable_security_center_adapter\", \"paloalto_panorama_adapter\"] },\n  { name: \"PC-Doris9920\", host: \"PC-DORIS9920.demo.local\", os: \"Windows\", ip: \"10.0.50.66\", mac: \"88:53:2E:12:51:C9\", seen: \"2026-06-08 08:59\", tags: [\"Corporate\", \"Managed\"], adapters: [\"active_directory_adapter\", \"crowd_strike_adapter\", \"tanium_adapter\", \"google_mdm_adapter\"] },\n  { name: \"kiosk-lobby-03\", host: \"kiosk-lobby-03.demo.local\", os: \"Linux\", ip: \"10.0.44.7\", mac: \"B8:27:EB:0C:5A:31\", seen: \"2026-06-07 19:20\", tags: [\"IoT\", \"Unmanaged\"], adapters: [\"counter_act_adapter\", \"claroty_adapter\"] },\n];\n\nconst INIT_COLS = [\n  { key: \"adapters\", label: \"Adapter Connections\", w: 230, pinned: false },\n  { key: \"name\", label: \"Asset Name\", w: 230, pinned: false },\n  { key: \"host\", label: \"Host Name\", w: 300, pinned: false },\n  { key: \"os\", label: \"OS Type\", w: 140, pinned: false },\n  { key: \"ip\", label: \"IP Address\", w: 140, pinned: false },\n  { key: \"mac\", label: \"MAC Address\", w: 160, pinned: false },\n  { key: \"seen\", label: \"Last Seen (UTC)\", w: 170, pinned: false },\n  { key: \"tags\", label: \"Tags\", w: 220, pinned: false },\n];\nconst CB_W = 46;\nconst mono = { fontFamily: \"ui-monospace, 'SF Mono', monospace\" };\nfunction cellContent(key, r) {\n  switch (key) {\n    case \"adapters\": return ;\n    case \"name\": return {r.name};\n    case \"host\": return {r.host};\n    case \"os\": return ;\n    case \"ip\": return {r.ip};\n    case \"mac\": return {r.mac};\n    case \"seen\": return {r.seen};\n    case \"tags\": return {r.tags.slice(0, 2).map((t, j) =&gt; )}{r.tags.length &gt; 2 &amp;&amp; +{r.tags.length - 2}};\n    default: return null;\n  }\n}\n\nfunction HeaderCell({ col, left, isLastPinned, onResize, onPin, onDragStart, regRef, dragging }) {\n  const [h, setH] = useState(false);\n  const sticky = col.pinned;\n  return (\n    \n regRef(col.key, el)} onPointerDown={(e) =&gt; onDragStart(e, col.key)} onMouseEnter={() =&gt; setH(true)} onMouseLeave={() =&gt; setH(false)}\n      style={{ width: col.w, flexShrink: 0, position: sticky ? \"sticky\" : \"relative\", left: sticky ? left : undefined, zIndex: sticky ? 4 : 1, background: T.canvas, height: 40, display: \"flex\", alignItems: \"center\", gap: 6, padding: \"0 12px\", boxShadow: isLastPinned ? \"2px 0 5px rgba(27,32,70,0.06)\" : \"none\", cursor: \"grab\", opacity: dragging ? 0.4 : 1 }}&gt;\n      {col.label}\n      {(h || col.pinned) &amp;&amp; (\n         onPin(col.key)} title={col.pinned ? \"Unpin column\" : \"Pin column\"}\n          style={{ width: 24, height: 24, borderRadius: 6, border: \"none\", cursor: \"pointer\", background: \"transparent\", color: col.pinned ? T.blue : T.faint, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", transform: col.pinned ? \"none\" : \"rotate(45deg)\", flexShrink: 0 }}&gt;\n          \n        \n      )}\n      \n onResize(e, col.key)} data-colresize title=\"Drag to resize\"\n        style={{ position: \"absolute\", right: -3, top: 8, width: 6, height: 24, cursor: \"col-resize\", display: \"flex\", justifyContent: \"center\", zIndex: 5 }}&gt;\n        \n      \n    \n  );\n}\n\nfunction AssetsTable() {\n  const [columns, setColumns] = useState(INIT_COLS);\n  const [hoverRow, setHoverRow] = useState(null);\n  const [perPage, setPerPage] = useState(20);\n  const [dragCol, setDragCol] = useState(null);\n  const colEls = useRef({});\n  const regRef = (k, el) =&gt; { colEls.current[k] = el; };\n\n  const startColDrag = (e, key) =&gt; {\n    if (e.button || (e.target.closest &amp;&amp; (e.target.closest(\"[data-colresize]\") || e.target.closest(\"button\")))) return;\n    const startX = e.clientX; let dragging = false;\n    const onMove = (ev) =&gt; {\n      if (!dragging) { if (Math.abs(ev.clientX - startX) &gt; 6) { dragging = true; setDragCol(key); document.body.style.userSelect = \"none\"; } else return; }\n      for (const k in colEls.current) {\n        const el = colEls.current[k]; if (!el || k === key) continue;\n        const r = el.getBoundingClientRect();\n        if (ev.clientX &gt;= r.left &amp;&amp; ev.clientX &lt;= r.right) {\n          setColumns(cs =&gt; { const from = cs.findIndex(c =&gt; c.key === key), to = cs.findIndex(c =&gt; c.key === k); if (from &lt; 0 || to &lt; 0 || from === to) return cs; const n = cs.slice(); const [m] = n.splice(from, 1); n.splice(to, 0, m); return n; });\n          break;\n        }\n      }\n    };\n    const onUp = () =&gt; { dragging = false; setDragCol(null); document.body.style.userSelect = \"\"; window.removeEventListener(\"pointermove\", onMove); window.removeEventListener(\"pointerup\", onUp); };\n    window.addEventListener(\"pointermove\", onMove); window.addEventListener(\"pointerup\", onUp);\n  };\n\n  const startColResize = (e, key) =&gt; {\n    e.preventDefault(); e.stopPropagation();\n    const sx = e.clientX, startW = columns.find(c =&gt; c.key === key).w;\n    const onMove = (ev) =&gt; { const w = Math.max(90, startW + (ev.clientX - sx)); setColumns(cs =&gt; cs.map(c =&gt; c.key === key ? { ...c, w } : c)); };\n    const onUp = () =&gt; { document.body.style.cursor = \"\"; window.removeEventListener(\"mousemove\", onMove); window.removeEventListener(\"mouseup\", onUp); };\n    document.body.style.cursor = \"col-resize\";\n    window.addEventListener(\"mousemove\", onMove); window.addEventListener(\"mouseup\", onUp);\n  };\n  const togglePin = (key) =&gt; setColumns(cs =&gt; cs.map(c =&gt; c.key === key ? { ...c, pinned: !c.pinned } : c));\n\n  const pinned = columns.filter(c =&gt; c.pinned), unpinned = columns.filter(c =&gt; !c.pinned);\n  const ordered = [...pinned, ...unpinned];\n  const leftMap = {}; let acc = CB_W;\n  pinned.forEach(c =&gt; { leftMap[c.key] = acc; acc += c.w; });\n  const lastPinned = pinned.length ? pinned[pinned.length - 1].key : null;\n  const totalW = CB_W + columns.reduce((a, c) =&gt; a + c.w, 0);\n\n  return (\n    \n\n      \n\n        \n\n          {/* header */}\n          \n\n            \n\n              \n            \n            {ordered.map(c =&gt; )}\n            \n\n          \n          {/* rows */}\n          {DEVICES.map((r, i) =&gt; {\n            const rowBg = hoverRow === i ? T.surface2 : T.surface;\n            return (\n              \n setHoverRow(i)} onMouseLeave={() =&gt; setHoverRow(null)}\n                style={{ display: \"flex\", height: 52, borderBottom: i &lt; DEVICES.length - 1 ? `1px solid ${T.hair}` : \"none\", background: rowBg }}&gt;\n                \n\n                  {hoverRow === i ?  : {i + 1}}\n                \n                {ordered.map(c =&gt; {\n                  const sticky = c.pinned;\n                  return (\n                    \n\n                      {cellContent(c.key, r)}\n                    \n                  );\n                })}\n                \n\n              \n            );\n          })}\n        \n      \n      {/* footer */}\n      \n\n        \n\n          Rows per page:\n           setPerPage(Number(e.target.value))}\n            style={{ height: 30, padding: \"0 28px 0 10px\", borderRadius: 999, border: `1px solid ${T.line}`, background: T.surface, color: T.ink, fontSize: 13, fontWeight: 500, fontFamily: T.font, cursor: \"pointer\", appearance: \"none\", WebkitAppearance: \"none\", backgroundImage: `url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238494B5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E\")`, backgroundRepeat: \"no-repeat\", backgroundPosition: \"right 8px center\", outline: \"none\" }}&gt;\n            {[20, 50, 100].map(n =&gt; {n})}\n          \n        \n        \n\n          1\u2013{perPage} of 12,409\n          \n\n            \n            1234\n            \u2026621\n            \n          \n        \n      \n    \n  );\n}\n\nfunction QueryPill({ children, primary, active, onClick }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, height: 32, padding: \"0 14px\", borderRadius: 999, cursor: \"pointer\", fontFamily: T.font, fontSize: 13.5, fontWeight: 600, border: primary ? \"none\" : `1px solid ${active || h ? T.lineStrong : T.line}`, background: primary ? T.blue : active || h ? T.surface2 : T.control, color: primary ? T.onAccent : T.body }}&gt;{children};\n}\nfunction ToolLink({ icon: Icon, children }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, height: 30, padding: \"0 10px\", border: \"none\", borderRadius: 999, background: h ? T.surface2 : \"transparent\", color: T.body, fontSize: 13.5, fontWeight: 600, cursor: \"pointer\", fontFamily: T.font }}&gt;{Icon &amp;&amp; }{children};\n}\nfunction SegTool({ icon: Icon, title, first }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)}&gt;\n      \n      {h &amp;&amp; {title}}\n    \n  );\n}\nfunction IconTool({ icon: Icon, title }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)}&gt;\n      \n      {h &amp;&amp; {title}}\n    \n  );\n}\nfunction Segmented({ options, value, onChange }) {\n  const idx = Math.max(0, options.indexOf(value));\n  const wpct = 100 / options.length;\n  return (\n    \n\n      \n      {options.map(o =&gt; (\n         onChange(o)} style={{ position: \"relative\", zIndex: 1, minWidth: 62, padding: \"0 12px\", border: \"none\", background: \"transparent\", color: value === o ? T.onAccent : T.body, fontSize: 13.5, fontWeight: value === o ? 600 : 500, cursor: \"pointer\", fontFamily: T.font, transition: \"color .2s\" }}&gt;{o}\n      ))}\n    \n  );\n}\n\nfunction QueriesPanel({ close }) {\n  const recent = [\n    { nm: \"FH: 1.4 Asset Context.Business Importance\", sub: \"Shared Queries\" },\n    { nm: \"low Risk Score_2025-12-24T11-53\", sub: \"Public Queries / Predefined Queries\" },\n    { nm: \"Unmanaged endpoints seen &lt; 7d\", sub: \"Shared Queries\" },\n    { nm: \"Critical CVEs on internet-facing\", sub: \"Public Queries / Predefined Queries\" },\n  ];\n  return createPortal(&lt;&gt;\n    \n\n    \n\n      \n\n        Queries\n        \n        \n        \n      \n      \n\n        \n\n          \n        \n      \n      \n\n        \nFavorites\n        \nRecently Used Saved Queries\n        {recent.map((q, i) =&gt; (\n           (e.currentTarget.style.background = T.surface2)} onMouseLeave={(e) =&gt; (e.currentTarget.style.background = \"transparent\")}\n            style={{ display: \"flex\", alignItems: \"center\", gap: 11, width: \"100%\", border: \"none\", background: \"transparent\", borderRadius: 10, cursor: \"pointer\", padding: \"9px\", textAlign: \"left\", fontFamily: T.font }}&gt;\n            \n            \n              {q.nm}\n              {q.sub}\n            \n          \n        ))}\n      \n    \n  , document.body);\n}\nfunction AssetsView() {\n  const [mode, setMode] = useState(\"Wizard\");\n  const [enrichOpen, setEnrichOpen] = useState(false);\n  const [queriesOpen, setQueriesOpen] = useState(false);\n  return (\n    \n\n      \n\n      {/* breadcrumb + title + module actions */}\n      \n\n        \n\n          \nInventory / Assets\n          \nDevices\n        \n        \n\n          \n\n             setEnrichOpen(o =&gt; !o)}&gt;Enrichment &amp; Investigation \n            {enrichOpen &amp;&amp; (&lt;&gt;\n              \n setEnrichOpen(false)} style={{ position: \"fixed\", inset: 0, zIndex: 40 }} /&gt;\n              \n\n                {[\"Business Data Enrichment\", \"Device Inventory Classification\", \"Asset Investigation\"].map(o =&gt; (\n                   setEnrichOpen(false)} onMouseEnter={(e) =&gt; (e.currentTarget.style.background = T.surface2)} onMouseLeave={(e) =&gt; (e.currentTarget.style.background = \"transparent\")}\n                    style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", gap: 10, width: \"100%\", border: \"none\", background: \"transparent\", borderRadius: 8, cursor: \"pointer\", padding: \"9px 11px\", fontSize: 13, fontWeight: 500, fontFamily: T.font, color: T.body, textAlign: \"left\" }}&gt;\n                    {o} \n                  \n                ))}\n              \n            )}\n          \n          \n        \n      \n      {/* query toolbar */}\n      \n\n        New Query\n        Save As\n        Reset\n        Copy Query\n        \n\n        Display by Date\n        \n\n         setQueriesOpen(o =&gt; !o)}&gt;{queriesOpen ?  : &lt;&gt; Queries}\n        \n      \n      {/* search + wizard / basic filters */}\n      {mode === \"Wizard\" ? (\n        \n\n          \n\n            \n            \n          \n          Query Wizard\n        \n      ) : (\n        \n\n          \n          \n          \nLast Seen\n          \nTags\n           Filter\n        \n      )}\n      {/* stats toolbar */}\n      \n\n        Total 12,409\n         Last updated: 2026-06-08 17:07:59\n        \n\n        \n\n          \n\n            \n            \n            \n          \n           New Asset\n        \n      \n      {/* end flexShrink header */}\n      \n\n        \n      \n      {queriesOpen &amp;&amp;  setQueriesOpen(false)} /&gt;}\n    \n  );\n}\nfunction PageBtn({ children, active, disabled, wide }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ minWidth: wide ? \"auto\" : 30, height: 30, padding: wide ? \"0 10px\" : 0, borderRadius: 999, border: active ? \"none\" : `1px solid ${h &amp;&amp; !disabled ? T.lineStrong : \"transparent\"}`, background: active ? T.blue : h &amp;&amp; !disabled ? T.surface2 : \"transparent\", color: active ? T.onAccent : disabled ? T.faint : T.body, fontSize: 13, fontWeight: 500, cursor: disabled ? \"default\" : \"pointer\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: T.font, ...tnum }}&gt;{children};\n}\n\n/* ============ Inventory tree panel (assets view) ============ */\nconst INV_GROUPS_INIT = [\n  { name: \"Compute\", icon: Server, open: true, items: [\n    { icon: Server, label: \"Corporate Devices\", count: \"8,204\", fav: true },\n    { icon: ShieldAlert, label: \"Internet-facing Assets\", count: \"312\", fav: true },\n    { folder: \"Endpoint\", open: true, items: [\n      { icon: Server, label: \"Devices\", count: \"12,409\", active: true },\n      { icon: Workflow, label: \"Processes\", count: \"0\" },\n    ]},\n    { folder: \"Infrastructure\", open: false, items: [\n      { icon: Boxes, label: \"Compute Services\", count: \"14\" },\n      { icon: Cable, label: \"Databases\", count: \"36\" },\n      { icon: Boxes, label: \"Containers\", count: \"258\" },\n      { icon: Cable, label: \"Serverless Functions\", count: \"103\" },\n      { icon: Boxes, label: \"Compute Images\", count: \"25\" },\n    ]},\n    { icon: Settings, label: \"Configurations\", count: \"0\" },\n    { icon: Clock, label: \"Latest Devices\", count: \"12,409\" },\n  ] },\n  { name: \"Identity\", icon: Users, open: false, items: [\n    { folder: \"Directory\", open: false, items: [\n      { icon: Users, label: \"Users\", count: \"4,821\" },\n      { icon: Users, label: \"Groups\", count: \"312\" },\n    ]},\n    { icon: ShieldAlert, label: \"Service Accounts\", count: \"87\" },\n  ] },\n  { name: \"Applications\", icon: Layers, open: false, items: [\n    { icon: Layers, label: \"SaaS Apps\", count: \"143\" },\n    { icon: Layers, label: \"Installed Software\", count: \"2,904\" },\n  ] },\n  { name: \"Tickets\", icon: Bell, open: false, items: [\n    { icon: Bell, label: \"Open Tickets\", count: \"48\" },\n    { icon: Bell, label: \"Resolved\", count: \"203\" },\n  ] },\n];\nfunction InventoryPanel({ collapsed, setCollapsed }) {\n  const [groups, setGroups] = useState(INV_GROUPS_INIT);\n  const [favOpen, setFavOpen] = useState(true);\n\n  const collectItems = (items) =&gt; items.flatMap(it =&gt; it.folder ? collectItems(it.items) : [it]);\n  const favItems = groups.flatMap(g =&gt; collectItems(g.items)).filter(it =&gt; it.fav);\n\n  const toggleFavInItems = (items, label) =&gt; items.map(it =&gt;\n    it.folder ? { ...it, items: toggleFavInItems(it.items, label) }\n              : it.label === label ? { ...it, fav: !it.fav } : it\n  );\n  const toggleFav = (label) =&gt; setGroups(gs =&gt; gs.map(g =&gt; ({ ...g, items: toggleFavInItems(g.items, label) })));\n  const compute = groups.find(g =&gt; g.name === \"Compute\");\n  const computeLeafItems = compute ? compute.items.filter(it =&gt; !it.folder) : [];\n  return (\n    \n\n       setCollapsed(!collapsed)} title={collapsed ? \"Expand inventory\" : \"Collapse panel\"} className=\"ax-edge-collapse\"\n        style={{ position: \"absolute\", top: 16, right: -14, zIndex: 6, width: 28, height: 28, borderRadius: \"50%\", border: `1px solid ${T.line}`, background: T.surface, color: T.muted, cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", boxShadow: T.shadow }}&gt;\n        {collapsed ?  : }\n      \n      \n\n        {collapsed ? (\n          \n\n            \n\n              \n            \n            \n\n            {computeLeafItems.map((it, i) =&gt; {\n              const Icon = it.icon;\n              return (\n                \n\n                  \n                \n              );\n            })}\n          \n        ) : (\n          &lt;&gt;\n            \n\n              \n\n                \n              \n            \n            \n\n              \n\n                \n setFavOpen(o =&gt; !o)} style={{ display: \"flex\", alignItems: \"center\", gap: 9, padding: \"9px 8px\", cursor: \"pointer\", borderRadius: 8, color: T.body }}&gt;\n                  \n                  \n                  Favorites\n                  {favItems.length}\n                \n                {favOpen &amp;&amp; favItems.map((it, ii) =&gt; )}\n              \n              {groups.map((g, gi) =&gt; { const Icon = g.icon; return (\n                \n\n                  \n setGroups(gs =&gt; gs.map((x, i) =&gt; i === gi ? { ...x, open: !x.open } : x))}\n                    style={{ display: \"flex\", alignItems: \"center\", gap: 9, padding: \"9px 8px\", cursor: \"pointer\", borderRadius: 8, color: g.open ? T.accentText : T.body }}&gt;\n                    \n                    \n                    {g.name}\n                  \n                  {g.open &amp;&amp; g.items.map((it, ii) =&gt; it.folder ?  : )}\n                \n              ); })}\n            \n          \n        )}\n      \n    \n  );\n}\nfunction InvFolder({ folder, toggleFav }) {\n  const [open, setOpen] = useState(folder.open);\n  const [h, setH] = useState(false);\n  const FIcon = open ? FolderOpen : Folder;\n  return (\n    \n\n      \n setH(true)} onMouseLeave={() =&gt; setH(false)} onClick={() =&gt; setOpen(o =&gt; !o)}\n        style={{ display: \"flex\", alignItems: \"center\", gap: 8, height: 34, padding: \"0 8px 0 22px\", borderRadius: 8, cursor: \"pointer\", background: h ? T.surface2 : \"transparent\", transition: \"background .12s\" }}&gt;\n        \n        \n        {folder.folder}\n      \n      {open &amp;&amp; folder.items.map((it, i) =&gt; )}\n    \n  );\n}\nfunction InvRow({ icon: Icon, label, count, active, fav, indent, toggleFav }) {\n  const [h, setH] = useState(false);\n  const left = indent || 28;\n  return (\n    \n setH(true)} onMouseLeave={() =&gt; setH(false)}\n      style={{ position: \"relative\", display: \"flex\", alignItems: \"center\", gap: 11, height: 36, margin: \"1px 0\", padding: `0 8px 0 ${left}px`, borderRadius: 8, cursor: \"pointer\", background: active ? T.accentSoft : h ? T.surface2 : \"transparent\", transition: \"background .14s\" }}&gt;\n      \n      {label}\n      {(h || fav) &amp;&amp; toggleFav &amp;&amp; (\n         { e.stopPropagation(); toggleFav(label); }} title={fav ? \"Remove from favorites\" : \"Add to favorites\"} className=\"ax-star\"\n          style={{ width: 22, height: 22, borderRadius: 6, border: \"none\", background: \"transparent\", cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", padding: 0, flexShrink: 0 }}&gt;\n          \n        \n      )}\n      {!h &amp;&amp; !fav &amp;&amp; {count}}\n    \n  );\n}\n\n/* ============ chrome ============ */\nfunction AppHeader({ theme, setTheme }) {\n  return (\n    \n\n      \n\n        \n      \n      \n\n        \n\n          Search Axonius\n          \u2318K\n        \n      \n      \n\n        New navigation BETA\n         setTheme(theme === \"dark\" ? \"light\" : \"dark\")} title=\"Toggle light / dark\"&gt;{theme === \"dark\" ?  : }\n        \n        \nSB\n      \n    \n  );\n}\nfunction HBtn({ children, onClick, title }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ width: 32, height: 32, borderRadius: 8, border: \"none\", cursor: \"pointer\", background: h ? T.surface2 : \"transparent\", color: h ? T.ink : T.bg80, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}&gt;{children};\n}\n\nconst HICONS = {\n  dashboard: '',\n  inventory: '',\n  shield: '',\n  bolt: '',\n  link: '',\n  chart: '',\n  cog: '',\n};\nfunction HIcon({ name, size = 20, color = \"currentColor\", sw = 1.6, className }) {\n  return ${HICONS[name] || \"\"}` }} /&gt;;\n}\nconst RAIL = [\n  { key: \"dashboard\", hi: \"dashboard\", label: \"Dashboard\" }, { key: \"assets\", hi: \"inventory\", label: \"Inventory\" },\n  { key: \"exposures\", hi: \"shield\", label: \"Exposures\" }, { key: \"action\", hi: \"bolt\", label: \"Action Center\" },\n  { key: \"adapters\", hi: \"link\", label: \"Adapters\" }, { key: \"analysis\", hi: \"chart\", label: \"Analysis\" },\n];\nfunction IconRail({ view, setView }) {\n  return (\n    \n\n      {RAIL.map(r =&gt;  setView(r.key)} /&gt;)}\n      \n\n      \n\n    \n  );\n}\nfunction RailGlyph({ hi, label, active, onClick }) {\n  const [h, setH] = useState(false);\n  return (\n    \n setH(true)} onMouseLeave={() =&gt; setH(false)} title={label} className=\"ax-rail ax-press\"\n      style={{ position: \"relative\", width: 44, height: 44, borderRadius: 14, cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", background: active ? T.accentSoft : h ? T.surface2 : \"transparent\", transition: \"background .15s\" }}&gt;\n      \n    \n  );\n}\n\nconst DASH_TREE_INIT = [\n  { g: \"Favorites\", open: true, items: [\n    { nm: \"My Dashboard\", fav: true }, { nm: \"Asset Profile - Copy_2024-06-09\", fav: true },\n    { nm: \"Axonius Dashboard\", fav: true, active: true, def: true }, { nm: \"Cost Optimization\", fav: true }, { nm: \"Cloud Compliance\", fav: true },\n  ] },\n  { g: \"Public\", open: false, items: [\n    { folder: \"Security\", open: false, items: [{ nm: \"SecOps Overview\" }, { nm: \"Threat Intelligence\" }] },\n    { folder: \"Executive\", open: false, items: [{ nm: \"Executive Summary\" }, { nm: \"Board Report\" }] },\n    { nm: \"Asset Inventory V 1.0\" }, { nm: \"Active Directory Insights\" },\n  ] },\n  { g: \"Shared\", open: false, items: [\n    { folder: \"Team SOC\", open: false, items: [{ nm: \"Vulnerability Posture\" }, { nm: \"Incident Response\" }] },\n    { nm: \"Cloud Inventory\" }, { nm: \"Compliance Overview\" },\n  ] },\n  { g: \"Private\", open: false, items: [{ nm: \"My Drafts\" }, { nm: \"Work in Progress\" }] },\n  { g: \"Managed by Axonius\", open: false, items: [{ nm: \"Device Overview\" }, { nm: \"User Overview\" }, { nm: \"Adapters Health\" }, { nm: \"Coverage &amp; Gaps\" }] },\n];\nfunction PanelBtn({ primary, filled, icon: Icon, children, onClick }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)} className={filled ? \"ax-primary\" : \"ax-press\"}\n      style={{ height: 38, borderRadius: 999, border: filled ? \"none\" : primary ? `1.5px solid ${T.accentText}` : `1px solid ${h ? T.lineStrong : T.line}`, cursor: \"pointer\", width: \"100%\", background: filled ? (h ? T.blueDeep : T.blue) : primary ? (h ? T.accentSoft : \"transparent\") : h ? T.surface2 : \"transparent\", color: filled ? T.onAccent : primary ? T.accentText : T.body, fontSize: 13, fontWeight: 600, fontFamily: T.font, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", gap: 7, boxShadow: \"none\", transition: \"background .14s ease, border-color .14s ease\" }}&gt;\n       {children}\n    \n  );\n}\nfunction CreateDashModal({ onCreate, close }) {\n  const [name, setName] = useState(\"\");\n  const [tpl, setTpl] = useState(\"blank\");\n  const tpls = [{ k: \"blank\", t: \"Blank dashboard\", d: \"Start from an empty canvas\" }, { k: \"template\", t: \"From a template\", d: \"Prebuilt charts to tweak\" }];\n  return createPortal(\n    \n\n      \n e.stopPropagation()} style={{ width: 480, maxWidth: \"92vw\", background: T.canvas, border: `1px solid ${T.line}`, borderRadius: 14, boxShadow: \"0 30px 80px rgba(0,0,0,.55)\", overflow: \"hidden\", animation: \"axRise .26s cubic-bezier(.22,1,.36,1) both\" }}&gt;\n        \n\n          Create dashboard\n          \n        \n        \n\n          \n\n            Dashboard name\n             setName(e.target.value)} placeholder=\"e.g. Security Posture\" style={{ width: \"100%\", height: 40, padding: \"0 14px\", border: `1px solid ${T.line}`, borderRadius: 8, background: T.control, color: T.ink, fontFamily: T.font, fontSize: 14, outline: \"none\", boxSizing: \"border-box\" }} /&gt;\n          \n          \n\n            Starting point\n            \n\n              {tpls.map(o =&gt; (\n                 setTpl(o.k)} style={{ flex: 1, textAlign: \"left\", padding: \"12px 13px\", borderRadius: 10, border: `1.5px solid ${tpl === o.k ? T.accentText : T.line}`, background: tpl === o.k ? T.accentSoft : \"transparent\", cursor: \"pointer\", fontFamily: T.font }}&gt;\n                  \n{o.t}\n                  \n{o.d}\n                \n              ))}\n            \n          \n        \n        \n\n          Cancel\n           name.trim() &amp;&amp; onCreate(name.trim())} className=\"ax-press\" style={{ height: 38, padding: \"0 18px\", borderRadius: 999, border: \"none\", background: name.trim() ? T.blue : T.surface2, color: name.trim() ? T.onAccent : T.faint, fontSize: 13.5, fontWeight: 600, cursor: name.trim() ? \"pointer\" : \"default\", fontFamily: T.font }}&gt;Create dashboard\n        \n      \n    , document.body);\n}\nfunction SidePanel({ collapsed, setCollapsed, activeDash, setActiveDash }) {\n  const [tree, setTree] = useState(DASH_TREE_INIT);\n  const [menu, setMenu] = useState(null);\n  const [editing, setEditing] = useState(null);\n  const [createOpen, setCreateOpen] = useState(false);\n\n  const mut = (fn) =&gt; setTree(t =&gt; fn(t.map(g =&gt; ({ ...g, items: g.items.map(it =&gt; ({ ...it })) }))));\n  const toggleGroup = (gi) =&gt; setTree(t =&gt; t.map((g, i) =&gt; i === gi ? { ...g, open: !g.open } : g));\n  const setActive = (gi, ii) =&gt; { mut(t =&gt; { t.forEach(g =&gt; g.items.forEach(it =&gt; it.active = false)); t[gi].items[ii].active = true; return t; }); if (setActiveDash) setActiveDash(tree[gi].items[ii].nm); };\n  const toggleFav = (gi, ii) =&gt; mut(t =&gt; { t[gi].items[ii].fav = !t[gi].items[ii].fav; return t; });\n  const setDefault = (gi, ii) =&gt; mut(t =&gt; { t.forEach(g =&gt; g.items.forEach(it =&gt; it.def = false)); t[gi].items[ii].def = true; return t; });\n  const duplicate = (gi, ii) =&gt; mut(t =&gt; { t[gi].items.splice(ii + 1, 0, { nm: t[gi].items[ii].nm + \" (copy)\" }); return t; });\n  const rename = (gi, ii, nm) =&gt; mut(t =&gt; { t[gi].items[ii].nm = nm || t[gi].items[ii].nm; return t; });\n  const remove = (gi, ii) =&gt; mut(t =&gt; { t[gi].items.splice(ii, 1); return t; });\n  const createDash = (nm) =&gt; mut(t =&gt; { const p = t.find(g =&gt; g.g === \"Private\"); p.open = true; p.items.push({ nm: nm || \"Untitled dashboard\" }); return t; });\n  const openMenu = (gi, ii, e) =&gt; { const r = e.currentTarget.getBoundingClientRect(); setMenu({ gi, ii, top: r.bottom + 6, left: Math.max(8, r.right - 196) }); };\n\n  if (collapsed) {\n    const favItems = tree.flatMap(g =&gt; g.items).filter(it =&gt; it.fav || it.active);\n    return (\n      \n\n         setCollapsed(false)} title=\"Expand dashboards\" className=\"ax-edge-collapse\"\n          style={{ position: \"absolute\", top: 16, right: -14, zIndex: 6, width: 28, height: 28, borderRadius: \"50%\", border: `1px solid ${T.line}`, background: T.surface, color: T.muted, cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", boxShadow: T.shadow }}&gt;\n          \n        \n         setCreateOpen(true)} style={{ width: 38, height: 38, borderRadius: 8, border: `1.5px solid ${T.accentText}`, background: \"transparent\", cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", color: T.accentText }}&gt;\n          \n        \n        \n\n        {favItems.map((s, i) =&gt; (\n          \n\n            \n            {s.fav &amp;&amp; }\n          \n        ))}\n        {createOpen &amp;&amp;  { createDash(nm); setCreateOpen(false); }} close={() =&gt; setCreateOpen(false)} /&gt;}\n      \n    );\n  }\n  return (\n    \n\n       setCollapsed(true)} title=\"Collapse panel\" className=\"ax-edge-collapse\"\n        style={{ position: \"absolute\", top: 16, right: -14, zIndex: 6, width: 28, height: 28, borderRadius: \"50%\", border: `1px solid ${T.line}`, background: T.surface, color: T.muted, cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", boxShadow: T.shadow }}&gt;\n        \n      \n      \n\n         setCreateOpen(true)}&gt;Create Dashboard\n        Manage Dashboards\n      \n      \n\n        \n\n          \n        \n      \n      \n\n        {tree.map((g, gi) =&gt; (\n          \n        ))}\n      \n      {menu &amp;&amp;  setMenu(null)}\n        actions={{ rename: () =&gt; setEditing({ gi: menu.gi, ii: menu.ii }), duplicate, toggleFav, setDefault, remove }} /&gt;}\n      {createOpen &amp;&amp;  { createDash(nm); setCreateOpen(false); }} close={() =&gt; setCreateOpen(false)} /&gt;}\n    \n  );\n}\nconst DASH_CAT_ICON = { Favorites: Star, Public: Globe, Shared: Users, Private: Lock, \"Managed by Axonius\": ShieldCheck };\nfunction DashGroup({ group, gi, toggleGroup, setActive, toggleFav, openMenu, editing, setEditing, rename }) {\n  const Icon = DASH_CAT_ICON[group.g] || LayoutGrid;\n  return (\n    \n\n       toggleGroup(gi)} className=\"ax-catrow\"\n        style={{ display: \"flex\", alignItems: \"center\", gap: 9, width: \"100%\", border: \"none\", background: \"transparent\", cursor: \"pointer\", padding: \"8px 8px\", borderRadius: 8, fontFamily: T.font }}&gt;\n        \n        \n        {group.g}\n        {group.g === \"Favorites\" ? group.items.filter(it =&gt; it.fav !== false).length : group.items.length}\n      \n      {group.open &amp;&amp; group.items\n        .filter(it =&gt; group.g !== \"Favorites\" || it.fav !== false)\n        .map((it, ii) =&gt;\n          it.folder\n            ? \n            : \n        )}\n    \n  );\n}\nfunction DashFolder({ folder, indent, onToggle }) {\n  const [h, setH] = useState(false);\n  const [open, setOpen] = useState(folder.open);\n  const toggle = () =&gt; { setOpen(o =&gt; !o); if (onToggle) onToggle(!open); };\n  const FIcon = open ? FolderOpen : Folder;\n  return (\n    \n\n      \n setH(true)} onMouseLeave={() =&gt; setH(false)} onClick={toggle}\n        style={{ display: \"flex\", alignItems: \"center\", gap: 8, height: 34, padding: `0 8px 0 ${indent || 22}px`, borderRadius: 8, cursor: \"pointer\", background: h ? T.surface2 : \"transparent\", transition: \"background .12s\" }}&gt;\n        \n        \n        {folder.folder}\n      \n      {open &amp;&amp; folder.items.map((it, i) =&gt; (\n        \n e.currentTarget.style.background = T.surface2} onMouseLeave={(e) =&gt; e.currentTarget.style.background = \"transparent\"}&gt;\n          \n          {it.nm}\n        \n      ))}\n    \n  );\n}\nfunction DashItem({ item, gi, ii, setActive, toggleFav, openMenu, editing, setEditing, rename }) {\n  const [h, setH] = useState(false);\n  return (\n    \n setH(true)} onMouseLeave={() =&gt; setH(false)} onClick={() =&gt; !editing &amp;&amp; setActive(gi, ii)} className=\"ax-press\"\n      style={{ position: \"relative\", display: \"flex\", alignItems: \"center\", gap: 9, height: 36, padding: \"0 8px 0 28px\", margin: \"1px 0\", cursor: \"pointer\", borderRadius: 8, border: \"none\", background: item.active ? T.accentSoft : h ? T.surface2 : \"transparent\", transition: \"background .14s\" }}&gt;\n      \n      {editing ? (\n         e.stopPropagation()}\n          onBlur={(e) =&gt; { rename(gi, ii, e.target.value.trim()); setEditing(null); }}\n          onKeyDown={(e) =&gt; { if (e.key === \"Enter\") { rename(gi, ii, e.target.value.trim()); setEditing(null); } if (e.key === \"Escape\") setEditing(null); }}\n          style={{ flex: 1, minWidth: 0, border: `1px solid ${T.blue}`, borderRadius: 6, padding: \"2px 6px\", fontSize: 13.5, fontFamily: T.font, color: T.ink, outline: \"none\", background: T.surface }} /&gt;\n      ) : (\n        {item.nm}\n      )}\n      {(h || item.fav) &amp;&amp; !editing &amp;&amp; (\n         { e.stopPropagation(); toggleFav(gi, ii); }} title={item.fav ? \"Remove from favorites\" : \"Add to favorites\"} className=\"ax-star\"\n          style={{ width: 22, height: 22, borderRadius: 6, border: \"none\", background: \"transparent\", cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", padding: 0, flexShrink: 0 }}&gt;\n          \n        \n      )}\n      {h &amp;&amp; !editing &amp;&amp; (\n         { e.stopPropagation(); openMenu(gi, ii, e); }} title=\"More actions\"\n          style={{ width: 22, height: 22, borderRadius: 6, border: \"none\", background: \"transparent\", cursor: \"pointer\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", padding: 0, flexShrink: 0, color: T.muted }}&gt;\n          \n        \n      )}\n    \n  );\n}\nfunction RowMenu({ menu, tree, close, actions }) {\n  const it = tree[menu.gi]?.items?.[menu.ii] || {};\n  const rows = [\n    { ic: Pencil, l: \"Rename\", fn: () =&gt; actions.rename() },\n    { ic: Copy, l: \"Duplicate\", fn: () =&gt; actions.duplicate(menu.gi, menu.ii) },\n    { ic: Star, l: it.fav ? \"Remove from favorites\" : \"Add to favorites\", fn: () =&gt; actions.toggleFav(menu.gi, menu.ii) },\n    { ic: Check, l: it.def ? \"Default dashboard\" : \"Set as default\", fn: () =&gt; actions.setDefault(menu.gi, menu.ii) },\n    { ic: Download, l: \"Export\", fn: () =&gt; {} },\n    { sep: true },\n    { ic: Trash2, l: \"Delete\", danger: true, fn: () =&gt; actions.remove(menu.gi, menu.ii) },\n  ];\n  return (\n    &lt;&gt;\n      \n\n      \n\n        {rows.map((a, i) =&gt; a.sep\n          ? \n\n          :  { a.fn(); close(); }}&gt;{a.l})}\n      \n    \n  );\n}\nfunction MenuRow({ icon: Icon, danger, children, onClick }) {\n  const [h, setH] = useState(false);\n  return (\n     setH(true)} onMouseLeave={() =&gt; setH(false)}\n      style={{ display: \"flex\", alignItems: \"center\", gap: 9, width: \"100%\", border: \"none\", borderRadius: 8, cursor: \"pointer\", padding: \"7px 9px\", fontSize: 13, fontFamily: T.font, fontWeight: 500, textAlign: \"left\", color: danger ? T.red : T.body, background: h ? (danger ? `${T.red}10` : T.canvas) : \"transparent\" }}&gt;\n       {children}\n    \n  );\n}\n\nfunction ModuleHeader({ view, activeDash }) {\n  const isDash = view === \"dashboard\";\n  return (\n    \n\n      \n{isDash ? \"Dashboards / Favorites\" : \"Assets / All Devices\"}\n      \n\n        \n\n          \n{isDash ? activeDash : \"Assets\"}\n          {isDash &amp;&amp; }\n        \n        \n\n          {isDash ? &lt;&gt;\n             Updated 2m ago\n            Add Chart\n           : &lt;&gt;\n            Export\n            New Query\n          }\n          \n        \n      \n    \n  );\n}\nfunction HdrBtn({ icon: Icon, children, primary, filled }) {\n  const [h, setH] = useState(false);\n  return  setH(true)} onMouseLeave={() =&gt; setH(false)} style={{ display: \"flex\", alignItems: \"center\", gap: 6, height: 34, padding: \"0 14px\", borderRadius: 999, cursor: \"pointer\", fontFamily: T.font, fontSize: 13.5, fontWeight: 600, border: filled ? \"none\" : (primary ? `1.5px solid ${T.accentText}` : `1px solid ${h ? T.lineStrong : T.line}`), background: filled ? (h ? T.blueDeep : T.blue) : (primary ? (h ? T.accentSoft : \"transparent\") : (h ? T.surface2 : T.control)), color: filled ? T.onAccent : (primary ? T.accentText : T.body) }}&gt;{children};\n}\n\nfunction AxoniusApp() {\n  const [view, setView] = useState(\"dashboard\");\n  const [collapsed, setCollapsed] = useState(false);\n  const [invCollapsed, setInvCollapsed] = useState(false);\n  const [theme, setTheme] = useState(\"dark\");\n  const [activeDash, setActiveDash] = useState(\"Axonius Dashboard\");\n  T = THEMES[theme];\n  useEffect(() =&gt; {\n    document.body.style.background = THEMES[theme].shell;\n    const el = document.documentElement;\n    el.classList.add(\"theming\");\n    const id = setTimeout(() =&gt; el.classList.remove(\"theming\"), 450);\n    return () =&gt; clearTimeout(id);\n  }, [theme]);\n  return (\n    \n\n      {`@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&amp;family=Schibsted+Grotesk:wght@500;600;700&amp;display=swap'); * { box-sizing: border-box; } .theming, .theming * { transition: background-color .4s ease, border-color .4s ease, color .35s ease, fill .3s ease, stroke .3s ease !important; } *::-webkit-scrollbar { width: 9px; height: 9px; } *::-webkit-scrollbar-thumb { background: ${T.bg40}; border-radius: 5px; } *::-webkit-scrollbar-track { background: transparent; }\n        .ax-tile *::-webkit-scrollbar-thumb { background: transparent; transition: background .2s ease; }\n        .ax-tile:hover *::-webkit-scrollbar-thumb { background: ${T.bg40}; }\n        .ax-tbl::-webkit-scrollbar-thumb { background: transparent; } .ax-tbl:hover::-webkit-scrollbar-thumb { background: ${T.bg40}; } .ax-tbl::-webkit-scrollbar-corner { background: transparent; }\n        input::placeholder { color: ${T.muted}; opacity: 1; }\n        .ax-catrow:hover { background: ${T.surface2}; }\n        .ax-catrow:hover .ax-favbtn { opacity: 1 !important; }\n        .ax-edge-collapse { opacity: 0; transform: scale(.8); transition: opacity .15s ease, transform .15s ease; }\n        .ax-sidepanel:hover .ax-edge-collapse { opacity: 1; transform: scale(1); }\n        button, [role=\"button\"] { transition: transform .14s cubic-bezier(.34,1.56,.64,1), background-color .15s ease, box-shadow .18s ease, border-color .15s ease, color .12s ease; }\n        button:not(:disabled):active { transform: scale(.92); }\n        .ax-press { transition: transform .14s cubic-bezier(.34,1.56,.64,1), background-color .14s ease; }\n        .ax-press:active { transform: scale(.95); }\n        .ax-card { transition: transform .2s cubic-bezier(.34,1.56,.64,1), box-shadow .2s ease, border-color .15s ease; }\n        .ax-card:hover { border-color: ${T.lineStrong} !important; box-shadow: ${T.shadow} !important; }\n        .ax-primary:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(37,99,235,.4) !important; }\n        .ax-primary:active { transform: translateY(0) scale(.96); }\n        @keyframes axRise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }\n        .ax-rise { animation: axRise .45s cubic-bezier(.22,1,.36,1) both; }\n        @keyframes axJiggle { 0%, 100% { transform: rotate(-0.5deg); } 50% { transform: rotate(0.5deg); } }\n        .ax-jiggle { animation: axJiggle .32s ease-in-out infinite; }\n        @keyframes axPop { 0% { transform: scale(1); } 45% { transform: scale(1.25) rotate(-8deg); } 100% { transform: scale(1); } }\n        .ax-star:hover { animation: axPop .4s ease; }\n        @keyframes axSpin { to { transform: rotate(360deg); } }\n        .ax-spin:hover { animation: axSpin .6s ease; }\n        button:not(:disabled):hover { transform: translateY(-1px); }\n        @keyframes axGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }\n        @keyframes axGrowY { from { transform: scaleY(0); } to { transform: scaleY(1); } }\n        @keyframes axDraw { to { stroke-dashoffset: 0; } }\n        @keyframes axFade { from { opacity: 0; } to { opacity: 1; } }\n        @keyframes axSlideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }\n        @keyframes axBounce { 0% { transform: translateY(0) scale(1); } 35% { transform: translateY(-4px) scale(1.14); } 70% { transform: translateY(0) scale(1); } 100% { transform: translateY(0) scale(1); } }\n        .ax-rail:hover .ax-railicon { animation: axBounce .5s ease; }\n        .ax-flat:hover { transform: none !important; }`}\n      \n      \n\n        \n        \n\n          {view === \"dashboard\" &amp;&amp; }\n          {view === \"assets\" &amp;&amp; }\n          \n\n            {view === \"dashboard\" &amp;&amp; }\n            {view === \"dashboard\" ? (\n              \n\n                {DASH_TILESETS[activeDash]\n                  ? \n                  : &lt;&gt;}\n              \n            ) : view === \"assets\" ? (\n              \n            ) : (\n              \n\n                {RAIL.find(r =&gt; r.key === view)?.label} view \u2014 coming soon\n              \n            )}\n          \n        \n      \n    \n  );\n}\n\ncreateRoot(document.getElementById(\"root\")).render();\n    \n  \n    fetch('/.inspector/overlay.js')\n      .then(r =&gt; r.text())\n      .then(code =&gt; { const s = document.createElement('script'); s.textContent = code; document.head.appendChild(s); })\n      .catch(() =&gt; {});\n  \n  \n\n", "creation_timestamp": "2026-06-23T12:21:01.000000Z"}, {"uuid": "33a876f0-e3e8-4c47-89d2-6c2838607b6e", "vulnerability_lookup_origin": "caeb2787-0d58-4236-9039-7c86c3e566f3", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23225", "type": "exploited", "source": "https://vulnerability.circl.lu/known-exploited-vulnerabilities-catalog/366d8b84-7846-404a-8873-fba5e0888c4e", "content": "", "creation_timestamp": "2026-06-23T14:05:52.061685Z"}]}