From f43efc1431a206388f640be87ae8240a2d473059 Mon Sep 17 00:00:00 2001 From: "community-scripts-pr-app[bot]" <189241966+community-scripts-pr-app[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 01:12:09 +0100 Subject: [PATCH 01/33] Update versions.json (#9344) Co-authored-by: GitHub Actions[bot] --- frontend/public/json/versions.json | 180 ++++++++++++++--------------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/frontend/public/json/versions.json b/frontend/public/json/versions.json index 771c3f1a2..bac21cb8a 100644 --- a/frontend/public/json/versions.json +++ b/frontend/public/json/versions.json @@ -1,4 +1,89 @@ [ + { + "name": "TwiN/gatus", + "version": "v5.33.0", + "date": "2025-11-21T22:54:49Z" + }, + { + "name": "BerriAI/litellm", + "version": "v1.78.5-stable-patch-1", + "date": "2025-11-21T19:57:45Z" + }, + { + "name": "laurent22/joplin", + "version": "server-v3.4.4", + "date": "2025-09-25T13:19:26Z" + }, + { + "name": "homarr-labs/homarr", + "version": "v1.44.0", + "date": "2025-11-21T19:17:09Z" + }, + { + "name": "gelbphoenix/autocaliweb", + "version": "v0.11.0", + "date": "2025-11-21T18:42:15Z" + }, + { + "name": "rclone/rclone", + "version": "v1.72.0", + "date": "2025-11-21T18:20:58Z" + }, + { + "name": "readeck/readeck", + "version": "0.21.1", + "date": "2025-11-21T17:17:52Z" + }, + { + "name": "mattermost/mattermost", + "version": "v10.11.8", + "date": "2025-11-21T17:06:07Z" + }, + { + "name": "home-assistant/core", + "version": "2025.11.3", + "date": "2025-11-21T17:03:22Z" + }, + { + "name": "Bubka/2FAuth", + "version": "v5.6.1", + "date": "2025-11-21T16:51:21Z" + }, + { + "name": "steveiliop56/tinyauth", + "version": "v4.0.1", + "date": "2025-10-15T16:53:55Z" + }, + { + "name": "BookStackApp/BookStack", + "version": "v25.11.3", + "date": "2025-11-21T14:06:50Z" + }, + { + "name": "pommee/goaway", + "version": "v0.62.19", + "date": "2025-11-21T13:24:09Z" + }, + { + "name": "fuma-nama/fumadocs", + "version": "fumadocs-ui@16.1.0", + "date": "2025-11-21T13:02:57Z" + }, + { + "name": "forgejo/forgejo", + "version": "v13.0.3", + "date": "2025-11-21T12:43:04Z" + }, + { + "name": "bunkerity/bunkerweb", + "version": "v1.6.5", + "date": "2025-10-06T15:25:17Z" + }, + { + "name": "chrisbenincasa/tunarr", + "version": "v0.23.0-alpha.24", + "date": "2025-11-21T12:16:39Z" + }, { "name": "rcourtman/Pulse", "version": "v4.32.3", @@ -29,11 +114,6 @@ "version": "v1.11.0", "date": "2025-11-21T08:09:03Z" }, - { - "name": "mattermost/mattermost", - "version": "v10.11.7", - "date": "2025-11-17T08:40:53Z" - }, { "name": "MariaDB/server", "version": "mariadb-12.1.2", @@ -69,11 +149,6 @@ "version": "v2.1.11", "date": "2025-11-20T20:14:44Z" }, - { - "name": "readeck/readeck", - "version": "0.21.0", - "date": "2025-11-20T18:35:59Z" - }, { "name": "saltstack/salt", "version": "v3007.9", @@ -89,6 +164,11 @@ "version": "v1.30.12", "date": "2025-11-20T16:13:19Z" }, + { + "name": "neo4j/neo4j", + "version": "5.26.17", + "date": "2025-11-20T16:09:28Z" + }, { "name": "meilisearch/meilisearch", "version": "prototype-v1.26.0.arm-native-docker-build-1", @@ -124,11 +204,6 @@ "version": "v2.1.1", "date": "2025-11-20T10:54:07Z" }, - { - "name": "fuma-nama/fumadocs", - "version": "fumadocs-ui@16.0.15", - "date": "2025-11-20T10:52:04Z" - }, { "name": "Athou/commafeed", "version": "5.12.0", @@ -149,16 +224,6 @@ "version": "v1.56.0", "date": "2025-11-20T02:28:35Z" }, - { - "name": "BerriAI/litellm", - "version": "v1.80.0.dev6", - "date": "2025-11-20T02:03:21Z" - }, - { - "name": "steveiliop56/tinyauth", - "version": "v4.0.1", - "date": "2025-10-15T16:53:55Z" - }, { "name": "coder/code-server", "version": "v4.106.2", @@ -199,11 +264,6 @@ "version": "0.51.3", "date": "2025-11-19T15:45:28Z" }, - { - "name": "BookStackApp/BookStack", - "version": "v25.11.2", - "date": "2025-11-19T15:26:29Z" - }, { "name": "grafana/grafana", "version": "v12.3.0", @@ -259,16 +319,6 @@ "version": "v1.0.1-stable", "date": "2025-11-10T16:51:44Z" }, - { - "name": "bunkerity/bunkerweb", - "version": "testing", - "date": "2025-11-17T16:24:26Z" - }, - { - "name": "chrisbenincasa/tunarr", - "version": "v0.23.0-alpha.23", - "date": "2025-11-18T21:36:15Z" - }, { "name": "umami-software/umami", "version": "v3.0.1", @@ -369,11 +419,6 @@ "version": "v0.21.0", "date": "2025-08-23T18:33:53Z" }, - { - "name": "TwiN/gatus", - "version": "v5.32.0", - "date": "2025-11-16T21:08:56Z" - }, { "name": "binwiederhier/ntfy", "version": "v2.15.0", @@ -439,16 +484,6 @@ "version": "v0.16.1", "date": "2025-11-14T22:50:06Z" }, - { - "name": "home-assistant/core", - "version": "2025.11.2", - "date": "2025-11-14T22:10:50Z" - }, - { - "name": "homarr-labs/homarr", - "version": "v1.43.3", - "date": "2025-11-14T19:16:17Z" - }, { "name": "mealie-recipes/mealie", "version": "v3.5.0", @@ -489,11 +524,6 @@ "version": "v1.7.7", "date": "2025-11-13T21:28:44Z" }, - { - "name": "pommee/goaway", - "version": "v0.62.18", - "date": "2025-11-13T19:49:21Z" - }, { "name": "pocketbase/pocketbase", "version": "v0.33.0", @@ -704,11 +734,6 @@ "version": "v0.9.1", "date": "2025-11-06T02:26:53Z" }, - { - "name": "neo4j/neo4j", - "version": "5.26.16", - "date": "2025-11-05T20:41:40Z" - }, { "name": "leiweibau/Pi.Alert", "version": "v2025-11-05", @@ -864,11 +889,6 @@ "version": "v1.11.1", "date": "2025-10-29T22:09:26Z" }, - { - "name": "laurent22/joplin", - "version": "server-v3.4.4", - "date": "2025-09-25T13:19:26Z" - }, { "name": "apache/cassandra", "version": "cassandra-5.0.6", @@ -899,11 +919,6 @@ "version": "v25.8.0", "date": "2025-10-26T14:23:37Z" }, - { - "name": "forgejo/forgejo", - "version": "v13.0.2", - "date": "2025-10-26T06:33:05Z" - }, { "name": "usememos/memos", "version": "v0.25.2", @@ -939,11 +954,6 @@ "version": "v3.2.5-beta", "date": "2025-10-21T16:49:14Z" }, - { - "name": "rclone/rclone", - "version": "v1.71.2", - "date": "2025-10-20T15:25:52Z" - }, { "name": "Part-DB/Part-DB-server", "version": "v2.2.1", @@ -1009,11 +1019,6 @@ "version": "v5.0.85", "date": "2025-10-12T19:55:18Z" }, - { - "name": "gelbphoenix/autocaliweb", - "version": "v0.10.4", - "date": "2025-10-11T19:53:39Z" - }, { "name": "projectsend/projectsend", "version": "r1945", @@ -1314,11 +1319,6 @@ "version": "v1.11.11", "date": "2025-06-18T18:04:50Z" }, - { - "name": "Bubka/2FAuth", - "version": "v5.6.0", - "date": "2025-06-18T12:19:54Z" - }, { "name": "TriliumNext/Notes", "version": "v0.95.0", From afa385d225b008545e6d05143d9d82105bdd3fc5 Mon Sep 17 00:00:00 2001 From: "community-scripts-pr-app[bot]" <189241966+community-scripts-pr-app[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 00:12:30 +0000 Subject: [PATCH 02/33] Update CHANGELOG.md (#9345) Co-authored-by: github-actions[bot] --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a36f2053a..77cefda2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ > [!CAUTION] Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit the project's popularity for potentially malicious purposes. +## 2025-11-22 + ## 2025-11-21 ### πŸš€ Updated Scripts From 6dd01959123cee59875908f6b95f4b8d424f0236 Mon Sep 17 00:00:00 2001 From: Bram <78373894+BramSuurdje@users.noreply.github.com> Date: Sat, 22 Nov 2025 09:10:29 +0100 Subject: [PATCH 03/33] Refactor /data page (#9343) --- frontend/bun.lock | 100 ++- frontend/package.json | 4 +- frontend/src/app/data/page.tsx | 720 ++++++++++++++---- frontend/src/components/application-chart.tsx | 199 ----- frontend/src/components/ui/chart.tsx | 369 +++++++++ frontend/src/components/ui/scroll-area.tsx | 48 ++ 6 files changed, 1053 insertions(+), 387 deletions(-) delete mode 100644 frontend/src/components/application-chart.tsx create mode 100644 frontend/src/components/ui/chart.tsx create mode 100644 frontend/src/components/ui/scroll-area.tsx diff --git a/frontend/bun.lock b/frontend/bun.lock index 33c68e500..560b2a003 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -11,6 +11,7 @@ "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-navigation-menu": "^1.2.5", "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", @@ -26,7 +27,7 @@ "date-fns": "^4.1.0", "framer-motion": "^11.18.2", "fuse.js": "^7.1.0", - "lucide-react": "^0.542.0", + "lucide-react": "^0.554.0", "mini-svg-data-uri": "^1.4.4", "motion": "^12.23.12", "next": "15.5.2", @@ -43,6 +44,7 @@ "react-icons": "^5.5.0", "react-simple-typewriter": "^5.0.1", "react-use-measure": "^2.1.7", + "recharts": "2.15.4", "sharp": "^0.33.5", "simple-icons": "^13.21.0", "sonner": "^1.7.4", @@ -417,6 +419,8 @@ "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q=="], + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.5", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA=="], "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], @@ -515,6 +519,24 @@ "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -765,6 +787,28 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], "data-urls": ["data-urls@5.0.0", "", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" } }, "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="], @@ -783,6 +827,8 @@ "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], + "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], @@ -807,6 +853,8 @@ "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], @@ -939,12 +987,16 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], + "fast-equals": ["fast-equals@5.3.3", "", {}, "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw=="], + "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], @@ -1059,6 +1111,8 @@ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + "is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="], "is-alphanumerical": ["is-alphanumerical@1.0.4", "", { "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A=="], @@ -1191,7 +1245,7 @@ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "lucide-react": ["lucide-react@0.542.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw=="], + "lucide-react": ["lucide-react@0.554.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA=="], "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], @@ -1455,7 +1509,7 @@ "react-icons": ["react-icons@5.5.0", "", { "peerDependencies": { "react": "*" } }, "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw=="], - "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], @@ -1465,16 +1519,24 @@ "react-simple-typewriter": ["react-simple-typewriter@5.0.1", "", { "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-vA5HkABwJKL/DJ4RshSlY/igdr+FiVY4MLsSQYJX6FZG/f1/VwN4y1i3mPXRyfaswrvI8xii1kOVe1dYtO2Row=="], + "react-smooth": ["react-smooth@4.0.4", "", { "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q=="], + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], "react-syntax-highlighter": ["react-syntax-highlighter@15.6.1", "", { "dependencies": { "@babel/runtime": "^7.3.1", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.27.0", "refractor": "^3.6.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg=="], + "react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="], + "react-use-measure": ["react-use-measure@2.1.7", "", { "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" } }, "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg=="], "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "recharts": ["recharts@2.15.4", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw=="], + + "recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="], + "refa": ["refa@0.12.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.8.0" } }, "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g=="], "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], @@ -1625,6 +1687,8 @@ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + "tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], @@ -1693,6 +1757,8 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], + "vite": ["vite@7.1.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx"], "bin": "bin/vite.js" }, "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ=="], "vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" } }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="], @@ -1757,18 +1823,18 @@ "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@eslint/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.15.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA=="], "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + "@radix-ui/react-scroll-area/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + + "@radix-ui/react-scroll-area/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], + "@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "@typescript-eslint/typescript-estree/fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], @@ -1797,24 +1863,18 @@ "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - "eslint-plugin-import/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "eslint-plugin-jsdoc/@es-joy/jsdoccomment": ["@es-joy/jsdoccomment@0.52.0", "", { "dependencies": { "@types/estree": "^1.0.8", "@typescript-eslint/types": "^8.34.1", "comment-parser": "1.4.1", "esquery": "^1.6.0", "jsdoc-type-pratt-parser": "~4.1.0" } }, "sha512-BXuN7BII+8AyNtn57euU2Yxo9yA/KUDNzrpXyi3pfqKmBhhysR6ZWOebFh3vyPoqA3/j1SOvGgucElMGwlXing=="], "eslint-plugin-jsonc/synckit": ["synckit@0.11.8", "", { "dependencies": { "@pkgr/core": "^0.2.4" } }, "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A=="], - "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "eslint-plugin-n/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="], "eslint-plugin-n/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "eslint-plugin-n/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "eslint-plugin-react/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -1851,6 +1911,8 @@ "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], @@ -1873,8 +1935,6 @@ "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": "lib/cli.js" }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], - "w3c-xmlserializer/xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], - "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1883,24 +1943,14 @@ "yaml-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - - "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "eslint-plugin-jsonc/synckit/@pkgr/core": ["@pkgr/core@0.2.7", "", {}, "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg=="], - "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "eslint-plugin-n/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "eslint-plugin-unicorn/@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.13.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw=="], "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], diff --git a/frontend/package.json b/frontend/package.json index ae07382e2..25fde7402 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,6 +23,7 @@ "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-navigation-menu": "^1.2.5", "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", @@ -38,7 +39,7 @@ "date-fns": "^4.1.0", "framer-motion": "^11.18.2", "fuse.js": "^7.1.0", - "lucide-react": "^0.542.0", + "lucide-react": "^0.554.0", "mini-svg-data-uri": "^1.4.4", "motion": "^12.23.12", "next": "15.5.2", @@ -55,6 +56,7 @@ "react-icons": "^5.5.0", "react-simple-typewriter": "^5.0.1", "react-use-measure": "^2.1.7", + "recharts": "2.15.4", "sharp": "^0.33.5", "simple-icons": "^13.21.0", "sonner": "^1.7.4", diff --git a/frontend/src/app/data/page.tsx b/frontend/src/app/data/page.tsx index b0694e3cd..04943c991 100644 --- a/frontend/src/app/data/page.tsx +++ b/frontend/src/app/data/page.tsx @@ -1,9 +1,67 @@ "use client"; -import React, { useEffect, useState } from "react"; -import "react-datepicker/dist/react-datepicker.css"; +import { + ArrowUpDown, + Box, + CheckCircle2, + ChevronLeft, + ChevronRight, + List, + Loader2, + Trophy, + XCircle, +} from "lucide-react"; +import { + Bar, + BarChart, + CartesianGrid, + Cell, + LabelList, + XAxis, +} from "recharts"; +import React, { useEffect, useMemo, useState } from "react"; -import ApplicationChart from "../../components/application-chart"; +import type { ChartConfig } from "@/components/ui/chart"; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { formattedBadge } from "@/components/command-menu"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; type DataModel = { id: number; @@ -30,42 +88,76 @@ type SummaryData = { nsapp_count: Record; }; -const DataFetcher: React.FC = () => { +// Chart colors optimized for both light and dark modes +// Medium-toned colors that are visible and not too flashy in both themes +const CHART_COLORS = [ + "#5B8DEF", // blue - medium tone + "#4ECDC4", // teal - medium tone + "#FF8C42", // orange - medium tone + "#A78BFA", // purple - medium tone + "#F472B6", // pink - medium tone + "#38BDF8", // cyan - medium tone + "#4ADE80", // green - medium tone + "#FBBF24", // yellow - medium tone + "#818CF8", // indigo - medium tone + "#FB7185", // rose - medium tone + "#2DD4BF", // turquoise - medium tone + "#C084FC", // violet - medium tone + "#60A5FA", // sky blue - medium tone + "#84CC16", // lime - medium tone + "#F59E0B", // amber - medium tone + "#A855F7", // purple - medium tone + "#10B981", // emerald - medium tone + "#EAB308", // gold - medium tone + "#3B82F6", // royal blue - medium tone + "#EF4444", // red - medium tone +]; + +const chartConfigApps = { + count: { + label: "Installations", + color: "hsl(var(--chart-1))", + }, +} satisfies ChartConfig; + +export default function DataPage() { const [data, setData] = useState([]); const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(25); - const [sortConfig, setSortConfig] = useState<{ key: string; direction: "ascending" | "descending" } | null>(null); + const [sortConfig, setSortConfig] = useState<{ + key: string; + direction: "ascending" | "descending"; + } | null>(null); + const nf = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }); useEffect(() => { - const fetchSummary = async () => { - try { - const response = await fetch("https://api.htl-braunau.at/data/summary"); - if (!response.ok) - throw new Error(`Failed to fetch summary: ${response.statusText}`); - const result: SummaryData = await response.json(); - setSummary(result); - } - catch (err) { - setError((err as Error).message); - } - }; - - fetchSummary(); - }, []); - - useEffect(() => { - const fetchPaginatedData = async () => { + const fetchData = async () => { setLoading(true); try { - const response = await fetch(`https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage === 0 ? "" : itemsPerPage}`); - if (!response.ok) - throw new Error(`Failed to fetch data: ${response.statusText}`); - const result: DataModel[] = await response.json(); - setData(result); + const [summaryRes, dataRes] = await Promise.all([ + fetch("https://api.htl-braunau.at/data/summary"), + fetch( + `https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage === 0 ? "" : itemsPerPage + }`, + ), + ]); + + if (!summaryRes.ok) { + throw new Error(`Failed to fetch summary: ${summaryRes.statusText}`); + } + if (!dataRes.ok) { + throw new Error(`Failed to fetch data: ${dataRes.statusText}`); + } + + const summaryData: SummaryData = await summaryRes.json(); + const pageData: DataModel[] = await dataRes.json(); + + setSummary(summaryData); + setData(pageData); } catch (err) { setError((err as Error).message); @@ -75,13 +167,13 @@ const DataFetcher: React.FC = () => { } }; - fetchPaginatedData(); + fetchData(); }, [currentPage, itemsPerPage]); - const sortedData = React.useMemo(() => { + const sortedData = useMemo(() => { if (!sortConfig) return data; - const sorted = [...data].sort((a, b) => { + return [...data].sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === "ascending" ? -1 : 1; } @@ -90,23 +182,15 @@ const DataFetcher: React.FC = () => { } return 0; }); - return sorted; }, [data, sortConfig]); - if (loading) - return

Loading...

; - if (error) { - return ( -

- Error: - {error} -

- ); - } - const requestSort = (key: string) => { let direction: "ascending" | "descending" = "ascending"; - if (sortConfig && sortConfig.key === key && sortConfig.direction === "ascending") { + if ( + sortConfig + && sortConfig.key === key + && sortConfig.direction === "ascending" + ) { direction = "descending"; } setSortConfig({ key, direction }); @@ -114,135 +198,447 @@ const DataFetcher: React.FC = () => { const formatDate = (dateString: string): string => { const date = new Date(dateString); - const year = date.getFullYear(); - const month = date.getMonth() + 1; - const day = date.getDate(); - const hours = String(date.getHours()).padStart(2, "0"); - const minutes = String(date.getMinutes()).padStart(2, "0"); - const timezoneOffset = dateString.slice(-6); - return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`; + return new Intl.DateTimeFormat("en-US", { + dateStyle: "medium", + timeStyle: "short", + }).format(date); }; - return ( -
-

Created LXCs

- -

-
-

- {nf.format( - summary?.total_entries ?? 0, - )} - {" "} - results found -

-

- Status Legend: πŸ”„ installing - {" "} - {nf.format(summary?.status_count.installing ?? 0)} - {" "} - | βœ”οΈ completed - {" "} - {nf.format(summary?.status_count.done ?? 0)} - {" "} - | ❌ failed - {" "} - {nf.format(summary?.status_count.failed ?? 0)} - {" "} - | ❓ unknown + const getTypeBadge = (type: string) => { + if (type === "lxc") + return formattedBadge("ct"); + if (type === "vm") + return formattedBadge("vm"); + return null; + }; + + // Stats calculations + const successCount = summary?.status_count.done ?? 0; + const failureCount = summary?.status_count.failed ?? 0; + const totalCount = summary?.total_entries ?? 0; + const successRate = totalCount > 0 ? (successCount / totalCount) * 100 : 0; + + const allApps = useMemo(() => { + if (!summary?.nsapp_count) + return []; + return Object.entries(summary.nsapp_count).sort(([, a], [, b]) => b - a); + }, [summary]); + + const topApps = useMemo(() => { + return allApps.slice(0, 15); + }, [allApps]); + + const mostPopularApp = topApps[0]; + + // Chart Data + const appsChartData = topApps.map(([name, count], index) => ({ + app: name, + count, + fill: CHART_COLORS[index % CHART_COLORS.length], + })); + + if (error) { + return ( +

+

+ Error loading data: + {error}

-
-
- - - - - - - - - - - - - - - - - - - {sortedData.map((item, index) => ( - -
requestSort("status")}>Status requestSort("type")}>Type requestSort("nsapp")}>Application requestSort("os_type")}>OS requestSort("os_version")}>OS Version requestSort("disk_size")}>Disk Size requestSort("core_count")}>Core Count requestSort("ram_size")}>RAM Size requestSort("method")}>Method requestSort("pve_version")}>PVE Version requestSort("error")}>Error Message requestSort("created_at")}>Created At
- {item.status === "done" + ); + } + + return ( +
+
+
+ {/* Header */} +
+

Analytics

+

+ Overview of container installations and system statistics. +

+
+ + {/* Widgets */} +
+ + + Total Created + + + +
{nf.format(totalCount)}
+

+ Total LXC/VM entries found +

+
+
+ + + + Success Rate + + + +
+ {successRate.toFixed(1)} + % +
+

+ {nf.format(successCount)} + {" "} + successful installations +

+
+
+ + + + Failures + + + +
{nf.format(failureCount)}
+

+ Installations encountered errors +

+
+
+ + + + Most Popular + + + +
+ {mostPopularApp ? mostPopularApp[0] : "N/A"} +
+

+ {mostPopularApp ? nf.format(mostPopularApp[1]) : 0} + {" "} + installations +

+
+
+
+ + {/* Graphs */} + + +
+ Top Applications + + The most frequently installed applications. + +
+ + + + + + + Application Statistics + + Installation counts for all + {" "} + {allApps.length} + {" "} + applications. + + + +
+ {allApps.map(([name, count], index) => ( +
+
+ + {index + 1} + . + + {name} +
+ {nf.format(count)} +
+ ))} +
+
+
+
+
+ +
+ {loading + ? ( +
+ +
+ ) + : ( + + + + (value.length > 8 ? `${value.slice(0, 8)}...` : value)} + /> + } + /> + + {appsChartData.map((entry, index) => ( + + ))} + + + + + )} +
+
+
+ + {/* Data Table */} + + +
+ Installation Log + + Detailed records of all container creation attempts. + +
+
+ +
+
+ +
+ + + + requestSort("status")} + > + Status + {sortConfig?.key === "status" && ( + + )} + + requestSort("type")} + > + Type + {sortConfig?.key === "type" && ( + + )} + + requestSort("nsapp")} + > + Application + {sortConfig?.key === "nsapp" && ( + + )} + + requestSort("os_type")} + > + OS + {sortConfig?.key === "os_type" && ( + + )} + + requestSort("disk_size")} + > + Disk Size + {sortConfig?.key === "disk_size" && ( + + )} + + requestSort("core_count")} + > + Core Count + {sortConfig?.key === "core_count" && ( + + )} + + requestSort("ram_size")} + > + RAM Size + {sortConfig?.key === "ram_size" && ( + + )} + + requestSort("created_at")} + > + Created At + {sortConfig?.key === "created_at" && ( + + )} + + + + + {loading ? ( - "βœ”οΈ" + + +
+ + {" "} + Loading data... +
+
+
) - : item.status === "failed" + : sortedData.length > 0 ? ( - "❌" - ) - : item.status === "installing" - ? ( - "πŸ”„" - ) - : ( - item.status - )} - -
- - - - - - - - - - - - ))} - -
- {item.type === "lxc" - ? ( - "πŸ“¦" - ) - : item.type === "vm" - ? ( - "πŸ–₯️" + sortedData.map((item, idx) => ( + + + {item.status === "done" + ? ( + + Success + + ) + : item.status === "failed" + ? ( + + Failed + + ) + : item.status === "installing" + ? ( + + Installing + + ) + : ( + + {item.status} + + )} + + + {getTypeBadge(item.type) || ( + + {item.type} + + )} + + + {item.nsapp} + + + {item.os_type} + {" "} + {item.os_version} + + + {item.disk_size} + MB + + + {item.core_count} + + + {item.ram_size} + MB + + + {formatDate(item.created_at)} + + + )) ) : ( - item.type + + + No results found. + + )} - {item.nsapp}{item.os_type}{item.os_version}{item.disk_size}{item.core_count}{item.ram_size}{item.method}{item.pve_version}{item.error}{formatDate(item.created_at)}
+ +
+
+ +
+ +
+ Page + {" "} + {currentPage} +
+ +
+ +
-
- - - Page - {currentPage} - - - -
); -}; - -export default DataFetcher; +} diff --git a/frontend/src/components/application-chart.tsx b/frontend/src/components/application-chart.tsx deleted file mode 100644 index 0311754af..000000000 --- a/frontend/src/components/application-chart.tsx +++ /dev/null @@ -1,199 +0,0 @@ -"use client"; - -import { ArcElement, Chart as ChartJS, Tooltip as ChartTooltip, Legend } from "chart.js"; -import ChartDataLabels from "chartjs-plugin-datalabels"; -import { BarChart3, PieChart } from "lucide-react"; -import React, { useState } from "react"; -import { Pie } from "react-chartjs-2"; - -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Button } from "@/components/ui/button"; - -ChartJS.register(ArcElement, ChartTooltip, Legend, ChartDataLabels); - -type SummaryData = { - nsapp_count: Record; -}; - -type ApplicationChartProps = { - data: SummaryData | null; -}; - -const ITEMS_PER_PAGE = 20; -const CHART_COLORS = [ - "#ff6384", - "#36a2eb", - "#ffce56", - "#4bc0c0", - "#9966ff", - "#ff9f40", - "#4dc9f6", - "#f67019", - "#537bc4", - "#acc236", - "#166a8f", - "#00a950", - "#58595b", - "#8549ba", -]; - -export default function ApplicationChart({ data }: ApplicationChartProps) { - const [isChartOpen, setIsChartOpen] = useState(false); - const [isTableOpen, setIsTableOpen] = useState(false); - const [chartStartIndex, setChartStartIndex] = useState(0); - const [tableLimit, setTableLimit] = useState(ITEMS_PER_PAGE); - - if (!data) - return null; - - const sortedApps = Object.entries(data.nsapp_count) - .sort(([, a], [, b]) => b - a); - - const chartApps = sortedApps.slice( - chartStartIndex, - chartStartIndex + ITEMS_PER_PAGE, - ); - - const chartData = { - labels: chartApps.map(([name]) => name), - datasets: [ - { - data: chartApps.map(([, count]) => count), - backgroundColor: CHART_COLORS, - }, - ], - }; - - const chartOptions = { - plugins: { - legend: { display: false }, - datalabels: { - color: "white", - font: { weight: "bold" as const }, - formatter: (value: number, context: any) => { - const label = context.chart.data.labels?.[context.dataIndex]; - return `${label}\n(${value})`; - }, - }, - }, - responsive: true, - maintainAspectRatio: false, - }; - - return ( -
- - - - - - Open Chart View - - - - - - - Open Table View - - - - - - - Applications Distribution - -
- -
-
- - -
-
-
- - - - - Applications Count - -
- - - - Application - Count - - - - {sortedApps.slice(0, tableLimit).map(([name, count]) => ( - - {name} - {count} - - ))} - -
-
- {tableLimit < sortedApps.length && ( - - )} -
-
-
- ); -} diff --git a/frontend/src/components/ui/chart.tsx b/frontend/src/components/ui/chart.tsx new file mode 100644 index 000000000..f7b762f55 --- /dev/null +++ b/frontend/src/components/ui/chart.tsx @@ -0,0 +1,369 @@ +"use client" + +import * as React from "react" +import * as RechartsPrimitive from "recharts" + +import { cn } from "@/lib/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode + icon?: React.ComponentType + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ) +} + +type ChartContextProps = { + config: ChartConfig +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"] + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + +
+ + + {children} + +
+
+ ) +}) +ChartContainer.displayName = "Chart" + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([, config]) => config.theme || config.color + ) + + if (!colorConfig.length) { + return null + } + + return ( +