From 1af64eccd90ea78005be53d7c674634bf83b808b Mon Sep 17 00:00:00 2001 From: dushixiang Date: Sun, 9 Aug 2026 22:45:43 +0800 Subject: [PATCH] refactor: simplify theme system to filesystem-only with self-contained portal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主题系统从带数据库/事务/repo 层/远程下载/配置管理/主题市场的重型架构, 彻底简化为纯文件系统操作: 后端: - 主题数据全部存放在 data/themes/ 目录,不经过数据库 - 删除主题配置功能(managed/raw/redirect schema、配置存取 API、 ThemeConfiguration model、theme_configurations 表、repo 层) - 删除远程下载(URL 安装、GitHub Release 更新、SSRF 防护、SHA-256) - 删除版本范围检查(semver 解析/比较/匹配、pikaVersion 校验) - 删除主题市场 - ThemeService 不再持有 db,合并为 theme.go + theme_util.go 两个文件 - 主题类型从 models 包移到 service 包(非数据库模型) - 保留:ZIP 上传解压(含目录穿越防护)、目录扫描列表、启用/删除、 覆盖安装、默认主题回退、启动恢复、入口 HTML 渲染缓存 前端 portal(官方默认主题): - 从 47 个碎片文件精简到 15 个,按标准目录组织 - 删除 theme-sdk 包,API 客户端和类型内联回 portal,完全自包含 - 按职责拆分:types.ts / api.ts / constants.ts / lib/utils.ts / contexts/ColorMode.tsx / hooks/index.ts / components.tsx / pages/ - 每个页面一个文件,图表和子组件内联,fork 二开只需看一个文件 --- .github/workflows/build-test-ghcr.yml | 11 - .github/workflows/docker-publish.yml | 11 - Dockerfile | 1 - Makefile | 9 +- config.postgresql.yaml | 8 +- config.sqlite.yaml | 9 +- docker-compose.postgresql.yml | 2 +- docs/theme-guide.md | 135 + docs/theme-system-refactor.md | 301 ++ internal/app.go | 69 +- internal/assets/paths.go | 2 +- internal/assets/ui.go | 81 - internal/assets/ui_test.go | 75 - internal/config/config.go | 6 + internal/handler/property_handler.go | 8 - internal/handler/theme_handler.go | 88 + internal/handler/web_handler.go | 216 + internal/handler/web_handler_test.go | 127 + internal/service/property_service.go | 28 + internal/service/theme.go | 590 +++ internal/service/theme_service_test.go | 321 ++ internal/service/theme_util.go | 265 ++ internal/wire.go | 6 + internal/wire_gen.go | 12 + web/README.md | 90 +- web/{ => admin}/components.json | 0 web/admin/eslint.config.js | 14 + web/admin/index.html | 13 + web/{ => admin}/package-lock.json | 2471 ++--------- web/{ => admin}/package.json | 19 +- web/{ => admin}/src/App.css | 0 web/admin/src/App.tsx | 15 + web/{ => admin}/src/admin/assets/github.svg | 0 web/{ => admin}/src/admin/assets/os/apple.png | Bin web/{ => admin}/src/admin/assets/os/linux.png | Bin web/{ => admin}/src/admin/assets/os/win11.png | Bin web/{ => admin}/src/admin/assets/react.svg | 0 .../src/admin/components/PageHeader.tsx | 0 .../src/admin/components/PrivateRoute.tsx | 2 +- web/{ => admin}/src/admin/components/index.ts | 0 .../src/admin/pages/AdminLayout/index.tsx | 6 +- .../src/admin/pages/Agents/AgentAudit.tsx | 0 .../src/admin/pages/Agents/AgentBasicInfo.tsx | 0 .../src/admin/pages/Agents/AgentDetail.tsx | 0 .../src/admin/pages/Agents/AgentEditModal.tsx | 0 .../admin/pages/Agents/AgentInstallManual.tsx | 0 .../pages/Agents/AgentInstallOneClick.tsx | 0 .../admin/pages/Agents/AgentInstallShared.tsx | 0 .../src/admin/pages/Agents/AgentList.tsx | 0 .../admin/pages/Agents/AuditResultView.tsx | 0 .../pages/Agents/BatchSSHLoginConfigModal.tsx | 0 .../src/admin/pages/Agents/BatchTagsModal.tsx | 0 .../Agents/BatchTamperProtectionModal.tsx | 0 .../pages/Agents/BatchVisibilityModal.tsx | 0 .../src/admin/pages/Agents/SSHLoginConfig.tsx | 0 .../src/admin/pages/Agents/SSHLoginEvents.tsx | 0 .../admin/pages/Agents/SSHLoginMonitor.tsx | 0 .../admin/pages/Agents/TamperProtection.tsx | 0 .../pages/Agents/TamperProtectionConfig.tsx | 0 .../pages/Agents/TamperProtectionEvents.tsx | 0 .../src/admin/pages/Agents/TrafficStats.tsx | 0 .../pages/Agents/useAgentInstallConfig.ts | 0 .../src/admin/pages/AlertRecords/index.tsx | 0 .../src/admin/pages/ApiKeys/ApiKeyList.tsx | 0 .../src/admin/pages/ApiKeys/ApiKeyModal.tsx | 0 .../admin/pages/ApiKeys/ShowApiKeyModal.tsx | 0 .../src/admin/pages/DDNS/DDNSModal.tsx | 0 .../src/admin/pages/DDNS/DNSProviderModal.tsx | 0 .../src/admin/pages/DDNS/RecordsDrawer.tsx | 0 .../src/admin/pages/DDNS/index.tsx | 0 .../src/admin/pages/Login/GitHubCallback.tsx | 4 +- .../src/admin/pages/Login/OIDCCallback.tsx | 6 +- .../src/admin/pages/Login/index.tsx | 6 +- .../pages/ManageApiKeys/ManageApiKeyList.tsx | 0 .../pages/ManageApiKeys/ManageApiKeyModal.tsx | 0 .../ManageApiKeys/ShowManageApiKeyModal.tsx | 0 .../src/admin/pages/Monitors/MonitorList.tsx | 0 .../src/admin/pages/Monitors/MonitorModal.tsx | 0 .../admin/pages/Settings/AlertSettings.tsx | 0 .../pages/Settings/NotificationChannels.tsx | 0 .../pages/Settings/NotificationCustomHelp.tsx | 0 .../admin/pages/Settings/PublicIPConfig.tsx | 0 .../src/admin/pages/Settings/SystemConfig.tsx | 18 +- .../admin/pages/Settings/ThemeSettings.tsx | 112 + .../src/admin/pages/Settings/index.tsx | 13 +- web/{ => admin}/src/api/agent.ts | 0 web/{ => admin}/src/api/alert.ts | 0 web/{ => admin}/src/api/apiKey.ts | 0 web/{ => admin}/src/api/auth.ts | 0 web/{ => admin}/src/api/ddns.ts | 0 web/{ => admin}/src/api/dnsProvider.ts | 0 web/{ => admin}/src/api/manageApiKey.ts | 0 web/{ => admin}/src/api/monitor.ts | 0 web/{ => admin}/src/api/property.ts | 0 web/{ => admin}/src/api/request.ts | 2 +- web/{ => admin}/src/api/tamper.ts | 0 web/admin/src/api/theme.ts | 17 + web/{ => admin}/src/api/version.ts | 0 web/admin/src/bootstrap.tsx | 39 + web/{ => admin}/src/components/ui/button.tsx | 0 .../src/components/ui/scroll-area.tsx | 0 web/admin/src/global.d.ts | 30 + web/{ => admin}/src/index.css | 0 web/{ => admin}/src/lib/format.ts | 0 web/{ => admin}/src/lib/strings.ts | 0 web/{ => admin}/src/lib/utils.ts | 0 web/admin/src/main.tsx | 9 + web/admin/src/router/admin.tsx | 49 + .../src/shared/contexts/ColorModeContext.tsx | 56 + web/{ => admin}/src/types/ddns.ts | 0 web/{ => admin}/src/types/index.ts | 0 web/{ => admin}/tsconfig.app.json | 3 - web/{ => admin}/tsconfig.json | 3 - web/{ => admin}/tsconfig.node.json | 0 web/admin/vite.config.ts | 23 + web/index.html | 31 - web/portal/eslint.config.js | 14 + web/portal/index.html | 15 + web/portal/package-lock.json | 3618 +++++++++++++++++ web/portal/package.json | 36 + web/portal/pika-theme.json | 14 + web/{ => portal}/public/logo.png | Bin web/portal/src/App.tsx | 28 + web/portal/src/api.ts | 76 + web/portal/src/components.tsx | 768 ++++ web/portal/src/constants.ts | 49 + web/portal/src/contexts/ColorMode.tsx | 55 + web/portal/src/hooks/index.ts | 128 + web/portal/src/index.css | 169 + web/portal/src/lib/utils.ts | 71 + web/portal/src/main.tsx | 51 + web/portal/src/pages/MonitorDetail.tsx | 795 ++++ web/portal/src/pages/MonitorList.tsx | 431 ++ .../src}/pages/PublicLayout.tsx | 3 +- web/portal/src/pages/ServerDetail.tsx | 2057 ++++++++++ .../src}/pages/ServerList.tsx | 399 +- web/portal/src/types.ts | 255 ++ web/portal/tsconfig.app.json | 28 + web/portal/tsconfig.json | 18 + web/portal/tsconfig.node.json | 18 + web/portal/vite.config.ts | 20 + web/portal/yarn.lock | 1384 +++++++ web/scripts/assemble-web.mjs | 16 + web/scripts/verify-dist.mjs | 32 + web/src/App.tsx | 25 - web/src/global.d.ts | 13 - web/src/main.tsx | 22 - web/src/portal/components/Card.tsx | 41 - .../portal/components/ChartPlaceholder.tsx | 36 - .../portal/components/CompactResourceBar.tsx | 91 - web/src/portal/components/CustomTooltip.tsx | 70 - web/src/portal/components/CyberCard.tsx | 39 - web/src/portal/components/EmptyState.tsx | 34 - web/src/portal/components/LittleStatCard.tsx | 19 - web/src/portal/components/LoadingSpinner.tsx | 24 - web/src/portal/components/NetworkStatCard.tsx | 56 - web/src/portal/components/PublicFooter.tsx | 54 - web/src/portal/components/PublicHeader.tsx | 263 -- web/src/portal/components/ServerCard.tsx | 265 -- web/src/portal/components/StatBlock.tsx | 56 - web/src/portal/components/StatusBadge.tsx | 38 - .../portal/components/TimeRangeSelector.tsx | 126 - .../components/monitor/AgentStatsTable.tsx | 197 - .../portal/components/monitor/CertBadge.tsx | 27 - .../portal/components/monitor/MiniChart.tsx | 71 - .../portal/components/monitor/MonitorCard.tsx | 166 - .../portal/components/monitor/MonitorHero.tsx | 87 - .../components/monitor/ResponseTimeChart.tsx | 453 --- .../portal/components/monitor/TypeIcon.tsx | 21 - .../components/server/ChartContainer.tsx | 30 - web/src/portal/components/server/CpuChart.tsx | 119 - .../portal/components/server/DiskIOChart.tsx | 165 - web/src/portal/components/server/GpuChart.tsx | 151 - .../components/server/GpuMonitorSection.tsx | 92 - web/src/portal/components/server/InfoGrid.tsx | 16 - .../portal/components/server/MemoryChart.tsx | 119 - .../portal/components/server/MonitorChart.tsx | 479 --- .../server/NetworkAddressSection.tsx | 86 - .../portal/components/server/NetworkChart.tsx | 212 - .../server/NetworkConnectionChart.tsx | 172 - .../server/NetworkConnectionSection.tsx | 61 - .../portal/components/server/ServerHero.tsx | 87 - .../portal/components/server/SnapshotGrid.tsx | 74 - .../components/server/SnapshotSection.tsx | 20 - .../components/server/SystemInfoSection.tsx | 174 - .../components/server/TemperatureChart.tsx | 160 - .../server/TemperatureMonitorSection.tsx | 37 - web/src/portal/constants/colors.ts | 15 - web/src/portal/constants/server.ts | 43 - web/src/portal/constants/time.ts | 30 - web/src/portal/contexts/ThemeContext.tsx | 87 - web/src/portal/hooks/server.ts | 86 - web/src/portal/hooks/use-mobile.ts | 19 - web/src/portal/hooks/useLiveBuffer.ts | 74 - web/src/portal/pages/MonitorDetail.tsx | 79 - web/src/portal/pages/MonitorList.tsx | 191 - web/src/portal/pages/ServerDetail.tsx | 163 - web/src/portal/utils/server.ts | 4 - web/src/router/index.tsx | 134 - web/vite.config.ts | 27 - web/yarn.lock | 2733 ------------- 201 files changed, 13471 insertions(+), 10742 deletions(-) create mode 100644 docs/theme-guide.md create mode 100644 docs/theme-system-refactor.md delete mode 100644 internal/assets/ui.go delete mode 100644 internal/assets/ui_test.go create mode 100644 internal/handler/theme_handler.go create mode 100644 internal/handler/web_handler.go create mode 100644 internal/handler/web_handler_test.go create mode 100644 internal/service/theme.go create mode 100644 internal/service/theme_service_test.go create mode 100644 internal/service/theme_util.go rename web/{ => admin}/components.json (100%) create mode 100644 web/admin/eslint.config.js create mode 100644 web/admin/index.html rename web/{ => admin}/package-lock.json (61%) rename web/{ => admin}/package.json (68%) rename web/{ => admin}/src/App.css (100%) create mode 100644 web/admin/src/App.tsx rename web/{ => admin}/src/admin/assets/github.svg (100%) rename web/{ => admin}/src/admin/assets/os/apple.png (100%) rename web/{ => admin}/src/admin/assets/os/linux.png (100%) rename web/{ => admin}/src/admin/assets/os/win11.png (100%) rename web/{ => admin}/src/admin/assets/react.svg (100%) rename web/{ => admin}/src/admin/components/PageHeader.tsx (100%) rename web/{ => admin}/src/admin/components/PrivateRoute.tsx (91%) rename web/{ => admin}/src/admin/components/index.ts (100%) rename web/{ => admin}/src/admin/pages/AdminLayout/index.tsx (98%) rename web/{ => admin}/src/admin/pages/Agents/AgentAudit.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/AgentBasicInfo.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/AgentDetail.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/AgentEditModal.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/AgentInstallManual.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/AgentInstallOneClick.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/AgentInstallShared.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/AgentList.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/AuditResultView.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/BatchSSHLoginConfigModal.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/BatchTagsModal.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/BatchTamperProtectionModal.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/BatchVisibilityModal.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/SSHLoginConfig.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/SSHLoginEvents.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/SSHLoginMonitor.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/TamperProtection.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/TamperProtectionConfig.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/TamperProtectionEvents.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/TrafficStats.tsx (100%) rename web/{ => admin}/src/admin/pages/Agents/useAgentInstallConfig.ts (100%) rename web/{ => admin}/src/admin/pages/AlertRecords/index.tsx (100%) rename web/{ => admin}/src/admin/pages/ApiKeys/ApiKeyList.tsx (100%) rename web/{ => admin}/src/admin/pages/ApiKeys/ApiKeyModal.tsx (100%) rename web/{ => admin}/src/admin/pages/ApiKeys/ShowApiKeyModal.tsx (100%) rename web/{ => admin}/src/admin/pages/DDNS/DDNSModal.tsx (100%) rename web/{ => admin}/src/admin/pages/DDNS/DNSProviderModal.tsx (100%) rename web/{ => admin}/src/admin/pages/DDNS/RecordsDrawer.tsx (100%) rename web/{ => admin}/src/admin/pages/DDNS/index.tsx (100%) rename web/{ => admin}/src/admin/pages/Login/GitHubCallback.tsx (95%) rename web/{ => admin}/src/admin/pages/Login/OIDCCallback.tsx (94%) rename web/{ => admin}/src/admin/pages/Login/index.tsx (98%) rename web/{ => admin}/src/admin/pages/ManageApiKeys/ManageApiKeyList.tsx (100%) rename web/{ => admin}/src/admin/pages/ManageApiKeys/ManageApiKeyModal.tsx (100%) rename web/{ => admin}/src/admin/pages/ManageApiKeys/ShowManageApiKeyModal.tsx (100%) rename web/{ => admin}/src/admin/pages/Monitors/MonitorList.tsx (100%) rename web/{ => admin}/src/admin/pages/Monitors/MonitorModal.tsx (100%) rename web/{ => admin}/src/admin/pages/Settings/AlertSettings.tsx (100%) rename web/{ => admin}/src/admin/pages/Settings/NotificationChannels.tsx (100%) rename web/{ => admin}/src/admin/pages/Settings/NotificationCustomHelp.tsx (100%) rename web/{ => admin}/src/admin/pages/Settings/PublicIPConfig.tsx (100%) rename web/{ => admin}/src/admin/pages/Settings/SystemConfig.tsx (95%) create mode 100644 web/admin/src/admin/pages/Settings/ThemeSettings.tsx rename web/{ => admin}/src/admin/pages/Settings/index.tsx (86%) rename web/{ => admin}/src/api/agent.ts (100%) rename web/{ => admin}/src/api/alert.ts (100%) rename web/{ => admin}/src/api/apiKey.ts (100%) rename web/{ => admin}/src/api/auth.ts (100%) rename web/{ => admin}/src/api/ddns.ts (100%) rename web/{ => admin}/src/api/dnsProvider.ts (100%) rename web/{ => admin}/src/api/manageApiKey.ts (100%) rename web/{ => admin}/src/api/monitor.ts (100%) rename web/{ => admin}/src/api/property.ts (100%) rename web/{ => admin}/src/api/request.ts (98%) rename web/{ => admin}/src/api/tamper.ts (100%) create mode 100644 web/admin/src/api/theme.ts rename web/{ => admin}/src/api/version.ts (100%) create mode 100644 web/admin/src/bootstrap.tsx rename web/{ => admin}/src/components/ui/button.tsx (100%) rename web/{ => admin}/src/components/ui/scroll-area.tsx (100%) create mode 100644 web/admin/src/global.d.ts rename web/{ => admin}/src/index.css (100%) rename web/{ => admin}/src/lib/format.ts (100%) rename web/{ => admin}/src/lib/strings.ts (100%) rename web/{ => admin}/src/lib/utils.ts (100%) create mode 100644 web/admin/src/main.tsx create mode 100644 web/admin/src/router/admin.tsx create mode 100644 web/admin/src/shared/contexts/ColorModeContext.tsx rename web/{ => admin}/src/types/ddns.ts (100%) rename web/{ => admin}/src/types/index.ts (100%) rename web/{ => admin}/tsconfig.app.json (92%) rename web/{ => admin}/tsconfig.json (83%) rename web/{ => admin}/tsconfig.node.json (100%) create mode 100644 web/admin/vite.config.ts delete mode 100644 web/index.html create mode 100644 web/portal/eslint.config.js create mode 100644 web/portal/index.html create mode 100644 web/portal/package-lock.json create mode 100644 web/portal/package.json create mode 100644 web/portal/pika-theme.json rename web/{ => portal}/public/logo.png (100%) create mode 100644 web/portal/src/App.tsx create mode 100644 web/portal/src/api.ts create mode 100644 web/portal/src/components.tsx create mode 100644 web/portal/src/constants.ts create mode 100644 web/portal/src/contexts/ColorMode.tsx create mode 100644 web/portal/src/hooks/index.ts create mode 100644 web/portal/src/index.css create mode 100644 web/portal/src/lib/utils.ts create mode 100644 web/portal/src/main.tsx create mode 100644 web/portal/src/pages/MonitorDetail.tsx create mode 100644 web/portal/src/pages/MonitorList.tsx rename web/{src/portal => portal/src}/pages/PublicLayout.tsx (93%) create mode 100644 web/portal/src/pages/ServerDetail.tsx rename web/{src/portal => portal/src}/pages/ServerList.tsx (58%) create mode 100644 web/portal/src/types.ts create mode 100644 web/portal/tsconfig.app.json create mode 100644 web/portal/tsconfig.json create mode 100644 web/portal/tsconfig.node.json create mode 100644 web/portal/vite.config.ts create mode 100644 web/portal/yarn.lock create mode 100644 web/scripts/assemble-web.mjs create mode 100644 web/scripts/verify-dist.mjs delete mode 100644 web/src/App.tsx delete mode 100644 web/src/global.d.ts delete mode 100644 web/src/main.tsx delete mode 100644 web/src/portal/components/Card.tsx delete mode 100644 web/src/portal/components/ChartPlaceholder.tsx delete mode 100644 web/src/portal/components/CompactResourceBar.tsx delete mode 100644 web/src/portal/components/CustomTooltip.tsx delete mode 100644 web/src/portal/components/CyberCard.tsx delete mode 100644 web/src/portal/components/EmptyState.tsx delete mode 100644 web/src/portal/components/LittleStatCard.tsx delete mode 100644 web/src/portal/components/LoadingSpinner.tsx delete mode 100644 web/src/portal/components/NetworkStatCard.tsx delete mode 100644 web/src/portal/components/PublicFooter.tsx delete mode 100644 web/src/portal/components/PublicHeader.tsx delete mode 100644 web/src/portal/components/ServerCard.tsx delete mode 100644 web/src/portal/components/StatBlock.tsx delete mode 100644 web/src/portal/components/StatusBadge.tsx delete mode 100644 web/src/portal/components/TimeRangeSelector.tsx delete mode 100644 web/src/portal/components/monitor/AgentStatsTable.tsx delete mode 100644 web/src/portal/components/monitor/CertBadge.tsx delete mode 100644 web/src/portal/components/monitor/MiniChart.tsx delete mode 100644 web/src/portal/components/monitor/MonitorCard.tsx delete mode 100644 web/src/portal/components/monitor/MonitorHero.tsx delete mode 100644 web/src/portal/components/monitor/ResponseTimeChart.tsx delete mode 100644 web/src/portal/components/monitor/TypeIcon.tsx delete mode 100644 web/src/portal/components/server/ChartContainer.tsx delete mode 100644 web/src/portal/components/server/CpuChart.tsx delete mode 100644 web/src/portal/components/server/DiskIOChart.tsx delete mode 100644 web/src/portal/components/server/GpuChart.tsx delete mode 100644 web/src/portal/components/server/GpuMonitorSection.tsx delete mode 100644 web/src/portal/components/server/InfoGrid.tsx delete mode 100644 web/src/portal/components/server/MemoryChart.tsx delete mode 100644 web/src/portal/components/server/MonitorChart.tsx delete mode 100644 web/src/portal/components/server/NetworkAddressSection.tsx delete mode 100644 web/src/portal/components/server/NetworkChart.tsx delete mode 100644 web/src/portal/components/server/NetworkConnectionChart.tsx delete mode 100644 web/src/portal/components/server/NetworkConnectionSection.tsx delete mode 100644 web/src/portal/components/server/ServerHero.tsx delete mode 100644 web/src/portal/components/server/SnapshotGrid.tsx delete mode 100644 web/src/portal/components/server/SnapshotSection.tsx delete mode 100644 web/src/portal/components/server/SystemInfoSection.tsx delete mode 100644 web/src/portal/components/server/TemperatureChart.tsx delete mode 100644 web/src/portal/components/server/TemperatureMonitorSection.tsx delete mode 100644 web/src/portal/constants/colors.ts delete mode 100644 web/src/portal/constants/server.ts delete mode 100644 web/src/portal/constants/time.ts delete mode 100644 web/src/portal/contexts/ThemeContext.tsx delete mode 100644 web/src/portal/hooks/server.ts delete mode 100644 web/src/portal/hooks/use-mobile.ts delete mode 100644 web/src/portal/hooks/useLiveBuffer.ts delete mode 100644 web/src/portal/pages/MonitorDetail.tsx delete mode 100644 web/src/portal/pages/MonitorList.tsx delete mode 100644 web/src/portal/pages/ServerDetail.tsx delete mode 100644 web/src/portal/utils/server.ts delete mode 100644 web/src/router/index.tsx delete mode 100644 web/vite.config.ts delete mode 100644 web/yarn.lock diff --git a/.github/workflows/build-test-ghcr.yml b/.github/workflows/build-test-ghcr.yml index c3e9921..b22b14f 100644 --- a/.github/workflows/build-test-ghcr.yml +++ b/.github/workflows/build-test-ghcr.yml @@ -48,17 +48,6 @@ jobs: make build-agents make build-servers - # 验证 index.html 模板代码 - - name: 验证 index.html 模板代码 - run: | - if grep -Fq "__PIKA_CUSTOM_JS__" web/dist/index.html && grep -Fq "__PIKA_CUSTOM_CSS__" web/dist/index.html && grep -Fq "[[.SystemNameZh]]" web/dist/index.html; then - cp web/dist/index.html web/dist/index.html.tmpl - echo "✓ index.html 模板已准备完成" - else - echo "✗ index.html 模板代码未找到" - exit 1 - fi - # 列出编译产物 - name: 列出编译产物 run: | diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index cc7148e..fe87d4a 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -54,17 +54,6 @@ jobs: run: | make build-release - # 验证 index.html 模板代码 - - name: 验证 index.html 模板代码 - run: | - if grep -Fq "__PIKA_CUSTOM_JS__" web/dist/index.html && grep -Fq "__PIKA_CUSTOM_CSS__" web/dist/index.html && grep -Fq "[[.SystemNameZh]]" web/dist/index.html; then - cp web/dist/index.html web/dist/index.html.tmpl - echo "✓ index.html 模板已准备完成" - else - echo "✗ index.html 模板代码未找到" - exit 1 - fi - # 列出编译产物 - name: 列出编译产物 run: | diff --git a/Dockerfile b/Dockerfile index ba2a49a..c343803 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,6 @@ ARG TARGETARCH COPY ./bin/pika-linux-${TARGETARCH} ./pika COPY ./bin/agents ./bin/agents COPY ./web/dist ./web/dist -COPY ./web/public/logo.png ./web/public/logo.png # 暴露端口 EXPOSE 8080 diff --git a/Makefile b/Makefile index 1ea2d6f..8b1817b 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,12 @@ GOFLAGS=CGO_ENABLED=0 # 构建前端 build-web: - cd web && yarn && yarn build + npm ci --prefix web/admin + npm run build --prefix web/admin + npm ci --prefix web/portal + npm run build --prefix web/portal + node web/scripts/assemble-web.mjs + node web/scripts/verify-dist.mjs # 构建服务端(开发) build-server: @@ -72,6 +77,7 @@ build-release: clean: rm -rf bin/* rm -rf web/dist + rm -rf web/admin/dist web/portal/dist # 运行测试 test: @@ -80,7 +86,6 @@ test: # 代码格式化 fmt: go fmt ./... - cd web && yarn format # 代码检查 lint: diff --git a/config.postgresql.yaml b/config.postgresql.yaml index 67cb8c2..a348387 100644 --- a/config.postgresql.yaml +++ b/config.postgresql.yaml @@ -19,6 +19,10 @@ server: - "0.0.0.0/0" App: + # 主题文件存储在数据库之外,Docker 部署必须持久化 /app/data。 + Theme: + Dir: "./data/themes" + JWT: Secret: "" # 替换为任意 UUID 字符串 ExpiresHours: 168 # 7天 @@ -35,7 +39,7 @@ App: Issuer: "https://your-oidc-provider.com" ClientID: "your-client-id" ClientSecret: "your-client-secret" - RedirectURL: "http://localhost:8080/oidc/callback" # 前端回调页面 + RedirectURL: "http://localhost:8080/admin/oidc/callback" # 前端回调页面 # GitHub OAuth 认证配置(可选) # 创建 GitHub OAuth App: https://github.com/settings/developers @@ -43,7 +47,7 @@ App: Enabled: false ClientID: "your-github-client-id" ClientSecret: "your-github-client-secret" - RedirectURL: "http://localhost:8080/github/callback" # 前端回调页面 + RedirectURL: "http://localhost:8080/admin/github/callback" # 前端回调页面 AllowedUsers: # 允许登录的 GitHub 用户名白名单,不配置或留空则允许所有用户 - "your-github-username" - "another-username" diff --git a/config.sqlite.yaml b/config.sqlite.yaml index b3db900..480a455 100644 --- a/config.sqlite.yaml +++ b/config.sqlite.yaml @@ -15,6 +15,10 @@ server: - "0.0.0.0/0" App: + # 可安装公开主题。PIKA_THEME_DIR 非空时会覆盖 Dir。 + Theme: + Dir: "./data/themes" + JWT: Secret: "" # 替换为任意 UUID 字符串 ExpiresHours: 168 # 7天 @@ -31,7 +35,7 @@ App: Issuer: "https://your-oidc-provider.com" ClientID: "your-client-id" ClientSecret: "your-client-secret" - RedirectURL: "http://localhost:8080/oidc/callback" # 前端回调页面 + RedirectURL: "http://localhost:8080/admin/oidc/callback" # 前端回调页面 # GitHub OAuth 认证配置(可选) # 创建 GitHub OAuth App: https://github.com/settings/developers @@ -39,7 +43,7 @@ App: Enabled: false ClientID: "your-github-client-id" ClientSecret: "your-github-client-secret" - RedirectURL: "http://localhost:8080/github/callback" # 前端回调页面 + RedirectURL: "http://localhost:8080/admin/github/callback" # 前端回调页面 AllowedUsers: # 允许登录的 GitHub 用户名白名单,不配置或留空则允许所有用户 - "your-github-username" - "another-username" @@ -52,4 +56,3 @@ App: RetentionDays: 7 # 数据保留时长 WriteTimeout: 60 # 写超时时间(秒) QueryTimeout: 60 # 读超时时间(秒) - diff --git a/docker-compose.postgresql.yml b/docker-compose.postgresql.yml index 47268b9..4f5a41e 100644 --- a/docker-compose.postgresql.yml +++ b/docker-compose.postgresql.yml @@ -37,6 +37,7 @@ services: container_name: pika-server volumes: - ./config.yaml:/app/config.yaml + - ./data/pika:/app/data # 开启 Geo 配置后打开下面的配置 # - ./GeoLite2-City.mmdb:/app/GeoLite2-City.mmdb ports: @@ -53,4 +54,3 @@ services: networks: pika-network: driver: bridge - diff --git a/docs/theme-guide.md b/docs/theme-guide.md new file mode 100644 index 0000000..e4657a1 --- /dev/null +++ b/docs/theme-guide.md @@ -0,0 +1,135 @@ +# Pika 主题使用与开发指南 + +Pika 的第三方主题是一个完整的公开页面 SPA。它可以替换服务器列表、服务器详情、监控列表和监控详情,但不能替换登录页、OAuth/OIDC 回调或管理后台。 + +## 管理员使用 + +进入“管理后台 → 系统设置 → 主题管理”后,可以: + +- 上传本地 ZIP 安装主题; +- 查看预览、兼容状态、作者和版本; +- 启用、配置或删除主题。 + +默认主题 `default` 随 Pika 构建发布,不允许覆盖或删除。当前主题目录、清单或入口损坏时,Pika 会整体回退到默认主题并修正活动主题配置。当前启用的第三方主题必须先切换到其他主题才能删除。 + +主题切换不会刷新管理后台。打开或刷新公开页面即可看到新主题。系统名称、自定义 CSS/JS 等公开配置保存后同样在公开页面刷新时生效。 + +### 服务端配置 + +```yaml +App: + Theme: + Dir: "./data/themes" +``` + +- `PIKA_THEME_DIR` 非空时覆盖 `Dir`; +- SQLite 与 PostgreSQL Docker 部署都必须持久化 `/app/data`。 + +## 安全边界 + +第三方主题包含 JavaScript,并与 Pika 在同一 origin 执行。它是“受信任插件”,不是 CSS 皮肤,也不是安全沙箱。主题可以读取浏览器同源存储并访问同源 API,因此只应安装完全信任、能审查源码和发布流程的主题。 + +Pika 会校验 ZIP 路径、符号链接、文件数量、解压大小、清单、入口和预览图,但这些检查不能证明主题 JavaScript 没有恶意行为。 + +登录页和管理后台始终从官方 `/admin/assets/*` 加载;第三方主题只从 `/theme-assets/*` 加载。管理员设置的 `customCSS` 和 `customJS` 也只注入公开主题入口。 + +## 主题包格式 + +ZIP 根目录必须直接包含以下内容,不能额外套一层目录: + +```text +pika-theme.json +preview.png +dist/ +├── index.html +└── assets/ + ├── index-.js + └── index-.css +``` + +`dist/index.html` 是固定入口。所有构建资源必须使用 `/theme-assets/` 绝对前缀,并建议使用内容 hash 文件名。 + +Vite 示例: + +```ts +import {defineConfig} from 'vite'; + +export default defineConfig(({command}) => ({ + base: command === 'serve' ? '/' : '/theme-assets/', + build: { + outDir: 'dist', + assetsDir: 'assets', + }, +})); +``` + +入口 HTML 建议保留注入点: + +```html + + + + + + +
+ + +``` + +服务端会在响应入口时注入 `window.PikaRuntime`,并设置站点标题、description 和 `/api/logo` favicon。没有注入标记时,会在 `` 和 `` 前注入。 + +## 清单示例 + +```json +{ + "schemaVersion": 1, + "id": "pika-minimal", + "name": "Pika Minimal", + "description": "简洁状态页", + "version": "1.2.0", + "author": "example", + "homepage": "https://github.com/example/pika-minimal", + "license": "MIT", + "preview": "preview.png", + "entry": "dist/index.html", + "apiVersion": "v1", + "capabilities": [ + "server-list", + "server-detail", + "monitor-list", + "monitor-detail" + ] +} +``` + +`id` 只能包含字母、数字、下划线和连字符,最长 64 字符;`default`、`admin`、`official`、`system`、`api`、`assets` 和 `theme-assets` 是保留名称。四项核心 capability 缺一时主题不可启用。 + +## 公开 API 与 SDK + +主题启动时可读取: + +```ts +window.PikaRuntime +``` + +也可以请求: + +```http +GET /api/config +``` + +该接口只返回公开字段,不返回 Logo Base64、自定义 CSS/JS 原文、JWT/OAuth Secret、API Key、通知或 DNS 凭据。 + +稳定 TypeScript 契约位于 `web/theme-sdk`。官方默认主题 `web/portal` 也使用同一个 SDK,通过 `pika` 客户端取得公开数据,通过 `getRuntimeConfig()` 读取注入配置。第三方主题不得引用 `web/admin` 或 `web/portal` 源码,也不应复制管理 API 或自行维护第二套请求封装。站内链接使用以 `/` 开头的绝对路径,以保证直接刷新 SPA 路由时仍由 Pika 正确回退到主题入口。 + +## 打包前检查 + +发布 ZIP 前至少确认: + +1. `pika-theme.json` 位于 ZIP 根目录,ID 与主题一致; +2. `dist/index.html` 和预览图片存在; +3. JS/CSS 引用 `/theme-assets/` 且文件名包含内容 hash; +4. `/`、`/servers/:id`、`/monitors`、`/monitors/:id` 可直接刷新; +5. 不依赖管理后台源码或未公开 API; +6. 在 light、dark、system 和移动宽度下测试。 diff --git a/docs/theme-system-refactor.md b/docs/theme-system-refactor.md new file mode 100644 index 0000000..658b348 --- /dev/null +++ b/docs/theme-system-refactor.md @@ -0,0 +1,301 @@ +# Pika 可安装主题系统 + +Pika 的「主题」是一套可安装的公开页面 SPA。它可以替换公开首页、服务器详情、监控列表和监控详情,但**不能**替换登录页、OAuth/OIDC 回调或管理后台。 + +本文描述当前已落地的实现形态,是代码改动的验收基线。 + +## 1. 设计取舍与安全定位 + +### 1.1 当前形态:单 origin,无主题市场 + +Pika 公开主题和管理后台运行在**同一 origin**,由单个 Pika 进程通过路由前缀区分: + +```text +/api/*、/ws/* → Pika Go API / WebSocket +/admin/* → 官方管理 SPA(web/dist/admin),不允许主题覆盖 +/admin/assets/* → 官方管理静态资源 +/theme-assets/* → 当前公开主题静态资源 +其他公开 GET 路径 → 当前公开主题入口(运行时渲染注入) +``` + +主题来源**只有一种**:管理员本地上传 ZIP。Pika 不从远程地址下载主题,也不提供主题市场。 + +**没有主题市场、没有远程下载、没有自动更新**。曾考虑过可配置的远程主题索引(市场)和 URL/GitHub Release 安装,但由于下面的安全约束,这些能力在单 origin 模型下会引入不可接受的攻击面,因此全部移除。 + +### 1.2 同源执行风险(必须理解的前提) + +第三方主题包含 JavaScript,并在 Pika 公开站点的**同一 origin** 执行。它本质上是「受信任插件」,不是无权限皮肤,也不是安全沙箱: + +- 主题可以读取浏览器同源存储(包括 localStorage 中的登录 token); +- 主题可以带着浏览器自动附加的凭证发起同源 API 请求; +- 即使把登录态从 localStorage 换成 HttpOnly Cookie,HttpOnly 只阻止「JS 读取 Cookie 值」,**不阻止**「JS 发起带凭证的认证请求」。 + +Pika 是单用户系统,登录 token 即全站最高权限。因此**恶意主题 = 管理后台被完全接管**,进而等于所有被监控服务器的信息暴露。 + +正因如此,当前的安全策略是: + +1. **不提供公开主题市场,不做远程下载**,避免「任何人都能投毒主题」的入口; +2. 主题来源限于「管理员本地上传 ZIP」,安全责任收敛到「管理员是否信任该来源」; +3. 安装、启用时强制展示「主题是同源可信代码、非沙箱」的警告并二次确认; +4. 管理后台、登录页、认证回调**始终**从官方 `/admin/assets/*` 加载,第三方主题只从 `/theme-assets/*` 加载。 + +### 1.3 未来若要开放无审核市场的前置条件 + +如果未来确实需要开放「任何人可提交」的主题市场,**必须先完成 origin 隔离**,例如: + +```text +status.example.com # 公开主题(低信任) +admin.example.com # 管理后台 + 认证(高信任) +``` + +不同 origin 后,公开主题的 JS 物理上无法读取 admin origin 的存储或发起带凭证的 admin API 请求。 + +实现方式推荐「单进程 + Host 分流」(仍是单个 Pika 二进制,按 Host 头选择返回 admin SPA 还是公开主题),而非双进程。当前代码的主题管理逻辑(安装/ZIP 安全/清单校验/渲染缓存/文件系统回滚)完全不关心 origin,origin 隔离只需要调整路由层,绝大多数代码可原样复用。 + +**在完成 origin 隔离之前,不应重新引入主题市场。** + +## 2. 目录结构 + +### 2.1 源码目录 + +```text +web/ +├── admin/ # 独立的官方管理 React/Vite 项目(base: /admin/) +├── portal/ # 独立的官方默认主题 React/Vite 项目(生产 base: /theme-assets/) +├── theme-sdk/ # 第三方主题开发契约和 TypeScript 类型 +└── scripts/ # 组装并校验服务端 Web 产物 +``` + +`admin`、`portal`、`theme-sdk` 分别维护依赖、锁文件和构建产物,根 `web` 不是 npm workspace。admin 与 portal 不共享源码,也不能相互 import;admin 不依赖 theme-sdk,portal 通过 `file:../theme-sdk` 依赖 theme-sdk。第三方主题只能依赖 `theme-sdk`。 + +### 2.2 构建产物 + +```text +web/dist/ +├── admin/ +│ ├── index.html +│ └── assets/ +└── default-theme/ + ├── pika-theme.json + ├── preview.png(或 dist/logo.png) + └── dist/ + ├── index.html + └── assets/ +``` + +`web/scripts/assemble-web.mjs` 把 admin 和 portal 各自的构建产物组装到上述结构;`web/scripts/verify-dist.mjs` 校验 admin 的 HTML 含 `/admin/assets/` 且不含 `/theme-assets/`,portal 的 HTML 含 `/theme-assets/` 且不含 `/admin/assets/`——这是防止主题与后台资源前缀串台的硬护栏。 + +### 2.3 运行时安装目录 + +```text +data/themes/ +├── pika-minimal/ +│ ├── pika-theme.json +│ ├── preview.png +│ └── dist/ +└── ... +``` + +`default` 主题不复制到 `data/themes`,始终从 `web/dist/default-theme` 读取,且不可删除或覆盖。 + +## 3. 后端模块结构 + +主题后端逻辑按职责拆分在 `internal/service/` 下,共享状态集中在 `ThemeService` 结构体: + +```text +internal/service/ +├── theme_service.go # 常量/错误/正则、ThemeService 结构体、构造函数 +├── theme_manager.go # 列表/安装/启用/删除/活动主题回退 +├── theme_manifest.go # 清单解析与字段校验 +├── theme_archive.go # ZIP 安全解压 +├── theme_filesystem.go # 默认主题访问、路径校验、启动恢复 +├── theme_renderer.go # 入口 HTML 运行时注入与缓存 +└── theme_service_test.go # 上述能力的单元测试 + +internal/models/theme.go # 清单与外观的数据模型 +internal/handler/theme_handler.go # 主题管理 HTTP handler +internal/handler/web_handler.go # 公开配置 / 静态资源 / SPA fallback +``` + +主题数据全部存放在文件系统,**不经过数据库**。`ThemeService` 不持有 `*gorm.DB`,所有操作(列表/安装/启用/删除)都是对 `data/themes` 目录的扫描与文件操作。唯一需要持久化的运行时状态是「当前启用哪个主题」,通过 `appearance_config` Property 存储。 + +## 4. 主题包协议 + +### 4.1 ZIP 结构 + +ZIP 根目录必须直接包含清单,不允许额外套一层目录: + +```text +pika-minimal.zip +├── pika-theme.json +├── preview.png +└── dist/ + ├── index.html + └── assets/ +``` + +### 4.2 `pika-theme.json` + +```json +{ + "schemaVersion": 1, + "id": "pika-minimal", + "name": "Pika Minimal", + "description": "简约服务器状态主题", + "version": "1.2.0", + "author": "example", + "homepage": "https://github.com/example/pika-minimal", + "license": "MIT", + "preview": "preview.png", + "entry": "dist/index.html", + "apiVersion": "v1", + "capabilities": ["server-list", "server-detail", "monitor-list", "monitor-detail"] +} +``` + +### 4.3 字段约束 + +- `schemaVersion`:必须为 `1`。 +- `id`:正则 `^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$`,保留名 `default`/`admin`/`official`/`system`/`api`/`assets`/`theme-assets`,大小写不敏感。 +- `version`:主题版本号,仅作展示。 +- `entry`:必须是 `dist/index.html`。 +- `apiVersion`:必须为 `v1`。 +- `capabilities`:四项核心能力 `server-list`/`server-detail`/`monitor-list`/`monitor-detail` 缺一不可。 +- `preview`:包内相对路径,最大 5 MiB。 + +## 5. 数据模型 + +主题数据全部存放在文件系统,不使用数据库表。`pika-theme.json` 是主题元数据的唯一事实来源,运行时直接扫描目录读取。唯一需要持久化的是「当前启用哪个主题」: + +```go +const PropertyIDAppearanceConfig = "appearance_config" + +type AppearanceConfig struct { + ActiveTheme string `json:"activeTheme"` // 默认 "default" + DefaultColorMode string `json:"defaultColorMode"` // 默认 "system" +} +``` + +浏览器用户的个人明暗偏好保存在浏览器本地;`defaultColorMode` 只在用户没有个人偏好时生效。 + +## 6. HTTP API + +### 6.1 公开配置 + +```http +GET /api/config +``` + +只返回公开字段(系统名称、Logo 路径、ICP、版本、默认视图、默认明暗模式、当前主题 id/version、功能开关)。不返回 Logo Base64 原文、customJS/customCSS 原文、JWT/OAuth Secret、API Key、通知渠道、DNS 凭据。 + +### 6.2 管理接口(均在 `/api/admin/themes` 下,需管理员认证) + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | `/` | 列出所有主题(含默认) | +| POST | `/upload` | 上传 ZIP 安装(multipart `file`) | +| PUT | `/:id/activate` | 启用主题(重新校验清单/入口/兼容性) | +| DELETE | `/:id` | 删除主题(default 和当前启用主题不可删) | +| GET | `/:id/preview` | 读取预览图 | + +上传新主题时 id 已存在返回 `409`;当前启用主题不可删除返回 `409`;默认主题不可删除/修改返回 `400`。 + +## 7. 静态资源与 SPA 路由 + +路由优先级(由具体前缀到通配): + +1. `/api/*`、`/ws/*`:API 与 WebSocket。 +2. `/admin/assets/*`:官方管理静态资源,禁止目录浏览/穿越/HTML5 fallback。 +3. `/theme-assets/*`:当前主题 `dist/*`,缺失文件返回 404,不逐文件回退默认主题。 +4. `/admin`、`/admin/*`:官方管理 SPA,返回管理 `index.html`(含登录与认证回调)。 +5. 公开 GET 请求:路径对应主题真实文件则返回文件;带扩展名但文件不存在返回 404;其余返回渲染后的当前主题 `index.html`;当前主题入口损坏则整体回退默认主题。 +6. 非 GET 或未知 `/api/*`:JSON 404,绝不返回 SPA HTML。 + +全局静态中间件已删除,改为显式 handler。官方和主题静态文件统一设置 `X-Content-Type-Options: nosniff` 和 `Referrer-Policy: strict-origin-when-cross-origin`;HTML 入口 `Cache-Control: no-cache`,hash 资源 `immutable`。 + +## 8. 入口 HTML 运行时渲染 + +服务端读取主题 `index.html`,在内存注入 `window.PikaRuntime`(只含公开配置),并设置 ``、`<meta description>`、`<link icon>`。主题可在 HTML 中放 `<!-- pika:head -->`/`<!-- pika:body -->` 标记控制注入点,否则注入到 `</head>`/`</body>` 前。`customCSS`/`customJS` 单独注入到公开入口(不注入管理入口)。渲染结果按 `theme id + version + index 内容 + SystemConfig/Appearance UpdatedAt + pika version` 缓存,主题切换后主动清缓存。 + +HTML 转义使用 `html/template` 和 `encoding/json`,禁止字符串拼接生成 `<script>` 内 JSON。 + +## 9. 安装、更新与回滚 + +### 9.1 安装流程 + +```text +接收 ZIP → 写入 staging → 校验大小/文件数 → 安全解压 → 校验清单/入口/预览 → 检查 id 冲突 → rename 到正式目录 → 健康检查 → 清理 staging +``` + +任一步失败删除 staging,不触碰已安装主题。 + +### 9.2 覆盖安装 + +上传的 ZIP 与已安装主题 id 相同时,走覆盖安装(`installArchiveLocked` 的 replace 模式): + +```text +取写锁 → 校验且新包 id 与目标一致 → 原目录 rename 为 .backup-* → 新目录 rename 为正式 → 健康检查 → 删 backup +``` + +健康检查失败时移走新目录、backup rename 回正式、继续用旧主题、记录日志。 + +### 9.3 启动恢复 + +进程可能在原子替换期间被杀,启动时执行:清理 24h 以上的 staging;唯一有效 `.backup-*` 恢复;正式与备份并存时以校验通过的正式为准;状态不明则保留全部并告警,活动主题回退默认。 + +### 9.4 活动主题回退 + +`appearance_config.activeTheme` 为空、目录缺失、清单损坏、入口缺失时,自动回退默认主题并修正配置(用 `fallbackMu` 串行化,避免每个请求都写库)。 + +## 10. ZIP 安全 + +### 10.1 硬上限(代码内固定,不可配置关闭) + +```text +压缩包最大 64 MiB | 最大文件数 5000 | 单文件最大 64 MiB | 解压总大小 256 MiB | 清单 1 MiB | 预览图 5 MiB +``` + +### 10.2 路径与文件类型防御 + +ZIP Slip(`../`)、绝对路径、Windows 盘符、反斜杠、符号链接、硬链接、设备文件、命名管道、大小写重复、Unicode 归一化重复、清单路径越界、文件数/总大小溢出。只解压普通文件和目录,权限归一化为目录 0755 / 文件 0644。 + +## 11. 前端构建 + +- `web/admin`:`base: '/admin/'`,独立 React + antd 项目,自带 HTTP 客户端和 `ColorModeContext`,不依赖 theme-sdk。 +- `web/portal`:`base: 生产 '/theme-assets/' / 开发 '/'`,默认主题,所有数据访问经 `@pika-monitor/theme-sdk` 的 `pika` 客户端。 +- `web/theme-sdk`:导出 `pika` 客户端、`PikaRuntimeConfig`/`PikaThemeManifest` 等类型、`getRuntimeConfig`/`resolveColorMode` 工具。源码即产物(`tsc --noEmit` 仅类型检查)。 + +`make build-web` 依次构建 theme-sdk(类型检查)→ admin → portal,再 assemble + verify。 + +## 12. 管理后台交互 + +「系统设置 → 主题管理」包含: + +- 当前主题信息、已安装主题卡片(预览图、名称、版本、作者、兼容状态); +- 启用、删除; +- 上传 ZIP; +- 所有安装/启用统一经「主题是同源可信代码、非沙箱」的红色二次确认 Modal; +- 页面顶部常驻安全提示。 + +按钮约束:当前主题不显示删除;默认主题不显示删除;不兼容主题可查看可删除但不能启用;操作期间禁止重复提交;启用成功不刷新管理 SPA,只提示公开页面已切换。 + +## 13. 配置 + +```yaml +App: + Theme: + Dir: "./data/themes" +``` + +- `PIKA_THEME_DIR` 非空时覆盖 `Dir`; +- SQLite 与 PostgreSQL Docker 部署都必须持久化 `/app/data`。 + +## 14. 测试覆盖 + +Go 单元测试覆盖:ZIP 安全(slip/绝对路径/盘符/symlink/大小写·Unicode 重复/文件数超限)、清单校验(保留 id/非法 schema·api/缺 capability)、HTML 元数据转义、文件系统恢复、完整安装/启用/删除生命周期。 + +## 15. 不做的事(明确边界) + +- 不提供主题市场、不做远程下载、不做自动更新(见 §1.1); +- 不做 origin 隔离(当前单进程单 origin;未来开放市场的前置条件,见 §1.3); +- 不改前端 admin/portal/theme-sdk 的项目结构和构建。 diff --git a/internal/app.go b/internal/app.go index 4fd24df..0d1776b 100644 --- a/internal/app.go +++ b/internal/app.go @@ -2,14 +2,10 @@ package internal import ( "context" - "fmt" "log" "net/http" - "os" - "strings" "time" - "github.com/dushixiang/pika/internal/assets" "github.com/dushixiang/pika/internal/config" "github.com/dushixiang/pika/internal/handler" "github.com/dushixiang/pika/internal/migrate" @@ -124,36 +120,6 @@ func setupApi(app *orz.App, components *AppComponents) error { e.Use(middleware.Recover()) e.Use(ErrorHandler(logger)) - webDir := assets.WebDir() - if err := assets.RenderUIFilesInDir(webDir, components.PropertyService); err != nil { - return fmt.Errorf("render ui files: %w", err) - } - - // 静态文件服务 - e.Use(middleware.StaticWithConfig(middleware.StaticConfig{ - Skipper: func(c *echo.Context) bool { - // 不处理接口 - if strings.HasPrefix(c.Request().RequestURI, "/api") { - return true - } - // 不处理WebSocket - if strings.HasPrefix(c.Request().RequestURI, "/ws") { - return true - } - // 不暴露模板文件 - if strings.HasSuffix(c.Request().RequestURI, ".tmpl") { - return true - } - return false - }, - Root: ".", - Index: "index.html", - HTML5: true, - Browse: false, - IgnoreBase: false, - Filesystem: os.DirFS(webDir), - })) - customValidator := CustomValidator{Validator: validator.New()} if err := customValidator.TransInit(); err != nil { logger.Fatal("failed to init custom validator", zap.Error(err)) @@ -168,6 +134,7 @@ func setupApi(app *orz.App, components *AppComponents) error { publicApi.GET("/auth/config", components.AccountHandler.GetAuthConfig) publicApi.GET("/auth/oidc/url", components.AccountHandler.GetOIDCAuthURL) publicApi.GET("/auth/github/url", components.AccountHandler.GetGitHubAuthURL) + publicApi.GET("/config", components.WebHandler.PublicConfig) // Agent 版本和下载(完全公开,无需任何认证) publicApi.GET("/agent/version", components.AgentHandler.GetAgentVersion) @@ -293,6 +260,13 @@ func setupApi(app *orz.App, components *AppComponents) error { adminApi.POST("/ddns/:id/disable", components.DDNSHandler.Disable) adminApi.GET("/ddns/:id/records", components.DDNSHandler.GetRecords) adminApi.POST("/ddns/:id/trigger", components.DDNSHandler.TriggerUpdate) + + // 可安装主题管理 + adminApi.GET("/themes", components.ThemeHandler.List) + adminApi.POST("/themes/upload", components.ThemeHandler.Upload) + adminApi.PUT("/themes/:id/activate", components.ThemeHandler.Activate) + adminApi.DELETE("/themes/:id", components.ThemeHandler.Delete) + adminApi.GET("/themes/:id/preview", components.ThemeHandler.Preview) } // OIDC 认证路由(如果启用) @@ -301,23 +275,28 @@ func setupApi(app *orz.App, components *AppComponents) error { // GitHub 认证路由(如果启用) publicApi.POST("/auth/github/callback", components.AccountHandler.GitHubLogin) + // 官方管理 SPA 与活动公开主题使用互不重叠的资源前缀。 + e.GET("/admin/assets/*", components.WebHandler.ServeAdminAsset) + e.GET("/theme-assets/*", components.WebHandler.ServeThemeAsset) + e.GET("/*", components.WebHandler.ServeSPA) + return nil } func autoMigrate(database *gorm.DB) error { // 自动迁移数据库表 return database.AutoMigrate( - &models.Agent{}, // 探针 - &models.ApiKey{}, // ApiKey - &models.AuditResult{}, // 审计历史 - &models.Property{}, // 系统属性 - &models.AlertRecord{}, // 告警记录 - &models.AlertState{}, // 告警状态 - &models.MonitorTask{}, // 服务监控 - &models.TamperEvent{}, // 防篡改事件 - &models.DDNSConfig{}, // DDNS 配置 - &models.DDNSRecord{}, // DDNS 记录 - &models.SSHLoginEvent{}, // SSH 登录事件 + &models.Agent{}, // 探针 + &models.ApiKey{}, // ApiKey + &models.AuditResult{}, // 审计历史 + &models.Property{}, // 系统属性 + &models.AlertRecord{}, // 告警记录 + &models.AlertState{}, // 告警状态 + &models.MonitorTask{}, // 服务监控 + &models.TamperEvent{}, // 防篡改事件 + &models.DDNSConfig{}, // DDNS 配置 + &models.DDNSRecord{}, // DDNS 记录 + &models.SSHLoginEvent{}, // SSH 登录事件 ) } diff --git a/internal/assets/paths.go b/internal/assets/paths.go index f5ca31e..6de898c 100644 --- a/internal/assets/paths.go +++ b/internal/assets/paths.go @@ -9,7 +9,7 @@ import ( const ( defaultWebDir = "web/dist" defaultAgentDir = "bin/agents" - defaultLogoPath = "web/public/logo.png" + defaultLogoPath = "web/portal/public/logo.png" ) func WebDir() string { diff --git a/internal/assets/ui.go b/internal/assets/ui.go deleted file mode 100644 index cfc237e..0000000 --- a/internal/assets/ui.go +++ /dev/null @@ -1,81 +0,0 @@ -package assets - -import ( - "context" - "os" - "path/filepath" - "strings" - "text/template" - - "github.com/dushixiang/pika/internal/models" -) - -type SystemConfigProvider interface { - GetSystemConfig(context.Context) (*models.SystemConfig, error) -} - -func RenderUIFiles(provider SystemConfigProvider) error { - return RenderUIFilesInDir(WebDir(), provider) -} - -func RenderUIFilesInDir(dir string, provider SystemConfigProvider) error { - if err := ensureIndexTemplate(dir); err != nil { - return err - } - - systemConfig, err := provider.GetSystemConfig(context.Background()) - if err != nil { - return err - } - - return renderIndexHTML(dir, systemConfig) -} - -func ensureIndexTemplate(dir string) error { - tmplPath := filepath.Join(dir, "index.html.tmpl") - if _, err := os.Stat(tmplPath); err == nil { - return nil - } else if !os.IsNotExist(err) { - return err - } - - indexPath := filepath.Join(dir, "index.html") - src, err := os.ReadFile(indexPath) - if err != nil { - return err - } - - return os.WriteFile(tmplPath, src, 0644) -} - -func renderIndexHTML(dir string, systemConfig *models.SystemConfig) error { - tmplPath := filepath.Join(dir, "index.html.tmpl") - tmpl, err := template.New(filepath.Base(tmplPath)).Delims("[[", "]]").ParseFiles(tmplPath) - if err != nil { - return err - } - - data := struct { - *models.SystemConfig - }{ - SystemConfig: systemConfig, - } - - outPath := filepath.Join(dir, "index.html") - var rendered strings.Builder - if err := tmpl.Execute(&rendered, data); err != nil { - return err - } - - html := strings.ReplaceAll(rendered.String(), "/*__PIKA_CUSTOM_JS__*/", systemConfig.CustomJS) - html = strings.ReplaceAll(html, "/*__PIKA_CUSTOM_CSS__*/", systemConfig.CustomCSS) - - out, err := os.Create(outPath) - if err != nil { - return err - } - defer out.Close() - - _, err = out.WriteString(html) - return err -} diff --git a/internal/assets/ui_test.go b/internal/assets/ui_test.go deleted file mode 100644 index 3564279..0000000 --- a/internal/assets/ui_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package assets - -import ( - "context" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/dushixiang/pika/internal/models" -) - -type testSystemConfigProvider struct { - config *models.SystemConfig -} - -func (p testSystemConfigProvider) GetSystemConfig(context.Context) (*models.SystemConfig, error) { - return p.config, nil -} - -func TestRenderUIFilesInDir(t *testing.T) { - dir := t.TempDir() - indexPath := filepath.Join(dir, "index.html") - src := `<!doctype html> -<title>[[.SystemNameZh]][[if and .SystemNameZh .SystemNameEn]] | [[end]][[.SystemNameEn]] - - -` - if err := os.WriteFile(indexPath, []byte(src), 0644); err != nil { - t.Fatal(err) - } - - provider := testSystemConfigProvider{ - config: &models.SystemConfig{ - SystemNameZh: "皮卡监控", - SystemNameEn: "Pika Monitor", - ICPCode: "ICP-1", - DefaultView: "grid", - CustomJS: `console.log("pika");`, - CustomCSS: `body { color: red; }`, - Version: "v1.2.3", - }, - } - if err := RenderUIFilesInDir(dir, provider); err != nil { - t.Fatal(err) - } - - rendered, err := os.ReadFile(indexPath) - if err != nil { - t.Fatal(err) - } - html := string(rendered) - for _, want := range []string{ - "皮卡监控 | Pika Monitor", - `SystemNameZh: "皮卡监控"`, - `Version: "v1.2.3"`, - `console.log("pika");`, - `body { color: red; }`, - } { - if !strings.Contains(html, want) { - t.Fatalf("rendered index.html missing %q:\n%s", want, html) - } - } - if _, err := os.Stat(filepath.Join(dir, "index.html.tmpl")); err != nil { - t.Fatal(err) - } -} diff --git a/internal/config/config.go b/internal/config/config.go index 06f99d9..4601a51 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,6 +8,12 @@ type AppConfig struct { GitHub *GitHubOAuthConfig `json:"GitHub"` // GitHub OAuth配置(可选) GeoIP *GeoIPConfig `json:"GeoIP"` // GeoIP配置(可选) VictoriaMetrics *VMConfig `json:"VictoriaMetrics"` // VictoriaMetrics配置(可选) + Theme *ThemeConfig `json:"Theme"` // 主题系统配置(可选) +} + +// ThemeConfig 可安装主题系统配置。 +type ThemeConfig struct { + Dir string `json:"Dir"` } // JWTConfig JWT配置 diff --git a/internal/handler/property_handler.go b/internal/handler/property_handler.go index f9afbef..1f26f64 100644 --- a/internal/handler/property_handler.go +++ b/internal/handler/property_handler.go @@ -5,7 +5,6 @@ import ( "encoding/json" "net/http" - "github.com/dushixiang/pika/internal/assets" "github.com/dushixiang/pika/internal/models" "github.com/dushixiang/pika/internal/service" "github.com/go-orz/orz" @@ -89,13 +88,6 @@ func (h *PropertyHandler) SetProperty(c *echo.Context) error { h.logger.Error("设置属性失败", zap.String("id", id), zap.Error(err)) return orz.NewError(500, "设置属性失败") } - if id == service.PropertyIDSystemConfig { - if err := assets.RenderUIFiles(h.service); err != nil { - h.logger.Error("渲染前端模板失败", zap.String("id", id), zap.Error(err)) - return orz.NewError(500, "渲染前端模板失败") - } - } - return orz.Ok(c, orz.Map{}) } diff --git a/internal/handler/theme_handler.go b/internal/handler/theme_handler.go new file mode 100644 index 0000000..615840d --- /dev/null +++ b/internal/handler/theme_handler.go @@ -0,0 +1,88 @@ +package handler + +import ( + "errors" + "net/http" + + "github.com/dushixiang/pika/internal/service" + "github.com/go-orz/orz" + "github.com/labstack/echo/v5" + "go.uber.org/zap" +) + +type ThemeHandler struct { + logger *zap.Logger + service *service.ThemeService +} + +func NewThemeHandler(logger *zap.Logger, themeService *service.ThemeService) *ThemeHandler { + return &ThemeHandler{logger: logger, service: themeService} +} + +func (h *ThemeHandler) List(c *echo.Context) error { + items, err := h.service.List(c.Request().Context()) + if err != nil { + return err + } + return orz.Ok(c, items) +} + +func (h *ThemeHandler) Upload(c *echo.Context) error { + fileHeader, err := c.FormFile("file") + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "请选择主题 ZIP 文件") + } + if fileHeader.Size <= 0 || fileHeader.Size > 64<<20 { + return echo.NewHTTPError(http.StatusBadRequest, "主题 ZIP 为空或超过 64 MiB") + } + file, err := fileHeader.Open() + if err != nil { + return err + } + defer file.Close() + result, err := h.service.Install(c.Request().Context(), file) + if err != nil { + return themeHTTPError(err) + } + return orz.Ok(c, result) +} + +func (h *ThemeHandler) Activate(c *echo.Context) error { + if err := h.service.Activate(c.Request().Context(), c.Param("id")); err != nil { + return themeHTTPError(err) + } + return orz.Ok(c, orz.Map{}) +} + +func (h *ThemeHandler) Delete(c *echo.Context) error { + if err := h.service.Delete(c.Request().Context(), c.Param("id")); err != nil { + return themeHTTPError(err) + } + return orz.Ok(c, orz.Map{}) +} + +func (h *ThemeHandler) Preview(c *echo.Context) error { + path, contentType, err := h.service.PreviewPath(c.Request().Context(), c.Param("id")) + if err != nil { + return themeHTTPError(err) + } + if contentType != "" { + c.Response().Header().Set("Content-Type", contentType) + } + c.Response().Header().Set("Cache-Control", "private, max-age=300") + c.Response().Header().Set("X-Content-Type-Options", "nosniff") + return serveResolvedStaticFile(c, path, false) +} + +func themeHTTPError(err error) error { + switch { + case errors.Is(err, service.ErrThemeNotFound): + return echo.NewHTTPError(http.StatusNotFound, err.Error()) + case errors.Is(err, service.ErrThemeExists), errors.Is(err, service.ErrThemeActive): + return echo.NewHTTPError(http.StatusConflict, err.Error()) + case errors.Is(err, service.ErrDefaultTheme): + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + default: + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } +} diff --git a/internal/handler/web_handler.go b/internal/handler/web_handler.go new file mode 100644 index 0000000..fa5bc47 --- /dev/null +++ b/internal/handler/web_handler.go @@ -0,0 +1,216 @@ +package handler + +import ( + "mime" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/dushixiang/pika/internal/assets" + "github.com/dushixiang/pika/internal/service" + "github.com/dushixiang/pika/pkg/version" + "github.com/go-orz/orz" + "github.com/labstack/echo/v5" +) + +type WebHandler struct { + themeService *service.ThemeService + propertyService *service.PropertyService +} + +func NewWebHandler(themeService *service.ThemeService, propertyService *service.PropertyService) *WebHandler { + return &WebHandler{themeService: themeService, propertyService: propertyService} +} + +func (h *WebHandler) PublicConfig(c *echo.Context) error { + runtime, err := h.runtimeConfig(c) + if err != nil { + return err + } + return orz.Ok(c, runtime) +} + +func (h *WebHandler) ServeAdminAsset(c *echo.Context) error { + return serveStaticFile(c, filepath.Join(assets.WebDir(), "admin", "assets"), c.Param("*"), true) +} + +func (h *WebHandler) ServeThemeAsset(c *echo.Context) error { + _, dist, err := h.themeService.ActiveDistDir(c.Request().Context()) + if err != nil { + return err + } + return serveStaticFile(c, dist, c.Param("*"), true) +} + +func (h *WebHandler) ServeSPA(c *echo.Context) error { + if c.Request().Method != http.MethodGet && c.Request().Method != http.MethodHead { + return echo.NewHTTPError(http.StatusNotFound, "Not Found") + } + path := c.Request().URL.Path + if strings.HasPrefix(path, "/api") || strings.HasPrefix(path, "/ws") { + return echo.NewHTTPError(http.StatusNotFound, "Not Found") + } + if isAdminPage(path) { + return h.serveAdminIndex(c) + } + if path != "/" { + _, dist, err := h.themeService.ActiveDistDir(c.Request().Context()) + if err != nil { + return err + } + if full, ok := resolveStaticPath(dist, strings.TrimPrefix(path, "/")); ok { + return serveResolvedStaticFile(c, full, true) + } + } + if filepath.Ext(path) != "" { + return echo.NewHTTPError(http.StatusNotFound, "Not Found") + } + return h.serveThemeIndex(c) +} + +func isAdminPage(path string) bool { + return path == "/admin" || strings.HasPrefix(path, "/admin/") +} + +func (h *WebHandler) serveAdminIndex(c *echo.Context) error { + path := filepath.Join(assets.WebDir(), "admin", "index.html") + data, err := os.ReadFile(path) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "官方管理前端缺失") + } + setHTMLHeaders(c) + return c.Blob(http.StatusOK, "text/html; charset=utf-8", data) +} + +func (h *WebHandler) serveThemeIndex(c *echo.Context) error { + systemConfig, err := h.propertyService.GetSystemConfig(c.Request().Context()) + if err != nil { + return err + } + runtime, err := h.runtimeConfig(c) + if err != nil { + return err + } + data, err := h.themeService.RenderIndex(c.Request().Context(), runtime, systemConfig) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "公开主题入口不可用") + } + setHTMLHeaders(c) + return c.Blob(http.StatusOK, "text/html; charset=utf-8", data) +} + +func (h *WebHandler) runtimeConfig(c *echo.Context) (map[string]any, error) { + ctx := c.Request().Context() + systemConfig, err := h.propertyService.GetSystemConfig(ctx) + if err != nil { + return nil, err + } + appearance, err := h.propertyService.GetAppearanceConfig(ctx) + if err != nil { + return nil, err + } + theme, err := h.themeService.Active(ctx) + if err != nil { + return nil, err + } + legacy := map[string]any{ + "SystemNameZh": systemConfig.SystemNameZh, + "SystemNameEn": systemConfig.SystemNameEn, + "ICPCode": systemConfig.ICPCode, + "DefaultView": systemConfig.DefaultView, + "Version": version.Version, + } + return map[string]any{ + "apiVersion": service.ThemeAPIVersion, + "system": map[string]any{ + "nameZh": systemConfig.SystemNameZh, + "nameEn": systemConfig.SystemNameEn, + "logo": "/api/logo", + "icpCode": systemConfig.ICPCode, + "version": version.Version, + "defaultView": systemConfig.DefaultView, + "defaultColorMode": appearance.DefaultColorMode, + }, + "theme": map[string]any{ + "id": theme.ID, + "version": theme.Version, + }, + "features": map[string]bool{ + "serverList": true, "serverDetail": true, + "monitorList": true, "monitorDetail": true, + }, + "legacySystemConfig": legacy, + }, nil +} + +func serveStaticFile(c *echo.Context, root, requested string, immutable bool) error { + full, ok := resolveStaticPath(root, requested) + if !ok { + return echo.NewHTTPError(http.StatusNotFound, "Not Found") + } + return serveResolvedStaticFile(c, full, immutable) +} + +func resolveStaticPath(root, requested string) (string, bool) { + requested = strings.TrimPrefix(requested, "/") + if requested == "" || strings.Contains(requested, "\\") { + return "", false + } + clean := filepath.Clean(requested) + if clean == "." || clean == ".." || strings.HasPrefix(clean, ".."+string(os.PathSeparator)) { + return "", false + } + full := filepath.Join(root, clean) + rel, err := filepath.Rel(root, full) + if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return "", false + } + stat, err := os.Lstat(full) + if err != nil || !stat.Mode().IsRegular() { + return "", false + } + realRoot, err := filepath.EvalSymlinks(root) + if err != nil { + return "", false + } + realFile, err := filepath.EvalSymlinks(full) + if err != nil { + return "", false + } + realRel, err := filepath.Rel(realRoot, realFile) + if err != nil || realRel == ".." || strings.HasPrefix(realRel, ".."+string(os.PathSeparator)) { + return "", false + } + return realFile, true +} + +func serveResolvedStaticFile(c *echo.Context, full string, immutable bool) error { + file, err := os.Open(full) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, "Not Found") + } + defer file.Close() + stat, err := file.Stat() + if err != nil || !stat.Mode().IsRegular() { + return echo.NewHTTPError(http.StatusNotFound, "Not Found") + } + contentType := mime.TypeByExtension(filepath.Ext(full)) + if contentType == "" { + contentType = "application/octet-stream" + } + c.Response().Header().Set("Content-Type", contentType) + c.Response().Header().Set("X-Content-Type-Options", "nosniff") + c.Response().Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") + if immutable { + c.Response().Header().Set("Cache-Control", "public, max-age=31536000, immutable") + } + http.ServeContent(c.Response(), c.Request(), stat.Name(), stat.ModTime(), file) + return nil +} + +func setHTMLHeaders(c *echo.Context) { + c.Response().Header().Set("Cache-Control", "no-cache") + c.Response().Header().Set("X-Content-Type-Options", "nosniff") + c.Response().Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") +} diff --git a/internal/handler/web_handler_test.go b/internal/handler/web_handler_test.go new file mode 100644 index 0000000..48fafbd --- /dev/null +++ b/internal/handler/web_handler_test.go @@ -0,0 +1,127 @@ +package handler + +import ( + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/labstack/echo/v5" +) + +func TestAdminPageRoutingBoundary(t *testing.T) { + for _, path := range []string{"/admin", "/admin/login", "/admin/github/callback", "/admin/oidc/callback", "/admin/agents"} { + if !isAdminPage(path) { + t.Errorf("expected admin route: %s", path) + } + } + for _, path := range []string{"/", "/login", "/github/callback", "/oidc/callback", "/servers/id", "/monitors/id", "/administrator", "/api/admin/themes"} { + if isAdminPage(path) { + t.Errorf("public/API route classified as admin page: %s", path) + } + } +} + +func TestServeSPARejectsAPIWebSocketAssetsAndNonGET(t *testing.T) { + handler := &WebHandler{} + for _, test := range []struct { + method string + path string + }{ + {http.MethodGet, "/api/unknown"}, + {http.MethodGet, "/ws/unknown"}, + {http.MethodPost, "/servers/1"}, + } { + recorder := httptest.NewRecorder() + request := httptest.NewRequest(test.method, test.path, nil) + context := echo.New().NewContext(request, recorder) + err := handler.ServeSPA(context) + httpError, ok := err.(*echo.HTTPError) + if !ok || httpError.Code != http.StatusNotFound { + t.Errorf("%s %s should return 404, got %v", test.method, test.path, err) + } + } +} + +func TestResolveStaticPathAndHeaders(t *testing.T) { + root := t.TempDir() + asset := filepath.Join(root, "assets", "app.js") + if err := os.MkdirAll(filepath.Dir(asset), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(asset, []byte("console.log('ok')"), 0o644); err != nil { + t.Fatal(err) + } + if resolved, ok := resolveStaticPath(root, "assets/app.js"); !ok || resolved == "" { + t.Fatal("valid static asset was rejected") + } + for _, path := range []string{"", "../secret", "assets\\app.js", "/../../secret"} { + if _, ok := resolveStaticPath(root, path); ok { + t.Errorf("unsafe path accepted: %q", path) + } + } + + if runtime.GOOS != "windows" { + outside := filepath.Join(t.TempDir(), "secret.js") + if err := os.WriteFile(outside, []byte("secret"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.Symlink(outside, filepath.Join(root, "link.js")); err != nil { + t.Fatal(err) + } + if _, ok := resolveStaticPath(root, "link.js"); ok { + t.Fatal("symlink asset was accepted") + } + } + + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/admin/assets/app.js", nil) + context := echo.New().NewContext(request, recorder) + if err := serveStaticFile(context, root, "assets/app.js", true); err != nil { + t.Fatal(err) + } + if recorder.Header().Get("X-Content-Type-Options") != "nosniff" || + recorder.Header().Get("Cache-Control") != "public, max-age=31536000, immutable" || + recorder.Header().Get("Content-Type") != "text/javascript; charset=utf-8" { + t.Fatalf("unexpected static headers: %#v", recorder.Header()) + } +} + +func TestEchoWildcardRoutesKeepAdminAndAPISeparated(t *testing.T) { + webRoot := t.TempDir() + if err := os.MkdirAll(filepath.Join(webRoot, "admin", "assets"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(webRoot, "admin", "assets", "app.js"), []byte("admin"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(webRoot, "admin", "index.html"), []byte("admin"), 0o644); err != nil { + t.Fatal(err) + } + t.Setenv("PIKA_WEB_DIR", webRoot) + + e := echo.New() + handler := &WebHandler{} + e.GET("/admin/assets/*", handler.ServeAdminAsset) + e.GET("/*", handler.ServeSPA) + + assetRecorder := httptest.NewRecorder() + e.ServeHTTP(assetRecorder, httptest.NewRequest(http.MethodGet, "/admin/assets/app.js", nil)) + if assetRecorder.Code != http.StatusOK || assetRecorder.Body.String() != "admin" { + t.Fatalf("wildcard asset route failed: status=%d body=%q", assetRecorder.Code, assetRecorder.Body.String()) + } + + adminRecorder := httptest.NewRecorder() + e.ServeHTTP(adminRecorder, httptest.NewRequest(http.MethodGet, "/admin/agents", nil)) + if adminRecorder.Code != http.StatusOK || adminRecorder.Body.String() != "admin" { + t.Fatalf("admin SPA route failed: status=%d body=%q", adminRecorder.Code, adminRecorder.Body.String()) + } + + apiRecorder := httptest.NewRecorder() + e.ServeHTTP(apiRecorder, httptest.NewRequest(http.MethodGet, "/api/unknown", nil)) + if apiRecorder.Code != http.StatusNotFound || apiRecorder.Header().Get("Content-Type") == "text/html; charset=utf-8" { + t.Fatalf("unknown API entered SPA fallback: status=%d type=%q", apiRecorder.Code, apiRecorder.Header().Get("Content-Type")) + } +} diff --git a/internal/service/property_service.go b/internal/service/property_service.go index 2c1be93..82adff8 100644 --- a/internal/service/property_service.go +++ b/internal/service/property_service.go @@ -30,6 +30,8 @@ const ( PropertyIDDNSProviders = "dns_providers" // PropertyIDAgentInstallConfig 探针安装配置的固定 ID PropertyIDAgentInstallConfig = "agent_install_config" + // PropertyIDAppearanceConfig 当前公开主题和默认明暗模式。 + PropertyIDAppearanceConfig = "appearance_config" ) var defaultPublicIPv4APIs = []string{ @@ -142,6 +144,24 @@ func (s *PropertyService) GetSystemConfig(ctx context.Context) (*models.SystemCo return &systemConfig, nil } +func (s *PropertyService) GetAppearanceConfig(ctx context.Context) (*AppearanceConfig, error) { + var config AppearanceConfig + if err := s.GetValue(ctx, PropertyIDAppearanceConfig, &config); err != nil { + return nil, fmt.Errorf("获取外观配置失败: %w", err) + } + if config.ActiveTheme == "" { + config.ActiveTheme = "default" + } + if config.DefaultColorMode == "" { + config.DefaultColorMode = "system" + } + return &config, nil +} + +func (s *PropertyService) SetAppearanceConfig(ctx context.Context, config AppearanceConfig) error { + return s.Set(ctx, PropertyIDAppearanceConfig, "外观配置", config) +} + // GetPublicIPConfig 获取公网 IP 采集配置 func (s *PropertyService) GetPublicIPConfig(ctx context.Context) (*models.PublicIPConfig, error) { var config models.PublicIPConfig @@ -352,6 +372,14 @@ func (s *PropertyService) InitializeDefaultConfigs(ctx context.Context) error { DefaultView: "grid", }, }, + { + ID: PropertyIDAppearanceConfig, + Name: "外观配置", + Value: AppearanceConfig{ + ActiveTheme: "default", + DefaultColorMode: "system", + }, + }, { ID: PropertyIDPublicIPConfig, Name: "公网 IP 采集配置", diff --git a/internal/service/theme.go b/internal/service/theme.go new file mode 100644 index 0000000..6dc2fae --- /dev/null +++ b/internal/service/theme.go @@ -0,0 +1,590 @@ +package service + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "html" + "io" + "mime" + "os" + "path/filepath" + "regexp" + "strings" + "sync" + "time" + + "github.com/dushixiang/pika/internal/assets" + "github.com/dushixiang/pika/internal/config" + "github.com/dushixiang/pika/internal/models" + "github.com/dushixiang/pika/pkg/version" + "github.com/google/uuid" + "go.uber.org/zap" + "golang.org/x/text/unicode/norm" +) + +// ThemeManifest 是 pika-theme.json 的结构,只含展示用的关键信息。 +type ThemeManifest struct { + SchemaVersion int `json:"schemaVersion"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Version string `json:"version"` + Author string `json:"author"` + Homepage string `json:"homepage"` + License string `json:"license"` + Preview string `json:"preview"` + Entry string `json:"entry"` + APIVersion string `json:"apiVersion"` + Capabilities []string `json:"capabilities"` +} + +// AppearanceConfig 记录当前启用主题和默认明暗模式,存放在 appearance_config Property。 +type AppearanceConfig struct { + ActiveTheme string `json:"activeTheme"` + DefaultColorMode string `json:"defaultColorMode"` +} + +type ThemeInfo struct { + ThemeManifest + Active bool `json:"active"` + Official bool `json:"official"` + Compatible bool `json:"compatible"` + CompatibilityError string `json:"compatibilityError"` + PreviewURL string `json:"previewUrl"` +} + +type ThemeInstallResult struct { + Theme ThemeInfo `json:"theme"` + SHA256 string `json:"sha256"` +} + +const ( + DefaultThemeID = "default" + ThemeSchemaVersion = 1 + ThemeAPIVersion = "v1" + maxThemeArchiveSize = 64 << 20 + maxThemeFiles = 5000 + maxThemeFileSize = 64 << 20 + maxThemeExtracted = 256 << 20 + maxThemeManifestSize = 1 << 20 + maxThemePreviewSize = 5 << 20 + maxThemeIndexSize = 8 << 20 +) + +var ( + ErrThemeNotFound = errors.New("主题不存在") + ErrThemeExists = errors.New("主题已存在") + ErrThemeActive = errors.New("当前主题不能删除") + ErrDefaultTheme = errors.New("默认主题不能修改或删除") + themeIDPattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$`) + reservedThemeIDs = map[string]struct{}{ + "default": {}, "admin": {}, "official": {}, "system": {}, + "api": {}, "assets": {}, "theme-assets": {}, + } + requiredThemeCaps = []string{"server-list", "server-detail", "monitor-list", "monitor-detail"} + titleElement = regexp.MustCompile(`(?is)]*)?>.*?`) + descriptionMeta = regexp.MustCompile(`(?is)]*name\s*=\s*["']description["'][^>]*>`) + iconLink = regexp.MustCompile(`(?is)]*rel\s*=\s*["'][^"']*icon[^"']*["'][^>]*>`) + headEnd = regexp.MustCompile(`(?i)`) + bodyEnd = regexp.MustCompile(`(?i)`) +) + +// ThemeService 通过扫描本地主题目录提供主题的列表、安装、启用、删除和入口 HTML 渲染。 +// 主题数据全部存放在文件系统,不经过数据库。 +type ThemeService struct { + logger *zap.Logger + propertyService *PropertyService + themeDir string + defaultThemeDir string + mu sync.RWMutex + renderMu sync.RWMutex + renderCacheKey string + renderCache []byte + fallbackMu sync.Mutex +} + +func NewThemeService(logger *zap.Logger, propertyService *PropertyService, cfg *config.AppConfig) (*ThemeService, error) { + themeCfg := &config.ThemeConfig{} + if cfg != nil && cfg.Theme != nil { + themeCfg = cfg.Theme + } + themeDir := strings.TrimSpace(os.Getenv("PIKA_THEME_DIR")) + if themeDir == "" { + themeDir = strings.TrimSpace(themeCfg.Dir) + } + if themeDir == "" { + themeDir = filepath.Join("data", "themes") + } + s := &ThemeService{ + logger: logger, + propertyService: propertyService, + themeDir: themeDir, + defaultThemeDir: filepath.Join(assets.WebDir(), "default-theme"), + } + if err := os.MkdirAll(filepath.Join(themeDir, ".staging"), 0755); err != nil { + return nil, fmt.Errorf("创建主题目录失败: %w", err) + } + if err := validateOfficialWebBuild(assets.WebDir(), s.defaultThemeDir); err != nil { + return nil, err + } + if err := s.recoverFilesystem(); err != nil { + logger.Warn("主题目录启动恢复存在异常", zap.Error(err)) + } + return s, nil +} + +func (s *ThemeService) ActiveThemeID(ctx context.Context) string { + appearance, err := s.propertyService.GetAppearanceConfig(ctx) + if err != nil || appearance.ActiveTheme == "" { + return DefaultThemeID + } + return appearance.ActiveTheme +} + +func (s *ThemeService) Active(ctx context.Context) (*ThemeInfo, error) { + s.mu.RLock() + defer s.mu.RUnlock() + id := s.ActiveThemeID(ctx) + info, err := s.getUnlocked(ctx, id) + if err == nil && info.Compatible { + return info, nil + } + defaultInfo, defaultErr := s.getUnlocked(ctx, DefaultThemeID) + if defaultErr != nil { + return nil, fmt.Errorf("当前主题 %q 不可用且默认主题回退失败: %w", id, defaultErr) + } + if id != DefaultThemeID { + s.repairActiveTheme(ctx, id, err, info) + } + return defaultInfo, nil +} + +func (s *ThemeService) repairActiveTheme(ctx context.Context, brokenID string, loadErr error, info *ThemeInfo) { + s.fallbackMu.Lock() + defer s.fallbackMu.Unlock() + if s.ActiveThemeID(ctx) != brokenID { + return + } + appearance, err := s.propertyService.GetAppearanceConfig(ctx) + if err != nil { + s.logger.Error("读取损坏主题的外观配置失败", zap.String("theme", brokenID), zap.Error(err)) + return + } + appearance.ActiveTheme = DefaultThemeID + if err := s.propertyService.SetAppearanceConfig(ctx, *appearance); err != nil { + s.logger.Error("自动回退默认主题失败", zap.String("theme", brokenID), zap.Error(err)) + return + } + reason := loadErr + if reason == nil && info != nil && info.CompatibilityError != "" { + reason = errors.New(info.CompatibilityError) + } + s.logger.Warn("活动主题不可用,已自动回退默认主题", zap.String("theme", brokenID), zap.Error(reason)) + s.invalidateRenderCache() +} + +func (s *ThemeService) List(ctx context.Context) ([]ThemeInfo, error) { + s.mu.RLock() + defer s.mu.RUnlock() + active := s.ActiveThemeID(ctx) + items := make([]ThemeInfo, 0) + info, err := s.infoFromRoot(DefaultThemeID, s.defaultThemeDir, true, active) + if err != nil { + return nil, fmt.Errorf("默认主题无效: %w", err) + } + items = append(items, *info) + entries, err := os.ReadDir(s.themeDir) + if err != nil { + return nil, err + } + for _, entry := range entries { + if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") { + continue + } + item, loadErr := s.infoFromRoot(entry.Name(), filepath.Join(s.themeDir, entry.Name()), false, active) + if loadErr != nil { + s.logger.Warn("忽略无效主题目录", zap.String("theme", entry.Name()), zap.Error(loadErr)) + continue + } + items = append(items, *item) + } + return items, nil +} + +func (s *ThemeService) Get(ctx context.Context, id string) (*ThemeInfo, error) { + s.mu.RLock() + defer s.mu.RUnlock() + return s.getUnlocked(ctx, id) +} + +func (s *ThemeService) getUnlocked(ctx context.Context, id string) (*ThemeInfo, error) { + if id != DefaultThemeID && !validThemeID(id) { + return nil, ErrThemeNotFound + } + root := s.themeRoot(id) + if stat, err := os.Stat(root); err != nil || !stat.IsDir() { + return nil, ErrThemeNotFound + } + return s.infoFromRoot(id, root, id == DefaultThemeID, s.ActiveThemeID(ctx)) +} + +func (s *ThemeService) infoFromRoot(id, root string, official bool, active string) (*ThemeInfo, error) { + manifest, err := loadThemeManifest(filepath.Join(root, "pika-theme.json")) + if err != nil { + return nil, err + } + if manifest.ID != id { + return nil, fmt.Errorf("清单 id %q 与主题目录 %q 不一致", manifest.ID, id) + } + compatErr := validateThemeManifest(manifest, root) + info := &ThemeInfo{ + ThemeManifest: *manifest, + Active: active == id, + Official: official, + Compatible: compatErr == nil, + PreviewURL: "/api/admin/themes/" + id + "/preview", + } + if compatErr != nil { + info.CompatibilityError = compatErr.Error() + } + return info, nil +} + +func (s *ThemeService) Install(ctx context.Context, reader io.Reader) (*ThemeInstallResult, error) { + data, hash, err := readThemeArchive(reader) + if err != nil { + return nil, err + } + s.mu.Lock() + defer s.mu.Unlock() + info, err := s.installArchiveLocked(ctx, data, "", false) + if err != nil { + return nil, err + } + return &ThemeInstallResult{Theme: *info, SHA256: hash}, nil +} + +func (s *ThemeService) installArchiveLocked(ctx context.Context, data []byte, expectedID string, replace bool) (*ThemeInfo, error) { + stage := filepath.Join(s.themeDir, ".staging", uuid.NewString()) + unpacked := filepath.Join(stage, "unpacked") + if err := os.MkdirAll(unpacked, 0755); err != nil { + return nil, err + } + defer os.RemoveAll(stage) + if err := extractThemeArchive(data, unpacked); err != nil { + return nil, err + } + manifest, err := loadThemeManifest(filepath.Join(unpacked, "pika-theme.json")) + if err != nil { + return nil, err + } + if expectedID != "" && manifest.ID != expectedID { + return nil, errors.New("更新包的主题 id 与目标主题不一致") + } + if manifest.ID == DefaultThemeID { + return nil, ErrDefaultTheme + } + if err := validateThemeManifest(manifest, unpacked); err != nil { + return nil, err + } + if existing, found, err := s.findInstalledThemeID(manifest.ID); err != nil { + return nil, err + } else if found && (!replace || existing != manifest.ID) { + return nil, ErrThemeExists + } + destination := filepath.Join(s.themeDir, manifest.ID) + if _, err := os.Stat(destination); err == nil && !replace { + return nil, ErrThemeExists + } + backup := "" + if replace { + if _, err := os.Stat(destination); err != nil { + return nil, ErrThemeNotFound + } + backup = destination + ".backup-" + uuid.NewString() + if err := os.Rename(destination, backup); err != nil { + return nil, fmt.Errorf("备份旧主题失败: %w", err) + } + } + if err := os.Rename(unpacked, destination); err != nil { + if backup != "" { + _ = os.Rename(backup, destination) + } + return nil, fmt.Errorf("安装主题失败: %w", err) + } + installedInfo, err := s.infoFromRoot(manifest.ID, destination, false, s.ActiveThemeID(ctx)) + if err != nil { + _ = os.RemoveAll(destination) + if backup != "" { + _ = os.Rename(backup, destination) + } + return nil, fmt.Errorf("安装后健康检查失败: %w", err) + } + if backup != "" { + _ = os.RemoveAll(backup) + } + s.invalidateRenderCache() + return installedInfo, nil +} + +func (s *ThemeService) findInstalledThemeID(id string) (string, bool, error) { + entries, err := os.ReadDir(s.themeDir) + if err != nil { + return "", false, err + } + wanted := strings.ToLower(norm.NFC.String(id)) + for _, entry := range entries { + if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") || strings.Contains(entry.Name(), ".backup-") { + continue + } + if strings.ToLower(norm.NFC.String(entry.Name())) == wanted { + return entry.Name(), true, nil + } + } + return "", false, nil +} + +func (s *ThemeService) Activate(ctx context.Context, id string) error { + s.mu.Lock() + defer s.mu.Unlock() + info, err := s.getUnlocked(ctx, id) + if err != nil { + return err + } + if !info.Compatible { + return errors.New(info.CompatibilityError) + } + appearance, err := s.propertyService.GetAppearanceConfig(ctx) + if err != nil { + return err + } + appearance.ActiveTheme = id + if err := s.propertyService.SetAppearanceConfig(ctx, *appearance); err != nil { + return err + } + s.invalidateRenderCache() + return nil +} + +func (s *ThemeService) Delete(ctx context.Context, id string) error { + if id == DefaultThemeID { + return ErrDefaultTheme + } + if !validThemeID(id) { + return ErrThemeNotFound + } + s.mu.Lock() + defer s.mu.Unlock() + if s.ActiveThemeID(ctx) == id { + return ErrThemeActive + } + root := filepath.Join(s.themeDir, id) + if _, err := os.Stat(root); err != nil { + return ErrThemeNotFound + } + trash := root + ".delete-" + uuid.NewString() + if err := os.Rename(root, trash); err != nil { + return err + } + if err := os.RemoveAll(trash); err != nil { + return err + } + s.invalidateRenderCache() + return nil +} + +func (s *ThemeService) ActiveDistDir(ctx context.Context) (string, string, error) { + info, err := s.Active(ctx) + if err != nil { + return "", "", err + } + return info.ID, filepath.Join(s.themeRoot(info.ID), "dist"), nil +} + +func (s *ThemeService) PreviewPath(ctx context.Context, id string) (string, string, error) { + info, err := s.Get(ctx, id) + if err != nil { + return "", "", err + } + path := filepath.Join(s.themeRoot(id), filepath.Clean(info.Preview)) + return path, mime.TypeByExtension(filepath.Ext(path)), nil +} + +func (s *ThemeService) themeRoot(id string) string { + if id == DefaultThemeID { + return s.defaultThemeDir + } + return filepath.Join(s.themeDir, id) +} + +func (s *ThemeService) RenderIndex(ctx context.Context, runtime any, systemConfig *models.SystemConfig) ([]byte, error) { + _, dist, err := s.ActiveDistDir(ctx) + if err != nil { + return nil, err + } + data, err := os.ReadFile(filepath.Join(dist, "index.html")) + if err != nil { + return nil, err + } + runtimeJSON, err := json.Marshal(runtime) + if err != nil { + return nil, err + } + customCSS, customJS := "", "" + if systemConfig != nil { + customCSS = systemConfig.CustomCSS + customJS = systemConfig.CustomJS + } + cacheHash := sha256.New() + cacheHash.Write(data) + cacheHash.Write(runtimeJSON) + io.WriteString(cacheHash, "\x00"+customCSS+"\x00"+customJS+"\x00"+version.Version) + cacheKey := hex.EncodeToString(cacheHash.Sum(nil)) + s.renderMu.RLock() + if s.renderCacheKey == cacheKey && s.renderCache != nil { + cached := append([]byte(nil), s.renderCache...) + s.renderMu.RUnlock() + return cached, nil + } + s.renderMu.RUnlock() + + injection := `` + if customCSS != "" { + injection += `` + } + htmlText := string(data) + htmlText = applySystemMetadata(htmlText, systemConfig) + if strings.Contains(htmlText, "") { + htmlText = strings.Replace(htmlText, "", injection, 1) + } else { + htmlText = replaceBeforeFirst(htmlText, headEnd, injection) + } + if customJS != "" { + script := `` + if strings.Contains(htmlText, "") { + htmlText = strings.Replace(htmlText, "", script, 1) + } else { + htmlText = replaceBeforeFirst(htmlText, bodyEnd, script) + } + } + rendered := []byte(htmlText) + s.renderMu.Lock() + s.renderCacheKey = cacheKey + s.renderCache = append(s.renderCache[:0], rendered...) + s.renderMu.Unlock() + return append([]byte(nil), rendered...), nil +} + +func applySystemMetadata(htmlText string, systemConfig *models.SystemConfig) string { + if systemConfig == nil { + return htmlText + } + nameZh := strings.TrimSpace(systemConfig.SystemNameZh) + nameEn := strings.TrimSpace(systemConfig.SystemNameEn) + title := nameZh + if title == "" { + title = nameEn + } else if nameEn != "" && nameEn != nameZh { + title += " | " + nameEn + } + if title == "" { + title = "Pika Monitor" + } + titleTag := "" + html.EscapeString(title) + "" + description := html.EscapeString(title + " - 服务器状态与监控") + descriptionTag := `` + iconTag := `` + if titleElement.MatchString(htmlText) { + htmlText = titleElement.ReplaceAllStringFunc(htmlText, func(string) string { return titleTag }) + } else { + htmlText = replaceBeforeFirst(htmlText, headEnd, titleTag) + } + if descriptionMeta.MatchString(htmlText) { + htmlText = descriptionMeta.ReplaceAllStringFunc(htmlText, func(string) string { return descriptionTag }) + } else { + htmlText = replaceBeforeFirst(htmlText, headEnd, descriptionTag) + } + if iconLink.MatchString(htmlText) { + htmlText = iconLink.ReplaceAllStringFunc(htmlText, func(string) string { return iconTag }) + } else { + htmlText = replaceBeforeFirst(htmlText, headEnd, iconTag) + } + return htmlText +} + +func (s *ThemeService) invalidateRenderCache() { + s.renderMu.Lock() + s.renderCacheKey = "" + s.renderCache = nil + s.renderMu.Unlock() +} + +func (s *ThemeService) recoverFilesystem() error { + var recoveryErrors []error + staging := filepath.Join(s.themeDir, ".staging") + entries, err := os.ReadDir(staging) + if err == nil { + cutoff := time.Now().Add(-24 * time.Hour) + for _, entry := range entries { + info, infoErr := entry.Info() + if infoErr == nil && info.ModTime().Before(cutoff) { + _ = os.RemoveAll(filepath.Join(staging, entry.Name())) + } + } + } + entries, err = os.ReadDir(s.themeDir) + if err != nil { + return err + } + backups := map[string][]string{} + for _, entry := range entries { + name := entry.Name() + marker := strings.Index(name, ".backup-") + if marker <= 0 || !entry.IsDir() { + continue + } + id := name[:marker] + backups[id] = append(backups[id], filepath.Join(s.themeDir, name)) + } + for id, candidates := range backups { + official := filepath.Join(s.themeDir, id) + if themeRootValid(id, official) { + for _, candidate := range candidates { + if err := os.RemoveAll(candidate); err != nil { + recoveryErrors = append(recoveryErrors, fmt.Errorf("清理主题 %s 旧备份失败: %w", id, err)) + } + } + continue + } + validBackups := make([]string, 0, len(candidates)) + for _, candidate := range candidates { + if themeRootValid(id, candidate) { + validBackups = append(validBackups, candidate) + } + } + if len(validBackups) != 1 || len(candidates) != 1 { + recoveryErrors = append(recoveryErrors, fmt.Errorf("主题 %s 的备份状态不明确,已保留全部目录", id)) + continue + } + if _, statErr := os.Stat(official); statErr == nil { + broken := filepath.Join(s.themeDir, ".broken-"+id+"-"+uuid.NewString()) + if err := os.Rename(official, broken); err != nil { + recoveryErrors = append(recoveryErrors, fmt.Errorf("隔离主题 %s 损坏目录失败: %w", id, err)) + continue + } + } + if err := os.Rename(validBackups[0], official); err != nil { + recoveryErrors = append(recoveryErrors, fmt.Errorf("恢复主题 %s 备份失败: %w", id, err)) + } + } + return errors.Join(recoveryErrors...) +} + +func themeRootValid(id, root string) bool { + manifest, err := loadThemeManifest(filepath.Join(root, "pika-theme.json")) + return err == nil && manifest.ID == id && validateThemeManifest(manifest, root) == nil +} diff --git a/internal/service/theme_service_test.go b/internal/service/theme_service_test.go new file mode 100644 index 0000000..6b20e2b --- /dev/null +++ b/internal/service/theme_service_test.go @@ -0,0 +1,321 @@ +package service + +import ( + "archive/zip" + "bytes" + "context" + "encoding/json" + "errors" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/dushixiang/pika/internal/config" + "github.com/dushixiang/pika/internal/models" + "github.com/dushixiang/pika/pkg/version" + "github.com/glebarez/sqlite" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func TestValidateOfficialWebBuildAllowsMissingBuildInDevelopment(t *testing.T) { + previousVersion := version.Version + version.Version = "dev" + t.Cleanup(func() { version.Version = previousVersion }) + t.Setenv("PIKA_WEB_DIR", "") + + webRoot := t.TempDir() + if err := validateOfficialWebBuild(webRoot, filepath.Join(webRoot, "default-theme")); err != nil { + t.Fatalf("development build should not require packaged Web assets: %v", err) + } +} + +func TestValidateOfficialWebBuildChecksExplicitWebDirInDevelopment(t *testing.T) { + previousVersion := version.Version + version.Version = "dev" + t.Cleanup(func() { version.Version = previousVersion }) + + webRoot := t.TempDir() + t.Setenv("PIKA_WEB_DIR", webRoot) + if err := validateOfficialWebBuild(webRoot, filepath.Join(webRoot, "default-theme")); err == nil { + t.Fatal("explicit PIKA_WEB_DIR should be validated in development") + } +} + +func TestExtractThemeArchiveSecurity(t *testing.T) { + valid := makeThemeZIP(t, map[string]string{ + "pika-theme.json": `{}`, + "dist/index.html": ``, + "preview.png": "preview", + }) + if err := extractThemeArchive(valid, t.TempDir()); err != nil { + t.Fatalf("valid archive rejected: %v", err) + } + + tests := []struct { + name string + files map[string]string + }{ + {name: "zip slip", files: map[string]string{"../escape.js": "x"}}, + {name: "absolute", files: map[string]string{"/escape.js": "x"}}, + {name: "windows drive", files: map[string]string{"C:/escape.js": "x"}}, + {name: "case duplicate", files: map[string]string{"dist/app.js": "a", "DIST/App.js": "b"}}, + {name: "unicode duplicate", files: map[string]string{"dist/caf\u00e9.js": "a", "dist/cafe\u0301.js": "b"}}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if err := extractThemeArchive(makeThemeZIP(t, test.files), t.TempDir()); err == nil { + t.Fatal("unsafe archive was accepted") + } + }) + } +} + +func TestExtractThemeArchiveRejectsSymlinkAndFileCount(t *testing.T) { + var buffer bytes.Buffer + writer := zip.NewWriter(&buffer) + header := &zip.FileHeader{Name: "dist/link"} + header.SetMode(os.ModeSymlink | 0o777) + entry, err := writer.CreateHeader(header) + if err != nil { + t.Fatal(err) + } + _, _ = entry.Write([]byte("../../secret")) + if err := writer.Close(); err != nil { + t.Fatal(err) + } + if err := extractThemeArchive(buffer.Bytes(), t.TempDir()); err == nil { + t.Fatal("symlink archive was accepted") + } + + // 用 FileHeader 直接生成唯一名字,避免测试数据本身发生覆盖。 + buffer.Reset() + writer = zip.NewWriter(&buffer) + for index := 0; index <= maxThemeFiles; index++ { + if _, err := writer.Create("dist/file-" + strconv.Itoa(index)); err != nil { + t.Fatal(err) + } + } + if err := writer.Close(); err != nil { + t.Fatal(err) + } + if err := extractThemeArchive(buffer.Bytes(), t.TempDir()); err == nil { + t.Fatal("archive exceeding file count was accepted") + } +} + +func TestValidateThemeManifest(t *testing.T) { + root := createValidThemeRoot(t, "minimal") + manifest := validTestManifest("minimal") + if err := validateThemeManifest(&manifest, root); err != nil { + t.Fatalf("valid manifest rejected: %v", err) + } + + tests := []struct { + name string + mutate func(*ThemeManifest) + }{ + {name: "reserved id", mutate: func(value *ThemeManifest) { value.ID = "admin" }}, + {name: "invalid schema", mutate: func(value *ThemeManifest) { value.SchemaVersion = 2 }}, + {name: "invalid api", mutate: func(value *ThemeManifest) { value.APIVersion = "v2" }}, + {name: "invalid entry", mutate: func(value *ThemeManifest) { value.Entry = "index.html" }}, + {name: "missing capability", mutate: func(value *ThemeManifest) { value.Capabilities = []string{"server-list"} }}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + candidate := manifest + test.mutate(&candidate) + if err := validateThemeManifest(&candidate, root); err == nil { + t.Fatal("invalid manifest was accepted") + } + }) + } +} + +func TestApplySystemMetadataEscapesValues(t *testing.T) { + rendered := applySystemMetadata(`old`, &models.SystemConfig{ + SystemNameZh: ``, + SystemNameEn: `Pika $1`, + }) + if strings.Contains(rendered, ``) || !strings.Contains(rendered, `<script>`) { + t.Fatalf("metadata was not escaped: %s", rendered) + } + if !strings.Contains(rendered, ``) { + t.Fatalf("metadata tags missing: %s", rendered) + } +} + +func TestRecoverFilesystemRestoresUniqueBackup(t *testing.T) { + themeDir := t.TempDir() + if err := os.MkdirAll(filepath.Join(themeDir, ".staging"), 0o755); err != nil { + t.Fatal(err) + } + backup := filepath.Join(themeDir, "minimal.backup-test") + writeThemeRoot(t, backup, validTestManifest("minimal")) + service := &ThemeService{themeDir: themeDir} + if err := service.recoverFilesystem(); err != nil { + t.Fatalf("recovery failed: %v", err) + } + if !themeRootValid("minimal", filepath.Join(themeDir, "minimal")) { + t.Fatal("unique valid backup was not restored") + } +} + +func TestThemeInstallActivateDeleteLifecycle(t *testing.T) { + ctx := context.Background() + webRoot := t.TempDir() + writeOfficialWebBuild(t, webRoot) + t.Setenv("PIKA_WEB_DIR", webRoot) + themeDir := filepath.Join(t.TempDir(), "themes") + + database, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + if err != nil { + t.Fatal(err) + } + if err := database.AutoMigrate(&models.Property{}); err != nil { + t.Fatal(err) + } + properties := NewPropertyService(zap.NewNop(), database) + if err := properties.InitializeDefaultConfigs(ctx); err != nil { + t.Fatal(err) + } + themeService, err := NewThemeService(zap.NewNop(), properties, &config.AppConfig{Theme: &config.ThemeConfig{Dir: themeDir}}) + if err != nil { + t.Fatal(err) + } + + archive := makeInstallableThemeZIP(t, validTestManifest("minimal")) + result, err := themeService.Install(ctx, bytes.NewReader(archive)) + if err != nil { + t.Fatalf("install failed: %v", err) + } + if result.Theme.ID != "minimal" || len(result.SHA256) != 64 { + t.Fatalf("unexpected install result: %#v", result) + } + if _, err := themeService.Install(ctx, bytes.NewReader(archive)); !errors.Is(err, ErrThemeExists) { + t.Fatalf("duplicate install should fail with ErrThemeExists, got %v", err) + } + caseVariant := validTestManifest("Minimal") + if _, err := themeService.Install(ctx, bytes.NewReader(makeInstallableThemeZIP(t, caseVariant))); !errors.Is(err, ErrThemeExists) { + t.Fatalf("case-insensitive duplicate should fail with ErrThemeExists, got %v", err) + } + defaultManifest := validTestManifest(DefaultThemeID) + if _, err := themeService.Install(ctx, bytes.NewReader(makeInstallableThemeZIP(t, defaultManifest))); !errors.Is(err, ErrDefaultTheme) { + t.Fatalf("default override should fail with ErrDefaultTheme, got %v", err) + } + + if err := themeService.Activate(ctx, "minimal"); err != nil { + t.Fatalf("activate failed: %v", err) + } + if active, err := themeService.Active(ctx); err != nil || active.ID != "minimal" { + t.Fatalf("unexpected active theme: %#v, %v", active, err) + } + if err := themeService.Delete(ctx, "minimal"); !errors.Is(err, ErrThemeActive) { + t.Fatalf("active delete should fail with ErrThemeActive, got %v", err) + } + if err := os.Remove(filepath.Join(themeDir, "minimal", "dist", "index.html")); err != nil { + t.Fatal(err) + } + if active, err := themeService.Active(ctx); err != nil || active.ID != DefaultThemeID { + t.Fatalf("broken active theme did not fall back: %#v, %v", active, err) + } + appearance, err := properties.GetAppearanceConfig(ctx) + if err != nil || appearance.ActiveTheme != DefaultThemeID { + t.Fatalf("fallback did not repair appearance config: %#v, %v", appearance, err) + } + if err := themeService.Delete(ctx, "minimal"); err != nil { + t.Fatalf("inactive theme delete failed: %v", err) + } + if _, err := themeService.Get(ctx, "minimal"); !errors.Is(err, ErrThemeNotFound) { + t.Fatalf("deleted theme still exists: %v", err) + } +} + +func makeThemeZIP(t *testing.T, files map[string]string) []byte { + t.Helper() + var buffer bytes.Buffer + writer := zip.NewWriter(&buffer) + for name, content := range files { + entry, err := writer.Create(name) + if err != nil { + t.Fatal(err) + } + if _, err := entry.Write([]byte(content)); err != nil { + t.Fatal(err) + } + } + if err := writer.Close(); err != nil { + t.Fatal(err) + } + return buffer.Bytes() +} + +func makeInstallableThemeZIP(t *testing.T, manifest ThemeManifest) []byte { + t.Helper() + data, err := json.Marshal(manifest) + if err != nil { + t.Fatal(err) + } + return makeThemeZIP(t, map[string]string{ + "pika-theme.json": string(data), + "dist/index.html": ``, + "preview.png": "preview", + }) +} + +func writeOfficialWebBuild(t *testing.T, webRoot string) { + t.Helper() + if err := os.MkdirAll(filepath.Join(webRoot, "admin", "assets"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(webRoot, "admin"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(webRoot, "admin", "index.html"), []byte("admin"), 0o644); err != nil { + t.Fatal(err) + } + writeThemeRoot(t, filepath.Join(webRoot, "default-theme"), validTestManifest(DefaultThemeID)) +} + +func createValidThemeRoot(t *testing.T, id string) string { + t.Helper() + root := filepath.Join(t.TempDir(), id) + writeThemeRoot(t, root, validTestManifest(id)) + return root +} + +func writeThemeRoot(t *testing.T, root string, manifest ThemeManifest) { + t.Helper() + if err := os.MkdirAll(filepath.Join(root, "dist"), 0o755); err != nil { + t.Fatal(err) + } + data, err := json.Marshal(manifest) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(root, "pika-theme.json"), data, 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(root, "dist", "index.html"), []byte(``), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(root, "preview.png"), []byte("preview"), 0o644); err != nil { + t.Fatal(err) + } +} + +func validTestManifest(id string) ThemeManifest { + return ThemeManifest{ + SchemaVersion: 1, + ID: id, + Name: "Minimal", + Version: "1.2.3", + Author: "Pika", + Preview: "preview.png", + Entry: "dist/index.html", + APIVersion: ThemeAPIVersion, + Capabilities: append([]string(nil), requiredThemeCaps...), + } +} diff --git a/internal/service/theme_util.go b/internal/service/theme_util.go new file mode 100644 index 0000000..a7d38d2 --- /dev/null +++ b/internal/service/theme_util.go @@ -0,0 +1,265 @@ +package service + +import ( + "archive/zip" + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/dushixiang/pika/pkg/version" + "golang.org/x/text/unicode/norm" +) + +// validateOfficialWebBuild 启动时验证官方 web/dist 完整性。 +func validateOfficialWebBuild(webDir, defaultThemeDir string) error { + if version.GetVersion() == "dev" && strings.TrimSpace(os.Getenv("PIKA_WEB_DIR")) == "" { + return nil + } + requiredFiles := []string{ + filepath.Join(webDir, "admin", "index.html"), + filepath.Join(defaultThemeDir, "pika-theme.json"), + filepath.Join(defaultThemeDir, "dist", "index.html"), + } + for _, path := range requiredFiles { + stat, err := os.Stat(path) + if err != nil || !stat.Mode().IsRegular() { + return fmt.Errorf("官方 Web 构建缺失: %s", path) + } + } + assetStat, err := os.Stat(filepath.Join(webDir, "admin", "assets")) + if err != nil || !assetStat.IsDir() { + return fmt.Errorf("官方管理静态资源目录缺失: %s", filepath.Join(webDir, "admin", "assets")) + } + manifest, err := loadThemeManifest(filepath.Join(defaultThemeDir, "pika-theme.json")) + if err != nil { + return fmt.Errorf("官方默认主题清单无效: %w", err) + } + if manifest.ID != DefaultThemeID { + return errors.New("官方默认主题清单 id 必须为 default") + } + if err := validateThemeManifest(manifest, defaultThemeDir); err != nil { + return fmt.Errorf("官方默认主题无效: %w", err) + } + return nil +} + +// loadThemeManifest 读取并解析 pika-theme.json。 +func loadThemeManifest(path string) (*ThemeManifest, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + data, err := io.ReadAll(io.LimitReader(file, maxThemeManifestSize+1)) + if err != nil { + return nil, err + } + if len(data) > maxThemeManifestSize { + return nil, errors.New("主题清单超过大小限制") + } + var manifest ThemeManifest + if err := json.Unmarshal(data, &manifest); err != nil { + return nil, fmt.Errorf("解析主题清单失败: %w", err) + } + return &manifest, nil +} + +func validateThemeManifest(manifest *ThemeManifest, root string) error { + if manifest.SchemaVersion != ThemeSchemaVersion { + return fmt.Errorf("不支持的主题清单版本: %d", manifest.SchemaVersion) + } + if manifest.ID != DefaultThemeID && !validThemeID(manifest.ID) { + return errors.New("主题 id 无效或属于保留名称") + } + if strings.TrimSpace(manifest.Name) == "" || strings.TrimSpace(manifest.Author) == "" || strings.TrimSpace(manifest.Version) == "" { + return errors.New("主题清单缺少 name、author 或 version") + } + if manifest.APIVersion != ThemeAPIVersion { + return fmt.Errorf("主题 API 版本 %q 与系统 %q 不兼容", manifest.APIVersion, ThemeAPIVersion) + } + if manifest.Entry != "dist/index.html" { + return errors.New("entry 必须是 dist/index.html") + } + if err := validateRelativeFile(root, manifest.Entry, 0); err != nil { + return fmt.Errorf("主题入口无效: %w", err) + } + if err := validateThemeIndex(filepath.Join(root, manifest.Entry)); err != nil { + return fmt.Errorf("主题入口无效: %w", err) + } + if err := validateRelativeFile(root, manifest.Preview, maxThemePreviewSize); err != nil { + return fmt.Errorf("主题预览图无效: %w", err) + } + capabilities := make(map[string]bool, len(manifest.Capabilities)) + for _, capability := range manifest.Capabilities { + capabilities[capability] = true + } + for _, required := range requiredThemeCaps { + if !capabilities[required] { + return fmt.Errorf("主题缺少核心能力 %s", required) + } + } + return nil +} + +func validateThemeIndex(path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + data, err := io.ReadAll(io.LimitReader(file, maxThemeIndexSize+1)) + if err != nil { + return err + } + if len(data) == 0 || len(data) > maxThemeIndexSize { + return errors.New("index.html 为空或超过 8 MiB") + } + lower := strings.ToLower(string(data)) + if !strings.Contains(lower, "") || !strings.Contains(lower, "") { + return errors.New("index.html 缺少 head 或 body 结束标签") + } + return nil +} + +func validThemeID(id string) bool { + if !themeIDPattern.MatchString(id) { + return false + } + _, reserved := reservedThemeIDs[strings.ToLower(id)] + return !reserved +} + +// validateRelativeFile 防止文件路径穿越(含符号链接)。 +func validateRelativeFile(root, relative string, maxSize int64) error { + if relative == "" || filepath.IsAbs(relative) || strings.Contains(relative, "\\") { + return errors.New("文件路径无效") + } + clean := filepath.Clean(relative) + if clean == "." || clean == ".." || strings.HasPrefix(clean, ".."+string(os.PathSeparator)) { + return errors.New("文件路径越界") + } + full := filepath.Join(root, clean) + rel, err := filepath.Rel(root, full) + if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return errors.New("文件路径越界") + } + stat, err := os.Lstat(full) + if err != nil || !stat.Mode().IsRegular() { + return errors.New("文件不存在或不是普通文件") + } + realRoot, err := filepath.EvalSymlinks(root) + if err != nil { + return errors.New("主题根目录不可用") + } + realFile, err := filepath.EvalSymlinks(full) + if err != nil { + return errors.New("文件路径不可用") + } + realRel, err := filepath.Rel(realRoot, realFile) + if err != nil || realRel == ".." || strings.HasPrefix(realRel, ".."+string(os.PathSeparator)) { + return errors.New("文件路径经过符号链接越界") + } + if maxSize > 0 && stat.Size() > maxSize { + return errors.New("文件超过大小限制") + } + return nil +} + +// readThemeArchive 读取上传的 ZIP 数据并计算 SHA-256。 +func readThemeArchive(reader io.Reader) ([]byte, string, error) { + data, err := io.ReadAll(io.LimitReader(reader, maxThemeArchiveSize+1)) + if err != nil { + return nil, "", err + } + if len(data) == 0 || len(data) > maxThemeArchiveSize { + return nil, "", errors.New("主题压缩包为空或超过 64 MiB") + } + sum := sha256.Sum256(data) + return data, hex.EncodeToString(sum[:]), nil +} + +// extractThemeArchive 安全解压主题 ZIP,防御 ZIP Slip、符号链接、重复路径、大小溢出等。 +func extractThemeArchive(data []byte, destination string) error { + reader, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + if err != nil { + return errors.New("无法读取主题 ZIP") + } + if len(reader.File) == 0 || len(reader.File) > maxThemeFiles { + return errors.New("主题 ZIP 文件数量无效或超过限制") + } + seen := map[string]bool{} + var total uint64 + for _, file := range reader.File { + name := strings.ReplaceAll(file.Name, "\\", "/") + clean := filepath.ToSlash(filepath.Clean(name)) + if clean == "." || strings.HasPrefix(clean, "/") || strings.HasPrefix(clean, "../") || + strings.Contains(clean, "/../") || (len(clean) >= 2 && clean[1] == ':') { + return fmt.Errorf("主题 ZIP 包含非法路径: %s", file.Name) + } + key := strings.ToLower(norm.NFC.String(clean)) + if seen[key] { + return fmt.Errorf("主题 ZIP 包含重复路径: %s", file.Name) + } + seen[key] = true + mode := file.Mode() + if mode&os.ModeSymlink != 0 || (mode&os.ModeType != 0 && !mode.IsDir()) { + return fmt.Errorf("主题 ZIP 包含不支持的文件类型: %s", file.Name) + } + if file.UncompressedSize64 > maxThemeFileSize { + return fmt.Errorf("主题文件超过大小限制: %s", file.Name) + } + total += file.UncompressedSize64 + if total > maxThemeExtracted { + return errors.New("主题解压后总大小超过限制") + } + target := filepath.Join(destination, filepath.FromSlash(clean)) + rel, relErr := filepath.Rel(destination, target) + if relErr != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return errors.New("主题 ZIP 路径越界") + } + if file.FileInfo().IsDir() { + if err := os.MkdirAll(target, 0755); err != nil { + return err + } + continue + } + if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { + return err + } + src, err := file.Open() + if err != nil { + return err + } + dst, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + src.Close() + return err + } + _, copyErr := io.Copy(dst, io.LimitReader(src, maxThemeFileSize+1)) + closeErr := dst.Close() + src.Close() + if copyErr != nil { + return copyErr + } + if closeErr != nil { + return closeErr + } + } + return nil +} + +func replaceBeforeFirst(value string, marker *regexp.Regexp, insertion string) string { + location := marker.FindStringIndex(value) + if location == nil { + return value + } + return value[:location[0]] + insertion + value[location[0]:] +} diff --git a/internal/wire.go b/internal/wire.go index 11444dc..8def73d 100644 --- a/internal/wire.go +++ b/internal/wire.go @@ -39,6 +39,7 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App service.NewDDNSService, service.NewSSHLoginService, service.NewPublicIPService, + service.NewThemeService, service.NewNotifier, // WebSocket Manager @@ -55,6 +56,8 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App handler.NewDNSProviderHandler, handler.NewDDNSHandler, handler.NewSSHLoginHandler, + handler.NewThemeHandler, + handler.NewWebHandler, // App Components wire.Struct(new(AppComponents), "*"), @@ -74,6 +77,8 @@ type AppComponents struct { DNSProviderHandler *handler.DNSProviderHandler DDNSHandler *handler.DDNSHandler SSHLoginHandler *handler.SSHLoginHandler + ThemeHandler *handler.ThemeHandler + WebHandler *handler.WebHandler AgentService *service.AgentService TrafficService *service.TrafficService @@ -86,6 +91,7 @@ type AppComponents struct { DDNSService *service.DDNSService SSHLoginService *service.SSHLoginService PublicIPService *service.PublicIPService + ThemeService *service.ThemeService WSManager *websocket.Manager VMClient *vmclient.VMClient diff --git a/internal/wire_gen.go b/internal/wire_gen.go index 41ac319..c35cc49 100644 --- a/internal/wire_gen.go +++ b/internal/wire_gen.go @@ -28,6 +28,10 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App accountHandler := handler.NewAccountHandler(accountService) apiKeyService := service.NewApiKeyService(logger, db) propertyService := service.NewPropertyService(logger, db) + themeService, err := service.NewThemeService(logger, propertyService, cfg) + if err != nil { + return nil, err + } notifier := service.NewNotifier(logger) notificationService := service.NewNotificationService(logger, propertyService, notifier) trafficService := service.NewTrafficService(logger, db, notificationService) @@ -54,6 +58,8 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App dnsProviderHandler := handler.NewDNSProviderHandler(logger, propertyService) ddnsHandler := handler.NewDDNSHandler(logger, ddnsService) sshLoginHandler := handler.NewSSHLoginHandler(logger, sshLoginService) + themeHandler := handler.NewThemeHandler(logger, themeService) + webHandler := handler.NewWebHandler(themeService, propertyService) appComponents := &AppComponents{ AccountHandler: accountHandler, AgentHandler: agentHandler, @@ -65,6 +71,8 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App DNSProviderHandler: dnsProviderHandler, DDNSHandler: ddnsHandler, SSHLoginHandler: sshLoginHandler, + ThemeHandler: themeHandler, + WebHandler: webHandler, AgentService: agentService, TrafficService: trafficService, MetricService: metricService, @@ -76,6 +84,7 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App DDNSService: ddnsService, SSHLoginService: sshLoginService, PublicIPService: publicIPService, + ThemeService: themeService, WSManager: manager, VMClient: vmClient, } @@ -96,6 +105,8 @@ type AppComponents struct { DNSProviderHandler *handler.DNSProviderHandler DDNSHandler *handler.DDNSHandler SSHLoginHandler *handler.SSHLoginHandler + ThemeHandler *handler.ThemeHandler + WebHandler *handler.WebHandler AgentService *service.AgentService TrafficService *service.TrafficService @@ -108,6 +119,7 @@ type AppComponents struct { DDNSService *service.DDNSService SSHLoginService *service.SSHLoginService PublicIPService *service.PublicIPService + ThemeService *service.ThemeService WSManager *websocket.Manager VMClient *vmclient.VMClient diff --git a/web/README.md b/web/README.md index d2e7761..ed399fa 100644 --- a/web/README.md +++ b/web/README.md @@ -1,73 +1,39 @@ -# React + TypeScript + Vite +# Pika Web -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +Web 前端由两个相互隔离的 React/Vite 应用和一个主题 SDK 组成: -Currently, two official plugins are available: +- `admin/`:官方管理后台、登录和 OAuth/OIDC 回调,只发布到 `/admin/assets/*`; +- `portal/`:官方默认公开主题,只发布到 `/theme-assets/*`,构建后封装为不可删除的 `default-theme`; +- `theme-sdk/`:第三方主题可依赖的稳定公开 API 与 TypeScript 契约; +- 三个目录分别维护自己的 `package.json` 和 `package-lock.json`,根 `web` 不再是 npm workspace。 -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +两个 React 项目没有跨目录源码 import。第三方主题也不能引用 `admin` 或 `portal` 源码。 -## React Compiler +`portal/src/components` 只放跨页面共享的 UI;服务器和监控页面的私有卡片、区块及图表分别与页面放在 `portal/src/pages/servers` 和 `portal/src/pages/monitors`,避免把所有页面片段都提升为全局组件。 -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). +分别开发和构建: -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) +```bash +cd web/admin && npm ci && npm run dev +cd web/portal && npm ci && npm run dev +cd web/theme-sdk && npm ci && npm run build ``` -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: +默认开发地址分别为 portal `http://localhost:5173/`、admin `http://localhost:5174/admin/`,两者都把 `/api/*` 代理到 `http://localhost:8080`,可以同时启动。portal 只在生产构建时使用 `/theme-assets/` 资源前缀。 -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' +`admin` 和 `portal` 都只向各自目录的 `dist/` 输出。仓库发布构建使用 `make build-web` 独立安装和编译三个项目,再组装、校验以下服务端产物: -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) +```text +dist/admin/index.html +dist/admin/assets/* +dist/default-theme/pika-theme.json +dist/default-theme/dist/index.html +dist/default-theme/dist/assets/* ``` + +运行路径固定为: + +- portal:`/`、`/servers/*`、`/monitors/*`; +- admin:`/admin/*`,包括 `/admin/login` 和 OAuth/OIDC 回调; +- admin 资源:`/admin/assets/*`; +- 活动主题资源:`/theme-assets/*`。 diff --git a/web/components.json b/web/admin/components.json similarity index 100% rename from web/components.json rename to web/admin/components.json diff --git a/web/admin/eslint.config.js b/web/admin/eslint.config.js new file mode 100644 index 0000000..d9b4352 --- /dev/null +++ b/web/admin/eslint.config.js @@ -0,0 +1,14 @@ +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default [ + {ignores: ['dist', 'node_modules']}, + { + files: ['src/**/*.{ts,tsx}'], + languageOptions: { + parser: tseslint.parser, + parserOptions: {ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: {jsx: true}}, + globals: {...globals.browser, ...globals.es2022}, + }, + }, +]; diff --git a/web/admin/index.html b/web/admin/index.html new file mode 100644 index 0000000..21cb083 --- /dev/null +++ b/web/admin/index.html @@ -0,0 +1,13 @@ + + + + + + + Pika 管理后台 + + +
+ + + diff --git a/web/package-lock.json b/web/admin/package-lock.json similarity index 61% rename from web/package-lock.json rename to web/admin/package-lock.json index 4cc6be6..6fa4b7f 100644 --- a/web/package-lock.json +++ b/web/admin/package-lock.json @@ -1,18 +1,15 @@ { - "name": "web", + "name": "@pika/admin", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "web", + "name": "@pika/admin", "version": "0.0.0", "dependencies": { - "@radix-ui/react-dropdown-menu": "^2.1.20", "@radix-ui/react-scroll-area": "^1.2.14", "@radix-ui/react-slot": "^1.3.0", - "@radix-ui/react-tooltip": "^1.2.12", - "@tailwindcss/vite": "^4.3.2", "@tanstack/react-query": "^5.101.2", "@types/qs": "^6.15.1", "antd": "^6.5.0", @@ -24,26 +21,19 @@ "qs": "^6.15.3", "react": "^19.2.7", "react-dom": "^19.2.7", - "react-is": "^19.2.7", "react-router-dom": "^7.18.1", - "react-use": "^17.6.1", - "recharts": "^3.9.2", "tailwind-merge": "^3.6.0", - "tailwindcss": "^4.3.2" + "tw-animate-css": "^1.4.0" }, "devDependencies": { - "@eslint/js": "^10.0.1", + "@tailwindcss/vite": "^4.3.2", "@types/node": "^26.1.1", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@types/validator": "^13.15.10", "@vitejs/plugin-react": "^6.0.3", - "baseline-browser-mapping": "^2.10.43", "eslint": "^10.7.0", - "eslint-plugin-react-hooks": "^7.1.1", - "eslint-plugin-react-refresh": "^0.5.3", "globals": "^17.7.0", - "tw-animate-css": "^1.4.0", "typescript": "~6.0.3", "typescript-eslint": "^8.63.0", "vite": "8.1.4" @@ -142,198 +132,6 @@ "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/runtime": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", @@ -343,58 +141,11 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -406,6 +157,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -416,6 +168,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -435,9 +188,9 @@ "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -492,9 +245,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -517,27 +270,6 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, "node_modules/@eslint/object-schema": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", @@ -562,44 +294,6 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@floating-ui/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", - "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.12" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", - "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.8.0", - "@floating-ui/utils": "^0.2.12" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", - "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.8.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", - "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", - "license": "MIT" - }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -670,6 +364,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -680,6 +375,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -690,6 +386,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -699,12 +396,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -712,97 +411,53 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "node_modules/@oxc-project/types": { "version": "0.139.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@radix-ui/number": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", - "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", - "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", "license": "MIT" }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.11.tgz", - "integrity": "sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", - "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", - "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -815,9 +470,9 @@ } }, "node_modules/@radix-ui/react-context": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", - "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -830,80 +485,9 @@ } }, "node_modules/@radix-ui/react-direction": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", - "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz", - "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-effect-event": "0.0.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.20.tgz", - "integrity": "sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-menu": "2.1.20", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", - "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -915,152 +499,13 @@ } } }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz", - "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", - "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.20.tgz", - "integrity": "sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-callback-ref": "1.1.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.7.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.3.tgz", - "integrity": "sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-rect": "1.1.2", - "@radix-ui/react-use-size": "1.1.2", - "@radix-ui/rect": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz", - "integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-layout-effect": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", - "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1078,45 +523,12 @@ } }, "node_modules/@radix-ui/react-primitive": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", - "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.3.0" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz", - "integrity": "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-is-hydrated": "0.1.1", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -1134,20 +546,20 @@ } }, "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.14.tgz", - "integrity": "sha512-bBODCWZK7JTbQLHs0uIP4f73wIWatakK4OS33UzkR1x897wu0PuO658a3f+6P2GEGyDzGYMuHRatMVoAk9WZTw==", + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1165,12 +577,12 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", - "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3" + "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", @@ -1182,96 +594,10 @@ } } }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.12.tgz", - "integrity": "sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-visually-hidden": "1.2.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", - "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", - "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.3", - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", - "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz", - "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1284,9 +610,9 @@ } }, "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", - "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1298,71 +624,6 @@ } } }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", - "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", - "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.7.tgz", - "integrity": "sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", - "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", - "license": "MIT" - }, "node_modules/@rc-component/async-validator": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-6.0.0.tgz", @@ -1497,9 +758,9 @@ } }, "node_modules/@rc-component/form": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.8.5.tgz", - "integrity": "sha512-d24EYtvUOBhxEtSd/EqIu9DaMuqrWF2IRIvAFCTM6NQ/GJIYNr8DvEpUSUlv2uPxEJ0ZPwYQ+wwlGIAaiHvdrw==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.8.6.tgz", + "integrity": "sha512-1EXVsSKPZC6kGrIqxVck7kAWM6T65b6et/n5wIcyiaIa41VrDshD0nVLHoBDEIZr2ATQsOP6icb4XQkNFMLBAw==", "license": "MIT", "dependencies": { "@rc-component/async-validator": "^6.0.0", @@ -1685,9 +946,9 @@ } }, "node_modules/@rc-component/picker": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.11.0.tgz", - "integrity": "sha512-6qXGKtoJvO8sUd17m5cyNEbEJub0zflCHnaZTBBmj63DPRZYc0WEHN8rp6hFSl+yMCJS/dJY5G+1fQ8bLCuD7A==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.12.0.tgz", + "integrity": "sha512-0FZGgDiDZFMm2hcfGv4jyjm7yWIj2MiwfeTzLL0vWkO+8cCSKdiM9XhWpkn1aAsoI2EwUy48Oz2cGfvL8ZKrfw==", "license": "MIT", "dependencies": { "@rc-component/overflow": "^1.0.0", @@ -1994,9 +1255,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.10.0.tgz", - "integrity": "sha512-uC3QSG7Ax3qLOE5Q2jLqJCJc4iBtJEHzNTPhqGvlRvRcU8x8CT5moIavRVe24YSQKCp2/D1GSq7y76SCSheuVA==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.10.1.tgz", + "integrity": "sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==", "license": "MIT", "dependencies": { "@rc-component/motion": "^1.3.3", @@ -2028,29 +1289,23 @@ } }, "node_modules/@rc-component/util": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz", - "integrity": "sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.12.0.tgz", + "integrity": "sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ==", "license": "MIT", "dependencies": { "is-mobile": "^5.0.0", - "react-is": "^18.2.0" + "react-is": "^19.2.7" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, - "node_modules/@rc-component/util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, "node_modules/@rc-component/virtual-list": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.3.2.tgz", - "integrity": "sha512-/smuvWBFdP/Is9QuNDKynD0+T3XTXWFyNXXNKJ4sno8CE3bTOK8sfgYmQJtYwLUNX+lv0Ytd+PMshgpmdReq5g==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.5.1.tgz", + "integrity": "sha512-boqHxdtyWC88u8quYgEO49bcBy5fzRiOcnBge+N4nLzs2k8hUQ/yw7JE9dM6yCBE4jSm5YSHVCVMS+suBuJGKA==", "license": "MIT", "dependencies": { "@babel/runtime": "^8.0.0", @@ -2072,32 +1327,6 @@ "integrity": "sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==", "license": "MIT" }, - "node_modules/@reduxjs/toolkit": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", - "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@standard-schema/utils": "^0.3.0", - "immer": "^11.0.0", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0", - "reselect": "^5.1.0" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", - "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", @@ -2105,6 +1334,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2121,6 +1351,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2137,6 +1368,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2153,6 +1385,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2169,6 +1402,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2185,6 +1419,7 @@ "cpu": [ "arm64" ], + "dev": true, "libc": [ "glibc" ], @@ -2204,6 +1439,7 @@ "cpu": [ "arm64" ], + "dev": true, "libc": [ "musl" ], @@ -2223,6 +1459,7 @@ "cpu": [ "ppc64" ], + "dev": true, "libc": [ "glibc" ], @@ -2242,6 +1479,7 @@ "cpu": [ "s390x" ], + "dev": true, "libc": [ "glibc" ], @@ -2261,6 +1499,7 @@ "cpu": [ "x64" ], + "dev": true, "libc": [ "glibc" ], @@ -2280,6 +1519,7 @@ "cpu": [ "x64" ], + "dev": true, "libc": [ "musl" ], @@ -2299,6 +1539,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2315,6 +1556,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2333,6 +1575,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2349,6 +1592,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2362,65 +1606,57 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@standard-schema/utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "dev": true, "license": "MIT" }, "node_modules/@tailwindcss/node": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", - "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "5.21.6", + "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.2" + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", - "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-x64": "4.3.2", - "@tailwindcss/oxide-freebsd-x64": "4.3.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-x64-musl": "4.3.2", - "@tailwindcss/oxide-wasm32-wasi": "4.3.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", - "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2431,12 +1667,13 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", - "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2447,12 +1684,13 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", - "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2463,12 +1701,13 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", - "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2479,12 +1718,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", - "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2495,12 +1735,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", - "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], + "dev": true, "libc": [ "glibc" ], @@ -2514,12 +1755,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", - "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], + "dev": true, "libc": [ "musl" ], @@ -2533,12 +1775,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", - "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], + "dev": true, "libc": [ "glibc" ], @@ -2552,12 +1795,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", - "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], + "dev": true, "libc": [ "musl" ], @@ -2571,9 +1815,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", - "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2585,6 +1829,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2601,6 +1846,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { "version": "1.11.1", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -2611,6 +1857,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { "version": "1.11.1", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -2620,8 +1867,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -2631,6 +1877,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { "version": "1.1.4", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -2648,6 +1895,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { "version": "0.10.2", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -2657,19 +1905,19 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "inBundle": true, "license": "0BSD", "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", - "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2680,12 +1928,13 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", - "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2696,23 +1945,24 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", - "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "tailwindcss": "4.3.2" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "node_modules/@tanstack/query-core": { - "version": "5.101.2", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.2.tgz", - "integrity": "sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw==", + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", "license": "MIT", "funding": { "type": "github", @@ -2720,12 +1970,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.101.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.2.tgz", - "integrity": "sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg==", + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.101.2" + "@tanstack/query-core": "5.101.4" }, "funding": { "type": "github", @@ -2739,75 +1989,13 @@ "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-shape": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -2822,12 +2010,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/js-cookie": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", - "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2836,10 +2018,10 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", - "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", - "devOptional": true, + "version": "26.1.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", + "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~8.3.0" @@ -2852,9 +2034,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2862,21 +2044,15 @@ } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" } }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "license": "MIT" - }, "node_modules/@types/validator": { "version": "13.15.10", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", @@ -2885,17 +2061,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz", - "integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.63.0", - "@typescript-eslint/type-utils": "8.63.0", - "@typescript-eslint/utils": "8.63.0", - "@typescript-eslint/visitor-keys": "8.63.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2908,7 +2084,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.63.0", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -2924,16 +2100,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz", - "integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.63.0", - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/typescript-estree": "8.63.0", - "@typescript-eslint/visitor-keys": "8.63.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "engines": { @@ -2949,14 +2125,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz", - "integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.63.0", - "@typescript-eslint/types": "^8.63.0", + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "engines": { @@ -2971,14 +2147,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz", - "integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/visitor-keys": "8.63.0" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2989,9 +2165,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz", - "integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "dev": true, "license": "MIT", "engines": { @@ -3006,15 +2182,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz", - "integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/typescript-estree": "8.63.0", - "@typescript-eslint/utils": "8.63.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3031,9 +2207,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz", - "integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "dev": true, "license": "MIT", "engines": { @@ -3045,16 +2221,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz", - "integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.63.0", - "@typescript-eslint/tsconfig-utils": "8.63.0", - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/visitor-keys": "8.63.0", + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3072,30 +2248,17 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz", - "integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.63.0", - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/typescript-estree": "8.63.0" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3110,13 +2273,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz", - "integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3128,9 +2291,9 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", - "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { @@ -3153,16 +2316,10 @@ } } }, - "node_modules/@xobotyi/scrollbar-width": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", - "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==", - "license": "MIT" - }, "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", "bin": { @@ -3200,16 +2357,16 @@ } }, "node_modules/antd": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/antd/-/antd-6.5.0.tgz", - "integrity": "sha512-9zbVc9UukfGuqCvIAov01nlpDQWfARNmZQyt21ZhqLX7ilXmi4cdkp12xA48WEmXRXwZvno8A03qQuGE9JG8fg==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.5.3.tgz", + "integrity": "sha512-Q5r8sztf9Yk9B70bSUjnPYMCJ4A/eZM7uMoTj8UAhlSKR9aftjEuBEPcNSmRux7hB+87rxO8vN1X4HNjR97qyQ==", "license": "MIT", "dependencies": { "@ant-design/colors": "^8.0.1", "@ant-design/cssinjs": "^2.1.2", "@ant-design/cssinjs-utils": "^2.1.2", "@ant-design/fast-color": "^3.0.1", - "@ant-design/icons": "^6.3.1", + "@ant-design/icons": "^6.3.2", "@ant-design/react-slick": "~2.0.0", "@babel/runtime": "^7.29.2", "@rc-component/cascader": "~1.17.0", @@ -3218,8 +2375,8 @@ "@rc-component/color-picker": "~3.1.1", "@rc-component/dialog": "~1.10.0", "@rc-component/drawer": "~1.4.2", - "@rc-component/dropdown": "~1.0.2", - "@rc-component/form": "~1.8.5", + "@rc-component/dropdown": "~1.0.3", + "@rc-component/form": "~1.8.6", "@rc-component/image": "~1.9.0", "@rc-component/input": "~1.3.1", "@rc-component/input-number": "~1.6.2", @@ -3229,7 +2386,7 @@ "@rc-component/mutate-observer": "^2.0.1", "@rc-component/notification": "~2.0.7", "@rc-component/pagination": "~1.4.0", - "@rc-component/picker": "~1.11.0", + "@rc-component/picker": "~1.12.0", "@rc-component/progress": "~1.0.2", "@rc-component/qrcode": "~2.0.0", "@rc-component/rate": "~1.0.1", @@ -3239,15 +2396,15 @@ "@rc-component/slider": "~1.1.1", "@rc-component/steps": "~1.2.2", "@rc-component/switch": "~1.0.3", - "@rc-component/table": "~1.10.2", + "@rc-component/table": "~1.10.4", "@rc-component/tabs": "~1.11.0", "@rc-component/tooltip": "~1.4.0", "@rc-component/tour": "~2.4.0", "@rc-component/tree": "~1.3.2", "@rc-component/tree-select": "~1.11.0", - "@rc-component/trigger": "^3.9.1", + "@rc-component/trigger": "^3.10.1", "@rc-component/upload": "~1.1.1", - "@rc-component/util": "^1.11.1", + "@rc-component/util": "^1.12.0", "clsx": "^2.1.1", "dayjs": "^1.11.11", "scroll-into-view-if-needed": "^3.1.0", @@ -3262,18 +2419,6 @@ "react-dom": ">=18.0.0" } }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -3284,64 +2429,17 @@ "node": "18 || 20 || >=22" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.43", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", - "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/browserslist": { - "version": "4.28.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", - "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.42", - "caniuse-lite": "^1.0.30001803", - "electron-to-chromium": "^1.5.389", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": "20 || >=22" } }, "node_modules/call-bind-apply-helpers": { @@ -3373,27 +2471,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001805", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", - "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -3421,13 +2498,6 @@ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", "license": "MIT" }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "node_modules/cookie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", @@ -3462,155 +2532,12 @@ "node": ">= 8" } }, - "node_modules/css-in-js-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", - "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", - "license": "MIT", - "dependencies": { - "hyphenate-style-name": "^1.0.3" - } - }, - "node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "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" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/dayjs": { "version": "1.11.21", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", @@ -3635,12 +2562,6 @@ } } }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", - "license": "MIT" - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3652,17 +2573,12 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" } }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3677,17 +2593,11 @@ "node": ">= 0.4" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.389", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", - "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", - "dev": true, - "license": "ISC" - }, "node_modules/enhanced-resolve": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", - "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -3697,15 +2607,6 @@ "node": ">=10.13.0" } }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "license": "MIT", - "dependencies": { - "stackframe": "^1.3.4" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3736,26 +2637,6 @@ "node": ">= 0.4" } }, - "node_modules/es-toolkit": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", - "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3770,9 +2651,9 @@ } }, "node_modules/eslint": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", - "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -3782,7 +2663,7 @@ "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", + "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", @@ -3806,7 +2687,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", + "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -3828,36 +2709,6 @@ } } }, - "node_modules/eslint-plugin-react-hooks": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", - "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", - "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": "^9 || ^10" - } - }, "node_modules/eslint-scope": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", @@ -3954,16 +2805,11 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { @@ -3980,21 +2826,11 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-shallow-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", - "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" - }, - "node_modules/fastest-stable-stringify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", - "integrity": "sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==", - "license": "MIT" - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -4053,9 +2889,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -4063,6 +2899,8 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -4081,16 +2919,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -4115,15 +2943,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -4151,9 +2970,9 @@ } }, "node_modules/globals": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", - "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "version": "17.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", "dev": true, "license": "MIT", "engines": { @@ -4179,6 +2998,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/has-symbols": { @@ -4205,29 +3025,6 @@ "node": ">= 0.4" } }, - "node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "dev": true, - "license": "MIT" - }, - "node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, - "node_modules/hyphenate-style-name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", - "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4238,16 +3035,6 @@ "node": ">= 4" } }, - "node_modules/immer": { - "version": "11.1.11", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.11.tgz", - "integrity": "sha512-qzXuyXAkPySAGYkfsAwodDPWT8Zm7/Uo5BNt4BjhMhG5WlWyZZ4wQqnWwdS8kjlQ1Cwu6gjw3A6+0gTQwlyYtw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4258,24 +3045,6 @@ "node": ">=0.8.19" } }, - "node_modules/inline-style-prefixer": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", - "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", - "license": "MIT", - "dependencies": { - "css-in-js-utils": "^3.1.0" - } - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4316,37 +3085,12 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/js-cookie": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", - "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", - "license": "MIT" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4377,19 +3121,6 @@ "string-convert": "^0.2.0" } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4418,6 +3149,7 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -4450,6 +3182,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4470,6 +3203,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4490,6 +3224,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4510,6 +3245,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4530,6 +3266,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4550,6 +3287,7 @@ "cpu": [ "arm64" ], + "dev": true, "libc": [ "glibc" ], @@ -4573,6 +3311,7 @@ "cpu": [ "arm64" ], + "dev": true, "libc": [ "musl" ], @@ -4596,6 +3335,7 @@ "cpu": [ "x64" ], + "dev": true, "libc": [ "glibc" ], @@ -4619,6 +3359,7 @@ "cpu": [ "x64" ], + "dev": true, "libc": [ "musl" ], @@ -4642,6 +3383,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4662,6 +3404,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4691,20 +3434,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/lucide-react": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.24.0.tgz", - "integrity": "sha512-YT6mBD8lGKkg4nM39enlm94/sfJIiW0YKUT60fBy4YK8tai31ylg1VhGNWxkpSKHo9UagfnZqwIff3HTDQwXeA==", + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.28.0.tgz", + "integrity": "sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -4714,6 +3447,7 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -4728,20 +3462,14 @@ "node": ">= 0.4" } }, - "node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "license": "CC0-1.0" - }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -4757,30 +3485,11 @@ "dev": true, "license": "MIT" }, - "node_modules/nano-css": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.6.2.tgz", - "integrity": "sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==", - "license": "Unlicense", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "css-tree": "^1.1.2", - "csstype": "^3.1.2", - "fastest-stable-stringify": "^2.0.2", - "inline-style-prefixer": "^7.0.1", - "rtl-css-js": "^1.16.1", - "stacktrace-js": "^2.0.2", - "stylis": "^4.3.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, "node_modules/nanoid": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, "funding": [ { "type": "github", @@ -4802,16 +3511,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.51", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", - "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -4898,12 +3597,14 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4913,9 +3614,10 @@ } }, "node_modules/postcss": { - "version": "8.5.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz", - "integrity": "sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4932,7 +3634,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -4977,106 +3679,36 @@ } }, "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.7" + "react": "^19.2.8" } }, "node_modules/react-is": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", - "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", "license": "MIT" }, - "node_modules/react-redux": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", - "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", - "license": "MIT", - "dependencies": { - "@types/use-sync-external-store": "^0.0.6", - "use-sync-external-store": "^1.4.0" - }, - "peerDependencies": { - "@types/react": "^18.2.25 || ^19", - "react": "^18.0 || ^19", - "redux": "^5.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/react-router": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", - "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", + "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -5096,12 +3728,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", - "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", + "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", "license": "MIT", "dependencies": { - "react-router": "7.18.1" + "react-router": "7.18.2" }, "engines": { "node": ">=20.0.0" @@ -5111,142 +3743,11 @@ "react-dom": ">=18" } }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-universal-interface": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", - "integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==", - "peerDependencies": { - "react": "*", - "tslib": "*" - } - }, - "node_modules/react-use": { - "version": "17.6.1", - "resolved": "https://registry.npmjs.org/react-use/-/react-use-17.6.1.tgz", - "integrity": "sha512-uibb3pgzV4LFsYPHyXYGu7dD2+pyk/ZJlPH+AizBR3zolqPWyCleKcWWbUQaKSKfydwgnQ3ymGm1Ab3/saGHCA==", - "license": "Unlicense", - "dependencies": { - "@types/js-cookie": "^3.0.0", - "@xobotyi/scrollbar-width": "^1.9.5", - "copy-to-clipboard": "^3.3.1", - "fast-deep-equal": "^3.1.3", - "fast-shallow-equal": "^1.0.0", - "js-cookie": "^3.0.0", - "nano-css": "^5.6.2", - "react-universal-interface": "^0.6.2", - "resize-observer-polyfill": "^1.5.1", - "screenfull": "^5.1.0", - "set-harmonic-interval": "^1.0.1", - "throttle-debounce": "^3.0.1", - "ts-easing": "^0.2.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/react-use/node_modules/copy-to-clipboard": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", - "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", - "license": "MIT", - "dependencies": { - "toggle-selection": "^1.0.6" - } - }, - "node_modules/react-use/node_modules/throttle-debounce": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", - "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/recharts": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.9.2.tgz", - "integrity": "sha512-G4fy+Pk46RaXgwWMh+Nzhyo/lbFAVqXo9gtetlyehe6Ehge9CsgDuOTwQDD+i1+llaLktNBiNq4bhnGlDRXFtw==", - "license": "MIT", - "workspaces": [ - "www" - ], - "dependencies": { - "@reduxjs/toolkit": "^1.9.0 || 2.x.x", - "clsx": "^2.1.1", - "decimal.js-light": "^2.5.1", - "es-toolkit": "^1.39.3", - "eventemitter3": "^5.0.1", - "immer": "^11.1.8", - "react-redux": "8.x.x || 9.x.x", - "reselect": "5.2.0", - "tiny-invariant": "^1.3.3", - "use-sync-external-store": "^1.2.2", - "victory-vendor": "^37.0.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" - }, - "node_modules/redux-thunk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", - "license": "MIT", - "peerDependencies": { - "redux": "^5.0.0" - } - }, - "node_modules/reselect": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", - "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", - "license": "MIT" - }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "license": "MIT" - }, "node_modules/rolldown": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, "license": "MIT", "dependencies": { "@oxc-project/types": "=0.139.0", @@ -5276,33 +3777,12 @@ "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, - "node_modules/rtl-css-js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", - "integrity": "sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.1.2" - } - }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, - "node_modules/screenfull": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", - "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/scroll-into-view-if-needed": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", @@ -5313,13 +3793,16 @@ } }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/set-cookie-parser": { @@ -5328,15 +3811,6 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, - "node_modules/set-harmonic-interval": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz", - "integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==", - "license": "Unlicense", - "engines": { - "node": ">=6.9" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5432,69 +3906,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/stack-generator": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", - "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", - "license": "MIT", - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "license": "MIT" - }, - "node_modules/stacktrace-gps": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", - "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", - "license": "MIT", - "dependencies": { - "source-map": "0.5.6", - "stackframe": "^1.3.4" - } - }, - "node_modules/stacktrace-gps/node_modules/source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stacktrace-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", - "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", - "license": "MIT", - "dependencies": { - "error-stack-parser": "^2.0.6", - "stack-generator": "^2.0.5", - "stacktrace-gps": "^3.0.4" - } - }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", @@ -5518,15 +3939,17 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", - "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, "license": "MIT" }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5545,16 +3968,11 @@ "node": ">=12.22" } }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -5567,12 +3985,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", - "license": "MIT" - }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -5586,23 +3998,18 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-easing": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", - "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==", - "license": "Unlicense" - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "dev": true, + "license": "0BSD", + "optional": true }, "node_modules/tw-animate-css": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Wombosvideo" @@ -5636,16 +4043,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz", - "integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.63.0", - "@typescript-eslint/parser": "8.63.0", - "@typescript-eslint/typescript-estree": "8.63.0", - "@typescript-eslint/utils": "8.63.0" + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5663,39 +4070,8 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } + "license": "MIT" }, "node_modules/uri-js": { "version": "4.4.1", @@ -5707,84 +4083,11 @@ "punycode": "^2.1.0" } }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/victory-vendor": { - "version": "37.3.6", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", - "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", - "license": "MIT AND ISC", - "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" - } - }, "node_modules/vite": { "version": "8.1.4", "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", @@ -5884,13 +4187,6 @@ "node": ">=0.10.0" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5903,29 +4199,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-validation-error": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", - "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } } } } diff --git a/web/package.json b/web/admin/package.json similarity index 68% rename from web/package.json rename to web/admin/package.json index bb0ab86..c1db0eb 100644 --- a/web/package.json +++ b/web/admin/package.json @@ -1,20 +1,16 @@ { - "name": "web", + "name": "@pika/admin", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" + "lint": "eslint src" }, "dependencies": { - "@radix-ui/react-dropdown-menu": "^2.1.20", "@radix-ui/react-scroll-area": "^1.2.14", "@radix-ui/react-slot": "^1.3.0", - "@radix-ui/react-tooltip": "^1.2.12", - "@tailwindcss/vite": "^4.3.2", "@tanstack/react-query": "^5.101.2", "@types/qs": "^6.15.1", "antd": "^6.5.0", @@ -26,26 +22,19 @@ "qs": "^6.15.3", "react": "^19.2.7", "react-dom": "^19.2.7", - "react-is": "^19.2.7", "react-router-dom": "^7.18.1", - "react-use": "^17.6.1", - "recharts": "^3.9.2", "tailwind-merge": "^3.6.0", - "tailwindcss": "^4.3.2" + "tw-animate-css": "^1.4.0" }, "devDependencies": { - "@eslint/js": "^10.0.1", + "@tailwindcss/vite": "^4.3.2", "@types/node": "^26.1.1", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@types/validator": "^13.15.10", "@vitejs/plugin-react": "^6.0.3", - "baseline-browser-mapping": "^2.10.43", "eslint": "^10.7.0", - "eslint-plugin-react-hooks": "^7.1.1", - "eslint-plugin-react-refresh": "^0.5.3", "globals": "^17.7.0", - "tw-animate-css": "^1.4.0", "typescript": "~6.0.3", "typescript-eslint": "^8.63.0", "vite": "8.1.4" diff --git a/web/src/App.css b/web/admin/src/App.css similarity index 100% rename from web/src/App.css rename to web/admin/src/App.css diff --git a/web/admin/src/App.tsx b/web/admin/src/App.tsx new file mode 100644 index 0000000..2da7f93 --- /dev/null +++ b/web/admin/src/App.tsx @@ -0,0 +1,15 @@ +import {RouterProvider} from 'react-router-dom'; +import {App as AntdApp, ConfigProvider} from 'antd'; +import zhCN from 'antd/locale/zh_CN'; +import adminRouter from '@/router/admin'; +import {ColorModeProvider} from '@/shared/contexts/ColorModeContext'; + +export default function AdminApp() { + return ( + + + + + + ); +} diff --git a/web/src/admin/assets/github.svg b/web/admin/src/admin/assets/github.svg similarity index 100% rename from web/src/admin/assets/github.svg rename to web/admin/src/admin/assets/github.svg diff --git a/web/src/admin/assets/os/apple.png b/web/admin/src/admin/assets/os/apple.png similarity index 100% rename from web/src/admin/assets/os/apple.png rename to web/admin/src/admin/assets/os/apple.png diff --git a/web/src/admin/assets/os/linux.png b/web/admin/src/admin/assets/os/linux.png similarity index 100% rename from web/src/admin/assets/os/linux.png rename to web/admin/src/admin/assets/os/linux.png diff --git a/web/src/admin/assets/os/win11.png b/web/admin/src/admin/assets/os/win11.png similarity index 100% rename from web/src/admin/assets/os/win11.png rename to web/admin/src/admin/assets/os/win11.png diff --git a/web/src/admin/assets/react.svg b/web/admin/src/admin/assets/react.svg similarity index 100% rename from web/src/admin/assets/react.svg rename to web/admin/src/admin/assets/react.svg diff --git a/web/src/admin/components/PageHeader.tsx b/web/admin/src/admin/components/PageHeader.tsx similarity index 100% rename from web/src/admin/components/PageHeader.tsx rename to web/admin/src/admin/components/PageHeader.tsx diff --git a/web/src/admin/components/PrivateRoute.tsx b/web/admin/src/admin/components/PrivateRoute.tsx similarity index 91% rename from web/src/admin/components/PrivateRoute.tsx rename to web/admin/src/admin/components/PrivateRoute.tsx index d27aabc..ab5fb57 100644 --- a/web/src/admin/components/PrivateRoute.tsx +++ b/web/admin/src/admin/components/PrivateRoute.tsx @@ -15,7 +15,7 @@ const PrivateRoute = ({ children }: PrivateRouteProps) => { // 如果没有登录信息,重定向到登录页 if (!token || !userInfo) { - return ; + return ; } // 已登录,渲染子组件 diff --git a/web/src/admin/components/index.ts b/web/admin/src/admin/components/index.ts similarity index 100% rename from web/src/admin/components/index.ts rename to web/admin/src/admin/components/index.ts diff --git a/web/src/admin/pages/AdminLayout/index.tsx b/web/admin/src/admin/pages/AdminLayout/index.tsx similarity index 98% rename from web/src/admin/pages/AdminLayout/index.tsx rename to web/admin/src/admin/pages/AdminLayout/index.tsx index d07d350..261fdf5 100644 --- a/web/src/admin/pages/AdminLayout/index.tsx +++ b/web/admin/src/admin/pages/AdminLayout/index.tsx @@ -22,7 +22,7 @@ import {cn} from '@/lib/utils'; import {getServerVersion, type VersionInfo} from "@/api/version.ts"; import {flushSync} from "react-dom"; import {ScrollArea} from "@radix-ui/react-scroll-area"; -import {useTheme} from '@/portal/contexts/ThemeContext'; +import {useColorMode} from '@/shared/contexts/ColorModeContext'; interface NavItem { key: string; @@ -40,7 +40,7 @@ const AdminLayout = () => { const {message: messageApi, modal} = AntApp.useApp(); const [userInfo, setUserInfo] = useState(null); const [version, setVersion] = useState(); - const {appliedTheme, setTheme} = useTheme(); + const {resolvedColorMode: appliedTheme, setColorMode: setTheme} = useColorMode(); const themeButtonRef = useRef(null); const menuItems: NavItem[] = useMemo( @@ -96,7 +96,7 @@ const AdminLayout = () => { const userInfoStr = localStorage.getItem('userInfo'); if (!token || !userInfoStr) { - navigate('/login'); + navigate('/admin/login'); return; } diff --git a/web/src/admin/pages/Agents/AgentAudit.tsx b/web/admin/src/admin/pages/Agents/AgentAudit.tsx similarity index 100% rename from web/src/admin/pages/Agents/AgentAudit.tsx rename to web/admin/src/admin/pages/Agents/AgentAudit.tsx diff --git a/web/src/admin/pages/Agents/AgentBasicInfo.tsx b/web/admin/src/admin/pages/Agents/AgentBasicInfo.tsx similarity index 100% rename from web/src/admin/pages/Agents/AgentBasicInfo.tsx rename to web/admin/src/admin/pages/Agents/AgentBasicInfo.tsx diff --git a/web/src/admin/pages/Agents/AgentDetail.tsx b/web/admin/src/admin/pages/Agents/AgentDetail.tsx similarity index 100% rename from web/src/admin/pages/Agents/AgentDetail.tsx rename to web/admin/src/admin/pages/Agents/AgentDetail.tsx diff --git a/web/src/admin/pages/Agents/AgentEditModal.tsx b/web/admin/src/admin/pages/Agents/AgentEditModal.tsx similarity index 100% rename from web/src/admin/pages/Agents/AgentEditModal.tsx rename to web/admin/src/admin/pages/Agents/AgentEditModal.tsx diff --git a/web/src/admin/pages/Agents/AgentInstallManual.tsx b/web/admin/src/admin/pages/Agents/AgentInstallManual.tsx similarity index 100% rename from web/src/admin/pages/Agents/AgentInstallManual.tsx rename to web/admin/src/admin/pages/Agents/AgentInstallManual.tsx diff --git a/web/src/admin/pages/Agents/AgentInstallOneClick.tsx b/web/admin/src/admin/pages/Agents/AgentInstallOneClick.tsx similarity index 100% rename from web/src/admin/pages/Agents/AgentInstallOneClick.tsx rename to web/admin/src/admin/pages/Agents/AgentInstallOneClick.tsx diff --git a/web/src/admin/pages/Agents/AgentInstallShared.tsx b/web/admin/src/admin/pages/Agents/AgentInstallShared.tsx similarity index 100% rename from web/src/admin/pages/Agents/AgentInstallShared.tsx rename to web/admin/src/admin/pages/Agents/AgentInstallShared.tsx diff --git a/web/src/admin/pages/Agents/AgentList.tsx b/web/admin/src/admin/pages/Agents/AgentList.tsx similarity index 100% rename from web/src/admin/pages/Agents/AgentList.tsx rename to web/admin/src/admin/pages/Agents/AgentList.tsx diff --git a/web/src/admin/pages/Agents/AuditResultView.tsx b/web/admin/src/admin/pages/Agents/AuditResultView.tsx similarity index 100% rename from web/src/admin/pages/Agents/AuditResultView.tsx rename to web/admin/src/admin/pages/Agents/AuditResultView.tsx diff --git a/web/src/admin/pages/Agents/BatchSSHLoginConfigModal.tsx b/web/admin/src/admin/pages/Agents/BatchSSHLoginConfigModal.tsx similarity index 100% rename from web/src/admin/pages/Agents/BatchSSHLoginConfigModal.tsx rename to web/admin/src/admin/pages/Agents/BatchSSHLoginConfigModal.tsx diff --git a/web/src/admin/pages/Agents/BatchTagsModal.tsx b/web/admin/src/admin/pages/Agents/BatchTagsModal.tsx similarity index 100% rename from web/src/admin/pages/Agents/BatchTagsModal.tsx rename to web/admin/src/admin/pages/Agents/BatchTagsModal.tsx diff --git a/web/src/admin/pages/Agents/BatchTamperProtectionModal.tsx b/web/admin/src/admin/pages/Agents/BatchTamperProtectionModal.tsx similarity index 100% rename from web/src/admin/pages/Agents/BatchTamperProtectionModal.tsx rename to web/admin/src/admin/pages/Agents/BatchTamperProtectionModal.tsx diff --git a/web/src/admin/pages/Agents/BatchVisibilityModal.tsx b/web/admin/src/admin/pages/Agents/BatchVisibilityModal.tsx similarity index 100% rename from web/src/admin/pages/Agents/BatchVisibilityModal.tsx rename to web/admin/src/admin/pages/Agents/BatchVisibilityModal.tsx diff --git a/web/src/admin/pages/Agents/SSHLoginConfig.tsx b/web/admin/src/admin/pages/Agents/SSHLoginConfig.tsx similarity index 100% rename from web/src/admin/pages/Agents/SSHLoginConfig.tsx rename to web/admin/src/admin/pages/Agents/SSHLoginConfig.tsx diff --git a/web/src/admin/pages/Agents/SSHLoginEvents.tsx b/web/admin/src/admin/pages/Agents/SSHLoginEvents.tsx similarity index 100% rename from web/src/admin/pages/Agents/SSHLoginEvents.tsx rename to web/admin/src/admin/pages/Agents/SSHLoginEvents.tsx diff --git a/web/src/admin/pages/Agents/SSHLoginMonitor.tsx b/web/admin/src/admin/pages/Agents/SSHLoginMonitor.tsx similarity index 100% rename from web/src/admin/pages/Agents/SSHLoginMonitor.tsx rename to web/admin/src/admin/pages/Agents/SSHLoginMonitor.tsx diff --git a/web/src/admin/pages/Agents/TamperProtection.tsx b/web/admin/src/admin/pages/Agents/TamperProtection.tsx similarity index 100% rename from web/src/admin/pages/Agents/TamperProtection.tsx rename to web/admin/src/admin/pages/Agents/TamperProtection.tsx diff --git a/web/src/admin/pages/Agents/TamperProtectionConfig.tsx b/web/admin/src/admin/pages/Agents/TamperProtectionConfig.tsx similarity index 100% rename from web/src/admin/pages/Agents/TamperProtectionConfig.tsx rename to web/admin/src/admin/pages/Agents/TamperProtectionConfig.tsx diff --git a/web/src/admin/pages/Agents/TamperProtectionEvents.tsx b/web/admin/src/admin/pages/Agents/TamperProtectionEvents.tsx similarity index 100% rename from web/src/admin/pages/Agents/TamperProtectionEvents.tsx rename to web/admin/src/admin/pages/Agents/TamperProtectionEvents.tsx diff --git a/web/src/admin/pages/Agents/TrafficStats.tsx b/web/admin/src/admin/pages/Agents/TrafficStats.tsx similarity index 100% rename from web/src/admin/pages/Agents/TrafficStats.tsx rename to web/admin/src/admin/pages/Agents/TrafficStats.tsx diff --git a/web/src/admin/pages/Agents/useAgentInstallConfig.ts b/web/admin/src/admin/pages/Agents/useAgentInstallConfig.ts similarity index 100% rename from web/src/admin/pages/Agents/useAgentInstallConfig.ts rename to web/admin/src/admin/pages/Agents/useAgentInstallConfig.ts diff --git a/web/src/admin/pages/AlertRecords/index.tsx b/web/admin/src/admin/pages/AlertRecords/index.tsx similarity index 100% rename from web/src/admin/pages/AlertRecords/index.tsx rename to web/admin/src/admin/pages/AlertRecords/index.tsx diff --git a/web/src/admin/pages/ApiKeys/ApiKeyList.tsx b/web/admin/src/admin/pages/ApiKeys/ApiKeyList.tsx similarity index 100% rename from web/src/admin/pages/ApiKeys/ApiKeyList.tsx rename to web/admin/src/admin/pages/ApiKeys/ApiKeyList.tsx diff --git a/web/src/admin/pages/ApiKeys/ApiKeyModal.tsx b/web/admin/src/admin/pages/ApiKeys/ApiKeyModal.tsx similarity index 100% rename from web/src/admin/pages/ApiKeys/ApiKeyModal.tsx rename to web/admin/src/admin/pages/ApiKeys/ApiKeyModal.tsx diff --git a/web/src/admin/pages/ApiKeys/ShowApiKeyModal.tsx b/web/admin/src/admin/pages/ApiKeys/ShowApiKeyModal.tsx similarity index 100% rename from web/src/admin/pages/ApiKeys/ShowApiKeyModal.tsx rename to web/admin/src/admin/pages/ApiKeys/ShowApiKeyModal.tsx diff --git a/web/src/admin/pages/DDNS/DDNSModal.tsx b/web/admin/src/admin/pages/DDNS/DDNSModal.tsx similarity index 100% rename from web/src/admin/pages/DDNS/DDNSModal.tsx rename to web/admin/src/admin/pages/DDNS/DDNSModal.tsx diff --git a/web/src/admin/pages/DDNS/DNSProviderModal.tsx b/web/admin/src/admin/pages/DDNS/DNSProviderModal.tsx similarity index 100% rename from web/src/admin/pages/DDNS/DNSProviderModal.tsx rename to web/admin/src/admin/pages/DDNS/DNSProviderModal.tsx diff --git a/web/src/admin/pages/DDNS/RecordsDrawer.tsx b/web/admin/src/admin/pages/DDNS/RecordsDrawer.tsx similarity index 100% rename from web/src/admin/pages/DDNS/RecordsDrawer.tsx rename to web/admin/src/admin/pages/DDNS/RecordsDrawer.tsx diff --git a/web/src/admin/pages/DDNS/index.tsx b/web/admin/src/admin/pages/DDNS/index.tsx similarity index 100% rename from web/src/admin/pages/DDNS/index.tsx rename to web/admin/src/admin/pages/DDNS/index.tsx diff --git a/web/src/admin/pages/Login/GitHubCallback.tsx b/web/admin/src/admin/pages/Login/GitHubCallback.tsx similarity index 95% rename from web/src/admin/pages/Login/GitHubCallback.tsx rename to web/admin/src/admin/pages/Login/GitHubCallback.tsx index 3f1646a..a479a31 100644 --- a/web/src/admin/pages/Login/GitHubCallback.tsx +++ b/web/admin/src/admin/pages/Login/GitHubCallback.tsx @@ -15,7 +15,7 @@ const GitHubCallback = () => { if (!code || !state) { messageApi.error('缺少认证参数'); - navigate('/login'); + navigate('/admin/login'); return; } @@ -31,7 +31,7 @@ const GitHubCallback = () => { navigate('/admin/agents'); } catch (error: any) { messageApi.error(error.response?.data?.message || 'GitHub 认证失败'); - navigate('/login'); + navigate('/admin/login'); } }; diff --git a/web/src/admin/pages/Login/OIDCCallback.tsx b/web/admin/src/admin/pages/Login/OIDCCallback.tsx similarity index 94% rename from web/src/admin/pages/Login/OIDCCallback.tsx rename to web/admin/src/admin/pages/Login/OIDCCallback.tsx index 612c758..7e8b253 100644 --- a/web/src/admin/pages/Login/OIDCCallback.tsx +++ b/web/admin/src/admin/pages/Login/OIDCCallback.tsx @@ -18,7 +18,7 @@ const OIDCCallback = () => { ? decodeURIComponent(errorDescription) : `认证失败: ${error}`; messageApi.error(errorMessage); - navigate('/login'); + navigate('/admin/login'); return; } @@ -27,7 +27,7 @@ const OIDCCallback = () => { if (!code || !state) { messageApi.error('缺少认证参数'); - navigate('/login'); + navigate('/admin/login'); return; } @@ -43,7 +43,7 @@ const OIDCCallback = () => { navigate('/admin/agents'); } catch (error: any) { messageApi.error(error.response?.data?.message || 'OIDC 认证失败'); - navigate('/login'); + navigate('/admin/login'); } }; diff --git a/web/src/admin/pages/Login/index.tsx b/web/admin/src/admin/pages/Login/index.tsx similarity index 98% rename from web/src/admin/pages/Login/index.tsx rename to web/admin/src/admin/pages/Login/index.tsx index 981b17a..6407a8b 100644 --- a/web/src/admin/pages/Login/index.tsx +++ b/web/admin/src/admin/pages/Login/index.tsx @@ -5,7 +5,7 @@ import { GithubOutlined, GlobalOutlined, LockOutlined, UserOutlined } from '@ant import { Moon, Sun } from 'lucide-react'; import { getAuthConfig, getGitHubAuthURL, getOIDCAuthURL, login } from '@/api/auth.ts'; import type { LoginRequest } from '@/types'; -import { useTheme } from '@/portal/contexts/ThemeContext'; +import { useColorMode } from '@/shared/contexts/ColorModeContext'; import { flushSync } from 'react-dom'; const Login = () => { @@ -17,7 +17,7 @@ const Login = () => { const [githubLoading, setGithubLoading] = useState(false); const navigate = useNavigate(); const { message: messageApi } = App.useApp(); - const { appliedTheme, setTheme } = useTheme(); + const { resolvedColorMode: appliedTheme, setColorMode: setTheme } = useColorMode(); const themeButtonRef = useRef(null); useEffect(() => { @@ -249,4 +249,4 @@ const Login = () => { ); }; -export default Login; \ No newline at end of file +export default Login; diff --git a/web/src/admin/pages/ManageApiKeys/ManageApiKeyList.tsx b/web/admin/src/admin/pages/ManageApiKeys/ManageApiKeyList.tsx similarity index 100% rename from web/src/admin/pages/ManageApiKeys/ManageApiKeyList.tsx rename to web/admin/src/admin/pages/ManageApiKeys/ManageApiKeyList.tsx diff --git a/web/src/admin/pages/ManageApiKeys/ManageApiKeyModal.tsx b/web/admin/src/admin/pages/ManageApiKeys/ManageApiKeyModal.tsx similarity index 100% rename from web/src/admin/pages/ManageApiKeys/ManageApiKeyModal.tsx rename to web/admin/src/admin/pages/ManageApiKeys/ManageApiKeyModal.tsx diff --git a/web/src/admin/pages/ManageApiKeys/ShowManageApiKeyModal.tsx b/web/admin/src/admin/pages/ManageApiKeys/ShowManageApiKeyModal.tsx similarity index 100% rename from web/src/admin/pages/ManageApiKeys/ShowManageApiKeyModal.tsx rename to web/admin/src/admin/pages/ManageApiKeys/ShowManageApiKeyModal.tsx diff --git a/web/src/admin/pages/Monitors/MonitorList.tsx b/web/admin/src/admin/pages/Monitors/MonitorList.tsx similarity index 100% rename from web/src/admin/pages/Monitors/MonitorList.tsx rename to web/admin/src/admin/pages/Monitors/MonitorList.tsx diff --git a/web/src/admin/pages/Monitors/MonitorModal.tsx b/web/admin/src/admin/pages/Monitors/MonitorModal.tsx similarity index 100% rename from web/src/admin/pages/Monitors/MonitorModal.tsx rename to web/admin/src/admin/pages/Monitors/MonitorModal.tsx diff --git a/web/src/admin/pages/Settings/AlertSettings.tsx b/web/admin/src/admin/pages/Settings/AlertSettings.tsx similarity index 100% rename from web/src/admin/pages/Settings/AlertSettings.tsx rename to web/admin/src/admin/pages/Settings/AlertSettings.tsx diff --git a/web/src/admin/pages/Settings/NotificationChannels.tsx b/web/admin/src/admin/pages/Settings/NotificationChannels.tsx similarity index 100% rename from web/src/admin/pages/Settings/NotificationChannels.tsx rename to web/admin/src/admin/pages/Settings/NotificationChannels.tsx diff --git a/web/src/admin/pages/Settings/NotificationCustomHelp.tsx b/web/admin/src/admin/pages/Settings/NotificationCustomHelp.tsx similarity index 100% rename from web/src/admin/pages/Settings/NotificationCustomHelp.tsx rename to web/admin/src/admin/pages/Settings/NotificationCustomHelp.tsx diff --git a/web/src/admin/pages/Settings/PublicIPConfig.tsx b/web/admin/src/admin/pages/Settings/PublicIPConfig.tsx similarity index 100% rename from web/src/admin/pages/Settings/PublicIPConfig.tsx rename to web/admin/src/admin/pages/Settings/PublicIPConfig.tsx diff --git a/web/src/admin/pages/Settings/SystemConfig.tsx b/web/admin/src/admin/pages/Settings/SystemConfig.tsx similarity index 95% rename from web/src/admin/pages/Settings/SystemConfig.tsx rename to web/admin/src/admin/pages/Settings/SystemConfig.tsx index 872dbf3..6615b9c 100644 --- a/web/src/admin/pages/Settings/SystemConfig.tsx +++ b/web/admin/src/admin/pages/Settings/SystemConfig.tsx @@ -23,11 +23,19 @@ const SystemConfigComponent = () => { // 保存系统配置 mutation const saveMutation = useMutation({ mutationFn: saveSystemConfig, - onSuccess: () => { - messageApi.success('保存成功'); - queryClient.invalidateQueries({ queryKey: ['systemConfig'] }); - // 刷新页面以应用新的系统配置 - window.location.reload(); + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ['systemConfig'] }); + try { + const response = await fetch('/api/config'); + if (response.ok) { + const runtime = await response.json(); + window.PikaRuntime = runtime; + window.SystemConfig = runtime.legacySystemConfig; + } + } catch { + // 管理界面不依赖这次刷新成功;公开页面刷新后仍会读取最新配置。 + } + messageApi.success('保存成功,公开页面刷新后生效'); }, onError: (error: unknown) => { messageApi.error(getErrorMessage(error, '保存失败')); diff --git a/web/admin/src/admin/pages/Settings/ThemeSettings.tsx b/web/admin/src/admin/pages/Settings/ThemeSettings.tsx new file mode 100644 index 0000000..8b7f1d0 --- /dev/null +++ b/web/admin/src/admin/pages/Settings/ThemeSettings.tsx @@ -0,0 +1,112 @@ +import {useEffect, useState} from 'react'; +import {Alert, App, Button, Card, Col, Image, Popconfirm, Row, Space, Spin, Tag, Typography, Upload} from 'antd'; +import {Palette, ShieldAlert, Trash2, UploadCloud} from 'lucide-react'; +import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; +import {activateTheme, deleteTheme, listThemes, type ThemeInfo, uploadTheme} from '@/api/theme'; +import {getErrorMessage} from '@/lib/utils'; + +const {Text, Paragraph} = Typography; + +const ThemePreview = ({theme}: {theme: ThemeInfo}) => { + const [source, setSource] = useState(); + useEffect(() => { + const controller = new AbortController(); + let objectURL = ''; + const token = localStorage.getItem('token'); + fetch(theme.previewUrl, { + signal: controller.signal, + headers: token ? {Authorization: 'Bearer ' + token} : {}, + }) + .then((response) => response.ok ? response.blob() : Promise.reject()) + .then((blob) => { + objectURL = URL.createObjectURL(blob); + setSource(objectURL); + }) + .catch(() => setSource(undefined)); + return () => { + controller.abort(); + if (objectURL) URL.revokeObjectURL(objectURL); + }; + }, [theme.id, theme.previewUrl]); + return source + ? {theme.name}/ + :
; +}; + +const ThemeSettings = () => { + const {message, modal} = App.useApp(); + const queryClient = useQueryClient(); + const themesQuery = useQuery({queryKey: ['themes'], queryFn: listThemes}); + const refresh = () => queryClient.invalidateQueries({queryKey: ['themes']}); + const operation = useMutation({ + mutationFn: async (fn: () => Promise) => fn(), + onSuccess: async () => { message.success('操作成功'); await refresh(); }, + onError: (error) => message.error(getErrorMessage(error, '主题操作失败')), + }); + + const confirmUntrusted = (title: string, action: () => Promise) => { + modal.confirm({ + title, + icon: , + width: 560, + content: ( + + ), + okText: '我信任此主题,继续', + okButtonProps: {danger: true}, + onOk: () => operation.mutateAsync(action), + }); + }; + + const installedCards = (themesQuery.data || []).map((theme) => ( + + } + actions={[ + theme.active ? 当前主题 : + , + theme.official ? 内置 : + operation.mutateAsync(() => deleteTheme(theme.id))}> + + , + ]}> + {theme.name}{theme.version}{theme.official && 官方}} + description={<> + {theme.description || '暂无说明'} + 作者:{theme.author} + {!theme.compatible && } + }/> + + + )); + + return ( +
+
+
+

主题管理

+

安装和切换完整的公开页面主题;管理后台始终使用官方界面。

+
+ { + confirmUntrusted('上传并安装 ' + file.name, () => uploadTheme(file as File)); + return false; + }}> + + +
+ + {themesQuery.isLoading + ?
+ : {installedCards}} +
+ ); +}; + +export default ThemeSettings; diff --git a/web/src/admin/pages/Settings/index.tsx b/web/admin/src/admin/pages/Settings/index.tsx similarity index 86% rename from web/src/admin/pages/Settings/index.tsx rename to web/admin/src/admin/pages/Settings/index.tsx index 5067a9b..a702a38 100644 --- a/web/src/admin/pages/Settings/index.tsx +++ b/web/admin/src/admin/pages/Settings/index.tsx @@ -1,9 +1,10 @@ import {Tabs} from 'antd'; -import {Bell, MessageSquare, Settings2, Wifi} from 'lucide-react'; +import {Bell, MessageSquare, Palette, Settings2, Wifi} from 'lucide-react'; import AlertSettings from './AlertSettings'; import NotificationChannels from './NotificationChannels'; import SystemConfig from './SystemConfig'; import PublicIPConfig from './PublicIPConfig'; +import ThemeSettings from './ThemeSettings'; import {PageHeader} from "@admin/components"; import {useSearchParams} from "react-router-dom"; @@ -39,6 +40,16 @@ const Settings = () => { ), children: , }, + { + key: 'themes', + label: ( + + + 主题管理 + + ), + children: , + }, { key: 'channels', label: ( diff --git a/web/src/api/agent.ts b/web/admin/src/api/agent.ts similarity index 100% rename from web/src/api/agent.ts rename to web/admin/src/api/agent.ts diff --git a/web/src/api/alert.ts b/web/admin/src/api/alert.ts similarity index 100% rename from web/src/api/alert.ts rename to web/admin/src/api/alert.ts diff --git a/web/src/api/apiKey.ts b/web/admin/src/api/apiKey.ts similarity index 100% rename from web/src/api/apiKey.ts rename to web/admin/src/api/apiKey.ts diff --git a/web/src/api/auth.ts b/web/admin/src/api/auth.ts similarity index 100% rename from web/src/api/auth.ts rename to web/admin/src/api/auth.ts diff --git a/web/src/api/ddns.ts b/web/admin/src/api/ddns.ts similarity index 100% rename from web/src/api/ddns.ts rename to web/admin/src/api/ddns.ts diff --git a/web/src/api/dnsProvider.ts b/web/admin/src/api/dnsProvider.ts similarity index 100% rename from web/src/api/dnsProvider.ts rename to web/admin/src/api/dnsProvider.ts diff --git a/web/src/api/manageApiKey.ts b/web/admin/src/api/manageApiKey.ts similarity index 100% rename from web/src/api/manageApiKey.ts rename to web/admin/src/api/manageApiKey.ts diff --git a/web/src/api/monitor.ts b/web/admin/src/api/monitor.ts similarity index 100% rename from web/src/api/monitor.ts rename to web/admin/src/api/monitor.ts diff --git a/web/src/api/property.ts b/web/admin/src/api/property.ts similarity index 100% rename from web/src/api/property.ts rename to web/admin/src/api/property.ts diff --git a/web/src/api/request.ts b/web/admin/src/api/request.ts similarity index 98% rename from web/src/api/request.ts rename to web/admin/src/api/request.ts index 7d78fed..5a2c43e 100644 --- a/web/src/api/request.ts +++ b/web/admin/src/api/request.ts @@ -82,7 +82,7 @@ const sendRequest = async (url: string, config: RequestConfig = {}): Promise< localStorage.removeItem('token'); localStorage.removeItem('userInfo'); if (window.location.pathname.startsWith('/admin')) { - window.location.href = '/login'; + window.location.href = '/admin/login'; } throw new HttpError('未认证或认证已过期', { status: response.status, diff --git a/web/src/api/tamper.ts b/web/admin/src/api/tamper.ts similarity index 100% rename from web/src/api/tamper.ts rename to web/admin/src/api/tamper.ts diff --git a/web/admin/src/api/theme.ts b/web/admin/src/api/theme.ts new file mode 100644 index 0000000..8fb35fc --- /dev/null +++ b/web/admin/src/api/theme.ts @@ -0,0 +1,17 @@ +import {del, get, post, put} from './request'; + +export interface ThemeInfo { + schemaVersion: number; id: string; name: string; description?: string; version: string; author: string; + homepage?: string; license?: string; active: boolean; official: boolean; + compatible: boolean; compatibilityError?: string; previewUrl: string; +} +export interface ThemeInstallResult { theme: ThemeInfo; sha256: string; } + +export const listThemes = async () => (await get('/admin/themes')).data; +export const uploadTheme = async (file: File) => { + const form = new FormData(); + form.append('file', file); + return (await post('/admin/themes/upload', form, {timeout: 120000})).data; +}; +export const activateTheme = async (id: string) => { await put(`/admin/themes/${encodeURIComponent(id)}/activate`); }; +export const deleteTheme = async (id: string) => { await del(`/admin/themes/${encodeURIComponent(id)}`); }; diff --git a/web/src/api/version.ts b/web/admin/src/api/version.ts similarity index 100% rename from web/src/api/version.ts rename to web/admin/src/api/version.ts diff --git a/web/admin/src/bootstrap.tsx b/web/admin/src/bootstrap.tsx new file mode 100644 index 0000000..15af063 --- /dev/null +++ b/web/admin/src/bootstrap.tsx @@ -0,0 +1,39 @@ +import {StrictMode, type ReactNode} from 'react'; +import {createRoot} from 'react-dom/client'; +import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; +import dayjs from 'dayjs'; +import 'dayjs/locale/zh-cn'; +import './index.css'; + +dayjs.locale('zh-cn'); + +export const renderApplication = (application: ReactNode) => { + const queryClient = new QueryClient({ + defaultOptions: {queries: {refetchOnWindowFocus: false, retry: 1}}, + }); + createRoot(document.getElementById('root')!).render( + + {application} + , + ); +}; + +export const loadRuntimeConfig = async () => { + if (window.PikaRuntime) return; + const response = await fetch('/api/config'); + if (!response.ok) throw new Error('无法加载 Pika 运行时配置'); + const payload = await response.json(); + window.PikaRuntime = payload; + window.SystemConfig = payload.legacySystemConfig; +}; + +export const ensureRuntimeFallback = () => { + if (window.SystemConfig) return; + window.SystemConfig = { + SystemNameZh: '皮卡监控', + SystemNameEn: 'Pika Monitor', + ICPCode: '', + DefaultView: 'grid', + Version: '', + }; +}; diff --git a/web/src/components/ui/button.tsx b/web/admin/src/components/ui/button.tsx similarity index 100% rename from web/src/components/ui/button.tsx rename to web/admin/src/components/ui/button.tsx diff --git a/web/src/components/ui/scroll-area.tsx b/web/admin/src/components/ui/scroll-area.tsx similarity index 100% rename from web/src/components/ui/scroll-area.tsx rename to web/admin/src/components/ui/scroll-area.tsx diff --git a/web/admin/src/global.d.ts b/web/admin/src/global.d.ts new file mode 100644 index 0000000..67066d3 --- /dev/null +++ b/web/admin/src/global.d.ts @@ -0,0 +1,30 @@ +export {}; + +declare global { + interface AdminRuntimeConfig { + apiVersion: 'v1'; + system: { + nameZh: string; + nameEn: string; + logo: string; + icpCode: string; + version: string; + defaultView: string; + defaultColorMode: 'light' | 'dark' | 'system'; + }; + theme: {id: string; version: string}; + features: Record; + legacySystemConfig: Window['SystemConfig']; + } + + interface Window { + PikaRuntime: AdminRuntimeConfig; + SystemConfig: { + SystemNameZh: string; + SystemNameEn: string; + ICPCode: string; + DefaultView: string; + Version: string; + }; + } +} diff --git a/web/src/index.css b/web/admin/src/index.css similarity index 100% rename from web/src/index.css rename to web/admin/src/index.css diff --git a/web/src/lib/format.ts b/web/admin/src/lib/format.ts similarity index 100% rename from web/src/lib/format.ts rename to web/admin/src/lib/format.ts diff --git a/web/src/lib/strings.ts b/web/admin/src/lib/strings.ts similarity index 100% rename from web/src/lib/strings.ts rename to web/admin/src/lib/strings.ts diff --git a/web/src/lib/utils.ts b/web/admin/src/lib/utils.ts similarity index 100% rename from web/src/lib/utils.ts rename to web/admin/src/lib/utils.ts diff --git a/web/admin/src/main.tsx b/web/admin/src/main.tsx new file mode 100644 index 0000000..9b42b49 --- /dev/null +++ b/web/admin/src/main.tsx @@ -0,0 +1,9 @@ +import AdminApp from './App'; +import {ensureRuntimeFallback, loadRuntimeConfig, renderApplication} from './bootstrap'; + +loadRuntimeConfig() + .catch((error) => { + console.error(error); + ensureRuntimeFallback(); + }) + .finally(() => renderApplication()); diff --git a/web/admin/src/router/admin.tsx b/web/admin/src/router/admin.tsx new file mode 100644 index 0000000..3ae3770 --- /dev/null +++ b/web/admin/src/router/admin.tsx @@ -0,0 +1,49 @@ +import {createBrowserRouter, Navigate} from 'react-router-dom'; +import {type ComponentType, lazy, type LazyExoticComponent, Suspense} from 'react'; +import PrivateRoute from '@admin/components/PrivateRoute'; + +const LoginPage = lazy(() => import('@admin/pages/Login')); +const GitHubCallbackPage = lazy(() => import('@admin/pages/Login/GitHubCallback')); +const OIDCCallbackPage = lazy(() => import('@admin/pages/Login/OIDCCallback')); +const AdminLayout = lazy(() => import('@admin/pages/AdminLayout')); +const AgentListPage = lazy(() => import('@admin/pages/Agents/AgentList')); +const AgentDetailPage = lazy(() => import('@admin/pages/Agents/AgentDetail')); +const AgentInstallOneClickPage = lazy(() => import('@admin/pages/Agents/AgentInstallOneClick')); +const AgentInstallManualPage = lazy(() => import('@admin/pages/Agents/AgentInstallManual')); +const ApiKeyListPage = lazy(() => import('@admin/pages/ApiKeys/ApiKeyList')); +const ManageApiKeyListPage = lazy(() => import('@admin/pages/ManageApiKeys/ManageApiKeyList')); +const SettingsPage = lazy(() => import('@admin/pages/Settings')); +const MonitorListPage = lazy(() => import('@admin/pages/Monitors/MonitorList')); +const DDNSPage = lazy(() => import('@admin/pages/DDNS')); +const AlertRecordListPage = lazy(() => import('@admin/pages/AlertRecords')); + +const lazyLoad = (Component: LazyExoticComponent>) => ( + 页面加载中...}> + + +); + +export default createBrowserRouter([ + {path: '/admin/login', element: lazyLoad(LoginPage)}, + {path: '/admin/github/callback', element: lazyLoad(GitHubCallbackPage)}, + {path: '/admin/oidc/callback', element: lazyLoad(OIDCCallbackPage)}, + { + path: '/admin', + element: , + children: [ + {index: true, element: }, + {path: 'agents', element: lazyLoad(AgentListPage)}, + {path: 'agents/:id', element: lazyLoad(AgentDetailPage)}, + {path: 'agents-install', element: }, + {path: 'agents-install/one-click', element: lazyLoad(AgentInstallOneClickPage)}, + {path: 'agents-install/manual', element: lazyLoad(AgentInstallManualPage)}, + {path: 'api-keys', element: lazyLoad(ApiKeyListPage)}, + {path: 'manage-api-keys', element: lazyLoad(ManageApiKeyListPage)}, + {path: 'monitors', element: lazyLoad(MonitorListPage)}, + {path: 'ddns', element: lazyLoad(DDNSPage)}, + {path: 'alert-records', element: lazyLoad(AlertRecordListPage)}, + {path: 'settings', element: lazyLoad(SettingsPage)}, + ], + }, + {path: '*', element: }, +]); diff --git a/web/admin/src/shared/contexts/ColorModeContext.tsx b/web/admin/src/shared/contexts/ColorModeContext.tsx new file mode 100644 index 0000000..dfc06f2 --- /dev/null +++ b/web/admin/src/shared/contexts/ColorModeContext.tsx @@ -0,0 +1,56 @@ +import {createContext, useContext, useEffect, useState, type ReactNode} from 'react'; + +export type ColorMode = 'light' | 'dark' | 'system'; +export type ResolvedColorMode = 'light' | 'dark'; + +interface ColorModeContextValue { + colorMode: ColorMode; + resolvedColorMode: ResolvedColorMode; + setColorMode: (mode: ColorMode) => void; +} + +const ColorModeContext = createContext(undefined); + +export const ColorModeProvider = ({children}: {children: ReactNode}) => { + const [colorMode, setColorModeState] = useState(() => { + const saved = localStorage.getItem('colorMode') || localStorage.getItem('theme'); + if (saved === 'auto') return 'system'; + if (saved === 'light' || saved === 'dark' || saved === 'system') return saved; + return window.PikaRuntime?.system?.defaultColorMode || 'system'; + }); + const [resolvedColorMode, setResolvedColorMode] = useState('dark'); + + const setColorMode = (mode: ColorMode) => { + setColorModeState(mode); + localStorage.setItem('colorMode', mode); + localStorage.removeItem('theme'); + }; + + useEffect(() => { + const apply = () => { + const resolved = colorMode === 'system' + ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') + : colorMode; + setResolvedColorMode(resolved); + document.documentElement.classList.toggle('dark', resolved === 'dark'); + document.documentElement.dataset.colorMode = resolved; + }; + apply(); + if (colorMode !== 'system') return; + const media = window.matchMedia('(prefers-color-scheme: dark)'); + media.addEventListener('change', apply); + return () => media.removeEventListener('change', apply); + }, [colorMode]); + + return ( + + {children} + + ); +}; + +export const useColorMode = () => { + const value = useContext(ColorModeContext); + if (!value) throw new Error('useColorMode must be used within ColorModeProvider'); + return value; +}; diff --git a/web/src/types/ddns.ts b/web/admin/src/types/ddns.ts similarity index 100% rename from web/src/types/ddns.ts rename to web/admin/src/types/ddns.ts diff --git a/web/src/types/index.ts b/web/admin/src/types/index.ts similarity index 100% rename from web/src/types/index.ts rename to web/admin/src/types/index.ts diff --git a/web/tsconfig.app.json b/web/admin/tsconfig.app.json similarity index 92% rename from web/tsconfig.app.json rename to web/admin/tsconfig.app.json index f3aa749..cd31f99 100644 --- a/web/tsconfig.app.json +++ b/web/admin/tsconfig.app.json @@ -21,9 +21,6 @@ "@/*": [ "./src/*" ], - "@portal/*": [ - "./src/portal/*" - ], "@admin/*": [ "./src/admin/*" ] diff --git a/web/tsconfig.json b/web/admin/tsconfig.json similarity index 83% rename from web/tsconfig.json rename to web/admin/tsconfig.json index a022ec1..8304678 100644 --- a/web/tsconfig.json +++ b/web/admin/tsconfig.json @@ -13,9 +13,6 @@ "@/*": [ "./src/*" ], - "@portal/*": [ - "./src/portal/*" - ], "@admin/*": [ "./src/admin/*" ] diff --git a/web/tsconfig.node.json b/web/admin/tsconfig.node.json similarity index 100% rename from web/tsconfig.node.json rename to web/admin/tsconfig.node.json diff --git a/web/admin/vite.config.ts b/web/admin/vite.config.ts new file mode 100644 index 0000000..bdb6c6c --- /dev/null +++ b/web/admin/vite.config.ts @@ -0,0 +1,23 @@ +import {defineConfig} from 'vite'; +import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; +import path from 'path'; + +export default defineConfig({ + base: '/admin/', + plugins: [react(), tailwindcss()], + publicDir: false, + resolve: {alias: { + '@': path.resolve(__dirname, './src'), + '@admin': path.resolve(__dirname, './src/admin'), + }}, + build: { + outDir: path.resolve(__dirname, 'dist'), + emptyOutDir: true, + assetsDir: 'assets', + }, + server: { + port: 5174, + proxy: {'/api/': {target: 'http://localhost:8080/', changeOrigin: true}}, + }, +}); diff --git a/web/index.html b/web/index.html deleted file mode 100644 index 9ea394e..0000000 --- a/web/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - [[.SystemNameZh]][[if and .SystemNameZh .SystemNameEn]] | [[end]][[.SystemNameEn]] - - - - - - -
- - - - diff --git a/web/portal/eslint.config.js b/web/portal/eslint.config.js new file mode 100644 index 0000000..d9b4352 --- /dev/null +++ b/web/portal/eslint.config.js @@ -0,0 +1,14 @@ +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default [ + {ignores: ['dist', 'node_modules']}, + { + files: ['src/**/*.{ts,tsx}'], + languageOptions: { + parser: tseslint.parser, + parserOptions: {ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: {jsx: true}}, + globals: {...globals.browser, ...globals.es2022}, + }, + }, +]; diff --git a/web/portal/index.html b/web/portal/index.html new file mode 100644 index 0000000..02bcbc6 --- /dev/null +++ b/web/portal/index.html @@ -0,0 +1,15 @@ + + + + + + + Pika Monitor + + + +
+ + + + diff --git a/web/portal/package-lock.json b/web/portal/package-lock.json new file mode 100644 index 0000000..141c3e7 --- /dev/null +++ b/web/portal/package-lock.json @@ -0,0 +1,3618 @@ +{ + "name": "@pika/default-theme", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@pika/default-theme", + "version": "1.0.0", + "dependencies": { + "@radix-ui/react-tooltip": "^1.2.12", + "@tanstack/react-query": "^5.101.2", + "clsx": "^2.1.1", + "dayjs": "^1.11.21", + "lucide-react": "^1.24.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-router-dom": "^7.18.1", + "recharts": "^3.9.2", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.2", + "@types/node": "^26.1.1", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "eslint": "^10.7.0", + "globals": "^17.7.0", + "typescript": "~6.0.3", + "typescript-eslint": "^8.63.0", + "vite": "8.1.4" + } + }, + "../theme-sdk": { + "name": "@pika-monitor/theme-sdk", + "version": "1.0.0", + "extraneous": true, + "devDependencies": { + "typescript": "~6.0.3" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", + "license": "MIT" + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", + "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-toolkit": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz", + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types" + ] + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "11.1.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.15.tgz", + "integrity": "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lucide-react": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.28.0.tgz", + "integrity": "sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", + "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", + "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/recharts": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^11.1.8", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.2.0", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "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" + } + }, + "node_modules/vite": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", + "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.16", + "rolldown": "~1.1.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/web/portal/package.json b/web/portal/package.json new file mode 100644 index 0000000..7375a3f --- /dev/null +++ b/web/portal/package.json @@ -0,0 +1,36 @@ +{ + "name": "@pika/default-theme", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint src" + }, + "dependencies": { + "@radix-ui/react-tooltip": "^1.2.12", + "@tanstack/react-query": "^5.101.2", + "clsx": "^2.1.1", + "dayjs": "^1.11.21", + "lucide-react": "^1.24.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-router-dom": "^7.18.1", + "recharts": "^3.9.2", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.2", + "@types/node": "^26.1.1", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "eslint": "^10.7.0", + "globals": "^17.7.0", + "typescript": "~6.0.3", + "typescript-eslint": "^8.63.0", + "vite": "8.1.4" + } +} diff --git a/web/portal/pika-theme.json b/web/portal/pika-theme.json new file mode 100644 index 0000000..8fcf07a --- /dev/null +++ b/web/portal/pika-theme.json @@ -0,0 +1,14 @@ +{ + "schemaVersion": 1, + "id": "default", + "name": "Pika Official", + "description": "Pika 官方默认公开主题", + "version": "1.0.0", + "author": "dushixiang", + "homepage": "https://github.com/dushixiang/pika", + "license": "MIT", + "preview": "dist/logo.png", + "entry": "dist/index.html", + "apiVersion": "v1", + "capabilities": ["server-list", "server-detail", "monitor-list", "monitor-detail"] +} diff --git a/web/public/logo.png b/web/portal/public/logo.png similarity index 100% rename from web/public/logo.png rename to web/portal/public/logo.png diff --git a/web/portal/src/App.tsx b/web/portal/src/App.tsx new file mode 100644 index 0000000..5ee5a49 --- /dev/null +++ b/web/portal/src/App.tsx @@ -0,0 +1,28 @@ +import {lazy, Suspense} from 'react'; +import {BrowserRouter, Route, Routes} from 'react-router-dom'; +import {ColorModeProvider} from './contexts/ColorMode'; + +const PublicLayout = lazy(() => import('./pages/PublicLayout')); +const ServerList = lazy(() => import('./pages/ServerList')); +const ServerDetail = lazy(() => import('./pages/ServerDetail')); +const MonitorList = lazy(() => import('./pages/MonitorList')); +const MonitorDetail = lazy(() => import('./pages/MonitorDetail')); + +export default function PortalApp() { + return ( + + + 页面加载中...}> + + }> + }/> + }/> + }/> + }/> + + + + + + ); +} diff --git a/web/portal/src/api.ts b/web/portal/src/api.ts new file mode 100644 index 0000000..4cef8b9 --- /dev/null +++ b/web/portal/src/api.ts @@ -0,0 +1,76 @@ +/* Pika 公开 API 客户端。自动拼接 /api 前缀并从 localStorage 读取登录 token。 */ + +import type { + PikaRuntimeConfig, ColorMode, + Agent, LatestMetrics, PublicMonitor, AgentMonitorStat, + MetricsResponse, MetricsParams, HistoryParams, + TagsResponse, NetworkInterfacesResponse, ListAgentParams, CurrentUser, +} from './types'; + +export class PikaAPIError extends Error { + constructor(public status: number, message: string, public data?: unknown) { + super(message); + this.name = 'PikaAPIError'; + } +} + +type Query = Record; + +const request = async (path: string, query?: Query): Promise => { + const search = new URLSearchParams(); + for (const [key, value] of Object.entries(query || {})) { + if (value !== undefined) search.set(key, String(value)); + } + const token = typeof localStorage === 'undefined' ? null : localStorage.getItem('token'); + const response = await fetch('/api' + path + (search.size ? '?' + search : ''), { + headers: token ? {Authorization: 'Bearer ' + token} : {}, + }); + const contentType = response.headers.get('content-type') || ''; + const data: unknown = contentType.includes('application/json') ? await response.json() : await response.text(); + if (!response.ok) { + const message = typeof data === 'object' && data !== null && 'message' in data && typeof data.message === 'string' + ? data.message + : 'Pika API 请求失败'; + throw new PikaAPIError(response.status, message, data); + } + return data as T; +}; + +export const pika = { + getConfig: () => request('/config'), + listAgents: (params: ListAgentParams = {}) => request('/agents', params), + getAgent: (id: string) => request('/agents/' + encodeURIComponent(id)), + getLatestMetrics: (id: string) => + request('/agents/' + encodeURIComponent(id) + '/metrics/latest'), + getMetrics: (id: string, params: MetricsParams = {}) => + request('/agents/' + encodeURIComponent(id) + '/metrics', params), + getTags: () => request('/agents/tags'), + getNetworkInterfaces: (id: string) => + request('/agents/' + encodeURIComponent(id) + '/network-interfaces'), + listMonitors: () => request('/monitors'), + getMonitorStats: >(id: string) => + request('/monitors/' + encodeURIComponent(id) + '/stats'), + getMonitorAgents: (id: string) => + request('/monitors/' + encodeURIComponent(id) + '/agents'), + getMonitorHistory: (id: string, params: HistoryParams = {}) => + request('/monitors/' + encodeURIComponent(id) + '/history', params), + getCurrentUser: () => request('/admin/account/info'), +}; + +/* 运行时配置(后端在入口 HTML 注入 window.PikaRuntime) */ + +declare global { + interface Window {PikaRuntime?: PikaRuntimeConfig} +} + +export const getRuntimeConfig = (): PikaRuntimeConfig => { + if (typeof window === 'undefined' || !window.PikaRuntime) { + throw new Error('PikaRuntime 尚未注入'); + } + return window.PikaRuntime; +}; + +export const resolveColorMode = (mode: ColorMode): Exclude => { + if (mode !== 'system') return mode; + return typeof matchMedia !== 'undefined' && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +}; diff --git a/web/portal/src/components.tsx b/web/portal/src/components.tsx new file mode 100644 index 0000000..557eda4 --- /dev/null +++ b/web/portal/src/components.tsx @@ -0,0 +1,768 @@ +import {type ReactNode, useEffect, useState} from 'react'; +import {Activity, AlertCircle, Heart, Loader2, LogIn, Menu, Moon, ServerIcon, Settings, Sun, TrendingUp, X} from 'lucide-react'; +import {Link, useLocation} from 'react-router-dom'; +import dayjs from 'dayjs'; +import type {TimeRangeOption} from './types'; +import {getRuntimeConfig, pika} from './api'; +import {cn} from './lib/utils'; +import {useColorMode} from './contexts/ColorMode'; + +/* ========================================== Card ========================================== */ + +interface CardProps { + title?: ReactNode; + description?: ReactNode; + action?: ReactNode; + children: ReactNode; + className?: string; + interactive?: boolean; +} + +export const Card = ({title, description, action, children, className, interactive}: CardProps) => { + const hasHeader = title || description || action; + + return ( +
+ + + + + {interactive && ( + + )} + +
+ {hasHeader && ( +
+
+ {title &&

{title}

} + {description &&

{description}

} +
+ {action &&
{action}
} +
+ )} +
+ {children} +
+
+
+ ); +}; + +/* ========================================== ChartPlaceholder ========================================== */ + +interface ChartPlaceholderProps { + icon?: typeof TrendingUp; + title?: string; + subtitle?: string; + heightClass?: string; + className?: string; +} + +export const ChartPlaceholder = ({ + icon: Icon = TrendingUp, + title = '暂无数据', + subtitle = '等待采集新数据后展示图表', + heightClass = 'h-52', + className, + }: ChartPlaceholderProps) => { + return ( +
+
+ +

{title}

+ {subtitle && ( +

+ {subtitle} +

+ )} +
+
+ ); +}; + +/* ========================================== CustomTooltip ========================================== */ + +type CustomTooltipProps = { + active?: boolean; + payload?: Array<{ + name?: string; + value?: number; + color?: string; + dataKey?: string; + payload?: { + timestamp?: number | string; + [key: string]: unknown; + }; + }>; + label?: string | number; + unit?: string; + className?: string; + timeFormat?: string; +}; + +export const CustomTooltip = ({active, payload, label, unit = '%', className, timeFormat = 'MM-DD HH:mm'}: CustomTooltipProps) => { + if (!active || !payload || payload.length === 0) { + return null; + } + + // 从 payload 中获取完整的时间戳信息(如果有的话) + const fullTimestamp = payload[0]?.payload?.timestamp; + const displayLabel = fullTimestamp + ? dayjs(fullTimestamp).format(timeFormat) + : label; + + return ( +
+

+ {displayLabel} +

+
+ {payload.map((entry, index) => { + if (!entry) { + return null; + } + + const dotColor = entry.color ?? '#6366f1'; + const title = entry.name ?? entry.dataKey ?? `系列 ${index + 1}`; + const value = typeof entry.value === 'number' + ? Number.isFinite(entry.value) + ? entry.value.toFixed(2) + : '-' + : entry.value; + + return ( +

+ + + {title}: {value}{unit} + +

+ ); + })} +
+
+ ); +}; + +/* ========================================== EmptyState ========================================== */ + +interface EmptyStateProps { + message?: string; + showBackButton?: boolean; + className?: string; +} + +export const EmptyState = ({message = '监控数据不存在', showBackButton = false, className}: EmptyStateProps) => { + return ( +
+
+
+ +
+

+ {message} +

+ {showBackButton && ( + + )} +
+
+ ); +}; + +/* ========================================== LoadingSpinner ========================================== */ + +interface LoadingSpinnerProps { + message?: string; +} + +export const LoadingSpinner = ({message}: LoadingSpinnerProps) => { + + return ( +
+
+ +

+ {message || '数据加载中,请稍候...'} +

+
+
+ ); +}; + +/* ========================================== MetricItem ========================================== */ + +// 统计卡片组件 +export const MetricItem = ({ + label, + value, + }: { + label: string; + value: string | number; +}) => ( +
+

{label}

+

{value}

+
+); + +/* ========================================== StatBlock ========================================== */ + +// 统计卡片组件 +interface StatBlockProps { + title: string; + value: any; + unit?: string + icon: any; + color: string; + alert?: boolean, + glow?: boolean, +} + +export const StatBlock = ({title, value, unit, icon: Icon, color, alert, glow}: StatBlockProps) => { + + const colorMap = { + cyan: 'dark:text-cyan-400 dark:border-cyan-500/30 dark:bg-cyan-500/5', + emerald: 'dark:text-emerald-400 dark:border-emerald-500/30 dark:bg-emerald-500/5', + rose: 'dark:text-rose-400 dark:border-rose-500/30 dark:bg-rose-500/5', + purple: 'dark:text-purple-400 dark:border-purple-500/30 dark:bg-purple-500/5' + }; + const style = colorMap[color] || colorMap.cyan; + + const iconColor = { + cyan: 'text-cyan-400', + emerald: 'text-emerald-400', + rose: 'text-rose-400', + purple: 'text-purple-400' + } + let iconStyle = iconColor[color] || colorMap.cyan; + + return ( +
+
+
+
+
{title}
+
{value}{unit && + {unit}}
+
+
+ +
+
+
+ ); +}; + +/* ========================================== StatusBadge ========================================== */ + +interface StatusBadgeProps { + status: string; +} + +export const StatusBadge = ({status}: StatusBadgeProps) => { + const styles = { + up: "bg-emerald-500/10 dark:bg-emerald-500/10 text-emerald-400 dark:text-emerald-400 border-emerald-500/80", + down: "bg-rose-500/10 dark:bg-rose-500/10 text-rose-400 dark:text-rose-400 border-rose-500/80", + unknown: "bg-slate-100 dark:bg-slate-800 text-slate-400 dark:text-slate-300 border-slate-200 dark:border-slate-700", + }; + + const labels = { + up: "正常", + down: "异常", + unknown: "未知", + }; + + const style = styles[status as keyof typeof styles] || styles.unknown; + const label = labels[status as keyof typeof labels] || labels.unknown; + + + return ( + + + {label} + + + + ); +}; + +/* ========================================== PublicHeader ========================================== */ + +export const PublicHeader = () => { + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [currentTime, setCurrentTime] = useState(new Date()); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const {resolvedColorMode: appliedTheme, setColorMode: setTheme} = useColorMode(); + let location = useLocation(); + + + useEffect(() => { + // 检查本地是否有 token + const token = localStorage.getItem('token'); + const userInfo = localStorage.getItem('userInfo'); + + if (!token || !userInfo) { + setIsLoggedIn(false); + return; + } + + // 调用后端接口验证 token 是否有效 + pika.getCurrentUser() + .then(() => { + setIsLoggedIn(true); + }) + .catch(() => { + // token 无效,清除本地存储 + localStorage.removeItem('token'); + localStorage.removeItem('userInfo'); + setIsLoggedIn(false); + }); + }, []); + + // 时钟特效 + useEffect(() => { + const t = setInterval(() => setCurrentTime(new Date()), 1000); + return () => clearInterval(t); + }, []); + + // 判断导航是否激活 + const currentPath = location.pathname; + + let activeTab = 'servers'; + + if (currentPath.startsWith('/monitors')) { + activeTab = 'monitors'; + } + + const runtime = getRuntimeConfig(); + let systemName = runtime.system.nameEn; + + let leftName = ''; + let rightName = ''; + + if (systemName) { + // 优先在空格处分割 + const spaceIndex = systemName.indexOf(' '); + if (spaceIndex > 0) { + leftName = systemName.substring(0, spaceIndex); + rightName = systemName.substring(spaceIndex); // 保留空格 + } else { + // 如果没有空格,从中间分割 + const mid = Math.floor(systemName.length / 2); + leftName = systemName.substring(0, mid); + rightName = systemName.substring(mid); + } + } + + return ( + <> +
+
+
+ +
+
+ {'logo'} { + e.currentTarget.src = '/logo.png'; + }} + /> +
+
+

+ {leftName}{rightName} +

+

+ {runtime.system.nameZh} +

+
+
+ + + {/* HUD Navigation - Desktop Only */} +
+ {[ + {id: 'servers', icon: ServerIcon, label: '设备监控', to: '/'}, + {id: 'monitors', icon: Activity, label: '服务监控', to: '/monitors'} + ].map(tab => ( + + + + ))} +
+
+ + {/* Desktop Right Section */} +
+
+ {currentTime.toLocaleTimeString()} + {currentTime.toLocaleDateString()} +
+
+ + {/* 主题切换按钮 - Desktop */} + + + {/* 登录/管理后台按钮 - Desktop */} + {isLoggedIn ? ( + + + Admin + + ) : ( + + + Login + + )} +
+ + {/* Mobile Menu Button */} + +
+
+
+ + {/* Mobile Menu */} + {mobileMenuOpen && ( +
+
+ {/* Mobile Navigation */} + {[ + {id: 'servers', icon: ServerIcon, label: '设备监控', to: '/'}, + {id: 'monitors', icon: Activity, label: '服务监控', to: '/monitors'} + ].map(tab => ( + setMobileMenuOpen(false)} + className={` + flex items-center gap-3 p-4 rounded-lg border transition-all + ${activeTab === tab.id + ? 'bg-blue-50 dark:bg-cyan-500/20 border-blue-500 dark:border-cyan-500/80 text-blue-600 dark:text-cyan-500' + : 'bg-slate-50/50 dark:bg-cyan-500/5 border-slate-200 dark:border-cyan-500/30 text-slate-600 dark:text-slate-400 hover:bg-blue-50 dark:hover:bg-cyan-500/10 hover:border-blue-300 dark:hover:border-cyan-500/50' + } + `} + > + + {tab.label} + + ))} + + {/* Divider */} +
+ + {/* Mobile Theme Toggle Button */} + + + {/* Mobile Login/Admin Button */} + {isLoggedIn ? ( + + + 管理后台 + + ) : ( + + + 登录 + + )} +
+
+ )} + + ); +}; + +/* ========================================== PublicFooter ========================================== */ + +export const PublicFooter = () => { + const currentYear = new Date().getFullYear(); + const runtime = getRuntimeConfig(); + const icpCode = runtime.system.icpCode; + + return ( +
+
+
+
+
+ © {currentYear} + | + {/* GitHub 链接 */} + + Pika Monitor + + {runtime.system.version} + {/* ICP 备案号 */} + {icpCode && ( + <> + | + + {icpCode} + + + )} +
+
+ 用 + + 构建 +
+
+
+
+
+
+ ); +}; + +/* ========================================== TimeRangeSelector ========================================== */ + +interface CustomRange { + start: number; + end: number; +} + +interface TimeRangeSelectorProps { + value: string; + onChange: (value: string) => void; + options: readonly TimeRangeOption[]; + enableCustom?: boolean; + customRange?: CustomRange | null; + onCustomRangeApply?: (range: CustomRange) => void; + className?: string; +} + +const parseDateTimeLocal = (value: string): number | null => { + if (!value) return null; + const timestamp = new Date(value).getTime(); + if (Number.isNaN(timestamp)) { + return null; + } + return timestamp; +}; + +const toDateTimeLocal = (timestamp: number): string => { + const date = new Date(timestamp); + const offsetMs = date.getTimezoneOffset() * 60000; + return new Date(timestamp - offsetMs).toISOString().slice(0, 16); +}; + +export const TimeRangeSelector = ({ + value, + onChange, + options, + enableCustom = false, + customRange, + onCustomRangeApply, + className, + }: TimeRangeSelectorProps) => { + const [customStart, setCustomStart] = useState(''); + const [customEnd, setCustomEnd] = useState(''); + + useEffect(() => { + if (customRange?.start) { + setCustomStart(toDateTimeLocal(customRange.start)); + } + if (customRange?.end) { + setCustomEnd(toDateTimeLocal(customRange.end)); + } + }, [customRange]); + + const startMs = parseDateTimeLocal(customStart); + const endMs = parseDateTimeLocal(customEnd); + const canApply = startMs !== null && endMs !== null && startMs < endMs; + const showCustomOption = enableCustom && value === 'custom'; + + return ( +
+ + {enableCustom && ( +
+ + 自定义 + + setCustomStart(event.target.value)} + className="rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 focus:border-blue-400 dark:focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-500/30 px-2 py-1 text-xs font-medium" + /> + + 至 + + setCustomEnd(event.target.value)} + className="rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 focus:border-blue-400 dark:focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-500/30 px-2 py-1 text-xs font-medium" + /> + +
+ )} +
+ ); +}; diff --git a/web/portal/src/constants.ts b/web/portal/src/constants.ts new file mode 100644 index 0000000..406f60e --- /dev/null +++ b/web/portal/src/constants.ts @@ -0,0 +1,49 @@ +import type {TimeRangeOption} from './types'; + +export const LIVE_RANGE = 'live'; +export const LIVE_INITIAL_RANGE = '2m'; +export const LIVE_WINDOW_MS = 2 * 60 * 1000; + +export const SERVER_TIME_RANGE_OPTIONS: TimeRangeOption[] = [ + {label: '实时', value: LIVE_RANGE}, + {label: '15分钟', value: '15m'}, + {label: '30分钟', value: '30m'}, + {label: '1小时', value: '1h'}, + {label: '3小时', value: '3h'}, + {label: '6小时', value: '6h'}, + {label: '12小时', value: '12h'}, + {label: '1天', value: '1d'}, + {label: '3天', value: '3d'}, + {label: '7天', value: '7d'}, +]; + +export const MONITOR_TIME_RANGE_OPTIONS: TimeRangeOption[] = [ + {label: '12小时', value: '12h'}, + {label: '1天', value: '1d'}, + {label: '3天', value: '3d'}, + {label: '7天', value: '7d'}, +]; + +export const AGENT_COLORS = [ + '#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899', '#06b6d4', '#f97316', '#14b8a6', +]; + +export const INTERFACE_COLORS = [ + {upload: '#6FD598', download: '#2C70F6'}, + {upload: '#f59e0b', download: '#8b5cf6'}, + {upload: '#ec4899', download: '#06b6d4'}, + {upload: '#10b981', download: '#f97316'}, + {upload: '#14b8a6', download: '#2563eb'}, +]; + +export const TEMPERATURE_COLORS: Record = { + 'CPU': '#f97316', 'GPU': '#8b5cf6', 'DISK': '#06b6d4', 'BATTERY': '#10b981', + 'CHIPSET': '#f59e0b', 'SYSTEM': '#6366f1', 'PSU': '#ec4899', +}; + +export const ACCENT_THEMES: Record<'blue' | 'emerald' | 'purple' | 'amber', { icon: string; badge: string; highlight: string }> = { + blue: {icon: 'text-blue-400', badge: 'text-blue-400', highlight: 'text-blue-400'}, + emerald: {icon: 'text-emerald-400', badge: 'text-emerald-400', highlight: 'text-emerald-400'}, + purple: {icon: 'text-purple-400', badge: 'text-purple-400', highlight: 'text-purple-400'}, + amber: {icon: 'text-amber-400', badge: 'text-amber-400', highlight: 'text-amber-400'}, +}; diff --git a/web/portal/src/contexts/ColorMode.tsx b/web/portal/src/contexts/ColorMode.tsx new file mode 100644 index 0000000..676fc8d --- /dev/null +++ b/web/portal/src/contexts/ColorMode.tsx @@ -0,0 +1,55 @@ +import {createContext, useContext, useEffect, useState, type ReactNode} from 'react'; +import {getRuntimeConfig, resolveColorMode} from '../api'; +import type {ColorMode} from '../types'; + +export type ResolvedColorMode = 'light' | 'dark'; + +interface ColorModeContextValue { + colorMode: ColorMode; + resolvedColorMode: ResolvedColorMode; + setColorMode: (mode: ColorMode) => void; +} + +const ColorModeContext = createContext(undefined); + +export const ColorModeProvider = ({children}: {children: ReactNode}) => { + const [colorMode, setColorModeState] = useState(() => { + const saved = localStorage.getItem('colorMode') || localStorage.getItem('theme'); + if (saved === 'auto') return 'system'; + if (saved === 'light' || saved === 'dark' || saved === 'system') return saved; + return getRuntimeConfig().system.defaultColorMode; + }); + const [resolvedColorMode, setResolvedColorMode] = useState('dark'); + + const setColorMode = (mode: ColorMode) => { + setColorModeState(mode); + localStorage.setItem('colorMode', mode); + localStorage.removeItem('theme'); + }; + + useEffect(() => { + const apply = () => { + const resolved = resolveColorMode(colorMode); + setResolvedColorMode(resolved); + document.documentElement.classList.toggle('dark', resolved === 'dark'); + document.documentElement.dataset.colorMode = resolved; + }; + apply(); + if (colorMode !== 'system') return; + const media = window.matchMedia('(prefers-color-scheme: dark)'); + media.addEventListener('change', apply); + return () => media.removeEventListener('change', apply); + }, [colorMode]); + + return ( + + {children} + + ); +}; + +export const useColorMode = () => { + const value = useContext(ColorModeContext); + if (!value) throw new Error('useColorMode must be used within ColorModeProvider'); + return value; +}; diff --git a/web/portal/src/hooks/index.ts b/web/portal/src/hooks/index.ts new file mode 100644 index 0000000..2fada52 --- /dev/null +++ b/web/portal/src/hooks/index.ts @@ -0,0 +1,128 @@ +import {useEffect, useRef, useState} from 'react'; +import {useQuery} from '@tanstack/react-query'; +import {pika} from '../api'; +import type {Agent, LatestMetrics, MetricsAggregation, MetricsParams, MetricsResponse} from '../types'; + +interface UseMetricsQueryOptions { + agentId: string; + type: MetricsParams['type']; + range?: string; + start?: number; + end?: number; + interfaceName?: string; + aggregation?: MetricsAggregation; + refetchIntervalMs?: number; +} + +export const useAgentQuery = (agentId?: string) => { + return useQuery({ + queryKey: ['agent', agentId], + queryFn: () => pika.getAgent(agentId!), + enabled: !!agentId, + staleTime: 60000, + }); +}; + +export const useLatestMetricsQuery = (agentId?: string, intervalMs: number = 5000) => { + return useQuery({ + queryKey: ['agent', agentId, 'metrics', 'latest'], + queryFn: () => pika.getLatestMetrics(agentId!), + enabled: !!agentId, + refetchInterval: intervalMs > 0 ? intervalMs : false, + }); +}; + +export const useMetricsQuery = ({agentId, type, range, start, end, interfaceName, aggregation, refetchIntervalMs}: UseMetricsQueryOptions) => { + return useQuery({ + queryKey: ['agent', agentId, 'metrics', type, range, start, end, interfaceName, aggregation], + queryFn: () => + pika.getMetrics(agentId, { + type, + range: start !== undefined && end !== undefined ? undefined : range, + start, + end, + interface: interfaceName, + aggregation, + }), + enabled: !!agentId, + refetchInterval: refetchIntervalMs && refetchIntervalMs > 0 ? refetchIntervalMs : false, + }); +}; + +export const useNetworkInterfacesQuery = (agentId?: string) => { + return useQuery({ + queryKey: ['agent', agentId, 'network-interfaces'], + queryFn: () => pika.getNetworkInterfaces(agentId!), + enabled: !!agentId, + }); +}; + +export function useLiveBuffer( + initial: T[], + isLive: boolean, + livePoint: T | null, + windowMs: number, + resetKey?: unknown, +): T[] { + const [buffer, setBuffer] = useState([]); + const lastTsRef = useRef(0); + const seededRef = useRef(false); + + useEffect(() => { + seededRef.current = false; + lastTsRef.current = 0; + setBuffer([]); + }, [isLive, resetKey]); + + useEffect(() => { + if (!isLive) return; + if (seededRef.current) return; + if (!initial || initial.length === 0) return; + seededRef.current = true; + + const initLastTs = initial[initial.length - 1].timestamp; + setBuffer(prev => { + if (prev.length === 0) { + return initial.slice(); + } + const earliestExisting = prev[0].timestamp; + const olderHistory = initial.filter(p => p.timestamp < earliestExisting); + return olderHistory.length > 0 ? [...olderHistory, ...prev] : prev; + }); + if (initLastTs > lastTsRef.current) { + lastTsRef.current = initLastTs; + } + }, [isLive, initial, resetKey]); + + useEffect(() => { + if (!isLive || !livePoint) return; + const ts = livePoint.timestamp; + if (!Number.isFinite(ts) || ts <= 0) return; + if (ts <= lastTsRef.current) return; + lastTsRef.current = ts; + + setBuffer(prev => { + const next = prev.length === 0 ? [livePoint] : [...prev, livePoint]; + const cutoff = ts - windowMs; + let drop = 0; + while (drop < next.length && next[drop].timestamp < cutoff) drop++; + return drop > 0 ? next.slice(drop) : next; + }); + }, [livePoint, isLive, windowMs]); + + return isLive ? buffer : initial; +} + +const MOBILE_BREAKPOINT = 768; + +export function useIsMobile() { + const [isMobile, setIsMobile] = useState(undefined); + useEffect(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); + const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + mql.addEventListener('change', onChange); + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + return () => mql.removeEventListener('change', onChange); + }, []); + return !!isMobile; +} diff --git a/web/portal/src/index.css b/web/portal/src/index.css new file mode 100644 index 0000000..7f04aa2 --- /dev/null +++ b/web/portal/src/index.css @@ -0,0 +1,169 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +/* Tailwind v4 暗色模式配置 */ +@variant dark (&:where(.dark, .dark *)); + +/* 标签过滤器滚动条样式 */ +.tag-filter-scrollbar { + scrollbar-width: thin; + scrollbar-color: rgb(203, 213, 225) transparent; + scrollbar-gutter: stable; +} + +.tag-filter-scrollbar::-webkit-scrollbar { + height: 6px; +} + +.tag-filter-scrollbar::-webkit-scrollbar-track { + background: rgb(241, 245, 249); + border-radius: 9999px; +} + +.tag-filter-scrollbar::-webkit-scrollbar-thumb { + background: rgb(203, 213, 225); + border-radius: 9999px; + transition: background-color 0.2s; +} + +.tag-filter-scrollbar::-webkit-scrollbar-thumb:hover { + background: rgb(148, 163, 184); +} + +/* 暗色模式标签过滤器滚动条 */ +.dark .tag-filter-scrollbar { + scrollbar-color: rgb(71, 85, 105) transparent; +} + +.dark .tag-filter-scrollbar::-webkit-scrollbar-track { + background: rgb(30, 41, 59); +} + +.dark .tag-filter-scrollbar::-webkit-scrollbar-thumb { + background: rgb(71, 85, 105); +} + +.dark .tag-filter-scrollbar::-webkit-scrollbar-thumb:hover { + background: rgb(100, 116, 139); +} + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --radius-2xl: calc(var(--radius) + 8px); + --radius-3xl: calc(var(--radius) + 12px); + --radius-4xl: calc(var(--radius) + 16px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/web/portal/src/lib/utils.ts b/web/portal/src/lib/utils.ts new file mode 100644 index 0000000..8974321 --- /dev/null +++ b/web/portal/src/lib/utils.ts @@ -0,0 +1,71 @@ +import {type ClassValue, clsx} from 'clsx'; +import {twMerge} from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export const formatBytes = (value: number | undefined | null, precision = 2): string => { + if (value === undefined || value === null || !Number.isFinite(value) || value <= 0) return '0 B'; + const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; + const index = Math.min(Math.floor(Math.log(value) / Math.log(1024)), units.length - 1); + return `${(value / 1024 ** index).toFixed(precision)} ${units[index]}`; +}; + +export const formatSpeed = (bytesPerSecond: number): string => { + if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) return '0 B/s'; + const units = ['B/s', 'K/s', 'M/s', 'G/s', 'T/s']; + const index = Math.min(Math.floor(Math.log(bytesPerSecond) / Math.log(1024)), units.length - 1); + const value = bytesPerSecond / 1024 ** index; + const decimals = value >= 100 ? 0 : value >= 10 ? 1 : 2; + return `${value.toFixed(decimals)} ${units[index]}`; +}; + +export const formatTime = (milliseconds: number): string => { + if (!Number.isFinite(milliseconds) || milliseconds <= 0) return '0 ms'; + if (milliseconds < 1000) return `${milliseconds.toFixed(0)} ms`; + return `${(milliseconds / 1000).toFixed(2)} s`; +}; + +export const formatDateTime = (value: string | number | undefined | null): string => { + if (value === undefined || value === null || value === '') return '-'; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return '-'; + const pad = (part: number) => String(part).padStart(2, '0'); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; +}; + +export const formatPercentValue = (value: number | undefined | null): string => { + if (value === undefined || value === null || Number.isNaN(value)) return '0.0'; + return value.toFixed(1); +}; + +export const formatUptime = (seconds: number | undefined | null): string => { + if (seconds === undefined || seconds === null) return '-'; + if (seconds <= 0) return '0 秒'; + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + if (days > 0) return `${days} 天${hours > 0 ? ` ${hours} 小时` : ''}`; + if (hours > 0) return `${hours} 小时${minutes > 0 ? ` ${minutes} 分钟` : ''}`; + return minutes > 0 ? `${minutes} 分钟` : '不到 1 分钟'; +}; + +export const formatChartTime = (timestamp: number, timeRange: string, rangeMs?: number): string => { + const date = new Date(timestamp); + if (timeRange === 'live') { + return date.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false}); + } + const isLongRange = rangeMs !== undefined + ? rangeMs >= 24 * 60 * 60 * 1000 + : timeRange === '1d' || timeRange === '24h' || (timeRange.endsWith('d') && Number.parseInt(timeRange) > 1); + if (isLongRange) { + const pad = (part: number) => String(part).padStart(2, '0'); + return `${pad(date.getMonth() + 1)}/${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`; + } + return date.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'}); +}; + +export const isExpired = (expireTime?: number) => { + return Boolean(expireTime && expireTime > 0 && expireTime - Date.now() < 30 * 24 * 60 * 60 * 1000); +}; diff --git a/web/portal/src/main.tsx b/web/portal/src/main.tsx new file mode 100644 index 0000000..23ad142 --- /dev/null +++ b/web/portal/src/main.tsx @@ -0,0 +1,51 @@ +import {StrictMode} from 'react'; +import {createRoot} from 'react-dom/client'; +import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; +import {pika} from './api'; +import type {PikaRuntimeConfig} from './types'; +import PortalApp from './App'; +import './index.css'; + +const fallbackRuntime: PikaRuntimeConfig = { + apiVersion: 'v1', + system: { + nameZh: '皮卡监控', + nameEn: 'Pika Monitor', + logo: '/api/logo', + icpCode: '', + version: '', + defaultView: 'grid', + defaultColorMode: 'system', + }, + theme: {id: 'default', version: '1.0.0'}, + features: { + serverList: true, + serverDetail: true, + monitorList: true, + monitorDetail: true, + }, +}; + +const start = async () => { + if (!window.PikaRuntime) { + try { + window.PikaRuntime = await pika.getConfig(); + } catch (error) { + window.PikaRuntime = fallbackRuntime; + console.error(error); + } + } + + const queryClient = new QueryClient({ + defaultOptions: {queries: {refetchOnWindowFocus: false, retry: 1}}, + }); + createRoot(document.getElementById('root')!).render( + + + + + , + ); +}; + +start().catch(console.error); diff --git a/web/portal/src/pages/MonitorDetail.tsx b/web/portal/src/pages/MonitorDetail.tsx new file mode 100644 index 0000000..41377b9 --- /dev/null +++ b/web/portal/src/pages/MonitorDetail.tsx @@ -0,0 +1,795 @@ +import {useEffect, useMemo, useState} from 'react'; +import {useNavigate, useParams} from 'react-router-dom'; +import {useQuery} from '@tanstack/react-query'; +import {Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; +import {AlertCircle, ArrowLeft, ChevronDown, ChevronUp, Clock, Globe, MapPin, RotateCcw, Server, ShieldCheck, Wifi} from 'lucide-react'; +import {pika} from '../api'; +import type {AgentMonitorStat, MetricsResponse, PublicMonitor} from '../types'; +import {AGENT_COLORS, MONITOR_TIME_RANGE_OPTIONS} from '../constants'; +import {cn, formatChartTime, formatDateTime, formatTime} from '../lib/utils'; +import {useIsMobile} from '../hooks'; +import { + Card, + ChartPlaceholder, + CustomTooltip, + EmptyState, + LoadingSpinner, + MetricItem, + StatusBadge, + TimeRangeSelector +} from '../components'; + +/* ========================================== TypeIcon ========================================== */ + +const TypeIcon = ({type}: {type: string}) => { + switch (type.toLowerCase()) { + case 'https': + return ; + case 'http': + return ; + case 'tcp': + return ; + case 'icmp': + case 'ping': + return ; + default: + return ; + } +}; + +/* ========================================== CertBadge ========================================== */ + +const CertBadge = ({expiryTime, daysLeft}: {expiryTime: number; daysLeft: number}) => { + if (!expiryTime || daysLeft === undefined) return null; + + const isExpired = daysLeft < 0; + let colorClass = "text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 border-emerald-200 dark:border-emerald-500/20"; + + if (isExpired) { + colorClass = "text-rose-600 dark:text-rose-400 bg-rose-50 dark:bg-rose-500/10 border-rose-200 dark:border-rose-500/20"; + } else if (daysLeft < 30) { + colorClass = "text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/10 border-amber-200 dark:border-amber-500/20"; + } + + return ( +
+ + {isExpired ? "已过期" : `${daysLeft} 天后过期`} +
+ ); +}; + +/* ========================================== MonitorHero ========================================== */ + +interface MonitorHeroProps { + monitor: PublicMonitor; + onBack: () => void; +} + +const MonitorHero = ({monitor, onBack}: MonitorHeroProps) => { + return ( + + {/* 返回按钮 */} + + + {/* 监控信息 */} +
+
+
+ +
+
+
+

{monitor.name}

+ +
+

+ {monitor.showTargetPublic ? monitor.target : '******'} +

+
+
+ + {/* 统计卡片 */} +
+ + + + +
+
+ + {/* 证书信息(如果存在证书数据)*/} + {monitor.certExpiryTime > 0 && ( +
+ SSL 证书: +
+ + + 到期时间: {formatDateTime(monitor.certExpiryTime)} + +
+
+ )} +
+ ); +}; + +/* ========================================== AgentStatsTable ========================================== */ + +interface AgentStatsTableProps { + monitorStats: AgentMonitorStat[]; + monitorType: string; +} + +const AgentStatsTable = ({monitorStats, monitorType}: AgentStatsTableProps) => { + if (monitorStats.length === 0) { + return ( +
+

暂无探针数据

+
+ ); + } + + return ( + +
+

探针监控详情

+

各探针的当前状态和统计数据

+
+ + {/* 移动端卡片布局 */} +
+ {monitorStats.map((stat, index) => { + const color = AGENT_COLORS[index % AGENT_COLORS.length]; + return ( +
+ {/* 探针名称和状态 */} +
+
+ + + + {stat.agentName || stat.agentId.substring(0, 8)} + +
+ +
+ + {/* 响应时间和最后检测 */} +
+
+ + + {formatTime(stat.responseTime)} + +
+ + {formatDateTime(stat.checkedAt)} + +
+ + {/* 证书信息 */} + {monitorType === 'https' && stat.certExpiryTime && ( +
+
+ 证书: + +
+
+ )} + + {/* 错误信息 */} + {stat.status === 'down' && stat.message && ( +
+
+ + + {stat.message} + +
+
+ )} +
+ ); + })} +
+ + {/* 桌面端表格布局 */} +
+ + + + + + + + {monitorType === 'https' && ( + + )} + + + + + {monitorStats.map((stat, index) => { + const color = AGENT_COLORS[index % AGENT_COLORS.length]; + return ( + + + + + + {monitorType === 'https' && ( + + )} + + + ); + })} + +
+ 探针名称 + + 状态 + + 响应时间 + + 最后检测 + + 证书信息 + + 错误信息 +
+
+ +
+ + + {stat.agentName || stat.agentId.substring(0, 8)} + +
+
+
+ + +
+ + + {formatTime(stat.responseTime)} + +
+
+ {formatDateTime(stat.checkedAt)} + + {stat.certExpiryTime ? ( + + ) : ( + - + )} + + {stat.status === 'down' && stat.message ? ( +
+ + + {stat.message} + +
+ ) : ( + - + )} +
+
+
+ ); +}; + +/* ========================================== ResponseTimeChart ========================================== */ + +/** + * 根据时间范围确定最大数据点数 + */ +const getMaxDataPoints = (timeRange: string): number => { + switch (timeRange) { + case '15m': + case '1h': + return 200; // 短时间:详细数据 + case '12h': + return 300; + case '24h': + return 400; + case '7d': + return 500; + case '30d': + return 600; + default: + return 400; + } +}; + +/** + * 生成不重复的颜色 + * 使用 HSL 色轮均匀分布,支持无限数量的探针 + */ +const generateColors = (count: number): string[] => { + const colors: string[] = []; + const hueStep = 360 / count; + + for (let i = 0; i < count; i++) { + const hue = (i * hueStep) % 360; + const saturation = 65 + (i % 3) * 10; + const lightness = 45 + (i % 2) * 10; + colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`); + } + + return colors; +}; + +/** + * 自定义图例组件 + */ +const CustomLegend = ({onClick, selectedAgents, allAgents, colors, collapsed}: any) => { + if (!allAgents || allAgents.length === 0) return null; + + if (collapsed) return null; + + return ( +
+ {allAgents.map((agent: {id: string; name: string}, index: number) => { + const isSelected = selectedAgents.has(agent.id); + const color = colors[index]; + + return ( +
onClick(agent.id)} + className="flex items-center gap-2 cursor-pointer transition-opacity" + style={{ + opacity: isSelected ? 1 : 0.4, + }} + > + + + + + {agent.name} + +
+ ); + })} +
+ ); +}; + +interface ResponseTimeChartProps { + monitorId: string; + monitorStats: AgentMonitorStat[]; +} + +const ResponseTimeChart = ({monitorId, monitorStats}: ResponseTimeChartProps) => { + const [selectedAgents, setSelectedAgents] = useState>(new Set()); + const [timeRange, setTimeRange] = useState('12h'); + const [customRange, setCustomRange] = useState<{start: number; end: number} | null>(null); + const [legendCollapsed, setLegendCollapsed] = useState(true); // 移动端默认收起 + const isMobile = useIsMobile(); + const customStart = timeRange === 'custom' ? customRange?.start : undefined; + const customEnd = timeRange === 'custom' ? customRange?.end : undefined; + const rangeMs = customStart !== undefined && customEnd !== undefined ? customEnd - customStart : undefined; + + // 获取历史数据 + const {data: historyData} = useQuery({ + queryKey: ['monitorHistory', monitorId, timeRange, customStart, customEnd], + queryFn: async () => { + if (!monitorId) throw new Error('Monitor ID is required'); + return pika.getMonitorHistory(monitorId, { + range: timeRange, + start: customStart, + end: customEnd, + }); + }, + refetchInterval: 30000, + enabled: !!monitorId, + }); + + // 获取所有可用的探针列表 + const availableAgents = useMemo(() => { + if (monitorStats.length === 0) return []; + return monitorStats.map(stat => ({ + id: stat.agentId, + name: stat.agentName || stat.agentId.substring(0, 8), + })); + }, [monitorStats]); + + // 初始化选中所有探针 + useEffect(() => { + if (availableAgents.length > 0 && selectedAgents.size === 0) { + setSelectedAgents(new Set(availableAgents.map(a => a.id))); + } + }, [availableAgents, selectedAgents.size]); + + // 动态生成颜色 + const colors = useMemo(() => { + return generateColors(availableAgents.length); + }, [availableAgents.length]); + + const toggleAgent = (agentId: string) => { + setSelectedAgents((current) => { + if (current.size === availableAgents.length) return new Set([agentId]); + const next = new Set(current); + if (next.has(agentId)) next.delete(agentId); + else next.add(agentId); + return next; + }); + }; + + const handleAreaClick = (data: unknown) => { + const key = (data as {dataKey?: string})?.dataKey; + if (key) toggleAgent(key.replace('agent_', '')); + }; + + // 恢复全选 + const handleSelectAll = () => { + setSelectedAgents(new Set(availableAgents.map(a => a.id))); + }; + + // 是否有探针未选中 + const hasUnselected = selectedAgents.size < availableAgents.length; + + // 切换图例显示/隐藏(仅移动端) + const toggleLegend = () => setLegendCollapsed((collapsed) => !collapsed); + + // 生成图表数据 - 使用统一时间网格对齐 + const chartData = useMemo(() => { + if (!historyData?.series) return []; + + const seriesList = historyData.series.filter(s => s.name === 'response_time'); + if (seriesList.length === 0) return []; + + // 收集所有选中探针的数据 + const selectedSeriesData: Array<{key: string; data: Array<{timestamp: number; value: number}>}> = []; + + seriesList.forEach((s) => { + const agentId = s.labels?.agent_id || 'unknown'; + if (!selectedAgents.has(agentId)) return; + if (!s.data || s.data.length === 0) return; + + selectedSeriesData.push({ + key: `agent_${agentId}`, + data: [...s.data].sort((a, b) => a.timestamp - b.timestamp) + }); + }); + + if (selectedSeriesData.length === 0) return []; + + // 确定全局时间范围 + let minTime = Infinity, maxTime = -Infinity; + selectedSeriesData.forEach(s => { + minTime = Math.min(minTime, s.data[0].timestamp); + maxTime = Math.max(maxTime, s.data[s.data.length - 1].timestamp); + }); + + if (minTime >= maxTime) return []; + + // 生成均匀的目标时间点 + const maxPoints = getMaxDataPoints(timeRange); + const timeStep = (maxTime - minTime) / (maxPoints - 1); + const targetTimestamps: number[] = []; + for (let i = 0; i < maxPoints; i++) { + targetTimestamps.push(minTime + i * timeStep); + } + + // 线性插值函数 + const interpolate = (data: Array<{timestamp: number; value: number}>, targetTime: number): number | null => { + if (data.length === 0) return null; + if (data.length === 1) return data[0].timestamp === targetTime ? data[0].value : null; + + // 超出范围不插值(防止产生虚假连线) + if (targetTime < data[0].timestamp || targetTime > data[data.length - 1].timestamp) { + // 如果距离最近的点足够近(比如小于两个采样间隔),可以考虑保留,否则返回null + return null; + } + + // 二分查找 + let left = 0, right = data.length - 1; + while (right - left > 1) { + const mid = Math.floor((left + right) / 2); + if (data[mid].timestamp <= targetTime) { + left = mid; + } else { + right = mid; + } + } + + const leftPoint = data[left]; + const rightPoint = data[right]; + const ratio = (targetTime - leftPoint.timestamp) / (rightPoint.timestamp - leftPoint.timestamp); + return leftPoint.value + ratio * (rightPoint.value - leftPoint.value); + }; + + // 对齐所有探针数据 + return targetTimestamps.map(timestamp => { + const dataPoint: any = {timestamp}; + selectedSeriesData.forEach(s => { + const value = interpolate(s.data, timestamp); + if (value !== null) { + dataPoint[s.key] = Number(value.toFixed(2)); + } + }); + return dataPoint; + }); + }, [historyData, selectedAgents, timeRange, customStart, customEnd]); + + return ( + +
+
+

响应时间趋势

+

监控各探针的响应时间变化

+
+
+ { + setCustomRange(range); + }} + /> +
+
+ + {/* 使用提示和恢复按钮 */} + {availableAgents.length > 0 && ( +
+
+ 💡 点击图表线条或图例切换显示 +
+ {hasUnselected && ( + + )} +
+ )} + + {chartData.length > 0 ? ( +
+ + + + {Array.from(selectedAgents).map((agentId, index) => { + const originalIndex = availableAgents.findIndex(a => a.id === agentId); + const agentKey = `agent_${agentId}`; + return ( + + + + + ); + })} + + + formatChartTime(Number(value), timeRange, rangeMs)} + className="text-xs text-gray-600 dark:text-cyan-500 font-mono" + stroke="currentColor" + tickLine={false} + axisLine={false} + angle={-15} + textAnchor="end" + /> + `${value}ms`} + /> + } + wrapperStyle={{zIndex: 9999}} + /> + {Array.from(selectedAgents).map((agentId) => { + const originalIndex = availableAgents.findIndex(a => a.id === agentId); + const agentKey = `agent_${agentId}`; + const agent = availableAgents.find(a => a.id === agentId); + return ( + + ); + })} + + + + {/* 桌面端:直接显示图例 */} + {!isMobile && availableAgents.length > 0 && ( + + )} + + {/* 移动端:可折叠图例 */} + {isMobile && availableAgents.length > 0 && ( +
+ + +
+ )} +
+ ) : ( + + )} +
+ ); +}; + +/* ========================================== MonitorDetail ========================================== */ + +/** + * 监控详情页面 + * 显示监控的详细信息、响应时间趋势和各探针统计 + */ +const MonitorDetail = () => { + const navigate = useNavigate(); + const {id} = useParams<{id: string}>(); + + // 获取监控详情(聚合数据) + const {data: monitorDetail, isLoading} = useQuery({ + queryKey: ['monitorDetail', id], + queryFn: async () => { + if (!id) throw new Error('Monitor ID is required'); + return pika.getMonitorStats(id); + }, + refetchInterval: 30000, + enabled: !!id, + }); + + // 获取各探针的统计数据 + const {data: monitorStats = []} = useQuery({ + queryKey: ['monitorAgentStats', id], + queryFn: async () => { + if (!id) return []; + return pika.getMonitorAgents(id); + }, + refetchInterval: 30000, + enabled: !!id, + }); + + if (isLoading) { + return ; + } + + if (!monitorDetail) { + return ; + } + + return ( +
+
+ {/* 头部区域 */} + navigate('/monitors')} + /> + + {/* 主内容区 */} +
+ {/* 响应时间趋势图表 */} + + + {/* 各探针详细数据 */} + +
+
+
+ ); +}; + +export default MonitorDetail; diff --git a/web/portal/src/pages/MonitorList.tsx b/web/portal/src/pages/MonitorList.tsx new file mode 100644 index 0000000..c258027 --- /dev/null +++ b/web/portal/src/pages/MonitorList.tsx @@ -0,0 +1,431 @@ +import {useEffect, useMemo, useState} from 'react'; +import {Link} from 'react-router-dom'; +import {useQuery} from '@tanstack/react-query'; +import {AlertTriangle, BarChart3, CheckCircle2, Globe, Loader2, Maximize2, Search, Server, Shield, ShieldCheck, Wifi, Zap} from 'lucide-react'; +import {Area, AreaChart, ResponsiveContainer} from 'recharts'; +import {pika} from '../api'; +import type {MetricsResponse, PublicMonitor} from '../types'; +import {cn, formatDateTime} from '../lib/utils'; +import {Card, StatBlock, StatusBadge} from '../components'; + +/* ========================================== TypeIcon ========================================== */ + +const TypeIcon = ({type}: {type: string}) => { + switch (type.toLowerCase()) { + case 'https': + return ; + case 'http': + return ; + case 'tcp': + return ; + case 'icmp': + case 'ping': + return ; + default: + return ; + } +}; + +/* ========================================== CertBadge ========================================== */ + +const CertBadge = ({expiryTime, daysLeft}: {expiryTime: number; daysLeft: number}) => { + if (!expiryTime || daysLeft === undefined) return null; + + const isExpired = daysLeft < 0; + let colorClass = "text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 border-emerald-200 dark:border-emerald-500/20"; + + if (isExpired) { + colorClass = "text-rose-600 dark:text-rose-400 bg-rose-50 dark:bg-rose-500/10 border-rose-200 dark:border-rose-500/20"; + } else if (daysLeft < 30) { + colorClass = "text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/10 border-amber-200 dark:border-amber-500/20"; + } + + return ( +
+ + {isExpired ? "已过期" : `${daysLeft} 天后过期`} +
+ ); +}; + +/* ========================================== MonitorCard ========================================== */ + +export type DisplayMode = 'avg' | 'max'; + +const MiniChart = ({data, lastValue, id}: { + data: Array<{timestamp: number; value: number}>; + lastValue?: number; + id: string; +}) => { + const chartData = useMemo(() => [...data].sort((a, b) => a.timestamp - b.timestamp), [data]); + if (chartData.length === 0) { + return
暂无数据
; + } + + const color = lastValue && lastValue <= 200 ? '#22d3ee' : '#fbbf24'; + return ( +
+ + + + + + + + + + + + + + + + + + +
+ ); +}; + +const MonitorCard = ({monitor, displayMode}: { + monitor: PublicMonitor; + displayMode: DisplayMode; +}) => { + // 为每个监控卡片查询历史数据 + const {data: historyData} = useQuery({ + queryKey: ['monitorHistory', monitor.id, '12h'], // 对应后端 60 秒步长 + queryFn: async () => { + return pika.getMonitorHistory(monitor.id, {range: '1h'}); + }, + refetchInterval: 60000, + staleTime: 30000, + }); + + // 转换时序数据为图表数据 - 使用统一格点对该对齐多探针数据 + const chartData = useMemo(() => { + if (!historyData?.series || historyData.series.length === 0) { + return []; + } + + const validSeries = historyData.series.filter(s => s.data && s.data.length > 0); + if (validSeries.length === 0) return []; + + // 确定全局时间范围 + let minTime = Infinity, maxTime = -Infinity; + validSeries.forEach(s => { + minTime = Math.min(minTime, s.data![0].timestamp); + maxTime = Math.max(maxTime, s.data![s.data!.length - 1].timestamp); + }); + + if (minTime >= maxTime) return []; + + // 定义目标采集点 (1小时数据,建议 60 个采集点) + const maxPoints = 60; + const timeStep = (maxTime - minTime) / (maxPoints - 1); + const targetTimestamps: number[] = []; + for (let i = 0; i < maxPoints; i++) { + targetTimestamps.push(minTime + i * timeStep); + } + + // 线性插值函数 + const interpolate = (data: Array<{ timestamp: number; value: number }>, targetTime: number): number | null => { + if (data.length === 0) return null; + if (data.length === 1) return data[0].timestamp === targetTime ? data[0].value : null; + if (targetTime < data[0].timestamp || targetTime > data[data.length - 1].timestamp) return null; + + let left = 0, right = data.length - 1; + while (right - left > 1) { + const mid = Math.floor((left + right) / 2); + if (data[mid].timestamp <= targetTime) left = mid; + else right = mid; + } + const leftPoint = data[left]; + const rightPoint = data[right]; + const ratio = (targetTime - leftPoint.timestamp) / (rightPoint.timestamp - leftPoint.timestamp); + return leftPoint.value + ratio * (rightPoint.value - leftPoint.value); + }; + + // 对每个目标时间点,计算所有探针的聚合值 + return targetTimestamps.map(timestamp => { + const values: number[] = []; + validSeries.forEach(s => { + const val = interpolate(s.data!, timestamp); + if (val !== null) values.push(val); + }); + + if (values.length === 0) return { timestamp, value: 0 }; + + return { + timestamp, + value: displayMode === 'avg' + ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) + : Math.max(...values), + }; + }); + }, [historyData, displayMode]); + + const displayValue = displayMode === 'avg' ? monitor.responseTime : monitor.responseTimeMax; + const displayLabel = displayMode === 'avg' ? '平均延迟' : '最差节点延迟'; + + return ( + + {/* 头部 */} +
+
+
+ +
+
+

+ {monitor.name} +

+
+ {monitor.target} +
+
+
+
+ +
+
+ + {/* 指标信息 */} +
+
+

+ {displayLabel} + {monitor.agentCount > 0 && ( + + {monitor.agentCount} 节点 + + )} +

+
200 ? 'text-amber-600 dark:text-amber-400 drop-shadow-[0_0_8px_rgba(251,191,36,0.3)] dark:drop-shadow-[0_0_8px_rgba(251,191,36,0.5)]' : 'text-slate-800 dark:text-white drop-shadow-none dark:drop-shadow-[0_0_8px_rgba(34,211,238,0.5)]'}`}> + {displayValue}ms +
+
+
+ {monitor.type === 'https' && monitor.certExpiryTime ? ( + <> +

SSL 证书

+ + + ) : ( + <> +

上次检测

+

+ {formatDateTime(monitor.lastCheckTime)} +

+ + )} +
+
+ + {/* 迷你走势图 */} + +
+ ); +}; + +/* ========================================== MonitorList 本地组件 ========================================== */ + +const MonitorListSpinner = () => ( +
+
+ + 加载监控数据中... +
+
+); + +const MonitorListEmpty = () => ( +
+ +

暂无监控数据

+

请先在管理后台添加监控任务

+
+); + + +interface Stats { + total: number; + online: number; + issues: number; + avgLatency: number; +} + +/* ========================================== MonitorList ========================================== */ + +const MonitorList = () => { + const [searchKeyword, setSearchKeyword] = useState(''); + const [displayMode, setDisplayMode] = useState('max'); + + const {data: monitors = [], isLoading} = useQuery({ + queryKey: ['publicMonitors'], + queryFn: () => pika.listMonitors(), + refetchInterval: 30000, + }); + + let [stats, setStats] = useState(); + + // 过滤和搜索 + const filteredMonitors = useMemo(() => { + let result = [...monitors]; + + // 搜索过滤 + if (searchKeyword.trim()) { + const keyword = searchKeyword.toLowerCase(); + result = result.filter(m => + m.name.toLowerCase().includes(keyword) || + m.target.toLowerCase().includes(keyword) + ); + } + + return result.sort((a, b) => Number(a.status !== 'up') - Number(b.status !== 'up')); + }, [monitors, searchKeyword]); + + // 统计信息 + const calculateStats = (monitors: PublicMonitor[]) => { + const total = monitors.length; + const online = monitors.filter(m => m.status === 'up').length; + const issues = total - online; + const avgLatency = total > 0 + ? Math.round(monitors.reduce((acc, curr) => acc + curr.responseTime, 0) / total) + : 0; + return {total, online, issues, avgLatency}; + } + + useEffect(() => { + let stats = calculateStats(monitors); + setStats(stats); + }, [monitors]); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+ {/* 统计卡片 */} +
+ + + 0} + /> + +
+ + {/* 过滤和搜索 */} +
+
+ {/* 显示模式切换 */} +
+ 卡片指标: + + +
+
+ + {/* 搜索框 */} +
+
+
+ + setSearchKeyword(e.target.value)} + className="w-full bg-transparent border-none text-xs text-gray-800 dark:text-cyan-100 p-2.5 focus:ring-0 placeholder-gray-400 dark:placeholder-cyan-600 font-mono focus:outline-none" + /> +
+
+
+ + {/* 监控卡片列表 */} + {filteredMonitors.length === 0 ? ( + + ) : ( +
+ {filteredMonitors.map(monitor => ( + + + + ))} +
+ )} +
+ ); +}; + +export default MonitorList; diff --git a/web/src/portal/pages/PublicLayout.tsx b/web/portal/src/pages/PublicLayout.tsx similarity index 93% rename from web/src/portal/pages/PublicLayout.tsx rename to web/portal/src/pages/PublicLayout.tsx index 7387e4c..754cbb4 100644 --- a/web/src/portal/pages/PublicLayout.tsx +++ b/web/portal/src/pages/PublicLayout.tsx @@ -1,6 +1,5 @@ import {Outlet} from 'react-router-dom'; -import PublicHeader from '@portal/components/PublicHeader'; -import PublicFooter from '@portal/components/PublicFooter'; +import {PublicHeader, PublicFooter} from '../components'; const PublicLayout = () => { return ( diff --git a/web/portal/src/pages/ServerDetail.tsx b/web/portal/src/pages/ServerDetail.tsx new file mode 100644 index 0000000..ca64ea6 --- /dev/null +++ b/web/portal/src/pages/ServerDetail.tsx @@ -0,0 +1,2057 @@ +import {memo, type ReactNode, useEffect, useMemo, useState} from 'react'; +import {useNavigate, useParams} from 'react-router-dom'; +import {Area, AreaChart, CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; +import { + Activity, + ArrowLeft, + ChevronDown, + ChevronUp, + Cpu, + HardDrive, + MemoryStick, + Network, + RotateCcw, + Thermometer, + Zap +} from 'lucide-react'; +import type {LucideIcon} from 'lucide-react'; +import type {Agent, LatestMetrics} from '../types'; +import { + INTERFACE_COLORS, + LIVE_INITIAL_RANGE, + LIVE_RANGE, + LIVE_WINDOW_MS, + SERVER_TIME_RANGE_OPTIONS, + TEMPERATURE_COLORS, +} from '../constants'; +import { + cn, + formatBytes, + formatChartTime, + formatDateTime, + formatPercentValue, + formatUptime, +} from '../lib/utils'; +import { + useAgentQuery, + useLatestMetricsQuery, + useLiveBuffer, + useMetricsQuery, + useIsMobile, + useNetworkInterfacesQuery, +} from '../hooks'; +import { + Card, + ChartPlaceholder, + CustomTooltip, + EmptyState, + LoadingSpinner, + MetricItem, + StatusBadge, + TimeRangeSelector +} from '../components'; + +/* ========================================== 共享工具 ========================================== */ + +const toMB = (bytes: number) => Number((bytes / 1024 / 1024).toFixed(2)); + +/* ========================================== ChartContainer ========================================== */ + +interface ChartContainerProps { + title: string; + icon: LucideIcon; + children: ReactNode; + action?: ReactNode; +} + +const ChartContainer = ({title, icon: Icon, children, action}: ChartContainerProps) => { + return ( +
+
+

+ + + + {title} +

+ {action} +
+ {children} +
+ ); +}; + +/* ========================================== ServerHero ========================================== */ + +interface ServerHeroProps { + agent: Agent; + latestMetrics: LatestMetrics | null; + onBack: () => void; +} + +const ServerHero = ({agent, latestMetrics, onBack}: ServerHeroProps) => { + const displayName = agent?.name?.trim() ? agent.name : '未命名探针'; + const isOnline = agent?.status === 1; + const statusDotStyles = isOnline ? 'bg-emerald-500' : 'bg-rose-500'; + const statusText = isOnline ? '在线' : '离线'; + + const platformDisplay = latestMetrics?.host?.platform + ? `${latestMetrics.host.platform} ${latestMetrics.host.platformVersion || ''}`.trim() + : agent?.os || '-'; + const architectureDisplay = latestMetrics?.host?.kernelArch || agent?.arch || '-'; + const uptimeDisplay = formatUptime(latestMetrics?.host?.uptime); + const lastSeenDisplay = agent ? formatDateTime(agent.lastSeenAt) : '-'; + + const networkSummary = latestMetrics?.network + ? `${formatBytes(latestMetrics.network.totalBytesSentTotal)} ↑ / ${formatBytes( + latestMetrics.network.totalBytesRecvTotal, + )} ↓` + : '—'; + + const heroStats = [ + {label: '运行系统', value: platformDisplay || '-'}, + {label: '硬件架构', value: architectureDisplay || '-'}, + {label: '系统进程', value: latestMetrics?.host?.procs || '-'}, + {label: '运行时长', value: uptimeDisplay}, + ]; + + return ( + +
+
+
+ +
+
+
+

{displayName}

+ +
+

+ {[agent.hostname].filter(Boolean).join(' · ') || '-'} +

+
+
+
+ +
+ {heroStats.map((stat) => ( + + ))} +
+
+
+ 探针 ID:{agent.id} + + 版本:{agent.version || '-'} + + 网络累计:{networkSummary} +
+
+
+ ); +}; + +/* ========================================== SystemInfoSection ========================================== */ + +interface SystemInfoSectionProps { + agent: Agent; + latestMetrics: LatestMetrics | null; +} + +const InfoGrid = ({items}: {items: Array<{label: string; value: ReactNode}>}) => ( +
+ {items.map((item) => ( +
+
{item.label}
+
{item.value}
+
+ ))} +
+); + +type SnapshotCardData = { + key: string; + icon: LucideIcon; + title: string; + usagePercent: string; + accent: 'blue' | 'emerald' | 'purple' | 'amber'; + metrics: Array<{label: string; value: ReactNode}>; +}; + +const snapshotColors = { + blue: 'text-blue-400', + emerald: 'text-emerald-400', + purple: 'text-purple-400', + amber: 'text-amber-400', +}; + +const SnapshotGrid = ({cards}: {cards: SnapshotCardData[]}) => ( +
+ {cards.map((card) => ( +
+
+
+ + + +

{card.title}

+
+ {card.usagePercent} +
+
+ {card.metrics.map((metric) => ( +
+ {metric.label} + {metric.value} +
+ ))} +
+
+ ))} +
+); + +const SystemInfoSection = ({agent, latestMetrics}: SystemInfoSectionProps) => { + // 环境信息 + const platformDisplay = latestMetrics?.host?.platform + ? `${latestMetrics.host.platform} ${latestMetrics.host.platformVersion || ''}`.trim() + : agent?.os || '-'; + const architectureDisplay = latestMetrics?.host?.kernelArch || agent?.arch || '-'; + + const environmentInfo = [ + {label: '操作系统', value: platformDisplay || '-'}, + {label: '内核版本', value: latestMetrics?.host?.kernelVersion || '-'}, + {label: '硬件架构', value: architectureDisplay || '-'}, + {label: 'CPU 型号', value: latestMetrics?.cpu?.modelName || '-'}, + {label: '逻辑核心', value: latestMetrics?.cpu?.logicalCores ?? '-'}, + {label: '物理核心', value: latestMetrics?.cpu?.physicalCores ?? '-'}, + ]; + + // 状态信息 + const uptimeDisplay = formatUptime(latestMetrics?.host?.uptime); + const bootTimeDisplay = latestMetrics?.host?.bootTime + ? formatDateTime(latestMetrics.host.bootTime * 1000) + : '-'; + const lastSeenDisplay = agent ? formatDateTime(agent.lastSeenAt) : '-'; + + const networkSummary = latestMetrics?.network + ? `${formatBytes(latestMetrics.network.totalBytesSentTotal)} ↑ / ${formatBytes( + latestMetrics.network.totalBytesRecvTotal, + )} ↓` + : '—'; + + const statusInfo = [ + {label: '启动时间', value: bootTimeDisplay}, + {label: '运行时间', value: uptimeDisplay}, + {label: '最近心跳', value: lastSeenDisplay}, + {label: '进程数', value: latestMetrics?.host?.procs ?? '-'}, + {label: '网络累计', value: networkSummary}, + {label: 'Load', value: `${latestMetrics?.host?.load1?.toFixed(2)} / ${latestMetrics?.host?.load5?.toFixed(2)} / ${latestMetrics?.host?.load15?.toFixed(2)}`}, + ]; + + // 快照卡片 + const snapshotCards: SnapshotCardData[] = []; + + if (latestMetrics) { + snapshotCards.push({ + key: 'cpu', + icon: Cpu, + title: 'CPU 使用', + usagePercent: `${formatPercentValue(latestMetrics.cpu?.usagePercent)}%`, + accent: 'blue', + metrics: [ + {label: '当前使用', value: `${formatPercentValue(latestMetrics.cpu?.usagePercent)}%`}, + ], + }); + + snapshotCards.push({ + key: 'memory', + icon: MemoryStick, + title: '内存使用', + usagePercent: `${formatPercentValue(latestMetrics.memory?.usagePercent)}%`, + accent: 'emerald', + metrics: [ + { + label: '已用 / 总量', + value: `${formatBytes(latestMetrics.memory?.used)} / ${formatBytes(latestMetrics.memory?.total)}` + }, + { + label: 'Swap 已用', + value: `${formatBytes(latestMetrics.memory?.swapUsed)} / ${formatBytes(latestMetrics.memory?.swapTotal)}` + }, + ], + }); + + snapshotCards.push({ + key: 'disk', + icon: HardDrive, + title: '磁盘使用', + usagePercent: latestMetrics.disk + ? `${formatPercentValue(latestMetrics.disk.usagePercent)}%` + : '—', + accent: 'purple', + metrics: [ + { + label: '已用 / 总量', + value: `${formatBytes(latestMetrics.disk?.used, 1)} / ${formatBytes(latestMetrics.disk?.total, 1)}` + }, + {label: '磁盘数量', value: latestMetrics.disk?.totalDisks ?? '-'}, + ], + }); + + // 网络流量卡片 - 整合流量统计信息 + const networkMetrics = [ + { + label: '上行 / 下行', + value: `${formatBytes(latestMetrics.network?.totalBytesSentRate, 1)}/s ↑ / ${formatBytes( + latestMetrics.network?.totalBytesRecvRate, 1, + )}/s ↓`, + }, + { + label: '网络累计', + value: `${formatBytes(latestMetrics.network?.totalBytesSentTotal, 1)} ↑ / ${formatBytes( + latestMetrics.network?.totalBytesRecvTotal, 1, + )} ↓`, + }, + ]; + + // 如果配置了流量限额,添加流量统计信息到网络卡片 + if (agent?.trafficStats?.enabled && agent.trafficStats.limit > 0) { + const trafficUsedPercent = (agent.trafficStats.used / agent.trafficStats.limit) * 100; + + networkMetrics.push({ + label: '流量限额', + value: `${formatBytes(agent.trafficStats.used, 1)} / ${formatBytes(agent.trafficStats.limit, 1)} (${formatPercentValue(trafficUsedPercent)}%)`, + }); + + if (agent.trafficStats.resetDay > 0) { + networkMetrics.push({ + label: '重置日期', + value: `每月${agent.trafficStats.resetDay}号`, + }); + } + } + + snapshotCards.push({ + key: 'network', + icon: Network, + title: '网络流量', + usagePercent: latestMetrics.network + ? `${formatBytes(latestMetrics.network.totalBytesSentRate)}/s` + : '—', + accent: 'amber', + metrics: networkMetrics, + }); + } + + return ( +
+
+
+ +

运行环境

+

来自最近一次探针上报的硬件与系统信息

+
+ +
+
+ +

运行状态

+

关键时间与网络指标,帮助快速判断主机健康状况

+
+ +
+
+
+ {snapshotCards.length > 0 && ( + +

+ 资源快照 +

+ +
+ )} +
+
+ ); +}; + +/* ========================================== ServerDetailSections ========================================== */ + +const NetworkAddressSection = ({ipv4, ipv6, deviceIpInterfaces}: { + ipv4?: string; + ipv6?: string; + deviceIpInterfaces: Array<{name: string; addrs: string[]}>; +}) => { + if (!ipv4 && !ipv6 && deviceIpInterfaces.length === 0) return null; + + return ( + +
+ {(ipv4 || ipv6) && ( +
+
公网地址
+
+ {[["IPv4", ipv4], ["IPv6", ipv6]].map(([label, value]) => ( +
+
{label}
+
{value || '-'}
+
+ ))} +
+
+ )} + + {(ipv4 || ipv6) && deviceIpInterfaces.length > 0 &&
} + + {deviceIpInterfaces.length > 0 && ( +
+
网卡地址
+
+ {deviceIpInterfaces.map((networkInterface) => ( +
+
{networkInterface.name}
+
+ {networkInterface.addrs.map((address) => ( + + {address} + + ))} +
+
+ ))} +
+
+ )} +
+ + ); +}; + +const NetworkConnectionSection = ({latestMetrics}: {latestMetrics: LatestMetrics | null}) => { + const metrics = latestMetrics?.networkConnection; + if (!metrics) return null; + const items = [ + {label: 'Total', value: metrics.total, color: 'text-slate-800 dark:text-cyan-100'}, + {label: 'ESTABLISHED', value: metrics.established, color: 'text-emerald-600 dark:text-emerald-400'}, + {label: 'TIME_WAIT', value: metrics.timeWait, color: 'text-amber-600 dark:text-amber-400'}, + {label: 'LISTEN', value: metrics.listen, color: 'text-blue-600 dark:text-blue-400'}, + {label: 'CLOSE_WAIT', value: metrics.closeWait, color: 'text-rose-600 dark:text-rose-400'}, + { + label: 'OTHER', + value: metrics.synSent + metrics.synRecv + metrics.finWait1 + metrics.finWait2 + metrics.close + metrics.lastAck + metrics.closing, + color: 'text-gray-700 dark:text-cyan-500', + }, + ]; + + return ( + +
+ {items.map((item) => ( +
+
{item.label}
+
{item.value}
+
+ ))} +
+
+ ); +}; + +const GpuMonitorSection = ({latestMetrics}: {latestMetrics: LatestMetrics | null}) => { + if (!latestMetrics?.gpu?.length) return null; + + return ( + +
+ {latestMetrics.gpu.map((gpu) => ( +
+
+
+ +
+

GPU {gpu.index}

+

{gpu.name}

+
+
+ {gpu.utilization?.toFixed(1) ?? 0}% +
+
+ {[ + ['温度', `${gpu.temperature?.toFixed(1)}°C`], + ['显存', `${formatBytes(gpu.memoryUsed)} / ${formatBytes(gpu.memoryTotal)}`], + ['功耗', `${gpu.powerUsage?.toFixed(1)}W`], + ['风扇转速', `${gpu.fanSpeed?.toFixed(0)}%`], + ].map(([label, value]) => ( +
+ {label} + {value} +
+ ))} +
+
+ ))} +
+
+ ); +}; + +const TemperatureMonitorSection = ({latestMetrics}: {latestMetrics: LatestMetrics | null}) => { + if (!latestMetrics?.temperature?.length) return null; + + return ( + +
+ {[...latestMetrics.temperature].sort((a, b) => a.sensorKey.localeCompare(b.sensorKey)).map((temperature) => ( +
+
+ +

{temperature.type}

+
+

{temperature.temperature.toFixed(1)}°C

+
+ ))} +
+
+ ); +}; + +/* ========================================== CpuChart ========================================== */ + +interface ChartPropsBase { + agentId: string; + timeRange: string; + start?: number; + end?: number; + isLive?: boolean; + latestMetrics?: LatestMetrics | null; +} + +interface CpuChartProps extends ChartPropsBase { +} + +interface CpuPoint { + timestamp: number; + usage: number; +} + +const CpuChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: CpuChartProps) => { + const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; + const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; + // 数据查询 + const {data: metricsResponse, isLoading} = useMetricsQuery({ + agentId, + type: 'cpu', + range: start !== undefined && end !== undefined ? undefined : effectiveRange, + start, + end, + }); + + // 历史数据 + const initialData = useMemo(() => { + const cpuSeries = metricsResponse?.series?.find(s => s.name === 'usage'); + if (!cpuSeries) return []; + return cpuSeries.data.map((point) => ({ + usage: Number(point.value.toFixed(2)), + timestamp: point.timestamp, + })); + }, [metricsResponse]); + + // 实时点 + const livePoint = useMemo(() => { + if (!isLive || !latestMetrics?.cpu || !latestMetrics.timestamp) return null; + const usage = latestMetrics.cpu.usagePercent; + if (typeof usage !== 'number' || !Number.isFinite(usage)) return null; + return {timestamp: latestMetrics.timestamp, usage: Number(usage.toFixed(2))}; + }, [isLive, latestMetrics]); + + const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, agentId); + + // 渲染 + if (isLoading) { + return ( + + + + ); + } + + return ( + + {chartData.length > 0 ? ( + + + + + + + + + + formatChartTime(Number(value), timeRange, rangeMs)} + stroke="currentColor" + angle={-15} + textAnchor="end" + className="text-xs text-gray-600 dark:text-cyan-500 font-mono" + /> + `${value}%`} + /> + }/> + + + + ) : ( + + )} + + ); +}; + +/* ========================================== MemoryChart ========================================== */ + +interface MemoryPoint { + timestamp: number; + usage: number; +} + +const MemoryChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: ChartPropsBase) => { + const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; + const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; + // 数据查询 + const {data: metricsResponse, isLoading} = useMetricsQuery({ + agentId, + type: 'memory', + range: start !== undefined && end !== undefined ? undefined : effectiveRange, + start, + end, + }); + + // 历史数据 + const initialData = useMemo(() => { + const memorySeries = metricsResponse?.series?.find(s => s.name === 'usage'); + if (!memorySeries) return []; + return memorySeries.data.map((point) => ({ + usage: Number(point.value.toFixed(2)), + timestamp: point.timestamp, + })); + }, [metricsResponse]); + + // 实时点 + const livePoint = useMemo(() => { + if (!isLive || !latestMetrics?.memory || !latestMetrics.timestamp) return null; + const usage = latestMetrics.memory.usagePercent; + if (typeof usage !== 'number' || !Number.isFinite(usage)) return null; + return {timestamp: latestMetrics.timestamp, usage: Number(usage.toFixed(2))}; + }, [isLive, latestMetrics]); + + const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, agentId); + + // 渲染 + if (isLoading) { + return ( + + + + ); + } + + return ( + + {chartData.length > 0 ? ( + + + + + + + + + + formatChartTime(Number(value), timeRange, rangeMs)} + stroke="currentColor" + angle={-15} + textAnchor="end" + className="text-xs text-gray-600 dark:text-cyan-500 font-mono" + /> + `${value}%`} + /> + }/> + + + + ) : ( + + )} + + ); +}; + +/* ========================================== DiskIOChart ========================================== */ + +interface DiskIOPoint { + timestamp: number; + read: number; + write: number; +} + +const DiskIOChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: ChartPropsBase) => { + const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; + const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; + // 数据查询 + const {data: metricsResponse, isLoading} = useMetricsQuery({ + agentId, + type: 'disk_io', + range: start !== undefined && end !== undefined ? undefined : effectiveRange, + start, + end, + }); + + // 历史数据 + const initialData = useMemo(() => { + if (!metricsResponse?.series || metricsResponse.series.length === 0) return []; + + const readSeries = metricsResponse.series.find(s => s.name === 'read'); + const writeSeries = metricsResponse.series.find(s => s.name === 'write'); + + if (!readSeries || !writeSeries) return []; + + const timeMap = new Map(); + + readSeries.data.forEach(point => { + timeMap.set(point.timestamp, { + timestamp: point.timestamp, + read: toMB(point.value), + write: 0, + }); + }); + + writeSeries.data.forEach(point => { + const existing = timeMap.get(point.timestamp); + if (existing) { + existing.write = toMB(point.value); + } else { + timeMap.set(point.timestamp, { + timestamp: point.timestamp, + read: 0, + write: toMB(point.value), + }); + } + }); + + return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp); + }, [metricsResponse]); + + // 实时点:用 latestMetrics.timestamp 作为同批次时间锚 + const livePoint = useMemo(() => { + if (!isLive || !latestMetrics?.diskIO || !latestMetrics.timestamp) return null; + return { + timestamp: latestMetrics.timestamp, + read: toMB(latestMetrics.diskIO.totalReadBytesRate), + write: toMB(latestMetrics.diskIO.totalWriteBytesRate), + }; + }, [isLive, latestMetrics]); + + const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, agentId); + + // 渲染 + if (isLoading) { + return ( + + + + ); + } + + return ( + + {chartData.length > 0 ? ( + + + + + + + + + + + + + + formatChartTime(Number(value), timeRange, rangeMs)} + stroke="currentColor" + angle={-15} + textAnchor="end" + className="text-xs text-gray-600 dark:text-cyan-500 font-mono" + height={45} + /> + `${value} MB`} + /> + }/> + + + + + + ) : ( + + )} + + ); +}; + +/* ========================================== NetworkChart ========================================== */ + +interface NetworkPoint { + timestamp: number; + upload: number; + download: number; +} + +const NetworkChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: ChartPropsBase) => { + const [selectedInterface, setSelectedInterface] = useState('all'); + const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; + const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; + + // 查询网卡列表 + const {data: interfacesData} = useNetworkInterfacesQuery(agentId); + const availableInterfaces = interfacesData?.interfaces || []; + + // 当网卡列表变化时,验证选中的网卡 + useEffect(() => { + if (selectedInterface !== 'all' && availableInterfaces.length > 0) { + if (!availableInterfaces.includes(selectedInterface)) { + setSelectedInterface('all'); + } + } + }, [availableInterfaces, selectedInterface]); + + // 查询网络数据 + const {data: metricsResponse, isLoading} = useMetricsQuery({ + agentId, + type: 'network', + range: start !== undefined && end !== undefined ? undefined : effectiveRange, + start, + end, + interfaceName: selectedInterface !== 'all' ? selectedInterface : undefined, + }); + + // 历史数据 + const initialData = useMemo(() => { + if (!metricsResponse?.series || metricsResponse.series.length === 0) return []; + + const uploadSeries = metricsResponse.series.find(s => s.name === 'upload'); + const downloadSeries = metricsResponse.series.find(s => s.name === 'download'); + + if (!uploadSeries || !downloadSeries) return []; + + const timeMap = new Map(); + + uploadSeries.data.forEach(point => { + timeMap.set(point.timestamp, { + timestamp: point.timestamp, + upload: toMB(point.value), + download: 0, + }); + }); + + downloadSeries.data.forEach(point => { + const existing = timeMap.get(point.timestamp); + if (existing) { + existing.download = toMB(point.value); + } else { + timeMap.set(point.timestamp, { + timestamp: point.timestamp, + upload: 0, + download: toMB(point.value), + }); + } + }); + + return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp); + }, [metricsResponse]); + + // 实时点:用 latestMetrics.timestamp 作为采集批次时间锚点 + const livePoint = useMemo(() => { + if (!isLive || !latestMetrics?.timestamp) return null; + + let sentRate = 0; + let recvRate = 0; + if (selectedInterface === 'all') { + const summary = latestMetrics.network; + if (!summary) return null; + sentRate = summary.totalBytesSentRate; + recvRate = summary.totalBytesRecvRate; + } else { + const iface = latestMetrics.networkInterfaces?.find(i => i.interface === selectedInterface); + if (!iface) return null; + sentRate = iface.bytesSentRate; + recvRate = iface.bytesRecvRate; + } + return { + timestamp: latestMetrics.timestamp, + upload: toMB(sentRate), + download: toMB(recvRate), + }; + }, [isLive, latestMetrics, selectedInterface]); + + const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, `${agentId}|${selectedInterface}`); + + // 网卡选择器 + const interfaceSelector = availableInterfaces.length > 0 && ( + + ); + + // 渲染 + if (isLoading) { + return ( + + + + ); + } + + return ( + + {chartData.length > 0 ? ( + + + + + + + + + + + + + + formatChartTime(Number(value), timeRange, rangeMs)} + stroke="currentColor" + angle={-15} + textAnchor="end" + className="text-xs text-gray-600 dark:text-cyan-500 font-mono" + height={45} + /> + `${value} MB`} + /> + }/> + + + + + + ) : ( + + )} + + ); +}; + +/* ========================================== NetworkConnectionChart ========================================== */ + +interface ConnPoint { + timestamp: number; + established: number; + time_wait: number; + close_wait: number; + listen: number; +} + +const NetworkConnectionChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: ChartPropsBase) => { + const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; + const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; + const {data: metricsResponse, isLoading} = useMetricsQuery({ + agentId, + type: 'network_connection', + range: start !== undefined && end !== undefined ? undefined : effectiveRange, + start, + end, + }); + + // 历史数据 + const initialData = useMemo(() => { + if (!metricsResponse?.series || metricsResponse.series.length === 0) return []; + + const timeMap = new Map(); + + metricsResponse.series.forEach(series => { + const stateName = series.name; + series.data.forEach(point => { + if (!timeMap.has(point.timestamp)) { + timeMap.set(point.timestamp, { + timestamp: point.timestamp, + established: 0, + time_wait: 0, + close_wait: 0, + listen: 0, + }); + } + const existing = timeMap.get(point.timestamp)!; + if (stateName === 'established' || stateName === 'time_wait' || stateName === 'close_wait' || stateName === 'listen') { + existing[stateName] = Number(point.value.toFixed(0)); + } + }); + }); + + return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp); + }, [metricsResponse]); + + // 实时点 + const livePoint = useMemo(() => { + if (!isLive || !latestMetrics?.networkConnection || !latestMetrics.timestamp) return null; + const c = latestMetrics.networkConnection; + return { + timestamp: latestMetrics.timestamp, + established: c.established ?? 0, + time_wait: c.timeWait ?? 0, + close_wait: c.closeWait ?? 0, + listen: c.listen ?? 0, + }; + }, [isLive, latestMetrics]); + + const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, agentId); + + // 渲染 + if (isLoading) { + return ( + + + + ); + } + + return ( + + {chartData.length > 0 ? ( + + + + formatChartTime(Number(value), timeRange, rangeMs)} + stroke="currentColor" + angle={-15} + textAnchor="end" + className="text-xs text-gray-600 dark:text-cyan-500 font-mono" + height={45} + /> + + }/> + + + + + + + + ) : ( + + )} + + ); +}; + +/* ========================================== GpuChart ========================================== */ + +const GpuChartImpl = ({agentId, timeRange, start, end, isLive}: ChartPropsBase) => { + const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; + const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; + // 数据查询 + const {data: metricsResponse, isLoading} = useMetricsQuery({ + agentId, + type: 'gpu', + range: start !== undefined && end !== undefined ? undefined : effectiveRange, + start, + end, + refetchIntervalMs: isLive ? 5000 : undefined, + }); + + // 数据转换 + const chartData = useMemo(() => { + if (!metricsResponse?.series || metricsResponse.series.length === 0) return []; + + const timeMap = new Map(); + + const utilizationSeries = metricsResponse.series.find(s => s.name === 'utilization'); + const temperatureSeries = metricsResponse.series.find(s => s.name === 'temperature'); + + utilizationSeries?.data.forEach(point => { + const existing = timeMap.get(point.timestamp); + if (existing) { + existing.utilization = Number(point.value.toFixed(2)); + } else { + timeMap.set(point.timestamp, { + timestamp: point.timestamp, + utilization: Number(point.value.toFixed(2)), + }); + } + }); + + temperatureSeries?.data.forEach(point => { + const existing = timeMap.get(point.timestamp); + if (existing) { + existing.temperature = Number(point.value.toFixed(2)); + } else { + timeMap.set(point.timestamp, { + timestamp: point.timestamp, + temperature: Number(point.value.toFixed(2)), + }); + } + }); + + return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp); + }, [metricsResponse]); + + // 渲染 + if (isLoading) { + return ( + + + + ); + } + + // 如果没有 GPU 数据,不渲染组件 + if (chartData.length === 0) { + return null; + } + + return ( + + + + + formatChartTime(Number(value), timeRange, rangeMs)} + stroke="currentColor" + className="stroke-gray-400 dark:stroke-cyan-600" + style={{fontSize: '12px'}} + /> + `${value}%`} + /> + `${value}°C`} + /> + }/> + + + + + + + ); +}; + +const GpuChart = memo(GpuChartImpl); + +/* ========================================== TemperatureChart ========================================== */ + +const TemperatureChartImpl = ({agentId, timeRange, start, end, isLive}: ChartPropsBase) => { + const [selectedTempType, setSelectedTempType] = useState('all'); + const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; + const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; + + // 数据查询:温度采集 5s 一次,实时模式 5s 重查 + const {data: metricsResponse, isLoading} = useMetricsQuery({ + agentId, + type: 'temperature', + range: start !== undefined && end !== undefined ? undefined : effectiveRange, + start, + end, + refetchIntervalMs: isLive ? 5000 : undefined, + }); + + // 数据转换 + const chartData = useMemo(() => { + if (!metricsResponse?.series || metricsResponse.series.length === 0) return []; + + const timeMap = new Map(); + + metricsResponse.series.forEach(series => { + const sensorName = series.name; + series.data.forEach(point => { + if (!timeMap.has(point.timestamp)) { + timeMap.set(point.timestamp, {timestamp: point.timestamp}); + } + + const existing = timeMap.get(point.timestamp)!; + existing[sensorName] = Number(point.value.toFixed(2)); + }); + }); + + return Array.from(timeMap.values()); + }, [metricsResponse]); + + // 提取所有唯一的温度类型 + const temperatureTypes = useMemo(() => { + return metricsResponse?.series?.map(s => s.name).sort() || []; + }, [metricsResponse]); + + // 根据选中的类型过滤温度数据 + const filteredTemperatureTypes = useMemo(() => { + if (selectedTempType === 'all') { + return temperatureTypes; + } + return temperatureTypes.filter(type => type === selectedTempType); + }, [temperatureTypes, selectedTempType]); + + // 当温度类型列表变化时,如果当前选中的类型不在列表中,重置为 'all' + useEffect(() => { + if (selectedTempType !== 'all' && temperatureTypes.length > 0) { + if (!temperatureTypes.includes(selectedTempType)) { + setSelectedTempType('all'); + } + } + }, [temperatureTypes, selectedTempType]); + + // 温度类型选择器 + const tempTypeSelector = temperatureTypes.length > 1 && ( + + ); + + // 渲染 + if (isLoading) { + return ( + + + + ); + } + + // 如果没有温度数据,不渲染组件 + if (chartData.length === 0 || temperatureTypes.length === 0) { + return null; + } + + return ( + + + + + formatChartTime(Number(value), timeRange, rangeMs)} + stroke="currentColor" + angle={-15} + textAnchor="end" + className="text-xs text-gray-600 dark:text-cyan-500 font-mono" + height={45} + /> + `${value}°C`} + /> + }/> + + {/* 为选中的温度类型渲染线条 */} + {filteredTemperatureTypes.map((type, index) => { + const color = TEMPERATURE_COLORS[type] || `hsl(${(index * 60) % 360}, 70%, 50%)`; + return ( + + ); + })} + + + + ); +}; + +const TemperatureChart = memo(TemperatureChartImpl); + +/* ========================================== MonitorChart ========================================== */ + +/** + * 降采样算法 - 使用LTTB (Largest Triangle Three Buckets) + * 确保输出精确的maxPoints个点,保留关键特征 + */ +const downsampleData = (data: any[], maxPoints: number): any[] => { + // 边界检查 + if (!data || data.length === 0) return []; + if (maxPoints < 2) maxPoints = 2; + if (data.length <= maxPoints) return [...data]; + + const result: any[] = [data[0]]; // 保留第一个点 + + // 桶大小 + const bucketSize = (data.length - 2) / (maxPoints - 2); + + for (let i = 0; i < maxPoints - 2; i++) { + // 计算当前桶的范围 + const start = Math.floor((i + 0) * bucketSize) + 1; + const end = Math.floor((i + 1) * bucketSize) + 1; + + // 计算前一个点和后一个点 + const previousPoint = result[result.length - 1]; + const nextPoint = data[Math.min(end, data.length - 1)]; + + // 在桶中选择与前后点形成的三角形面积最大的点 + let maxArea = -1; + let selectedPoint = data[start]; + + for (let j = start; j < end && j < data.length - 1; j++) { + // 计算三角形面积 + const area = Math.abs( + (previousPoint.timestamp - nextPoint.timestamp) * (data[j].value - previousPoint.value) - + (previousPoint.timestamp - data[j].timestamp) * (nextPoint.value - previousPoint.value) + ); + + if (area > maxArea) { + maxArea = area; + selectedPoint = data[j]; + } + } + + result.push(selectedPoint); + } + + result.push(data[data.length - 1]); // 保留最后一个点 + + return result; +}; + +/** + * 根据时间范围确定最大数据点数 + */ +const getMaxDataPoints = (timeRange: string): number => { + switch (timeRange) { + case '15m': + case '1h': + return 200; // 短时间:详细数据 + case '12h': + return 300; + case '24h': + return 400; + case '7d': + return 500; + case '30d': + return 600; + default: + return 400; + } +}; + +/** + * 生成不重复的颜色 + * 使用 HSL 色轮均匀分布,支持无限数量的监控项 + */ +const generateColors = (count: number): string[] => { + const colors: string[] = []; + const hueStep = 360 / count; // 色相间隔 + + for (let i = 0; i < count; i++) { + const hue = (i * hueStep) % 360; + const saturation = 65 + (i % 3) * 10; // 65%, 75%, 85% 循环 + const lightness = 45 + (i % 2) * 10; // 45%, 55% 循环 + colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`); + } + + return colors; +}; + +/** + * 自定义图例组件 + */ +const CustomLegend = ({ onClick, selectedMonitors, allMonitorKeys, colors, collapsed }: any) => { + if (!allMonitorKeys || allMonitorKeys.length === 0) return null; + + if (collapsed) return null; + + return ( +
+ {allMonitorKeys.map((monitorKey: string, index: number) => { + const isSelected = selectedMonitors.has(monitorKey); + const color = colors[index]; + + return ( +
onClick({ value: monitorKey })} + className="flex items-center gap-2 cursor-pointer transition-opacity" + style={{ + opacity: isSelected ? 1 : 0.4, + }} + > + + + + + {monitorKey} + +
+ ); + })} +
+ ); +}; + +const MonitorChartImpl = ({agentId, timeRange, start, end, isLive}: ChartPropsBase) => { + const isMobile = useIsMobile(); + const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; + const [selectedMonitors, setSelectedMonitors] = useState>(new Set()); + const [legendCollapsed, setLegendCollapsed] = useState(true); // 移动端默认收起 + // 监控任务由探针自定义周期上报,实时模式下保留 15m 视图,10s 重查 + const effectiveRange = isLive ? '15m' : timeRange; + + // 数据查询 + const {data: metricsResponse, isLoading} = useMetricsQuery({ + agentId, + type: 'monitor', + range: start !== undefined && end !== undefined ? undefined : effectiveRange, + start, + end, + refetchIntervalMs: isLive ? 10000 : undefined, + }); + + // 获取所有监控任务的列表(使用名称) + const allMonitorKeys = useMemo(() => { + const series = metricsResponse?.series || []; + return series.map(s => s.labels?.monitor_name || s.labels?.monitor_id || s.name); + }, [metricsResponse]); + + // 初始化选中所有监控任务 + useEffect(() => { + if (allMonitorKeys.length > 0 && selectedMonitors.size === 0) { + setSelectedMonitors(new Set(allMonitorKeys)); + } + }, [allMonitorKeys, selectedMonitors.size]); + + // 过滤后的监控任务列表 + const monitorKeys = useMemo(() => { + return allMonitorKeys.filter(key => selectedMonitors.has(key)); + }, [allMonitorKeys, selectedMonitors]); + + // 数据转换 - 支持多个监控任务(统一时间轴 + 线性插值) + const chartData = useMemo(() => { + const series = metricsResponse?.series || []; + if (series.length === 0) return []; + + // 收集所有监控任务的数据 + const seriesDataArray: Array<{ key: string; data: Array<{ timestamp: number; value: number }> }> = []; + + series.forEach((s) => { + const monitorKey = s.labels?.monitor_name || s.labels?.monitor_id || s.name; + if (!selectedMonitors.has(monitorKey)) return; + if (!s.data || s.data.length === 0) return; + + seriesDataArray.push({ + key: monitorKey, + data: [...s.data].sort((a, b) => a.timestamp - b.timestamp) + }); + }); + + if (seriesDataArray.length === 0) return []; + + // 取所有监控任务时间范围的交集,确保每个时间点所有任务都有数据 + let minTime = -Infinity, maxTime = Infinity; + seriesDataArray.forEach(s => { + if (s.data.length > 0) { + minTime = Math.max(minTime, s.data[0].timestamp); + maxTime = Math.min(maxTime, s.data[s.data.length - 1].timestamp); + } + }); + + // 如果没有交集,返回空数组 + if (minTime >= maxTime) return []; + + // 均匀生成目标时间点 + const maxPoints = getMaxDataPoints(timeRange); + const timeStep = (maxTime - minTime) / (maxPoints - 1); + const targetTimestamps: number[] = []; + for (let i = 0; i < maxPoints; i++) { + targetTimestamps.push(minTime + i * timeStep); + } + + // 线性插值函数 + const interpolate = (data: Array<{ timestamp: number; value: number }>, targetTime: number): number | null => { + if (data.length === 0) return null; + if (data.length === 1) { + // 单点数据,只有精确匹配才返回 + return data[0].timestamp === targetTime ? data[0].value : null; + } + + // 如果目标时间在数据范围外,返回 null(断开折线) + if (targetTime < data[0].timestamp || targetTime > data[data.length - 1].timestamp) { + return null; + } + + // 二分查找找到 targetTime 前后两个点 + let left = 0, right = data.length - 1; + while (right - left > 1) { + const mid = Math.floor((left + right) / 2); + if (data[mid].timestamp <= targetTime) { + left = mid; + } else { + right = mid; + } + } + + // 线性插值 + const leftPoint = data[left]; + const rightPoint = data[right]; + const ratio = (targetTime - leftPoint.timestamp) / (rightPoint.timestamp - leftPoint.timestamp); + return leftPoint.value + ratio * (rightPoint.value - leftPoint.value); + }; + + // 对每个时间点,从每个监控任务中插值获取值 + return targetTimestamps.map(timestamp => { + const dataPoint: any = { timestamp }; + seriesDataArray.forEach(s => { + const value = interpolate(s.data, timestamp); + if (value !== null) { + dataPoint[s.key] = Number(value.toFixed(2)); + } + }); + return dataPoint; + }); + }, [metricsResponse, selectedMonitors, timeRange, start, end]); + + // 动态生成颜色(根据监控项数量) + const colors = useMemo(() => { + return generateColors(allMonitorKeys.length); + }, [allMonitorKeys.length]); + + const toggleMonitor = (monitorKey: string) => { + setSelectedMonitors((current) => { + if (current.size === allMonitorKeys.length) return new Set([monitorKey]); + const next = new Set(current); + if (next.has(monitorKey)) next.delete(monitorKey); + else next.add(monitorKey); + return next; + }); + }; + + const handleAreaClick = (data: unknown) => { + const key = (data as {dataKey?: string})?.dataKey; + if (key) toggleMonitor(key); + }; + + const handleLegendClick = (data: {value?: string}) => { + if (data?.value) toggleMonitor(data.value); + }; + + // 恢复全选 + const handleSelectAll = () => { + setSelectedMonitors(new Set(allMonitorKeys)); + }; + + // 是否有监控项未选中(用于显示恢复按钮) + const hasUnselected = selectedMonitors.size < allMonitorKeys.length; + + // 切换图例显示/隐藏(仅移动端) + const toggleLegend = () => setLegendCollapsed((collapsed) => !collapsed); + + // 如果没有数据且不是加载中,不渲染组件 + if (!isLoading && chartData.length === 0) { + return null; + } + + // 渲染 + if (isLoading) { + return ( + + + + ); + } + + return ( + + {chartData.length > 0 ? ( + <> + {/* 使用提示和恢复按钮 */} + {allMonitorKeys.length > 1 && ( +
+
+ 💡 点击图表线条或图例切换显示 +
+ {hasUnselected && ( + + )} +
+ )} + + + + + {monitorKeys.map((key, index) => { + const originalIndex = allMonitorKeys.indexOf(key); + return ( + + + + + ); + })} + + + formatChartTime(Number(value), timeRange, rangeMs)} + stroke="currentColor" + angle={-15} + textAnchor="end" + className="text-xs text-gray-600 dark:text-cyan-500 font-mono" + height={45} + /> + `${value}ms`} + /> + } + wrapperStyle={{zIndex: 9999,}} + /> + {monitorKeys.map((key, index) => { + const originalIndex = allMonitorKeys.indexOf(key); + return ( + + ); + })} + + + + {/* 桌面端:直接显示图例 */} + {!isMobile && allMonitorKeys.length > 0 && ( + + )} + + {/* 移动端:可折叠图例 */} + {isMobile && allMonitorKeys.length > 0 && ( +
+ + +
+ )} + + ) : ( + + )} +
+ ); +}; + +const MonitorChart = memo(MonitorChartImpl); + +/* ========================================== ServerDetail ========================================== */ + +/** + * 服务器详情页面 + * 显示服务器的详细信息、实时指标和历史趋势图表 + */ +const ServerDetail = () => { + const {id} = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [timeRange, setTimeRange] = useState(LIVE_RANGE); + const [customRange, setCustomRange] = useState<{ start: number; end: number } | null>(null); + + const handleCustomRangeApply = (range: { start: number; end: number }) => { + setCustomRange(range); + }; + + const isLive = timeRange === LIVE_RANGE; + const customStart = timeRange === 'custom' ? customRange?.start : undefined; + const customEnd = timeRange === 'custom' ? customRange?.end : undefined; + + // 查询基础数据(用于页面头部和系统信息) + // 实时模式 1s 拉取最新指标,其余 5s + const {data: agentResponse, isLoading} = useAgentQuery(id); + const {data: latestMetricsResponse} = useLatestMetricsQuery(id, isLive ? 1000 : 5000); + + const agent = agentResponse; + const latestMetrics = latestMetricsResponse || null; + + const deviceIpInterfaces = (latestMetrics?.networkInterfaces || []) + .map((netInterface) => ({ + name: netInterface.interface, + addrs: Array.from(new Set((netInterface.addrs || []).map((addr) => addr.trim()).filter(Boolean))), + })) + .filter((netInterface) => netInterface.addrs.length > 0); + + if (isLoading) { + return ; + } + + if (!agent) { + return ; + } + + return ( +
+
+ {/* 头部区域 */} + navigate('/')} + /> + + {/* 主内容区 */} +
+ {/* 网络地址信息 */} + {(agent.ipv4 || agent.ipv6 || deviceIpInterfaces?.length > 0) && ( + + )} + + {/* 系统信息 */} + + + {/* 历史趋势图表 */} + + +
+ } + > +
+ {/* 核心指标:大屏 2 列,小屏 1 列 */} +
+ + +
+ + {/* 网络相关:大屏 2 列,中屏 1 列 */} +
+ + +
+ + {/* 进阶指标:单列全宽 */} +
+ +
+ + {/* 硬件指标:条件渲染,单列全宽 */} +
+ + +
+ + {/* 监控指标:单列全宽 */} +
+ +
+
+ + + {/* 网络连接统计 */} + + + {/* GPU 监控 */} + + + {/* 温度监控 */} + + +
+
+ ); +}; + +export default ServerDetail; diff --git a/web/src/portal/pages/ServerList.tsx b/web/portal/src/pages/ServerList.tsx similarity index 58% rename from web/src/portal/pages/ServerList.tsx rename to web/portal/src/pages/ServerList.tsx index bec4512..9223973 100644 --- a/web/src/portal/pages/ServerList.tsx +++ b/web/portal/src/pages/ServerList.tsx @@ -1,11 +1,13 @@ -import {type ReactNode, useMemo, useState} from 'react'; -import {useNavigate} from 'react-router-dom'; +import {type FC, type ReactNode, useMemo, useState} from 'react'; +import {Link, useNavigate} from 'react-router-dom'; import {useQuery} from '@tanstack/react-query'; +import * as Tooltip from '@radix-ui/react-tooltip'; import { Activity, AlertTriangle, ArrowDown, ArrowUp, + Calendar, Clock, Cpu, Filter, @@ -17,38 +19,111 @@ import { Thermometer, UnlinkIcon } from 'lucide-react'; -import {getPublicTags, listAgents} from '@/api/agent.ts'; -import type {Agent, LatestMetrics} from '@/types'; -import {cn} from '@/lib/utils.ts'; -import CompactResourceBar from "@portal/components/CompactResourceBar.tsx"; -import StatBlock from "@portal/components/StatBlock.tsx"; -import ServerCard from "@portal/components/ServerCard.tsx"; -import NetworkStatCard from "@portal/components/NetworkStatCard.tsx"; -import {formatBytes, formatSpeed, formatTime, formatUptime} from "@/lib/format.ts"; -import {isExpired} from "@portal/utils/server.ts"; -import {LoadingSpinner} from "@portal/components/LoadingSpinner.tsx"; +import {pika} from '../api'; +import type {Agent, LatestMetrics, TagsResponse} from '../types'; +import {cn, formatBytes, formatSpeed, formatTime, formatUptime, isExpired} from '../lib/utils'; +import {Card, LoadingSpinner, StatBlock} from '../components'; interface AgentWithMetrics extends Agent { metrics?: LatestMetrics; } -interface EmptyStateProps { - title: string; - description: string; - extra?: ReactNode; -} +/* ========================================== CompactResourceBar ========================================== */ -const EmptyState = ({title, description, extra}: EmptyStateProps) => ( -
-
- +// 紧凑型资源条组件 +const CompactResourceBar = ({value, label, subtext, icon: Icon, color = "bg-cyan-500"}: { + value: number; + label: string; + subtext?: string; + icon: any; + color?: string; +}) => { + const isCritical = value > 90; + const isWarning = value > 75; + + // 颜色定义 (Hex codes for precise control) + let barColor = ""; + let iconClass = ""; + let textClass = "dark:text-cyan-50"; // 默认高亮白/青 + + if (isCritical) { + barColor = "bg-rose-500"; + iconClass = "text-rose-600 dark:text-rose-500"; + textClass = "text-rose-400"; + } else if (isWarning) { + barColor = "bg-amber-500"; + iconClass = "text-amber-600 dark:text-amber-400"; + textClass = "text-amber-400"; + } else if (color.includes("purple")) { + barColor = "bg-purple-500"; + iconClass = "text-purple-600 dark:text-purple-400"; + } else if (color.includes("blue")) { + barColor = "bg-blue-500"; + iconClass = "text-blue-600 dark:text-blue-400"; + } else { + barColor = "bg-cyan-500"; + iconClass = "text-cyan-600 dark:text-cyan-400"; + } + + return ( +
+ + + +
+ {/* Icon & Label */} +
+ + {label} +
+ + {/* Track Container */} +
+ + {/* Scale Marks Background (The "Ruler" effect) */} +
+ +
+
+
+
+
+ {value.toFixed(1)}% +
+
+
+ + + {subtext} + + + +
+
-

{title}

-

{description}

- {extra ?
{extra}
: null} -
-); + ); +}; + +/* ========================================== 共享辅助函数 ========================================== */ const calculateNetworkSpeed = (metrics?: LatestMetrics) => { if (!metrics?.network) { @@ -82,16 +157,272 @@ const getTrafficProgressColor = (percent: number) => { return 'bg-emerald-500'; }; +/* ========================================== ServerCard ========================================== */ + +interface ServerCardProps { + server: AgentWithMetrics; +} + +const ServerCard: FC = ({server}) => { + const isOnline = server.status === 1; + const cpuUsage = server.metrics?.cpu?.usagePercent ?? 0; + const memoryUsage = server.metrics?.memory?.usagePercent ?? 0; + const memoryTotal = server.metrics?.memory?.total ?? 0; + const memoryUsed = server.metrics?.memory?.used ?? 0; + const diskUsage = calculateDiskUsage(server.metrics); + const diskTotal = server.metrics?.disk?.total ?? 0; + const diskUsed = server.metrics?.disk?.used ?? 0; + const {upload, download} = calculateNetworkSpeed(server.metrics); + const temperatures = getTemperatures(server.metrics); + const netConn = server.metrics?.networkConnection; + const traffic = server.trafficStats; + const trafficUsagePercent = traffic?.enabled && traffic.limit > 0 + ? Math.min(100, (traffic.used / traffic.limit) * 100) + : 0; + + return ( + + +
+ {/* 顶部:名称和状态 */} +
+
+
+ {server.name || server.hostname} +
+
+ {server.os} + + {server.arch} +
+
+ {server.tags && server.tags.length > 0 && ( +
+ {server.tags.slice(0, 2).map(tag => ( + + #{tag} + + ))} + {server.tags.length > 2 && ( + + +{server.tags.length - 2} + + )} +
+ )} +
+ + {isOnline && server.metrics?.host && ( +
+
+ + {formatUptime(server.metrics.host.uptime)} +
+ +
+ + {server.metrics.host.procs} 进程 +
+
+ )} + + {/* 资源使用情况 */} + {isOnline ? ( +
+ + + + {temperatures.length > 0 && ( +
+ + {temperatures.map((temp, index) => ( + + {temp.temperature?.toFixed(1)}°C + {temp.type} + {index < temperatures.length - 1 && + |} + + ))} +
+ )} +
+ ) : ( +
+ + 离线 +
+ )} + + {/* 网络和流量 */} +
+
+
+ + + {formatSpeed(download)} + + + + {formatSpeed(upload)} + +
+ {server.expireTime > 0 && ( +
+ + {new Date(server.expireTime).toLocaleDateString('zh-CN')} +
+ )} +
+ {isOnline && netConn && ( +
+ + + {netConn.established || 0} + ESTABLISHED + + + + {netConn.listen || 0} + LISTEN + + + + {netConn.closeWait || 0} + CLOSE_WAIT + +
+ )} + {traffic?.enabled && ( +
+
+ + {traffic.type === 'recv' ? '进站' : traffic.type === 'send' ? '出站' : '全部'}流量 +
+ {traffic.limit > 0 ? ( + <> +
+ + {formatBytes(traffic.used, 1)} / {formatBytes(traffic.limit, 1)} + + + {trafficUsagePercent.toFixed(1)}% + +
+
+
+
+
+ 重置日期: 每月 {traffic.resetDay} 号 +
+ + ) : ( +
+ 已使用: {formatBytes(traffic.used, 1)} +
仅统计模式
+
+ )} +
+ )} +
+
+ + + ); +}; + +/* ========================================== ServerList 本地组件 ========================================== */ + +const NetworkStatCard = ({uploadRate, downloadRate, uploadTotal, downloadTotal}: { + uploadRate: number; + downloadRate: number; + uploadTotal: number; + downloadTotal: number; +}) => ( +
+ +
+
+
网络统计
+
+
+ + {formatSpeed(uploadRate)} + ({formatBytes(uploadTotal)}) +
+
+ + {formatSpeed(downloadRate)} + ({formatBytes(downloadTotal)}) +
+
+
+ +
+
+); + +interface ServerListEmptyProps { + title: string; + description: string; + extra?: ReactNode; +} + +const ServerListEmpty = ({title, description, extra}: ServerListEmptyProps) => ( +
+
+ +
+

{title}

+

{description}

+ {extra ?
{extra}
: null} +
+); + +/* ========================================== ServerList ========================================== */ + const ServerList = () => { const navigate = useNavigate(); const [selectedTag, setSelectedTag] = useState(''); const {data: agents = [], isLoading} = useQuery({ queryKey: ['agents', 'online'], - queryFn: async () => { - const response = await listAgents(); - return (response.data || []) as AgentWithMetrics[]; - }, + queryFn: () => pika.listAgents(), refetchInterval: 3000, }); @@ -99,8 +430,8 @@ const ServerList = () => { const {data: tagsData} = useQuery({ queryKey: ['tags', 'public'], queryFn: async () => { - const response = await getPublicTags(); - return response.data.tags || []; + const response = await pika.getTags(); + return response.tags || []; }, refetchInterval: 30000, }); @@ -243,7 +574,7 @@ const ServerList = () => { {/* 服务器列表 */} {displayAgents.length === 0 ? ( - diff --git a/web/portal/src/types.ts b/web/portal/src/types.ts new file mode 100644 index 0000000..74d37d8 --- /dev/null +++ b/web/portal/src/types.ts @@ -0,0 +1,255 @@ +/* 主题前端使用的全部类型定义。 */ + +export type ColorMode = 'light' | 'dark' | 'system'; +export type MetricsAggregation = 'avg' | 'max' | 'raw'; + +export interface PikaRuntimeConfig { + apiVersion: 'v1'; + system: { + nameZh: string; + nameEn: string; + logo: string; + icpCode: string; + version: string; + defaultView: string; + defaultColorMode: ColorMode; + }; + theme: {id: string; version: string}; + features: Record; + legacySystemConfig?: LegacySystemConfig; +} + +interface LegacySystemConfig { + SystemNameZh: string; + SystemNameEn: string; + ICPCode: string; + DefaultView: string; + Version: string; +} + +export interface ListAgentParams {tag?: string; page?: number; pageSize?: number; [key: string]: string | number | boolean | undefined} +export interface MetricsParams { + type?: 'cpu' | 'memory' | 'disk' | 'network' | 'network_connection' | 'disk_io' | 'gpu' | 'temperature' | 'monitor'; + range?: string; + start?: number; + end?: number; + interface?: string; + aggregation?: MetricsAggregation; + [key: string]: string | number | boolean | undefined; +} +export interface HistoryParams { + range?: string; + start?: number; + end?: number; + aggregation?: MetricsAggregation; + [key: string]: string | number | boolean | undefined; +} +export interface TimeRangeOption {label: string; value: string} + +export interface Agent { + id: string; + name: string; + hostname: string; + ip: string; + ipv4?: string; + ipv6?: string; + os: string; + arch: string; + version: string; + tags?: string[]; + expireTime?: number; + status: number; + visibility?: string; + weight?: number; + remark?: string; + lastSeenAt: string | number; + trafficStats?: { + enabled: boolean; + type: string; + limit: number; + used: number; + resetDay: number; + periodStart: number; + periodEnd: number; + daysUntilReset: number; + baselineRecv: number; + baselineSend: number; + alertSent80: boolean; + alertSent90: boolean; + alertSent100: boolean; + }; +} + +export interface CPUMetric { + timestamp: number; + logicalCores: number; + physicalCores: number; + modelName: string; + usagePercent: number; +} + +export interface MemoryMetric { + timestamp: number; + total: number; + used: number; + free: number; + available: number; + usagePercent: number; + swapTotal: number; + swapUsed: number; + swapFree: number; +} + +export interface DiskSummary { + usagePercent: number; + totalDisks: number; + total: number; + used: number; + free: number; +} + +export interface DiskIOSummary { + totalReadBytesRate: number; + totalWriteBytesRate: number; + totalDevices: number; +} + +export interface NetworkSummary { + totalBytesSentRate: number; + totalBytesRecvRate: number; + totalBytesSentTotal: number; + totalBytesRecvTotal: number; + totalInterfaces: number; +} + +export interface NetworkInterfaceMetric { + interface: string; + macAddress?: string; + addrs?: string[]; + bytesSentRate: number; + bytesRecvRate: number; + bytesSentTotal: number; + bytesRecvTotal: number; +} + +export interface NetworkConnectionMetric { + established: number; + synSent: number; + synRecv: number; + finWait1: number; + finWait2: number; + timeWait: number; + close: number; + closeWait: number; + lastAck: number; + listen: number; + closing: number; + total: number; +} + +export interface HostInfo { + uptime: number; + bootTime: number; + procs: number; + load1: number; + load5: number; + load15: number; + platform: string; + platformVersion: string; + kernelVersion: string; + kernelArch: string; +} + +export interface GPUMetric { + index: number; + name: string; + utilization: number; + memoryTotal: number; + memoryUsed: number; + memoryFree: number; + temperature: number; + powerUsage: number; + fanSpeed: number; +} + +export interface TemperatureMetric { + sensorKey: string; + type: string; + temperature: number; +} + +export interface MonitorData { + agentId: string; + agentName: string; + monitorId: string; + monitorName?: string; + type: string; + target?: string; + status: string; + statusCode?: number; + responseTime: number; + error?: string; + checkedAt: number; + message?: string; + contentMatch?: boolean; + certExpiryTime?: number; + certDaysLeft?: number; +} + +export interface LatestMetrics { + timestamp?: number; + cpu?: CPUMetric; + memory?: MemoryMetric; + disk?: DiskSummary; + diskIO?: DiskIOSummary; + network?: NetworkSummary; + networkInterfaces?: NetworkInterfaceMetric[]; + networkConnection?: NetworkConnectionMetric; + host?: HostInfo; + gpu?: GPUMetric[]; + temperature?: TemperatureMetric[]; + monitors?: MonitorData[]; +} + +export interface PublicMonitor { + id: string; + name: string; + type: 'http' | 'https' | 'tcp' | 'icmp' | 'ping'; + target: string; + showTargetPublic: boolean; + description?: string; + enabled: boolean; + interval: number; + agentIds: string[]; + agentCount: number; + status: string; + responseTime: number; + responseTimeMin: number; + responseTimeMax: number; + certExpiryTime: number; + certDaysLeft: number; + agentStats: {up: number; down: number; unknown: number}; + lastCheckTime: number; +} + +export interface AgentMonitorStat { + agentId: string; + agentName: string; + monitorId: string; + type: string; + target: string; + status: string; + statusCode: number; + responseTime: number; + checkedAt: number; + message: string; + certExpiryTime: number; + certDaysLeft: number; +} + +export interface MetricPoint {timestamp: number; value: number} +export interface MetricSeries {name: string; labels?: Record; data: MetricPoint[]} +export interface MetricsResponse {agentId: string; type: string; range: string; series: MetricSeries[]} +export interface TagsResponse {tags: string[]} +export interface NetworkInterfacesResponse {interfaces: string[]} +export interface CurrentUser {userId: string; username: string} diff --git a/web/portal/tsconfig.app.json b/web/portal/tsconfig.app.json new file mode 100644 index 0000000..bf71a42 --- /dev/null +++ b/web/portal/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": false, + + "paths": { + "@/*": [ + "./src/*" + ] + }, + "allowSyntheticDefaultImports": true + }, + "include": ["src"] +} diff --git a/web/portal/tsconfig.json b/web/portal/tsconfig.json new file mode 100644 index 0000000..4fa15f4 --- /dev/null +++ b/web/portal/tsconfig.json @@ -0,0 +1,18 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.node.json" + } + ], + "compilerOptions": { + "paths": { + "@/*": [ + "./src/*" + ] + } + } +} diff --git a/web/portal/tsconfig.node.json b/web/portal/tsconfig.node.json new file mode 100644 index 0000000..5104571 --- /dev/null +++ b/web/portal/tsconfig.node.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + }, + "include": ["vite.config.ts"] +} diff --git a/web/portal/vite.config.ts b/web/portal/vite.config.ts new file mode 100644 index 0000000..76b917d --- /dev/null +++ b/web/portal/vite.config.ts @@ -0,0 +1,20 @@ +import {defineConfig} from 'vite'; +import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; +import path from 'path'; + +export default defineConfig(({command}) => ({ + base: command === 'serve' ? '/' : '/theme-assets/', + plugins: [react(), tailwindcss()], + publicDir: path.resolve(__dirname, 'public'), + resolve: {alias: {'@': path.resolve(__dirname, './src')}}, + build: { + outDir: path.resolve(__dirname, 'dist'), + emptyOutDir: true, + assetsDir: 'assets', + }, + server: { + port: 5173, + proxy: {'/api/': {target: 'http://localhost:8080/', changeOrigin: true}}, + }, +})); diff --git a/web/portal/yarn.lock b/web/portal/yarn.lock new file mode 100644 index 0000000..d802cd4 --- /dev/null +++ b/web/portal/yarn.lock @@ -0,0 +1,1384 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": + version "4.10.1" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz" + integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.12.2": + version "4.12.2" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/config-array@^0.23.5": + version "0.23.5" + resolved "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz" + integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA== + dependencies: + "@eslint/object-schema" "^3.0.5" + debug "^4.3.1" + minimatch "^10.2.4" + +"@eslint/config-helpers@^0.7.0": + version "0.7.0" + resolved "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz" + integrity sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw== + dependencies: + "@eslint/core" "^1.2.1" + +"@eslint/core@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz" + integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/object-schema@^3.0.5": + version "3.0.5" + resolved "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz" + integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw== + +"@eslint/plugin-kit@^0.7.2": + version "0.7.2" + resolved "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz" + integrity sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A== + dependencies: + "@eslint/core" "^1.2.1" + levn "^0.4.1" + +"@floating-ui/core@^1.8.0": + version "1.8.0" + resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz" + integrity sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ== + dependencies: + "@floating-ui/utils" "^0.2.12" + +"@floating-ui/dom@^1.8.0": + version "1.8.0" + resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz" + integrity sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg== + dependencies: + "@floating-ui/core" "^1.8.0" + "@floating-ui/utils" "^0.2.12" + +"@floating-ui/react-dom@^2.0.0": + version "2.1.9" + resolved "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz" + integrity sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg== + dependencies: + "@floating-ui/dom" "^1.8.0" + +"@floating-ui/utils@^0.2.12": + version "0.2.12" + resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz" + integrity sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww== + +"@humanfs/core@^0.19.2": + version "0.19.2" + resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz" + integrity sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA== + dependencies: + "@humanfs/types" "^0.15.0" + +"@humanfs/node@^0.16.6": + version "0.16.8" + resolved "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz" + integrity sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ== + dependencies: + "@humanfs/core" "^0.19.2" + "@humanfs/types" "^0.15.0" + "@humanwhocodes/retry" "^0.4.0" + +"@humanfs/types@^0.15.0": + version "0.15.0" + resolved "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz" + integrity sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q== + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.31" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@oxc-project/types@=0.139.0": + version "0.139.0" + resolved "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz" + integrity sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw== + +"@radix-ui/primitive@1.1.7": + version "1.1.7" + resolved "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz" + integrity sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q== + +"@radix-ui/react-arrow@1.1.15": + version "1.1.15" + resolved "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz" + integrity sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA== + dependencies: + "@radix-ui/react-primitive" "2.1.10" + +"@radix-ui/react-compose-refs@1.1.5": + version "1.1.5" + resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz" + integrity sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA== + +"@radix-ui/react-context@1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz" + integrity sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA== + +"@radix-ui/react-dismissable-layer@1.1.19": + version "1.1.19" + resolved "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz" + integrity sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w== + dependencies: + "@radix-ui/primitive" "1.1.7" + "@radix-ui/react-compose-refs" "1.1.5" + "@radix-ui/react-primitive" "2.1.10" + "@radix-ui/react-use-callback-ref" "1.1.4" + "@radix-ui/react-use-effect-event" "0.0.5" + +"@radix-ui/react-id@1.1.4": + version "1.1.4" + resolved "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz" + integrity sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.4" + +"@radix-ui/react-popper@1.3.7": + version "1.3.7" + resolved "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz" + integrity sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg== + dependencies: + "@floating-ui/react-dom" "^2.0.0" + "@radix-ui/react-arrow" "1.1.15" + "@radix-ui/react-compose-refs" "1.1.5" + "@radix-ui/react-context" "1.2.2" + "@radix-ui/react-primitive" "2.1.10" + "@radix-ui/react-use-callback-ref" "1.1.4" + "@radix-ui/react-use-layout-effect" "1.1.4" + "@radix-ui/react-use-rect" "1.1.4" + "@radix-ui/react-use-size" "1.1.4" + "@radix-ui/rect" "1.1.3" + +"@radix-ui/react-portal@1.1.17": + version "1.1.17" + resolved "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz" + integrity sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ== + dependencies: + "@radix-ui/react-primitive" "2.1.10" + "@radix-ui/react-use-layout-effect" "1.1.4" + +"@radix-ui/react-presence@1.1.10": + version "1.1.10" + resolved "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz" + integrity sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.4" + +"@radix-ui/react-primitive@2.1.10": + version "2.1.10" + resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz" + integrity sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg== + dependencies: + "@radix-ui/react-slot" "1.3.3" + +"@radix-ui/react-slot@1.3.3": + version "1.3.3" + resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz" + integrity sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q== + dependencies: + "@radix-ui/react-compose-refs" "1.1.5" + +"@radix-ui/react-tooltip@^1.2.12": + version "1.2.16" + resolved "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz" + integrity sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg== + dependencies: + "@radix-ui/primitive" "1.1.7" + "@radix-ui/react-compose-refs" "1.1.5" + "@radix-ui/react-context" "1.2.2" + "@radix-ui/react-dismissable-layer" "1.1.19" + "@radix-ui/react-id" "1.1.4" + "@radix-ui/react-popper" "1.3.7" + "@radix-ui/react-portal" "1.1.17" + "@radix-ui/react-presence" "1.1.10" + "@radix-ui/react-primitive" "2.1.10" + "@radix-ui/react-slot" "1.3.3" + "@radix-ui/react-use-controllable-state" "1.2.6" + "@radix-ui/react-use-layout-effect" "1.1.4" + "@radix-ui/react-visually-hidden" "1.2.11" + +"@radix-ui/react-use-callback-ref@1.1.4": + version "1.1.4" + resolved "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz" + integrity sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ== + +"@radix-ui/react-use-controllable-state@1.2.6": + version "1.2.6" + resolved "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz" + integrity sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ== + dependencies: + "@radix-ui/primitive" "1.1.7" + "@radix-ui/react-use-effect-event" "0.0.5" + "@radix-ui/react-use-layout-effect" "1.1.4" + +"@radix-ui/react-use-effect-event@0.0.5": + version "0.0.5" + resolved "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz" + integrity sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.4" + +"@radix-ui/react-use-layout-effect@1.1.4": + version "1.1.4" + resolved "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz" + integrity sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw== + +"@radix-ui/react-use-rect@1.1.4": + version "1.1.4" + resolved "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz" + integrity sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ== + dependencies: + "@radix-ui/rect" "1.1.3" + +"@radix-ui/react-use-size@1.1.4": + version "1.1.4" + resolved "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz" + integrity sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.4" + +"@radix-ui/react-visually-hidden@1.2.11": + version "1.2.11" + resolved "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz" + integrity sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ== + dependencies: + "@radix-ui/react-primitive" "2.1.10" + +"@radix-ui/rect@1.1.3": + version "1.1.3" + resolved "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz" + integrity sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw== + +"@reduxjs/toolkit@^1.9.0 || 2.x.x": + version "2.12.0" + resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz" + integrity sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw== + dependencies: + "@standard-schema/spec" "^1.0.0" + "@standard-schema/utils" "^0.3.0" + immer "^11.0.0" + redux "^5.0.1" + redux-thunk "^3.1.0" + reselect "^5.1.0" + +"@rolldown/binding-darwin-arm64@1.1.5": + version "1.1.5" + resolved "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz" + integrity sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw== + +"@rolldown/pluginutils@^1.0.0", "@rolldown/pluginutils@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz" + integrity sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw== + +"@standard-schema/spec@^1.0.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + +"@standard-schema/utils@^0.3.0": + version "0.3.0" + resolved "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz" + integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== + +"@tailwindcss/node@4.3.3": + version "4.3.3" + resolved "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz" + integrity sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg== + dependencies: + "@jridgewell/remapping" "^2.3.5" + enhanced-resolve "^5.24.1" + jiti "^2.7.0" + lightningcss "1.32.0" + magic-string "^0.30.21" + source-map-js "^1.2.1" + tailwindcss "4.3.3" + +"@tailwindcss/oxide-darwin-arm64@4.3.3": + version "4.3.3" + resolved "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz" + integrity sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw== + +"@tailwindcss/oxide@4.3.3": + version "4.3.3" + resolved "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz" + integrity sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA== + optionalDependencies: + "@tailwindcss/oxide-android-arm64" "4.3.3" + "@tailwindcss/oxide-darwin-arm64" "4.3.3" + "@tailwindcss/oxide-darwin-x64" "4.3.3" + "@tailwindcss/oxide-freebsd-x64" "4.3.3" + "@tailwindcss/oxide-linux-arm-gnueabihf" "4.3.3" + "@tailwindcss/oxide-linux-arm64-gnu" "4.3.3" + "@tailwindcss/oxide-linux-arm64-musl" "4.3.3" + "@tailwindcss/oxide-linux-x64-gnu" "4.3.3" + "@tailwindcss/oxide-linux-x64-musl" "4.3.3" + "@tailwindcss/oxide-wasm32-wasi" "4.3.3" + "@tailwindcss/oxide-win32-arm64-msvc" "4.3.3" + "@tailwindcss/oxide-win32-x64-msvc" "4.3.3" + +"@tailwindcss/vite@^4.3.2": + version "4.3.3" + resolved "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz" + integrity sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw== + dependencies: + "@tailwindcss/node" "4.3.3" + "@tailwindcss/oxide" "4.3.3" + tailwindcss "4.3.3" + +"@tanstack/query-core@5.101.4": + version "5.101.4" + resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz" + integrity sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw== + +"@tanstack/react-query@^5.101.2": + version "5.101.4" + resolved "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz" + integrity sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA== + dependencies: + "@tanstack/query-core" "5.101.4" + +"@types/d3-array@^3.0.3": + version "3.2.2" + resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz" + integrity sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-ease@^3.0.0": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-interpolate@^3.0.1": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.1" + resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz" + integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== + +"@types/d3-scale@^4.0.2": + version "4.0.9" + resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz" + integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== + dependencies: + "@types/d3-time" "*" + +"@types/d3-shape@^3.1.0": + version "3.1.8" + resolved "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz" + integrity sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time@*", "@types/d3-time@^3.0.0": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz" + integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== + +"@types/d3-timer@^3.0.0": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + +"@types/esrecurse@^4.3.1": + version "4.3.1" + resolved "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz" + integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== + +"@types/estree@^1.0.6", "@types/estree@^1.0.8": + version "1.0.9" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz" + integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@^20.19.0 || >=22.12.0", "@types/node@^26.1.1": + version "26.1.2" + resolved "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz" + integrity sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg== + dependencies: + undici-types "~8.3.0" + +"@types/react-dom@*", "@types/react-dom@^19.2.3": + version "19.2.4" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz" + integrity sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw== + +"@types/react@*", "@types/react@^18.2.25 || ^19", "@types/react@^19.2.0", "@types/react@^19.2.17": + version "19.2.18" + resolved "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz" + integrity sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w== + dependencies: + csstype "^3.2.2" + +"@types/use-sync-external-store@^0.0.6": + version "0.0.6" + resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz" + integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== + +"@typescript-eslint/eslint-plugin@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz" + integrity sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.65.0" + "@typescript-eslint/type-utils" "8.65.0" + "@typescript-eslint/utils" "8.65.0" + "@typescript-eslint/visitor-keys" "8.65.0" + ignore "^7.0.5" + natural-compare "^1.4.0" + ts-api-utils "^2.5.0" + +"@typescript-eslint/parser@^8.65.0", "@typescript-eslint/parser@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz" + integrity sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA== + dependencies: + "@typescript-eslint/scope-manager" "8.65.0" + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/typescript-estree" "8.65.0" + "@typescript-eslint/visitor-keys" "8.65.0" + debug "^4.4.3" + +"@typescript-eslint/project-service@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz" + integrity sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.65.0" + "@typescript-eslint/types" "^8.65.0" + debug "^4.4.3" + +"@typescript-eslint/scope-manager@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz" + integrity sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg== + dependencies: + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/visitor-keys" "8.65.0" + +"@typescript-eslint/tsconfig-utils@^8.65.0", "@typescript-eslint/tsconfig-utils@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz" + integrity sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg== + +"@typescript-eslint/type-utils@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz" + integrity sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g== + dependencies: + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/typescript-estree" "8.65.0" + "@typescript-eslint/utils" "8.65.0" + debug "^4.4.3" + ts-api-utils "^2.5.0" + +"@typescript-eslint/types@^8.65.0", "@typescript-eslint/types@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz" + integrity sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg== + +"@typescript-eslint/typescript-estree@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz" + integrity sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg== + dependencies: + "@typescript-eslint/project-service" "8.65.0" + "@typescript-eslint/tsconfig-utils" "8.65.0" + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/visitor-keys" "8.65.0" + debug "^4.4.3" + minimatch "^10.2.2" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.5.0" + +"@typescript-eslint/utils@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz" + integrity sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.65.0" + "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/typescript-estree" "8.65.0" + +"@typescript-eslint/visitor-keys@8.65.0": + version "8.65.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz" + integrity sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A== + dependencies: + "@typescript-eslint/types" "8.65.0" + eslint-visitor-keys "^5.0.0" + +"@vitejs/plugin-react@^6.0.3": + version "6.0.5" + resolved "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz" + integrity sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA== + dependencies: + "@rolldown/pluginutils" "^1.0.1" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.16.0: + version "8.18.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz" + integrity sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ== + +ajv@^6.14.0: + version "6.15.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz" + integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + +brace-expansion@^5.0.8: + version "5.0.9" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz" + integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg== + dependencies: + balanced-match "^4.0.2" + +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +cookie@^1.0.1: + version "1.1.1" + resolved "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz" + integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + +d3-array@^3.1.6, "d3-array@2 - 3", "d3-array@2.10.0 - 3": + version "3.2.4" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-ease@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-format@1 - 3": + version "3.1.2" + resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz" + integrity sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg== + +d3-interpolate@^3.0.1, "d3-interpolate@1.2.0 - 3": + version "3.0.1" + resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-scale@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + 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" + +d3-shape@^3.1.0: + version "3.2.0" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +d3-time@^3.0.0, "d3-time@1 - 3", "d3-time@2.1.1 - 3": + version "3.1.0" + resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +d3-timer@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +dayjs@^1.11.21: + version "1.11.21" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz" + integrity sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA== + +debug@^4.3.1, debug@^4.3.2, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +decimal.js-light@^2.5.1: + version "2.5.1" + resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +detect-libc@^2.0.3: + version "2.1.2" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + +enhanced-resolve@^5.24.1: + version "5.24.5" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz" + integrity sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.3.3" + +es-toolkit@^1.39.3: + version "1.50.0" + resolved "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz" + integrity sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^9.1.2: + version "9.1.2" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz" + integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ== + dependencies: + "@types/esrecurse" "^4.3.1" + "@types/estree" "^1.0.8" + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + +eslint@^10.7.0, "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0 || ^10.0.0": + version "10.8.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz" + integrity sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.2" + "@eslint/config-array" "^0.23.5" + "@eslint/config-helpers" "^0.7.0" + "@eslint/core" "^1.2.1" + "@eslint/plugin-kit" "^0.7.2" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.14.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^9.1.2" + eslint-visitor-keys "^5.0.1" + espree "^11.2.0" + esquery "^1.7.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + minimatch "^10.2.5" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^11.2.0: + version "11.2.0" + resolved "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz" + integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== + dependencies: + acorn "^8.16.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^5.0.1" + +esquery@^1.7.0: + version "1.7.0" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eventemitter3@^5.0.1: + version "5.0.4" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz" + integrity sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.4.4" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz" + integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q== + +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +globals@^17.7.0: + version "17.8.0" + resolved "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz" + integrity sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ== + +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^7.0.5: + version "7.0.6" + resolved "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz" + integrity sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw== + +immer@^11.0.0, immer@^11.1.8: + version "11.1.15" + resolved "https://registry.npmjs.org/immer/-/immer-11.1.15.tgz" + integrity sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jiti@*, jiti@^2.7.0, jiti@>=1.21.0: + version "2.7.0" + resolved "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz" + integrity sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lightningcss-darwin-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz" + integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== + +lightningcss@^1.32.0, lightningcss@1.32.0: + version "1.32.0" + resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz" + integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== + dependencies: + detect-libc "^2.0.3" + optionalDependencies: + lightningcss-android-arm64 "1.32.0" + lightningcss-darwin-arm64 "1.32.0" + lightningcss-darwin-x64 "1.32.0" + lightningcss-freebsd-x64 "1.32.0" + lightningcss-linux-arm-gnueabihf "1.32.0" + lightningcss-linux-arm64-gnu "1.32.0" + lightningcss-linux-arm64-musl "1.32.0" + lightningcss-linux-x64-gnu "1.32.0" + lightningcss-linux-x64-musl "1.32.0" + lightningcss-win32-arm64-msvc "1.32.0" + lightningcss-win32-x64-msvc "1.32.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lucide-react@^1.24.0: + version "1.28.0" + resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-1.28.0.tgz" + integrity sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg== + +magic-string@^0.30.21: + version "0.30.21" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +minimatch@^10.2.2, minimatch@^10.2.4, minimatch@^10.2.5: + version "10.2.6" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz" + integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A== + dependencies: + brace-expansion "^5.0.8" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@^3.3.16: + version "3.3.16" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz" + integrity sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +"picomatch@^3 || ^4", picomatch@^4.0.4, picomatch@^4.0.5: + version "4.0.5" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz" + integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A== + +postcss@^8.5.16: + version "8.5.25" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz" + integrity sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw== + dependencies: + nanoid "^3.3.16" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +"react-dom@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", react-dom@^19.2.7, react-dom@>=16.8.0, react-dom@>=18: + version "19.2.8" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz" + integrity sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ== + dependencies: + scheduler "^0.27.0" + +"react-is@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0": + version "19.2.8" + resolved "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz" + integrity sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ== + +"react-redux@^7.2.1 || ^8.1.3 || ^9.0.0", "react-redux@8.x.x || 9.x.x": + version "9.3.0" + resolved "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz" + integrity sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g== + dependencies: + "@types/use-sync-external-store" "^0.0.6" + use-sync-external-store "^1.4.0" + +react-router-dom@^7.18.1: + version "7.18.2" + resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz" + integrity sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw== + dependencies: + react-router "7.18.2" + +react-router@7.18.2: + version "7.18.2" + resolved "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz" + integrity sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg== + dependencies: + cookie "^1.0.1" + set-cookie-parser "^2.6.0" + +"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.9.0 || ^17.0.0 || ^18 || ^19", "react@^18 || ^19", "react@^18.0 || ^19", react@^19.2.7, react@^19.2.8, react@>=16.8.0, react@>=18: + version "19.2.8" + resolved "https://registry.npmjs.org/react/-/react-19.2.8.tgz" + integrity sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw== + +recharts@^3.9.2: + version "3.10.1" + resolved "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz" + integrity sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA== + dependencies: + "@reduxjs/toolkit" "^1.9.0 || 2.x.x" + clsx "^2.1.1" + decimal.js-light "^2.5.1" + es-toolkit "^1.39.3" + eventemitter3 "^5.0.1" + immer "^11.1.8" + react-redux "8.x.x || 9.x.x" + reselect "5.2.0" + tiny-invariant "^1.3.3" + use-sync-external-store "^1.2.2" + victory-vendor "^37.0.2" + +redux-thunk@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz" + integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== + +redux@^5.0.0, redux@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== + +reselect@^5.1.0, reselect@5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz" + integrity sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw== + +rolldown@~1.1.4: + version "1.1.5" + resolved "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz" + integrity sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA== + dependencies: + "@oxc-project/types" "=0.139.0" + "@rolldown/pluginutils" "^1.0.0" + optionalDependencies: + "@rolldown/binding-android-arm64" "1.1.5" + "@rolldown/binding-darwin-arm64" "1.1.5" + "@rolldown/binding-darwin-x64" "1.1.5" + "@rolldown/binding-freebsd-x64" "1.1.5" + "@rolldown/binding-linux-arm-gnueabihf" "1.1.5" + "@rolldown/binding-linux-arm64-gnu" "1.1.5" + "@rolldown/binding-linux-arm64-musl" "1.1.5" + "@rolldown/binding-linux-ppc64-gnu" "1.1.5" + "@rolldown/binding-linux-s390x-gnu" "1.1.5" + "@rolldown/binding-linux-x64-gnu" "1.1.5" + "@rolldown/binding-linux-x64-musl" "1.1.5" + "@rolldown/binding-openharmony-arm64" "1.1.5" + "@rolldown/binding-wasm32-wasi" "1.1.5" + "@rolldown/binding-win32-arm64-msvc" "1.1.5" + "@rolldown/binding-win32-x64-msvc" "1.1.5" + +scheduler@^0.27.0: + version "0.27.0" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== + +semver@^7.7.3: + version "7.8.5" + resolved "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz" + integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== + +set-cookie-parser@^2.6.0: + version "2.7.2" + resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz" + integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +tailwind-merge@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz" + integrity sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w== + +tailwindcss@4.3.3: + version "4.3.3" + resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz" + integrity sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ== + +tapable@^2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz" + integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A== + +tiny-invariant@^1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tinyglobby@^0.2.15, tinyglobby@^0.2.17: + version "0.2.17" + resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz" + integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.4" + +ts-api-utils@^2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz" + integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== + +tw-animate-css@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz" + integrity sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +typescript-eslint@^8.63.0: + version "8.65.0" + resolved "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz" + integrity sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.65.0" + "@typescript-eslint/parser" "8.65.0" + "@typescript-eslint/typescript-estree" "8.65.0" + "@typescript-eslint/utils" "8.65.0" + +typescript@>=4.8.4, "typescript@>=4.8.4 <6.1.0", typescript@~6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== + +undici-types@~8.3.0: + version "8.3.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz" + integrity sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0: + version "1.6.0" + resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + +victory-vendor@^37.0.2: + version "37.3.6" + resolved "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz" + integrity sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ== + 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" + +"vite@^5.2.0 || ^6 || ^7 || ^8", vite@^8.0.0, vite@8.1.4: + version "8.1.4" + resolved "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz" + integrity sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ== + dependencies: + lightningcss "^1.32.0" + picomatch "^4.0.5" + postcss "^8.5.16" + rolldown "~1.1.4" + tinyglobby "^0.2.17" + optionalDependencies: + fsevents "~2.3.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/web/scripts/assemble-web.mjs b/web/scripts/assemble-web.mjs new file mode 100644 index 0000000..b12ef67 --- /dev/null +++ b/web/scripts/assemble-web.mjs @@ -0,0 +1,16 @@ +import {access, copyFile, cp, mkdir, rm} from 'node:fs/promises'; +import {resolve} from 'node:path'; + +const webRoot = resolve(import.meta.dirname, '..'); +const output = resolve(webRoot, 'dist'); +const adminDist = resolve(webRoot, 'admin/dist'); +const portalDist = resolve(webRoot, 'portal/dist'); + +await access(resolve(adminDist, 'index.html')); +await access(resolve(portalDist, 'index.html')); +await rm(output, {recursive: true, force: true}); +await mkdir(resolve(output, 'default-theme'), {recursive: true}); +await cp(adminDist, resolve(output, 'admin'), {recursive: true}); +await cp(portalDist, resolve(output, 'default-theme/dist'), {recursive: true}); +await copyFile(resolve(webRoot, 'portal/pika-theme.json'), resolve(output, 'default-theme/pika-theme.json')); +await copyFile(resolve(webRoot, 'portal/public/logo.png'), resolve(output, 'logo.png')); diff --git a/web/scripts/verify-dist.mjs b/web/scripts/verify-dist.mjs new file mode 100644 index 0000000..8ff1ff9 --- /dev/null +++ b/web/scripts/verify-dist.mjs @@ -0,0 +1,32 @@ +import {access, readFile, stat} from 'node:fs/promises'; +import {resolve} from 'node:path'; + +const root = resolve(import.meta.dirname, '..'); +const requiredFiles = [ + 'dist/admin/index.html', + 'dist/default-theme/pika-theme.json', + 'dist/default-theme/dist/index.html', +]; +const requiredDirectories = ['dist/admin/assets', 'dist/default-theme/dist/assets']; + +for (const relative of requiredFiles) { + const target = resolve(root, relative); + await access(target); + if (!(await stat(target)).isFile()) throw new Error(`构建产物不是文件: ${relative}`); +} +for (const relative of requiredDirectories) { + const target = resolve(root, relative); + await access(target); + if (!(await stat(target)).isDirectory()) throw new Error(`构建产物不是目录: ${relative}`); +} + +const adminHTML = await readFile(resolve(root, 'dist/admin/index.html'), 'utf8'); +const portalHTML = await readFile(resolve(root, 'dist/default-theme/dist/index.html'), 'utf8'); +if (!adminHTML.includes('/admin/assets/') || adminHTML.includes('/theme-assets/')) { + throw new Error('管理 SPA 的资源前缀未与公开主题隔离'); +} +if (!portalHTML.includes('/theme-assets/') || portalHTML.includes('/admin/assets/')) { + throw new Error('默认公开主题的资源前缀未与管理 SPA 隔离'); +} + +console.log('admin SPA 与 default-theme SPA 构建产物校验通过'); diff --git a/web/src/App.tsx b/web/src/App.tsx deleted file mode 100644 index ae4b80f..0000000 --- a/web/src/App.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import {RouterProvider} from 'react-router-dom'; -import {App as AntdApp, ConfigProvider} from 'antd'; -import zhCN from 'antd/locale/zh_CN'; -import router from './router'; -import {ThemeProvider} from '@/portal/contexts/ThemeContext'; -import 'dayjs/locale/zh-cn'; -import dayjs from 'dayjs'; -import './App.css'; - -// 设置 dayjs 为中文 -dayjs.locale('zh-cn'); - -function App() { - return ( - - - - - - - - ); -} - -export default App; diff --git a/web/src/global.d.ts b/web/src/global.d.ts deleted file mode 100644 index e857811..0000000 --- a/web/src/global.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export {}; - -declare global { - interface Window { - SystemConfig: { - SystemNameZh: string; - SystemNameEn: string; - ICPCode: string; - DefaultView: string; - Version: string; - }; - } -} diff --git a/web/src/main.tsx b/web/src/main.tsx deleted file mode 100644 index 38180b9..0000000 --- a/web/src/main.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import {StrictMode} from 'react' -import {createRoot} from 'react-dom/client' -import {QueryClient, QueryClientProvider} from '@tanstack/react-query' -import './index.css' -import App from './App.tsx' - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - retry: 1, - }, - }, -}) - -createRoot(document.getElementById('root')!).render( - - - - - , -) diff --git a/web/src/portal/components/Card.tsx b/web/src/portal/components/Card.tsx deleted file mode 100644 index bbc3c6a..0000000 --- a/web/src/portal/components/Card.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type {ReactNode} from 'react'; -import CyberCard from "@portal/components/CyberCard.tsx"; - -interface CardProps { - title?: ReactNode; - description?: ReactNode; - action?: ReactNode; - children: ReactNode; - className?: string; -} - -export const Card = ({ - title, - description, - action, - children, - className, - }: CardProps) => { - return ( - - {(title || description || action) && ( -
-
- {title && ( -

- {title} -

- )} - {description && ( -

- {description} -

- )} -
- {action &&
{action}
} -
- )} -
{children}
-
- ); -}; diff --git a/web/src/portal/components/ChartPlaceholder.tsx b/web/src/portal/components/ChartPlaceholder.tsx deleted file mode 100644 index 71e2026..0000000 --- a/web/src/portal/components/ChartPlaceholder.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import {TrendingUp} from 'lucide-react'; -import {cn} from '@/lib/utils'; - -interface ChartPlaceholderProps { - icon?: typeof TrendingUp; - title?: string; - subtitle?: string; - heightClass?: string; - className?: string; -} - -export const ChartPlaceholder = ({ - icon: Icon = TrendingUp, - title = '暂无数据', - subtitle = '等待采集新数据后展示图表', - heightClass = 'h-52', - className, - }: ChartPlaceholderProps) => { - return ( -
-
- -

{title}

- {subtitle && ( -

- {subtitle} -

- )} -
-
- ); -}; diff --git a/web/src/portal/components/CompactResourceBar.tsx b/web/src/portal/components/CompactResourceBar.tsx deleted file mode 100644 index 4ed1fab..0000000 --- a/web/src/portal/components/CompactResourceBar.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import * as Tooltip from '@radix-ui/react-tooltip'; -import {cn} from "@/lib/utils.ts"; - -// 紧凑型资源条组件 -const CompactResourceBar = ({value, label, subtext, icon: Icon, color = "bg-cyan-500"}) => { - const isCritical = value > 90; - const isWarning = value > 75; - - // 颜色定义 (Hex codes for precise control) - let barColor = ""; - let iconClass = ""; - let textClass = "dark:text-cyan-50"; // 默认高亮白/青 - - if (isCritical) { - barColor = "bg-rose-500"; - iconClass = "text-rose-600 dark:text-rose-500"; - textClass = "text-rose-400"; - } else if (isWarning) { - barColor = "bg-amber-500"; - iconClass = "text-amber-600 dark:text-amber-400"; - textClass = "text-amber-400"; - } else if (color.includes("purple")) { - barColor = "bg-purple-500"; - iconClass = "text-purple-600 dark:text-purple-400"; - } else if (color.includes("blue")) { - barColor = "bg-blue-500"; - iconClass = "text-blue-600 dark:text-blue-400"; - } else { - barColor = "bg-cyan-500"; - iconClass = "text-cyan-600 dark:text-cyan-400"; - } - - return ( -
- - - -
- {/* Icon & Label */} -
- - {label} -
- - {/* Track Container */} -
- - {/* Scale Marks Background (The "Ruler" effect) */} -
- -
-
-
-
-
- {value.toFixed(1)}% -
-
-
- - - {subtext} - - - -
-
-
- ); -}; - -export default CompactResourceBar; diff --git a/web/src/portal/components/CustomTooltip.tsx b/web/src/portal/components/CustomTooltip.tsx deleted file mode 100644 index 1d913e3..0000000 --- a/web/src/portal/components/CustomTooltip.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import dayjs from 'dayjs'; -import {cn} from '@/lib/utils'; - -type CustomTooltipProps = { - active?: boolean; - payload?: Array<{ - name?: string; - value?: number; - color?: string; - dataKey?: string; - payload?: { - timestamp?: number | string; - [key: string]: unknown; - }; - }>; - label?: string | number; - unit?: string; - className?: string; - timeFormat?: string; -}; - -export const CustomTooltip = ({active, payload, label, unit = '%', className, timeFormat = 'MM-DD HH:mm'}: CustomTooltipProps) => { - if (!active || !payload || payload.length === 0) { - return null; - } - - // 从 payload 中获取完整的时间戳信息(如果有的话) - const fullTimestamp = payload[0]?.payload?.timestamp; - const displayLabel = fullTimestamp - ? dayjs(fullTimestamp).format(timeFormat) - : label; - - return ( -
-

- {displayLabel} -

-
- {payload.map((entry, index) => { - if (!entry) { - return null; - } - - const dotColor = entry.color ?? '#6366f1'; - const title = entry.name ?? entry.dataKey ?? `系列 ${index + 1}`; - const value = typeof entry.value === 'number' - ? Number.isFinite(entry.value) - ? entry.value.toFixed(2) - : '-' - : entry.value; - - return ( -

- - - {title}: {value}{unit} - -

- ); - })} -
-
- ); -}; diff --git a/web/src/portal/components/CyberCard.tsx b/web/src/portal/components/CyberCard.tsx deleted file mode 100644 index 5204620..0000000 --- a/web/src/portal/components/CyberCard.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import {cn} from "@/lib/utils.ts"; - -interface Props { - className?: string - children: React.ReactNode; - animation?: boolean - hover?: boolean -} - -const CyberCard = ({className, children, animation, hover}: Props) => { - return ( -
- {/* 装饰性边框 - 仅在暗色模式下显示 */} -
-
-
-
- {animation && -
- } - -
- {children} -
-
- ); -}; - -export default CyberCard; \ No newline at end of file diff --git a/web/src/portal/components/EmptyState.tsx b/web/src/portal/components/EmptyState.tsx deleted file mode 100644 index 1d7dcb5..0000000 --- a/web/src/portal/components/EmptyState.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import {AlertCircle} from 'lucide-react'; -import {cn} from '@/lib/utils'; - -interface EmptyStateProps { - message?: string; - showBackButton?: boolean; - className?: string; -} - -export const EmptyState = ({message = '监控数据不存在', showBackButton = false, className}: EmptyStateProps) => { - return ( -
-
-
- -
-

- {message} -

- {showBackButton && ( - - )} -
-
- ); -}; diff --git a/web/src/portal/components/LittleStatCard.tsx b/web/src/portal/components/LittleStatCard.tsx deleted file mode 100644 index 725a04d..0000000 --- a/web/src/portal/components/LittleStatCard.tsx +++ /dev/null @@ -1,19 +0,0 @@ -// 统计卡片组件 -const LittleStatCard = ({ - label, - value, - }: { - label: string; - value: string | number; - sublabel?: string; -}) => ( -
-

{label}

-

{value}

-
-); - -export default LittleStatCard; \ No newline at end of file diff --git a/web/src/portal/components/LoadingSpinner.tsx b/web/src/portal/components/LoadingSpinner.tsx deleted file mode 100644 index eb5c093..0000000 --- a/web/src/portal/components/LoadingSpinner.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import {Loader2} from 'lucide-react'; -import {cn} from '@/lib/utils'; - -interface LoadingSpinnerProps { - message?: string; -} - -export const LoadingSpinner = ({message}: LoadingSpinnerProps) => { - - return ( -
-
- -

- {message || '数据加载中,请稍候...'} -

-
-
- ); -}; diff --git a/web/src/portal/components/NetworkStatCard.tsx b/web/src/portal/components/NetworkStatCard.tsx deleted file mode 100644 index 2e1b15f..0000000 --- a/web/src/portal/components/NetworkStatCard.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import type {FC} from 'react'; -import {ArrowDown, ArrowUp, Network} from 'lucide-react'; -import {formatBytes, formatSpeed} from '@/lib/format.ts'; -import {cn} from "@/lib/utils.ts"; - -interface NetworkStatCardProps { - uploadRate: number; - downloadRate: number; - uploadTotal: number; - downloadTotal: number; -} - -const NetworkStatCard: FC = ({ - uploadRate, - downloadRate, - uploadTotal, - downloadTotal -}) => { - return ( -
-
- -
-
-
-
网络统计
-
-
- - {formatSpeed(uploadRate)} - - ({formatBytes(uploadTotal)}) - -
-
- - {formatSpeed(downloadRate)} - - ({formatBytes(downloadTotal)}) - -
-
-
-
- -
-
-
- ); -}; - -export default NetworkStatCard; diff --git a/web/src/portal/components/PublicFooter.tsx b/web/src/portal/components/PublicFooter.tsx deleted file mode 100644 index 574dae2..0000000 --- a/web/src/portal/components/PublicFooter.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import {Heart} from 'lucide-react'; - -const PublicFooter = () => { - const currentYear = new Date().getFullYear(); - const icpCode = window.SystemConfig?.ICPCode || ''; - - return ( -
-
-
-
-
- © {currentYear} - | - {/* GitHub 链接 */} - - Pika Monitor - - {window.SystemConfig.Version} - {/* ICP 备案号 */} - {icpCode && ( - <> - | - - {icpCode} - - - )} -
-
- 用 - - 构建 -
-
-
-
-
-
- ); -}; - -export default PublicFooter; diff --git a/web/src/portal/components/PublicHeader.tsx b/web/src/portal/components/PublicHeader.tsx deleted file mode 100644 index 2e48541..0000000 --- a/web/src/portal/components/PublicHeader.tsx +++ /dev/null @@ -1,263 +0,0 @@ -import {useEffect, useState} from 'react'; -import {Activity, LogIn, Menu, Moon, ServerIcon, Settings, Sun, X} from 'lucide-react'; -import {Link, useLocation} from "react-router-dom"; -import {useTheme} from '../contexts/ThemeContext'; -import {getCurrentUser} from "@/api/auth.ts"; - -const PublicHeader = () => { - const [isLoggedIn, setIsLoggedIn] = useState(false); - const [currentTime, setCurrentTime] = useState(new Date()); - const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - const {appliedTheme, setTheme} = useTheme(); - let location = useLocation(); - - - useEffect(() => { - // 检查本地是否有 token - const token = localStorage.getItem('token'); - const userInfo = localStorage.getItem('userInfo'); - - if (!token || !userInfo) { - setIsLoggedIn(false); - return; - } - - // 调用后端接口验证 token 是否有效 - getCurrentUser() - .then(() => { - setIsLoggedIn(true); - }) - .catch(() => { - // token 无效,清除本地存储 - localStorage.removeItem('token'); - localStorage.removeItem('userInfo'); - setIsLoggedIn(false); - }); - }, []); - - // 时钟特效 - useEffect(() => { - const t = setInterval(() => setCurrentTime(new Date()), 1000); - return () => clearInterval(t); - }, []); - - // 判断导航是否激活 - const currentPath = location.pathname; - - let activeTab = 'servers'; - - if (currentPath.startsWith('/monitors')) { - activeTab = 'monitors'; - } - - let systemName = window.SystemConfig?.SystemNameEn; - - let leftName = ''; - let rightName = ''; - - if (systemName) { - // 优先在空格处分割 - const spaceIndex = systemName.indexOf(' '); - if (spaceIndex > 0) { - leftName = systemName.substring(0, spaceIndex); - rightName = systemName.substring(spaceIndex); // 保留空格 - } else { - // 如果没有空格,从中间分割 - const mid = Math.floor(systemName.length / 2); - leftName = systemName.substring(0, mid); - rightName = systemName.substring(mid); - } - } - - return ( - <> -
-
-
- -
-
- {'logo'} { - e.currentTarget.src = '/logo.png'; - }} - /> -
-
-

- {leftName}{rightName} -

-

- {window.SystemConfig?.SystemNameZh} -

-
-
- - - {/* HUD Navigation - Desktop Only */} -
- {[ - {id: 'servers', icon: ServerIcon, label: '设备监控', to: '/'}, - {id: 'monitors', icon: Activity, label: '服务监控', to: '/monitors'} - ].map(tab => ( - - - - ))} -
-
- - {/* Desktop Right Section */} -
-
- {currentTime.toLocaleTimeString()} - {currentTime.toLocaleDateString()} -
-
- - {/* 主题切换按钮 - Desktop */} - - - {/* 登录/管理后台按钮 - Desktop */} - {isLoggedIn ? ( - - - Admin - - ) : ( - - - Login - - )} -
- - {/* Mobile Menu Button */} - -
-
-
- - {/* Mobile Menu */} - {mobileMenuOpen && ( -
-
- {/* Mobile Navigation */} - {[ - {id: 'servers', icon: ServerIcon, label: '设备监控', to: '/'}, - {id: 'monitors', icon: Activity, label: '服务监控', to: '/monitors'} - ].map(tab => ( - setMobileMenuOpen(false)} - className={` - flex items-center gap-3 p-4 rounded-lg border transition-all - ${activeTab === tab.id - ? 'bg-blue-50 dark:bg-cyan-500/20 border-blue-500 dark:border-cyan-500/80 text-blue-600 dark:text-cyan-500' - : 'bg-slate-50/50 dark:bg-cyan-500/5 border-slate-200 dark:border-cyan-500/30 text-slate-600 dark:text-slate-400 hover:bg-blue-50 dark:hover:bg-cyan-500/10 hover:border-blue-300 dark:hover:border-cyan-500/50' - } - `} - > - - {tab.label} - - ))} - - {/* Divider */} -
- - {/* Mobile Theme Toggle Button */} - - - {/* Mobile Login/Admin Button */} - {isLoggedIn ? ( - - - 管理后台 - - ) : ( - - - 登录 - - )} -
-
- )} - - ); -}; - -export default PublicHeader; diff --git a/web/src/portal/components/ServerCard.tsx b/web/src/portal/components/ServerCard.tsx deleted file mode 100644 index ec4afa2..0000000 --- a/web/src/portal/components/ServerCard.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import type {FC} from 'react'; -import { - Activity, - AlertTriangle, - ArrowDown, - ArrowUp, - Calendar, - Clock, - Cpu, - HardDrive, - MemoryStick, - Network, - Thermometer -} from 'lucide-react'; -import type {Agent, LatestMetrics} from '@/types'; -import CompactResourceBar from '@portal/components/CompactResourceBar'; -import {formatBytes, formatSpeed, formatUptime} from '@/lib/format.ts'; -import CyberCard from "@portal/components/CyberCard.tsx"; -import {Link} from "react-router-dom"; -import {isExpired} from "@portal/utils/server.ts"; -import {cn} from "@/lib/utils.ts"; - -interface AgentWithMetrics extends Agent { - metrics?: LatestMetrics; -} - -interface ServerCardProps { - server: AgentWithMetrics; -} - -const calculateNetworkSpeed = (metrics?: LatestMetrics) => { - if (!metrics?.network) { - return {upload: 0, download: 0}; - } - return { - upload: metrics.network.totalBytesSentRate, - download: metrics.network.totalBytesRecvRate - }; -}; - -const calculateDiskUsage = (metrics?: LatestMetrics) => { - if (!metrics?.disk) { - return 0; - } - return metrics.disk.usagePercent; -}; - -const getTemperatures = (metrics?: LatestMetrics) => { - if (!metrics?.temperature || metrics.temperature.length === 0) { - return []; - } - // 返回所有温度数据 - return metrics.temperature.sort((a, b) => a.type.localeCompare(b.type)); -}; - -const getTrafficProgressColor = (percent: number) => { - if (percent >= 100) return 'bg-red-500'; - if (percent >= 90) return 'bg-orange-500'; - if (percent >= 80) return 'bg-yellow-500'; - return 'bg-emerald-500'; -}; - -const ServerCard: FC = ({server}) => { - const isOnline = server.status === 1; - const cpuUsage = server.metrics?.cpu?.usagePercent ?? 0; - const memoryUsage = server.metrics?.memory?.usagePercent ?? 0; - const memoryTotal = server.metrics?.memory?.total ?? 0; - const memoryUsed = server.metrics?.memory?.used ?? 0; - const diskUsage = calculateDiskUsage(server.metrics); - const diskTotal = server.metrics?.disk?.total ?? 0; - const diskUsed = server.metrics?.disk?.used ?? 0; - const {upload, download} = calculateNetworkSpeed(server.metrics); - const temperatures = getTemperatures(server.metrics); - const netConn = server.metrics?.networkConnection; - const traffic = server.trafficStats; - const trafficUsagePercent = traffic?.enabled && traffic.limit > 0 - ? Math.min(100, (traffic.used / traffic.limit) * 100) - : 0; - - return ( - - -
- {/* 顶部:名称和状态 */} -
-
-
- {server.name || server.hostname} -
-
- {server.os} - - {server.arch} -
-
- {server.tags && server.tags.length > 0 && ( -
- {server.tags.slice(0, 2).map(tag => ( - - #{tag} - - ))} - {server.tags.length > 2 && ( - - +{server.tags.length - 2} - - )} -
- )} -
- - {isOnline && server.metrics?.host && ( -
-
- - {formatUptime(server.metrics.host.uptime)} -
- -
- - {server.metrics.host.procs} 进程 -
-
- )} - - {/* 资源使用情况 */} - {isOnline ? ( -
- - - - {temperatures.length > 0 && ( -
- - {temperatures.map((temp, index) => ( - - {temp.temperature?.toFixed(1)}°C - {temp.type} - {index < temperatures.length - 1 && - |} - - ))} -
- )} -
- ) : ( -
- - 离线 -
- )} - - {/* 网络和流量 */} -
-
-
- - - {formatSpeed(download)} - - - - {formatSpeed(upload)} - -
- {server.expireTime > 0 && ( -
- - {new Date(server.expireTime).toLocaleDateString('zh-CN')} -
- )} -
- {isOnline && netConn && ( -
- - - {netConn.established || 0} - ESTABLISHED - - - - {netConn.listen || 0} - LISTEN - - - - {netConn.closeWait || 0} - CLOSE_WAIT - -
- )} - {traffic?.enabled && ( -
-
- - {traffic.type === 'recv' ? '进站' : traffic.type === 'send' ? '出站' : '全部'}流量 -
- {traffic.limit > 0 ? ( - <> -
- - {formatBytes(traffic.used, 1)} / {formatBytes(traffic.limit, 1)} - - - {trafficUsagePercent.toFixed(1)}% - -
-
-
-
-
- 重置日期: 每月 {traffic.resetDay} 号 -
- - ) : ( -
- 已使用: {formatBytes(traffic.used, 1)} -
仅统计模式
-
- )} -
- )} -
-
- - - ); -}; - -export default ServerCard; diff --git a/web/src/portal/components/StatBlock.tsx b/web/src/portal/components/StatBlock.tsx deleted file mode 100644 index 7165886..0000000 --- a/web/src/portal/components/StatBlock.tsx +++ /dev/null @@ -1,56 +0,0 @@ -// 统计卡片组件 -import {cn} from "@/lib/utils.ts"; - -interface Props { - title: string; - value: any; - unit?: string - icon: any; - color: string; - alert?: boolean, - glow?: boolean, -} - -const StatBlock = ({title, value, unit, icon: Icon, color, alert, glow}: Props) => { - - const colorMap = { - cyan: 'dark:text-cyan-400 dark:border-cyan-500/30 dark:bg-cyan-500/5', - emerald: 'dark:text-emerald-400 dark:border-emerald-500/30 dark:bg-emerald-500/5', - rose: 'dark:text-rose-400 dark:border-rose-500/30 dark:bg-rose-500/5', - purple: 'dark:text-purple-400 dark:border-purple-500/30 dark:bg-purple-500/5' - }; - const style = colorMap[color] || colorMap.cyan; - - const iconColor = { - cyan: 'text-cyan-400', - emerald: 'text-emerald-400', - rose: 'text-rose-400', - purple: 'text-purple-400' - } - let iconStyle = iconColor[color] || colorMap.cyan; - - return ( -
-
-
-
-
{title}
-
{value}{unit && - {unit}}
-
-
- -
-
-
- ); -}; - -export default StatBlock; \ No newline at end of file diff --git a/web/src/portal/components/StatusBadge.tsx b/web/src/portal/components/StatusBadge.tsx deleted file mode 100644 index d526aaa..0000000 --- a/web/src/portal/components/StatusBadge.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import {cn} from '@/lib/utils.ts'; - -interface StatusBadgeProps { - status: string; -} - -export const StatusBadge = ({status}: StatusBadgeProps) => { - const styles = { - up: "bg-emerald-500/10 dark:bg-emerald-500/10 text-emerald-400 dark:text-emerald-400 border-emerald-500/80", - down: "bg-rose-500/10 dark:bg-rose-500/10 text-rose-400 dark:text-rose-400 border-rose-500/80", - unknown: "bg-slate-100 dark:bg-slate-800 text-slate-400 dark:text-slate-300 border-slate-200 dark:border-slate-700", - }; - - const labels = { - up: "正常", - down: "异常", - unknown: "未知", - }; - - const style = styles[status as keyof typeof styles] || styles.unknown; - const label = labels[status as keyof typeof labels] || labels.unknown; - - - return ( - - - {label} - - - - ); -}; diff --git a/web/src/portal/components/TimeRangeSelector.tsx b/web/src/portal/components/TimeRangeSelector.tsx deleted file mode 100644 index 5b858be..0000000 --- a/web/src/portal/components/TimeRangeSelector.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import {useEffect, useState} from 'react'; -import type {TimeRangeOption} from '@/api/property.ts'; -import {cn} from '@/lib/utils'; - -interface CustomRange { - start: number; - end: number; -} - -interface TimeRangeSelectorProps { - value: string; - onChange: (value: string) => void; - options: readonly TimeRangeOption[]; - enableCustom?: boolean; - customRange?: CustomRange | null; - onCustomRangeApply?: (range: CustomRange) => void; - className?: string; -} - -export const TimeRangeSelector = ({ - value, - onChange, - options, - enableCustom = false, - customRange, - onCustomRangeApply, - className, - }: TimeRangeSelectorProps) => { - const [customStart, setCustomStart] = useState(''); - const [customEnd, setCustomEnd] = useState(''); - - useEffect(() => { - if (customRange?.start) { - setCustomStart(toDateTimeLocal(customRange.start)); - } - if (customRange?.end) { - setCustomEnd(toDateTimeLocal(customRange.end)); - } - }, [customRange]); - - const startMs = parseDateTimeLocal(customStart); - const endMs = parseDateTimeLocal(customEnd); - const canApply = startMs !== null && endMs !== null && startMs < endMs; - const showCustomOption = enableCustom && value === 'custom'; - - return ( -
- - {enableCustom && ( -
- - 自定义 - - setCustomStart(event.target.value)} - className="rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 focus:border-blue-400 dark:focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-500/30 px-2 py-1 text-xs font-medium" - /> - - 至 - - setCustomEnd(event.target.value)} - className="rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 focus:border-blue-400 dark:focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-500/30 px-2 py-1 text-xs font-medium" - /> - -
- )} -
- ); -}; - -const parseDateTimeLocal = (value: string): number | null => { - if (!value) return null; - const timestamp = new Date(value).getTime(); - if (Number.isNaN(timestamp)) { - return null; - } - return timestamp; -}; - -const toDateTimeLocal = (timestamp: number): string => { - const date = new Date(timestamp); - const offsetMs = date.getTimezoneOffset() * 60000; - return new Date(timestamp - offsetMs).toISOString().slice(0, 16); -}; diff --git a/web/src/portal/components/monitor/AgentStatsTable.tsx b/web/src/portal/components/monitor/AgentStatsTable.tsx deleted file mode 100644 index 044188c..0000000 --- a/web/src/portal/components/monitor/AgentStatsTable.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import {AlertCircle, Clock, MapPin} from 'lucide-react'; -import {StatusBadge} from '@portal/components/StatusBadge'; -import {CertBadge} from './CertBadge'; -import {AGENT_COLORS} from '@portal/constants/colors'; -import {formatDateTime, formatTime} from '@/lib/format.ts'; -import type {AgentMonitorStat} from '@/types'; -import CyberCard from "@portal/components/CyberCard.tsx"; - -interface AgentStatsTableProps { - monitorStats: AgentMonitorStat[]; - monitorType: string; -} - -/** - * 探针监控统计表格组件 - * 显示各探针的当前状态和统计数据 - */ -export const AgentStatsTable = ({monitorStats, monitorType}: AgentStatsTableProps) => { - if (monitorStats.length === 0) { - return ( -
-

暂无探针数据

-
- ); - } - - return ( - -
-

探针监控详情

-

各探针的当前状态和统计数据

-
- - {/* 移动端卡片布局 */} -
- {monitorStats.map((stat, index) => { - const color = AGENT_COLORS[index % AGENT_COLORS.length]; - return ( -
- {/* 探针名称和状态 */} -
-
- - - - {stat.agentName || stat.agentId.substring(0, 8)} - -
- -
- - {/* 响应时间和最后检测 */} -
-
- - - {formatTime(stat.responseTime)} - -
- - {formatDateTime(stat.checkedAt)} - -
- - {/* 证书信息 */} - {monitorType === 'https' && stat.certExpiryTime && ( -
-
- 证书: - -
-
- )} - - {/* 错误信息 */} - {stat.status === 'down' && stat.message && ( -
-
- - - {stat.message} - -
-
- )} -
- ); - })} -
- - {/* 桌面端表格布局 */} -
- - - - - - - - {monitorType === 'https' && ( - - )} - - - - - {monitorStats.map((stat, index) => { - const color = AGENT_COLORS[index % AGENT_COLORS.length]; - return ( - - - - - - {monitorType === 'https' && ( - - )} - - - ); - })} - -
- 探针名称 - - 状态 - - 响应时间 - - 最后检测 - - 证书信息 - - 错误信息 -
-
- -
- - - {stat.agentName || stat.agentId.substring(0, 8)} - -
-
-
- - -
- - - {formatTime(stat.responseTime)} - -
-
- {formatDateTime(stat.checkedAt)} - - {stat.certExpiryTime ? ( - - ) : ( - - - )} - - {stat.status === 'down' && stat.message ? ( -
- - - {stat.message} - -
- ) : ( - - - )} -
-
-
- ); -}; diff --git a/web/src/portal/components/monitor/CertBadge.tsx b/web/src/portal/components/monitor/CertBadge.tsx deleted file mode 100644 index 75e4f9e..0000000 --- a/web/src/portal/components/monitor/CertBadge.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ShieldCheck } from 'lucide-react'; -import { cn } from '@/lib/utils'; - -interface CertBadgeProps { - expiryTime: number; - daysLeft: number; -} - -export const CertBadge = ({ expiryTime, daysLeft }: CertBadgeProps) => { - if (!expiryTime || daysLeft === undefined) return null; - - const isExpired = daysLeft < 0; - let colorClass = "text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 border-emerald-200 dark:border-emerald-500/20"; - - if (isExpired) { - colorClass = "text-rose-600 dark:text-rose-400 bg-rose-50 dark:bg-rose-500/10 border-rose-200 dark:border-rose-500/20"; - } else if (daysLeft < 30) { - colorClass = "text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/10 border-amber-200 dark:border-amber-500/20"; - } - - return ( -
- - {isExpired ? "已过期" : `${daysLeft} 天后过期`} -
- ); -}; diff --git a/web/src/portal/components/monitor/MiniChart.tsx b/web/src/portal/components/monitor/MiniChart.tsx deleted file mode 100644 index dee1cc3..0000000 --- a/web/src/portal/components/monitor/MiniChart.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import {useMemo} from 'react'; -import {Area, AreaChart, ResponsiveContainer} from 'recharts'; - -interface DataPoint { - timestamp: number; - value: number; -} - -interface MiniChartProps { - data: DataPoint[]; - lastValue?: number; - id: string; -} - -/** - * 迷你图表组件 - * 用于监控卡片中显示响应时间趋势 - */ -export const MiniChart = ({data, lastValue, id}: MiniChartProps) => { - // 处理图表数据 - 保持原始 timestamp 用于精确的时间轴计算 - const chartData = useMemo(() => { - if (!data || data.length === 0) return []; - - // 按时间戳排序并返回原始数据 - return [...data].sort((a, b) => a.timestamp - b.timestamp); - }, [data]); - - if (chartData.length === 0) { - return ( -
- 暂无数据 -
- ); - } - - // 根据最后一个值决定颜色 - const color = lastValue && lastValue <= 200 ? '#22d3ee' : '#fbbf24'; - - return ( -
- - - - - - - - - - - - - - - - - - -
- ); -}; diff --git a/web/src/portal/components/monitor/MonitorCard.tsx b/web/src/portal/components/monitor/MonitorCard.tsx deleted file mode 100644 index 2af79e0..0000000 --- a/web/src/portal/components/monitor/MonitorCard.tsx +++ /dev/null @@ -1,166 +0,0 @@ -// 监控卡片组件 -import type {PublicMonitor} from "@/types"; -import {useQuery} from "@tanstack/react-query"; -import {type GetMetricsResponse, getMonitorHistory} from "@/api/monitor.ts"; -import {useMemo} from "react"; -import CyberCard from "@portal/components/CyberCard.tsx"; -import {StatusBadge} from "@portal/components/StatusBadge"; -import {CertBadge} from "@portal/components/monitor/CertBadge.tsx"; -import {formatDateTime} from "@/lib/format.ts"; -import {MiniChart} from "@portal/components/monitor/MiniChart.tsx"; -import {TypeIcon} from "@portal/components/monitor/TypeIcon"; - -export type DisplayMode = 'avg' | 'max'; - -const MonitorCard = ({monitor, displayMode}: { - monitor: PublicMonitor; - displayMode: DisplayMode; -}) => { - // 为每个监控卡片查询历史数据 - const {data: historyData} = useQuery({ - queryKey: ['monitorHistory', monitor.id, '12h'], // 对应后端 60 秒步长 - queryFn: async () => { - const response = await getMonitorHistory(monitor.id, {range: '1h'}); - return response.data; - }, - refetchInterval: 60000, - staleTime: 30000, - }); - - // 转换时序数据为图表数据 - 使用统一格点对该对齐多探针数据 - const chartData = useMemo(() => { - if (!historyData?.series || historyData.series.length === 0) { - return []; - } - - const validSeries = historyData.series.filter(s => s.data && s.data.length > 0); - if (validSeries.length === 0) return []; - - // 确定全局时间范围 - let minTime = Infinity, maxTime = -Infinity; - validSeries.forEach(s => { - minTime = Math.min(minTime, s.data![0].timestamp); - maxTime = Math.max(maxTime, s.data![s.data!.length - 1].timestamp); - }); - - if (minTime >= maxTime) return []; - - // 定义目标采集点 (1小时数据,建议 60 个采集点) - const maxPoints = 60; - const timeStep = (maxTime - minTime) / (maxPoints - 1); - const targetTimestamps: number[] = []; - for (let i = 0; i < maxPoints; i++) { - targetTimestamps.push(minTime + i * timeStep); - } - - // 线性插值函数 - const interpolate = (data: Array<{ timestamp: number; value: number }>, targetTime: number): number | null => { - if (data.length === 0) return null; - if (data.length === 1) return data[0].timestamp === targetTime ? data[0].value : null; - if (targetTime < data[0].timestamp || targetTime > data[data.length - 1].timestamp) return null; - - let left = 0, right = data.length - 1; - while (right - left > 1) { - const mid = Math.floor((left + right) / 2); - if (data[mid].timestamp <= targetTime) left = mid; - else right = mid; - } - const leftPoint = data[left]; - const rightPoint = data[right]; - const ratio = (targetTime - leftPoint.timestamp) / (rightPoint.timestamp - leftPoint.timestamp); - return leftPoint.value + ratio * (rightPoint.value - leftPoint.value); - }; - - // 对每个目标时间点,计算所有探针的聚合值 - return targetTimestamps.map(timestamp => { - const values: number[] = []; - validSeries.forEach(s => { - const val = interpolate(s.data!, timestamp); - if (val !== null) values.push(val); - }); - - if (values.length === 0) return { timestamp, value: 0 }; - - return { - timestamp, - value: displayMode === 'avg' - ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) - : Math.max(...values), - }; - }); - }, [historyData, displayMode]); - - const displayValue = displayMode === 'avg' ? monitor.responseTime : monitor.responseTimeMax; - const displayLabel = displayMode === 'avg' ? '平均延迟' : '最差节点延迟'; - - return ( - - {/* 头部 */} -
-
-
- -
-
-

- {monitor.name} -

-
- {monitor.target} -
-
-
-
- -
-
- - {/* 指标信息 */} -
-
-

- {displayLabel} - {monitor.agentCount > 0 && ( - - {monitor.agentCount} 节点 - - )} -

-
200 ? 'text-amber-600 dark:text-amber-400 drop-shadow-[0_0_8px_rgba(251,191,36,0.3)] dark:drop-shadow-[0_0_8px_rgba(251,191,36,0.5)]' : 'text-slate-800 dark:text-white drop-shadow-none dark:drop-shadow-[0_0_8px_rgba(34,211,238,0.5)]'}`}> - {displayValue}ms -
-
-
- {monitor.type === 'https' && monitor.certExpiryTime ? ( - <> -

SSL 证书

- - - ) : ( - <> -

上次检测

-

- {formatDateTime(monitor.lastCheckTime)} -

- - )} -
-
- - {/* 迷你走势图 */} - -
- ); -}; - -export default MonitorCard; diff --git a/web/src/portal/components/monitor/MonitorHero.tsx b/web/src/portal/components/monitor/MonitorHero.tsx deleted file mode 100644 index a02334e..0000000 --- a/web/src/portal/components/monitor/MonitorHero.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import {ArrowLeft} from 'lucide-react'; -import {TypeIcon} from './TypeIcon'; -import {StatusBadge} from '@portal/components/StatusBadge'; -import {CertBadge} from './CertBadge'; -import LittleStatCard from '@portal/components/LittleStatCard'; -import type {PublicMonitor} from '@/types'; -import CyberCard from "@portal/components/CyberCard.tsx"; -import {formatDateTime} from "@/lib/format.ts"; - -interface MonitorHeroProps { - monitor: PublicMonitor; - onBack: () => void; -} - -/** - * 监控详情头部组件 - * 显示监控基本信息、状态和关键指标 - */ -export const MonitorHero = ({monitor, onBack}: MonitorHeroProps) => { - return ( - - {/* 返回按钮 */} - - - {/* 监控信息 */} -
-
-
- -
-
-
-

{monitor.name}

- -
-

- {monitor.showTargetPublic ? monitor.target : '******'} -

-
-
- - {/* 统计卡片 */} -
- - - - -
-
- - {/* 证书信息(如果存在证书数据)*/} - {monitor.certExpiryTime > 0 && ( -
- SSL 证书: -
- - - 到期时间: {formatDateTime(monitor.certExpiryTime)} - -
-
- )} -
- ); -}; diff --git a/web/src/portal/components/monitor/ResponseTimeChart.tsx b/web/src/portal/components/monitor/ResponseTimeChart.tsx deleted file mode 100644 index 971d7b1..0000000 --- a/web/src/portal/components/monitor/ResponseTimeChart.tsx +++ /dev/null @@ -1,453 +0,0 @@ -import {useEffect, useMemo, useState} from 'react'; -import {useQuery} from '@tanstack/react-query'; -import {Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -import {type GetMetricsResponse, getMonitorHistory} from '@/api/monitor'; -import {MONITOR_TIME_RANGE_OPTIONS} from '@portal/constants/time'; -import {useIsMobile} from '@portal/hooks/use-mobile'; -import type {AgentMonitorStat} from '@/types'; -import CyberCard from "@portal/components/CyberCard.tsx"; -import {ChartPlaceholder} from "@portal/components/ChartPlaceholder"; -import {CustomTooltip} from "@portal/components/CustomTooltip"; -import {TimeRangeSelector} from "@portal/components/TimeRangeSelector"; -import {formatChartTime} from '@/lib/format.ts'; -import {RotateCcw, ChevronDown, ChevronUp} from 'lucide-react'; - -interface ResponseTimeChartProps { - monitorId: string; - monitorStats: AgentMonitorStat[]; -} - -// 移除 LTTB 降采样,改用统一格点对齐插值以获得更好的多序列对齐效果 - - -/** - * 根据时间范围确定最大数据点数 - */ -const getMaxDataPoints = (timeRange: string): number => { - switch (timeRange) { - case '15m': - case '1h': - return 200; // 短时间:详细数据 - case '12h': - return 300; - case '24h': - return 400; - case '7d': - return 500; - case '30d': - return 600; - default: - return 400; - } -}; - -/** - * 生成不重复的颜色 - * 使用 HSL 色轮均匀分布,支持无限数量的探针 - */ -const generateColors = (count: number): string[] => { - const colors: string[] = []; - const hueStep = 360 / count; - - for (let i = 0; i < count; i++) { - const hue = (i * hueStep) % 360; - const saturation = 65 + (i % 3) * 10; - const lightness = 45 + (i % 2) * 10; - colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`); - } - - return colors; -}; - -/** - * 自定义图例组件 - */ -const CustomLegend = ({ onClick, selectedAgents, allAgents, colors, collapsed }: any) => { - if (!allAgents || allAgents.length === 0) return null; - - if (collapsed) return null; - - return ( -
- {allAgents.map((agent: { id: string; name: string }, index: number) => { - const isSelected = selectedAgents.has(agent.id); - const color = colors[index]; - - return ( -
onClick(agent.id)} - className="flex items-center gap-2 cursor-pointer transition-opacity" - style={{ - opacity: isSelected ? 1 : 0.4, - }} - > - - - - - {agent.name} - -
- ); - })} -
- ); -}; - -/** - * 响应时间趋势图表组件 - * 显示监控各探针的响应时间变化 - */ -export const ResponseTimeChart = ({monitorId, monitorStats}: ResponseTimeChartProps) => { - const [selectedAgents, setSelectedAgents] = useState>(new Set()); - const [timeRange, setTimeRange] = useState('12h'); - const [customRange, setCustomRange] = useState<{ start: number; end: number } | null>(null); - const [legendCollapsed, setLegendCollapsed] = useState(true); // 移动端默认收起 - const isMobile = useIsMobile(); - const customStart = timeRange === 'custom' ? customRange?.start : undefined; - const customEnd = timeRange === 'custom' ? customRange?.end : undefined; - const rangeMs = customStart !== undefined && customEnd !== undefined ? customEnd - customStart : undefined; - - // 获取历史数据 - const {data: historyData} = useQuery({ - queryKey: ['monitorHistory', monitorId, timeRange, customStart, customEnd], - queryFn: async () => { - if (!monitorId) throw new Error('Monitor ID is required'); - const response = await getMonitorHistory(monitorId, { - range: timeRange, - start: customStart, - end: customEnd, - }); - return response.data; - }, - refetchInterval: 30000, - enabled: !!monitorId, - }); - - // 获取所有可用的探针列表 - const availableAgents = useMemo(() => { - if (monitorStats.length === 0) return []; - return monitorStats.map(stat => ({ - id: stat.agentId, - name: stat.agentName || stat.agentId.substring(0, 8), - })); - }, [monitorStats]); - - // 初始化选中所有探针 - useEffect(() => { - if (availableAgents.length > 0 && selectedAgents.size === 0) { - setSelectedAgents(new Set(availableAgents.map(a => a.id))); - } - }, [availableAgents, selectedAgents.size]); - - // 动态生成颜色 - const colors = useMemo(() => { - return generateColors(availableAgents.length); - }, [availableAgents.length]); - - // 点击图例切换选中状态 - const handleLegendClick = (agentId: string) => { - const newSelected = new Set(selectedAgents); - - // 判断是否是全选状态 - const isAllSelected = selectedAgents.size === availableAgents.length; - - if (isAllSelected) { - // 全选状态下,点击某个 → 只选这一个 - setSelectedAgents(new Set([agentId])); - } else { - // 非全选状态下,点击切换 - if (newSelected.has(agentId)) { - newSelected.delete(agentId); - } else { - newSelected.add(agentId); - } - setSelectedAgents(newSelected); - } - }; - - // 点击图表线条切换选中状态 - const handleAreaClick = (data: any) => { - if (!data || !data.dataKey) return; - - // 从 dataKey 中提取 agentId (格式: agent_${agentId}) - const agentId = data.dataKey.replace('agent_', ''); - - const newSelected = new Set(selectedAgents); - - // 判断是否是全选状态 - const isAllSelected = selectedAgents.size === availableAgents.length; - - if (isAllSelected) { - // 全选状态下,点击某个 → 只选这一个 - setSelectedAgents(new Set([agentId])); - } else { - // 非全选状态下,点击切换 - if (newSelected.has(agentId)) { - newSelected.delete(agentId); - } else { - newSelected.add(agentId); - } - setSelectedAgents(newSelected); - } - }; - - // 恢复全选 - const handleSelectAll = () => { - setSelectedAgents(new Set(availableAgents.map(a => a.id))); - }; - - // 是否有探针未选中 - const hasUnselected = selectedAgents.size < availableAgents.length; - - // 切换图例显示/隐藏(仅移动端) - const toggleLegend = () => { - setLegendCollapsed(!legendCollapsed); - }; - - // 生成图表数据 - 使用统一时间网格对齐 - const chartData = useMemo(() => { - if (!historyData?.series) return []; - - const seriesList = historyData.series.filter(s => s.name === 'response_time'); - if (seriesList.length === 0) return []; - - // 收集所有选中探针的数据 - const selectedSeriesData: Array<{ key: string; data: Array<{ timestamp: number; value: number }> }> = []; - - seriesList.forEach((s) => { - const agentId = s.labels?.agent_id || 'unknown'; - if (!selectedAgents.has(agentId)) return; - if (!s.data || s.data.length === 0) return; - - selectedSeriesData.push({ - key: `agent_${agentId}`, - data: [...s.data].sort((a, b) => a.timestamp - b.timestamp) - }); - }); - - if (selectedSeriesData.length === 0) return []; - - // 确定全局时间范围 - let minTime = Infinity, maxTime = -Infinity; - selectedSeriesData.forEach(s => { - minTime = Math.min(minTime, s.data[0].timestamp); - maxTime = Math.max(maxTime, s.data[s.data.length - 1].timestamp); - }); - - if (minTime >= maxTime) return []; - - // 生成均匀的目标时间点 - const maxPoints = getMaxDataPoints(timeRange); - const timeStep = (maxTime - minTime) / (maxPoints - 1); - const targetTimestamps: number[] = []; - for (let i = 0; i < maxPoints; i++) { - targetTimestamps.push(minTime + i * timeStep); - } - - // 线性插值函数 - const interpolate = (data: Array<{ timestamp: number; value: number }>, targetTime: number): number | null => { - if (data.length === 0) return null; - if (data.length === 1) return data[0].timestamp === targetTime ? data[0].value : null; - - // 超出范围不插值(防止产生虚假连线) - if (targetTime < data[0].timestamp || targetTime > data[data.length - 1].timestamp) { - // 如果距离最近的点足够近(比如小于两个采样间隔),可以考虑保留,否则返回null - return null; - } - - // 二分查找 - let left = 0, right = data.length - 1; - while (right - left > 1) { - const mid = Math.floor((left + right) / 2); - if (data[mid].timestamp <= targetTime) { - left = mid; - } else { - right = mid; - } - } - - const leftPoint = data[left]; - const rightPoint = data[right]; - const ratio = (targetTime - leftPoint.timestamp) / (rightPoint.timestamp - leftPoint.timestamp); - return leftPoint.value + ratio * (rightPoint.value - leftPoint.value); - }; - - // 对齐所有探针数据 - return targetTimestamps.map(timestamp => { - const dataPoint: any = { timestamp }; - selectedSeriesData.forEach(s => { - const value = interpolate(s.data, timestamp); - if (value !== null) { - dataPoint[s.key] = Number(value.toFixed(2)); - } - }); - return dataPoint; - }); - }, [historyData, selectedAgents, timeRange, customStart, customEnd]); - - return ( - -
-
-

响应时间趋势

-

监控各探针的响应时间变化

-
-
- { - setCustomRange(range); - }} - /> -
-
- - {/* 使用提示和恢复按钮 */} - {availableAgents.length > 0 && ( -
-
- 💡 点击图表线条或图例切换显示 -
- {hasUnselected && ( - - )} -
- )} - - {chartData.length > 0 ? ( -
- - - - {Array.from(selectedAgents).map((agentId, index) => { - const originalIndex = availableAgents.findIndex(a => a.id === agentId); - const agentKey = `agent_${agentId}`; - return ( - - - - - ); - })} - - - formatChartTime(Number(value), timeRange, rangeMs)} - className="text-xs text-gray-600 dark:text-cyan-500 font-mono" - stroke="currentColor" - tickLine={false} - axisLine={false} - angle={-15} - textAnchor="end" - /> - `${value}ms`} - /> - } - wrapperStyle={{zIndex: 9999}} - /> - {Array.from(selectedAgents).map((agentId) => { - const originalIndex = availableAgents.findIndex(a => a.id === agentId); - const agentKey = `agent_${agentId}`; - const agent = availableAgents.find(a => a.id === agentId); - return ( - - ); - })} - - - - {/* 桌面端:直接显示图例 */} - {!isMobile && availableAgents.length > 0 && ( - - )} - - {/* 移动端:可折叠图例 */} - {isMobile && availableAgents.length > 0 && ( -
- - -
- )} -
- ) : ( - - )} -
- ); -}; diff --git a/web/src/portal/components/monitor/TypeIcon.tsx b/web/src/portal/components/monitor/TypeIcon.tsx deleted file mode 100644 index 9f21fde..0000000 --- a/web/src/portal/components/monitor/TypeIcon.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Globe, Server, ShieldCheck, Wifi } from 'lucide-react'; - -interface TypeIconProps { - type: string; -} - -export const TypeIcon = ({ type }: TypeIconProps) => { - switch (type.toLowerCase()) { - case 'https': - return ; - case 'http': - return ; - case 'tcp': - return ; - case 'icmp': - case 'ping': - return ; - default: - return ; - } -}; diff --git a/web/src/portal/components/server/ChartContainer.tsx b/web/src/portal/components/server/ChartContainer.tsx deleted file mode 100644 index 5fafeaf..0000000 --- a/web/src/portal/components/server/ChartContainer.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type {ReactNode} from 'react'; -import type {LucideIcon} from 'lucide-react'; - -interface ChartContainerProps { - title: string; - icon: LucideIcon; - children: ReactNode; - action?: ReactNode; -} - -/** - * 图表容器组件 - * 为所有图表提供统一的标题、图标和可选的操作区域 - */ -export const ChartContainer = ({title, icon: Icon, children, action}: ChartContainerProps) => { - return ( -
-
-

- - - - {title} -

- {action} -
- {children} -
- ); -}; diff --git a/web/src/portal/components/server/CpuChart.tsx b/web/src/portal/components/server/CpuChart.tsx deleted file mode 100644 index fe39f14..0000000 --- a/web/src/portal/components/server/CpuChart.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import {useMemo} from 'react'; -import {Cpu} from 'lucide-react'; -import {Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -import {ChartPlaceholder} from '@portal/components/ChartPlaceholder'; -import {CustomTooltip} from '@portal/components/CustomTooltip'; -import {useMetricsQuery} from '@portal/hooks/server'; -import {useLiveBuffer} from '@portal/hooks/useLiveBuffer'; -import {LIVE_INITIAL_RANGE, LIVE_WINDOW_MS} from '@portal/constants/time'; -import {ChartContainer} from './ChartContainer'; -import {formatChartTime} from '@/lib/format.ts'; -import type {LatestMetrics} from '@/types'; - -interface CpuChartProps { - agentId: string; - timeRange: string; - start?: number; - end?: number; - isLive?: boolean; - latestMetrics?: LatestMetrics | null; -} - -interface CpuPoint { - timestamp: number; - usage: number; -} - -/** - * CPU 使用率图表组件 - */ -export const CpuChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: CpuChartProps) => { - const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; - const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; - // 数据查询 - const {data: metricsResponse, isLoading} = useMetricsQuery({ - agentId, - type: 'cpu', - range: start !== undefined && end !== undefined ? undefined : effectiveRange, - start, - end, - }); - - // 历史数据 - const initialData = useMemo(() => { - const cpuSeries = metricsResponse?.data.series?.find(s => s.name === 'usage'); - if (!cpuSeries) return []; - return cpuSeries.data.map((point) => ({ - usage: Number(point.value.toFixed(2)), - timestamp: point.timestamp, - })); - }, [metricsResponse]); - - // 实时点 - const livePoint = useMemo(() => { - if (!isLive || !latestMetrics?.cpu || !latestMetrics.timestamp) return null; - const usage = latestMetrics.cpu.usagePercent; - if (typeof usage !== 'number' || !Number.isFinite(usage)) return null; - return {timestamp: latestMetrics.timestamp, usage: Number(usage.toFixed(2))}; - }, [isLive, latestMetrics]); - - const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, agentId); - - // 渲染 - if (isLoading) { - return ( - - - - ); - } - - return ( - - {chartData.length > 0 ? ( - - - - - - - - - - formatChartTime(Number(value), timeRange, rangeMs)} - stroke="currentColor" - angle={-15} - textAnchor="end" - className="text-xs text-gray-600 dark:text-cyan-500 font-mono" - /> - `${value}%`} - /> - }/> - - - - ) : ( - - )} - - ); -}; diff --git a/web/src/portal/components/server/DiskIOChart.tsx b/web/src/portal/components/server/DiskIOChart.tsx deleted file mode 100644 index aea0a45..0000000 --- a/web/src/portal/components/server/DiskIOChart.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import {useMemo} from 'react'; -import {HardDrive} from 'lucide-react'; -import {Area, AreaChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -import {ChartPlaceholder} from '@portal/components/ChartPlaceholder'; -import {CustomTooltip} from '@portal/components/CustomTooltip'; -import {useMetricsQuery} from '@portal/hooks/server'; -import {useLiveBuffer} from '@portal/hooks/useLiveBuffer'; -import {LIVE_INITIAL_RANGE, LIVE_WINDOW_MS} from '@portal/constants/time'; -import {ChartContainer} from './ChartContainer'; -import {formatChartTime} from '@/lib/format.ts'; -import type {LatestMetrics} from '@/types'; - -interface DiskIOChartProps { - agentId: string; - timeRange: string; - start?: number; - end?: number; - isLive?: boolean; - latestMetrics?: LatestMetrics | null; -} - -interface DiskIOPoint { - timestamp: number; - read: number; - write: number; -} - -const toMB = (bytes: number) => Number((bytes / 1024 / 1024).toFixed(2)); - -/** - * 磁盘 I/O 图表组件 - */ -export const DiskIOChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: DiskIOChartProps) => { - const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; - const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; - // 数据查询 - const {data: metricsResponse, isLoading} = useMetricsQuery({ - agentId, - type: 'disk_io', - range: start !== undefined && end !== undefined ? undefined : effectiveRange, - start, - end, - }); - - // 历史数据 - const initialData = useMemo(() => { - if (!metricsResponse?.data.series || metricsResponse.data.series?.length === 0) return []; - - const readSeries = metricsResponse.data.series?.find(s => s.name === 'read'); - const writeSeries = metricsResponse.data.series?.find(s => s.name === 'write'); - - if (!readSeries || !writeSeries) return []; - - const timeMap = new Map(); - - readSeries.data.forEach(point => { - timeMap.set(point.timestamp, { - timestamp: point.timestamp, - read: toMB(point.value), - write: 0, - }); - }); - - writeSeries.data.forEach(point => { - const existing = timeMap.get(point.timestamp); - if (existing) { - existing.write = toMB(point.value); - } else { - timeMap.set(point.timestamp, { - timestamp: point.timestamp, - read: 0, - write: toMB(point.value), - }); - } - }); - - return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp); - }, [metricsResponse]); - - // 实时点:用 latestMetrics.timestamp 作为同批次时间锚 - const livePoint = useMemo(() => { - if (!isLive || !latestMetrics?.diskIO || !latestMetrics.timestamp) return null; - return { - timestamp: latestMetrics.timestamp, - read: toMB(latestMetrics.diskIO.totalReadBytesRate), - write: toMB(latestMetrics.diskIO.totalWriteBytesRate), - }; - }, [isLive, latestMetrics]); - - const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, agentId); - - // 渲染 - if (isLoading) { - return ( - - - - ); - } - - return ( - - {chartData.length > 0 ? ( - - - - - - - - - - - - - - formatChartTime(Number(value), timeRange, rangeMs)} - stroke="currentColor" - angle={-15} - textAnchor="end" - className="text-xs text-gray-600 dark:text-cyan-500 font-mono" - height={45} - /> - `${value} MB`} - /> - }/> - - - - - - ) : ( - - )} - - ); -}; diff --git a/web/src/portal/components/server/GpuChart.tsx b/web/src/portal/components/server/GpuChart.tsx deleted file mode 100644 index a686a79..0000000 --- a/web/src/portal/components/server/GpuChart.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import {memo, useMemo} from 'react'; -import {Zap} from 'lucide-react'; -import {CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -import {ChartPlaceholder} from '@portal/components/ChartPlaceholder'; -import {CustomTooltip} from '@portal/components/CustomTooltip'; -import {useMetricsQuery} from '@portal/hooks/server'; -import {LIVE_INITIAL_RANGE} from '@portal/constants/time'; -import {ChartContainer} from './ChartContainer'; -import {formatChartTime} from '@/lib/format.ts'; - -interface GpuChartProps { - agentId: string; - timeRange: string; - start?: number; - end?: number; - isLive?: boolean; -} - -/** - * GPU 使用率与温度图表组件 - * 使用双 Y 轴显示使用率和温度 - * - * 实时模式下不走 livePoint 追加(多卡聚合复杂),改为 5s 轮询 refetch;用 React.memo - * 阻断 ServerDetail 1s 节奏的级联重渲染,避免 Recharts 每秒空转。 - */ -const GpuChartImpl = ({agentId, timeRange, start, end, isLive}: GpuChartProps) => { - const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; - const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; - // 数据查询 - const {data: metricsResponse, isLoading} = useMetricsQuery({ - agentId, - type: 'gpu', - range: start !== undefined && end !== undefined ? undefined : effectiveRange, - start, - end, - refetchIntervalMs: isLive ? 5000 : undefined, - }); - - // 数据转换 - const chartData = useMemo(() => { - if (!metricsResponse?.data.series || metricsResponse.data.series?.length === 0) return []; - - const timeMap = new Map(); - - const utilizationSeries = metricsResponse.data?.series?.find(s => s.name === 'utilization'); - const temperatureSeries = metricsResponse.data?.series?.find(s => s.name === 'temperature'); - - utilizationSeries?.data.forEach(point => { - const existing = timeMap.get(point.timestamp); - if (existing) { - existing.utilization = Number(point.value.toFixed(2)); - } else { - timeMap.set(point.timestamp, { - timestamp: point.timestamp, - utilization: Number(point.value.toFixed(2)), - }); - } - }); - - temperatureSeries?.data.forEach(point => { - const existing = timeMap.get(point.timestamp); - if (existing) { - existing.temperature = Number(point.value.toFixed(2)); - } else { - timeMap.set(point.timestamp, { - timestamp: point.timestamp, - temperature: Number(point.value.toFixed(2)), - }); - } - }); - - return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp); - }, [metricsResponse]); - - // 渲染 - if (isLoading) { - return ( - - - - ); - } - - // 如果没有 GPU 数据,不渲染组件 - if (chartData.length === 0) { - return null; - } - - return ( - - - - - formatChartTime(Number(value), timeRange, rangeMs)} - stroke="currentColor" - className="stroke-gray-400 dark:stroke-cyan-600" - style={{fontSize: '12px'}} - /> - `${value}%`} - /> - `${value}°C`} - /> - }/> - - - - - - - ); -}; - -export const GpuChart = memo(GpuChartImpl); diff --git a/web/src/portal/components/server/GpuMonitorSection.tsx b/web/src/portal/components/server/GpuMonitorSection.tsx deleted file mode 100644 index a4024cd..0000000 --- a/web/src/portal/components/server/GpuMonitorSection.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import {Zap} from 'lucide-react'; -import {Card} from '@portal/components/Card'; -import {formatBytes} from '@/lib/format.ts'; -import type {LatestMetrics} from '@/types'; - -interface GpuMonitorSectionProps { - latestMetrics: LatestMetrics | null; -} - -/** - * GPU 监控区块组件 - * 显示 GPU 使用情况、温度、显存和功耗信息 - */ -export const GpuMonitorSection = ({latestMetrics}: GpuMonitorSectionProps) => { - // 如果没有 GPU 数据,不渲染组件 - if (!latestMetrics?.gpu || latestMetrics.gpu.length === 0) { - return null; - // # mock 测试 - // const mockGpu: GPUMetric = { - // agentId: "agent123", // 代理 ID - // fanSpeed: 3000, // 风扇转速(单位:rpm) - // id: 1, // GPU ID - // index: 0, // GPU 索引 - // memoryFree: 4096, // 可用内存(单位:MB) - // memoryTotal: 8192, // 总内存(单位:MB) - // memoryUsed: 4096, // 已用内存(单位:MB) - // name: "NVIDIA GeForce RTX 3070", // GPU 名称 - // performanceState: "P0", // 性能状态(P0-P15) - // powerUsage: 150, // 功率使用(单位:W) - // temperature: 70, // 温度(单位:°C) - // timestamp: Date.now(), // 当前时间戳 - // utilization: 85 // GPU 利用率(单位:%) - // }; - // latestMetrics.gpu = [mockGpu]; - } - - return ( - -
- {latestMetrics.gpu.map((gpu) => ( -
-
-
- - - -
-

GPU {gpu.index}

-

{gpu.name}

-
-
- - {gpu.utilization?.toFixed(1) ?? 0}% - -
-
-
- 温度 - {gpu.temperature?.toFixed(1)}°C -
-
- 显存 - - {formatBytes(gpu.memoryUsed)} / {formatBytes(gpu.memoryTotal)} - -
-
- 功耗 - {gpu.powerUsage?.toFixed(1)}W -
-
- 风扇转速 - {gpu.fanSpeed?.toFixed(0)}% -
-
-
- ))} -
-
- ); -}; diff --git a/web/src/portal/components/server/InfoGrid.tsx b/web/src/portal/components/server/InfoGrid.tsx deleted file mode 100644 index 6217a24..0000000 --- a/web/src/portal/components/server/InfoGrid.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type {ReactNode} from 'react'; - -interface InfoGridProps { - items: Array<{ label: string; value: ReactNode }>; -} - -export const InfoGrid = ({items}: InfoGridProps) => ( -
- {items.map((item) => ( -
-
{item.label}
-
{item.value}
-
- ))} -
-); diff --git a/web/src/portal/components/server/MemoryChart.tsx b/web/src/portal/components/server/MemoryChart.tsx deleted file mode 100644 index 0035cea..0000000 --- a/web/src/portal/components/server/MemoryChart.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import {useMemo} from 'react'; -import {MemoryStick} from 'lucide-react'; -import {Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -import {ChartPlaceholder} from '@portal/components/ChartPlaceholder'; -import {CustomTooltip} from '@portal/components/CustomTooltip'; -import {useMetricsQuery} from '@portal/hooks/server'; -import {useLiveBuffer} from '@portal/hooks/useLiveBuffer'; -import {LIVE_INITIAL_RANGE, LIVE_WINDOW_MS} from '@portal/constants/time'; -import {ChartContainer} from './ChartContainer'; -import {formatChartTime} from '@/lib/format.ts'; -import type {LatestMetrics} from '@/types'; - -interface MemoryChartProps { - agentId: string; - timeRange: string; - start?: number; - end?: number; - isLive?: boolean; - latestMetrics?: LatestMetrics | null; -} - -interface MemoryPoint { - timestamp: number; - usage: number; -} - -/** - * 内存使用率图表组件 - */ -export const MemoryChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: MemoryChartProps) => { - const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; - const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; - // 数据查询 - const {data: metricsResponse, isLoading} = useMetricsQuery({ - agentId, - type: 'memory', - range: start !== undefined && end !== undefined ? undefined : effectiveRange, - start, - end, - }); - - // 历史数据 - const initialData = useMemo(() => { - const memorySeries = metricsResponse?.data.series?.find(s => s.name === 'usage'); - if (!memorySeries) return []; - return memorySeries.data.map((point) => ({ - usage: Number(point.value.toFixed(2)), - timestamp: point.timestamp, - })); - }, [metricsResponse]); - - // 实时点 - const livePoint = useMemo(() => { - if (!isLive || !latestMetrics?.memory || !latestMetrics.timestamp) return null; - const usage = latestMetrics.memory.usagePercent; - if (typeof usage !== 'number' || !Number.isFinite(usage)) return null; - return {timestamp: latestMetrics.timestamp, usage: Number(usage.toFixed(2))}; - }, [isLive, latestMetrics]); - - const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, agentId); - - // 渲染 - if (isLoading) { - return ( - - - - ); - } - - return ( - - {chartData.length > 0 ? ( - - - - - - - - - - formatChartTime(Number(value), timeRange, rangeMs)} - stroke="currentColor" - angle={-15} - textAnchor="end" - className="text-xs text-gray-600 dark:text-cyan-500 font-mono" - /> - `${value}%`} - /> - }/> - - - - ) : ( - - )} - - ); -}; diff --git a/web/src/portal/components/server/MonitorChart.tsx b/web/src/portal/components/server/MonitorChart.tsx deleted file mode 100644 index bdd6fe1..0000000 --- a/web/src/portal/components/server/MonitorChart.tsx +++ /dev/null @@ -1,479 +0,0 @@ -import {memo, useEffect, useMemo, useState} from 'react'; -import {Activity, RotateCcw, ChevronDown, ChevronUp} from 'lucide-react'; -import {Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -import {ChartPlaceholder} from '@portal/components/ChartPlaceholder'; -import {CustomTooltip} from '@portal/components/CustomTooltip'; -import {useMetricsQuery} from '@portal/hooks/server'; -import {useIsMobile} from '@portal/hooks/use-mobile'; -import {ChartContainer} from './ChartContainer'; -import {formatChartTime} from '@/lib/format.ts'; - -interface MonitorChartProps { - agentId: string; - timeRange: string; - start?: number; - end?: number; - isLive?: boolean; -} - -/** - * 降采样算法 - 使用LTTB (Largest Triangle Three Buckets) - * 确保输出精确的maxPoints个点,保留关键特征 - */ -const downsampleData = (data: any[], maxPoints: number): any[] => { - // 边界检查 - if (!data || data.length === 0) return []; - if (maxPoints < 2) maxPoints = 2; - if (data.length <= maxPoints) return [...data]; - - const result: any[] = [data[0]]; // 保留第一个点 - - // 桶大小 - const bucketSize = (data.length - 2) / (maxPoints - 2); - - for (let i = 0; i < maxPoints - 2; i++) { - // 计算当前桶的范围 - const start = Math.floor((i + 0) * bucketSize) + 1; - const end = Math.floor((i + 1) * bucketSize) + 1; - - // 计算前一个点和后一个点 - const previousPoint = result[result.length - 1]; - const nextPoint = data[Math.min(end, data.length - 1)]; - - // 在桶中选择与前后点形成的三角形面积最大的点 - let maxArea = -1; - let selectedPoint = data[start]; - - for (let j = start; j < end && j < data.length - 1; j++) { - // 计算三角形面积 - const area = Math.abs( - (previousPoint.timestamp - nextPoint.timestamp) * (data[j].value - previousPoint.value) - - (previousPoint.timestamp - data[j].timestamp) * (nextPoint.value - previousPoint.value) - ); - - if (area > maxArea) { - maxArea = area; - selectedPoint = data[j]; - } - } - - result.push(selectedPoint); - } - - result.push(data[data.length - 1]); // 保留最后一个点 - - return result; -}; - -/** - * 根据时间范围确定最大数据点数 - */ -const getMaxDataPoints = (timeRange: string): number => { - switch (timeRange) { - case '15m': - case '1h': - return 200; // 短时间:详细数据 - case '12h': - return 300; - case '24h': - return 400; - case '7d': - return 500; - case '30d': - return 600; - default: - return 400; - } -}; - -/** - * 生成不重复的颜色 - * 使用 HSL 色轮均匀分布,支持无限数量的监控项 - */ -const generateColors = (count: number): string[] => { - const colors: string[] = []; - const hueStep = 360 / count; // 色相间隔 - - for (let i = 0; i < count; i++) { - const hue = (i * hueStep) % 360; - const saturation = 65 + (i % 3) * 10; // 65%, 75%, 85% 循环 - const lightness = 45 + (i % 2) * 10; // 45%, 55% 循环 - colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`); - } - - return colors; -}; - -/** - * 自定义图例组件 - */ -const CustomLegend = ({ onClick, selectedMonitors, allMonitorKeys, colors, collapsed }: any) => { - if (!allMonitorKeys || allMonitorKeys.length === 0) return null; - - if (collapsed) return null; - - return ( -
- {allMonitorKeys.map((monitorKey: string, index: number) => { - const isSelected = selectedMonitors.has(monitorKey); - const color = colors[index]; - - return ( -
onClick({ value: monitorKey })} - className="flex items-center gap-2 cursor-pointer transition-opacity" - style={{ - opacity: isSelected ? 1 : 0.4, - }} - > - - - - - {monitorKey} - -
- ); - })} -
- ); -}; - -/** - * 监控响应时间图表组件 - */ -const MonitorChartImpl = ({agentId, timeRange, start, end, isLive}: MonitorChartProps) => { - const isMobile = useIsMobile(); - const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; - const [selectedMonitors, setSelectedMonitors] = useState>(new Set()); - const [legendCollapsed, setLegendCollapsed] = useState(true); // 移动端默认收起 - // 监控任务由探针自定义周期上报,实时模式下保留 15m 视图,10s 重查 - const effectiveRange = isLive ? '15m' : timeRange; - - // 数据查询 - const {data: metricsResponse, isLoading} = useMetricsQuery({ - agentId, - type: 'monitor', - range: start !== undefined && end !== undefined ? undefined : effectiveRange, - start, - end, - refetchIntervalMs: isLive ? 10000 : undefined, - }); - - // 获取所有监控任务的列表(使用名称) - const allMonitorKeys = useMemo(() => { - const series = metricsResponse?.data.series || []; - return series.map(s => s.labels?.monitor_name || s.labels?.monitor_id || s.name); - }, [metricsResponse]); - - // 初始化选中所有监控任务 - useEffect(() => { - if (allMonitorKeys.length > 0 && selectedMonitors.size === 0) { - setSelectedMonitors(new Set(allMonitorKeys)); - } - }, [allMonitorKeys, selectedMonitors.size]); - - // 过滤后的监控任务列表 - const monitorKeys = useMemo(() => { - return allMonitorKeys.filter(key => selectedMonitors.has(key)); - }, [allMonitorKeys, selectedMonitors]); - - // 数据转换 - 支持多个监控任务(统一时间轴 + 线性插值) - const chartData = useMemo(() => { - const series = metricsResponse?.data.series || []; - if (series.length === 0) return []; - - // 收集所有监控任务的数据 - const seriesDataArray: Array<{ key: string; data: Array<{ timestamp: number; value: number }> }> = []; - - series.forEach((s) => { - const monitorKey = s.labels?.monitor_name || s.labels?.monitor_id || s.name; - if (!selectedMonitors.has(monitorKey)) return; - if (!s.data || s.data.length === 0) return; - - seriesDataArray.push({ - key: monitorKey, - data: [...s.data].sort((a, b) => a.timestamp - b.timestamp) - }); - }); - - if (seriesDataArray.length === 0) return []; - - // 取所有监控任务时间范围的交集,确保每个时间点所有任务都有数据 - let minTime = -Infinity, maxTime = Infinity; - seriesDataArray.forEach(s => { - if (s.data.length > 0) { - minTime = Math.max(minTime, s.data[0].timestamp); - maxTime = Math.min(maxTime, s.data[s.data.length - 1].timestamp); - } - }); - - // 如果没有交集,返回空数组 - if (minTime >= maxTime) return []; - - // 均匀生成目标时间点 - const maxPoints = getMaxDataPoints(timeRange); - const timeStep = (maxTime - minTime) / (maxPoints - 1); - const targetTimestamps: number[] = []; - for (let i = 0; i < maxPoints; i++) { - targetTimestamps.push(minTime + i * timeStep); - } - - // 线性插值函数 - const interpolate = (data: Array<{ timestamp: number; value: number }>, targetTime: number): number | null => { - if (data.length === 0) return null; - if (data.length === 1) { - // 单点数据,只有精确匹配才返回 - return data[0].timestamp === targetTime ? data[0].value : null; - } - - // 如果目标时间在数据范围外,返回 null(断开折线) - if (targetTime < data[0].timestamp || targetTime > data[data.length - 1].timestamp) { - return null; - } - - // 二分查找找到 targetTime 前后两个点 - let left = 0, right = data.length - 1; - while (right - left > 1) { - const mid = Math.floor((left + right) / 2); - if (data[mid].timestamp <= targetTime) { - left = mid; - } else { - right = mid; - } - } - - // 线性插值 - const leftPoint = data[left]; - const rightPoint = data[right]; - const ratio = (targetTime - leftPoint.timestamp) / (rightPoint.timestamp - leftPoint.timestamp); - return leftPoint.value + ratio * (rightPoint.value - leftPoint.value); - }; - - // 对每个时间点,从每个监控任务中插值获取值 - return targetTimestamps.map(timestamp => { - const dataPoint: any = { timestamp }; - seriesDataArray.forEach(s => { - const value = interpolate(s.data, timestamp); - if (value !== null) { - dataPoint[s.key] = Number(value.toFixed(2)); - } - }); - return dataPoint; - }); - }, [metricsResponse, selectedMonitors, timeRange, start, end]); - - // 动态生成颜色(根据监控项数量) - const colors = useMemo(() => { - return generateColors(allMonitorKeys.length); - }, [allMonitorKeys.length]); - - // 点击图表区域切换选中状态 - const handleAreaClick = (data: any) => { - if (!data || !data.dataKey) return; - - const monitorKey = data.dataKey; - const newSelected = new Set(selectedMonitors); - - // 判断是否是全选状态 - const isAllSelected = selectedMonitors.size === allMonitorKeys.length; - - if (isAllSelected) { - // 全选状态下,点击某个 → 只选这一个 - setSelectedMonitors(new Set([monitorKey])); - } else { - // 非全选状态下,点击切换 - if (newSelected.has(monitorKey)) { - newSelected.delete(monitorKey); - } else { - newSelected.add(monitorKey); - } - setSelectedMonitors(newSelected); - } - }; - - // 点击图例切换选中状态 - const handleLegendClick = (data: any) => { - const monitorKey = data.value; - const newSelected = new Set(selectedMonitors); - - // 判断是否是全选状态 - const isAllSelected = selectedMonitors.size === allMonitorKeys.length; - - if (isAllSelected) { - // 全选状态下,点击某个 → 只选这一个 - setSelectedMonitors(new Set([monitorKey])); - } else { - // 非全选状态下,点击切换 - if (newSelected.has(monitorKey)) { - newSelected.delete(monitorKey); - } else { - newSelected.add(monitorKey); - } - setSelectedMonitors(newSelected); - } - }; - - // 恢复全选 - const handleSelectAll = () => { - setSelectedMonitors(new Set(allMonitorKeys)); - }; - - // 是否有监控项未选中(用于显示恢复按钮) - const hasUnselected = selectedMonitors.size < allMonitorKeys.length; - - // 切换图例显示/隐藏(仅移动端) - const toggleLegend = () => { - setLegendCollapsed(!legendCollapsed); - }; - - // 如果没有数据且不是加载中,不渲染组件 - if (!isLoading && chartData.length === 0) { - return null; - } - - // 渲染 - if (isLoading) { - return ( - - - - ); - } - - return ( - - {chartData.length > 0 ? ( - <> - {/* 使用提示和恢复按钮 */} - {allMonitorKeys.length > 1 && ( -
-
- 💡 点击图表线条或图例切换显示 -
- {hasUnselected && ( - - )} -
- )} - - - - - {monitorKeys.map((key, index) => { - const originalIndex = allMonitorKeys.indexOf(key); - return ( - - - - - ); - })} - - - formatChartTime(Number(value), timeRange, rangeMs)} - stroke="currentColor" - angle={-15} - textAnchor="end" - className="text-xs text-gray-600 dark:text-cyan-500 font-mono" - height={45} - /> - `${value}ms`} - /> - } - wrapperStyle={{zIndex: 9999,}} - /> - {monitorKeys.map((key, index) => { - const originalIndex = allMonitorKeys.indexOf(key); - return ( - - ); - })} - - - - {/* 桌面端:直接显示图例 */} - {!isMobile && allMonitorKeys.length > 0 && ( - - )} - - {/* 移动端:可折叠图例 */} - {isMobile && allMonitorKeys.length > 0 && ( -
- - -
- )} - - ) : ( - - )} -
- ); -}; - -export const MonitorChart = memo(MonitorChartImpl); diff --git a/web/src/portal/components/server/NetworkAddressSection.tsx b/web/src/portal/components/server/NetworkAddressSection.tsx deleted file mode 100644 index 7535de7..0000000 --- a/web/src/portal/components/server/NetworkAddressSection.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import {Card} from '@portal/components/Card.tsx'; - -interface NetworkInterface { - name: string; - addrs: string[]; -} - -interface NetworkAddressSectionProps { - ipv4?: string; - ipv6?: string; - deviceIpInterfaces: NetworkInterface[]; -} - -/** - * 网络地址信息展示组件 - * 包含公网地址(IPv4/IPv6)和设备网卡地址信息 - */ -export const NetworkAddressSection = ({ipv4, ipv6, deviceIpInterfaces}: NetworkAddressSectionProps) => { - const hasPublicIp = ipv4 || ipv6; - const hasDeviceIp = deviceIpInterfaces.length > 0; - - if (!hasPublicIp && !hasDeviceIp) { - return null; - } - - return ( - -
- {/* 公网地址 */} - {hasPublicIp && ( -
-
- 公网地址 -
-
-
-
IPv4
-
{ipv4 || '-'}
-
-
-
IPv6
-
{ipv6 || '-'}
-
-
-
- )} - - {/* 分割线 */} - {hasPublicIp && hasDeviceIp && ( -
- )} - - {/* 网卡地址 */} - {hasDeviceIp && ( -
-
- 网卡地址 -
-
- {deviceIpInterfaces.map((netInterface) => ( -
-
- {netInterface.name} -
-
- {netInterface.addrs.map((addr) => ( - - {addr} - - ))} -
-
- ))} -
-
- )} -
-
- ); -}; diff --git a/web/src/portal/components/server/NetworkChart.tsx b/web/src/portal/components/server/NetworkChart.tsx deleted file mode 100644 index eb87030..0000000 --- a/web/src/portal/components/server/NetworkChart.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import {useEffect, useMemo, useState} from 'react'; -import {Network} from 'lucide-react'; -import {Area, AreaChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -import {ChartPlaceholder} from '@portal/components/ChartPlaceholder'; -import {CustomTooltip} from '@portal/components/CustomTooltip'; -import {useMetricsQuery, useNetworkInterfacesQuery} from '@portal/hooks/server'; -import {useLiveBuffer} from '@portal/hooks/useLiveBuffer'; -import {LIVE_INITIAL_RANGE, LIVE_WINDOW_MS} from '@portal/constants/time'; -import {INTERFACE_COLORS} from '@portal/constants/server'; -import {ChartContainer} from './ChartContainer'; -import {formatChartTime} from '@/lib/format.ts'; -import type {LatestMetrics} from '@/types'; - -interface NetworkChartProps { - agentId: string; - timeRange: string; - start?: number; - end?: number; - isLive?: boolean; - latestMetrics?: LatestMetrics | null; -} - -interface NetworkPoint { - timestamp: number; - upload: number; - download: number; -} - -const toMB = (bytes: number) => Number((bytes / 1024 / 1024).toFixed(2)); - -/** - * 网络流量图表组件 - * 支持网卡切换 - */ -export const NetworkChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: NetworkChartProps) => { - const [selectedInterface, setSelectedInterface] = useState('all'); - const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; - const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; - - // 查询网卡列表 - const {data: interfacesData} = useNetworkInterfacesQuery(agentId); - const availableInterfaces = interfacesData?.data.interfaces || []; - - // 当网卡列表变化时,验证选中的网卡 - useEffect(() => { - if (selectedInterface !== 'all' && availableInterfaces.length > 0) { - if (!availableInterfaces.includes(selectedInterface)) { - setSelectedInterface('all'); - } - } - }, [availableInterfaces, selectedInterface]); - - // 查询网络数据 - const {data: metricsResponse, isLoading} = useMetricsQuery({ - agentId, - type: 'network', - range: start !== undefined && end !== undefined ? undefined : effectiveRange, - start, - end, - interfaceName: selectedInterface !== 'all' ? selectedInterface : undefined, - }); - - // 历史数据 - const initialData = useMemo(() => { - if (!metricsResponse?.data.series || metricsResponse.data.series?.length === 0) return []; - - const uploadSeries = metricsResponse.data.series?.find(s => s.name === 'upload'); - const downloadSeries = metricsResponse.data.series?.find(s => s.name === 'download'); - - if (!uploadSeries || !downloadSeries) return []; - - const timeMap = new Map(); - - uploadSeries.data.forEach(point => { - timeMap.set(point.timestamp, { - timestamp: point.timestamp, - upload: toMB(point.value), - download: 0, - }); - }); - - downloadSeries.data.forEach(point => { - const existing = timeMap.get(point.timestamp); - if (existing) { - existing.download = toMB(point.value); - } else { - timeMap.set(point.timestamp, { - timestamp: point.timestamp, - upload: 0, - download: toMB(point.value), - }); - } - }); - - return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp); - }, [metricsResponse]); - - // 实时点:用 latestMetrics.timestamp 作为采集批次时间锚点 - const livePoint = useMemo(() => { - if (!isLive || !latestMetrics?.timestamp) return null; - - let sentRate = 0; - let recvRate = 0; - if (selectedInterface === 'all') { - const summary = latestMetrics.network; - if (!summary) return null; - sentRate = summary.totalBytesSentRate; - recvRate = summary.totalBytesRecvRate; - } else { - const iface = latestMetrics.networkInterfaces?.find(i => i.interface === selectedInterface); - if (!iface) return null; - sentRate = iface.bytesSentRate; - recvRate = iface.bytesRecvRate; - } - return { - timestamp: latestMetrics.timestamp, - upload: toMB(sentRate), - download: toMB(recvRate), - }; - }, [isLive, latestMetrics, selectedInterface]); - - const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, `${agentId}|${selectedInterface}`); - - // 网卡选择器 - const interfaceSelector = availableInterfaces.length > 0 && ( - - ); - - // 渲染 - if (isLoading) { - return ( - - - - ); - } - - return ( - - {chartData.length > 0 ? ( - - - - - - - - - - - - - - formatChartTime(Number(value), timeRange, rangeMs)} - stroke="currentColor" - angle={-15} - textAnchor="end" - className="text-xs text-gray-600 dark:text-cyan-500 font-mono" - height={45} - /> - `${value} MB`} - /> - }/> - - - - - - ) : ( - - )} - - ); -}; diff --git a/web/src/portal/components/server/NetworkConnectionChart.tsx b/web/src/portal/components/server/NetworkConnectionChart.tsx deleted file mode 100644 index 4dcf5af..0000000 --- a/web/src/portal/components/server/NetworkConnectionChart.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import {useMemo} from 'react'; -import {Network} from 'lucide-react'; -import {CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -import {ChartPlaceholder} from '@portal/components/ChartPlaceholder'; -import {CustomTooltip} from '@portal/components/CustomTooltip'; -import {useMetricsQuery} from '@portal/hooks/server'; -import {useLiveBuffer} from '@portal/hooks/useLiveBuffer'; -import {LIVE_INITIAL_RANGE, LIVE_WINDOW_MS} from '@portal/constants/time'; -import {ChartContainer} from './ChartContainer'; -import {formatChartTime} from '@/lib/format.ts'; -import type {LatestMetrics} from '@/types'; - -interface NetworkConnectionChartProps { - agentId: string; - timeRange: string; - start?: number; - end?: number; - isLive?: boolean; - latestMetrics?: LatestMetrics | null; -} - -interface ConnPoint { - timestamp: number; - established: number; - time_wait: number; - close_wait: number; - listen: number; -} - -/** - * 网络连接统计图表组件 - * 实时模式下与 CPU/内存等快指标一样按 1s 节奏 append,便于压测观察 - */ -export const NetworkConnectionChart = ({agentId, timeRange, start, end, isLive, latestMetrics}: NetworkConnectionChartProps) => { - const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; - const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; - const {data: metricsResponse, isLoading} = useMetricsQuery({ - agentId, - type: 'network_connection', - range: start !== undefined && end !== undefined ? undefined : effectiveRange, - start, - end, - }); - - // 历史数据 - const initialData = useMemo(() => { - if (!metricsResponse?.data.series || metricsResponse.data.series?.length === 0) return []; - - const timeMap = new Map(); - - metricsResponse.data.series?.forEach(series => { - const stateName = series.name; - series.data.forEach(point => { - if (!timeMap.has(point.timestamp)) { - timeMap.set(point.timestamp, { - timestamp: point.timestamp, - established: 0, - time_wait: 0, - close_wait: 0, - listen: 0, - }); - } - const existing = timeMap.get(point.timestamp)!; - if (stateName === 'established' || stateName === 'time_wait' || stateName === 'close_wait' || stateName === 'listen') { - existing[stateName] = Number(point.value.toFixed(0)); - } - }); - }); - - return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp); - }, [metricsResponse]); - - // 实时点 - const livePoint = useMemo(() => { - if (!isLive || !latestMetrics?.networkConnection || !latestMetrics.timestamp) return null; - const c = latestMetrics.networkConnection; - return { - timestamp: latestMetrics.timestamp, - established: c.established ?? 0, - time_wait: c.timeWait ?? 0, - close_wait: c.closeWait ?? 0, - listen: c.listen ?? 0, - }; - }, [isLive, latestMetrics]); - - const chartData = useLiveBuffer(initialData, !!isLive, livePoint, LIVE_WINDOW_MS, agentId); - - // 渲染 - if (isLoading) { - return ( - - - - ); - } - - return ( - - {chartData.length > 0 ? ( - - - - formatChartTime(Number(value), timeRange, rangeMs)} - stroke="currentColor" - angle={-15} - textAnchor="end" - className="text-xs text-gray-600 dark:text-cyan-500 font-mono" - height={45} - /> - - }/> - - - - - - - - ) : ( - - )} - - ); -}; diff --git a/web/src/portal/components/server/NetworkConnectionSection.tsx b/web/src/portal/components/server/NetworkConnectionSection.tsx deleted file mode 100644 index 55c5359..0000000 --- a/web/src/portal/components/server/NetworkConnectionSection.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import {Network} from 'lucide-react'; -import {Card} from '@portal/components/Card'; -import type {LatestMetrics} from '@/types'; - -interface NetworkConnectionSectionProps { - latestMetrics: LatestMetrics | null; -} - -/** - * 网络连接统计区块组件 - * 显示 TCP 连接各状态的实时统计数据 - */ -export const NetworkConnectionSection = ({latestMetrics}: NetworkConnectionSectionProps) => { - // 如果没有网络连接数据,不渲染组件 - if (!latestMetrics?.networkConnection) { - return null; - } - - return ( - -
-
-
Total
-
{latestMetrics.networkConnection.total}
-
-
-
ESTABLISHED
-
{latestMetrics.networkConnection.established}
-
-
-
TIME_WAIT
-
{latestMetrics.networkConnection.timeWait}
-
-
-
LISTEN
-
{latestMetrics.networkConnection.listen}
-
-
-
CLOSE_WAIT
-
{latestMetrics.networkConnection.closeWait}
-
-
-
OTHER
-
- {latestMetrics.networkConnection.synSent + - latestMetrics.networkConnection.synRecv + - latestMetrics.networkConnection.finWait1 + - latestMetrics.networkConnection.finWait2 + - latestMetrics.networkConnection.close + - latestMetrics.networkConnection.lastAck + - latestMetrics.networkConnection.closing} -
-
-
-
- ); -}; diff --git a/web/src/portal/components/server/ServerHero.tsx b/web/src/portal/components/server/ServerHero.tsx deleted file mode 100644 index 16aec97..0000000 --- a/web/src/portal/components/server/ServerHero.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import {ArrowLeft} from 'lucide-react'; -import {formatBytes, formatDateTime, formatUptime} from '@/lib/format.ts'; -import type {Agent, LatestMetrics} from '@/types'; -import LittleStatCard from '@portal/components/LittleStatCard'; -import CyberCard from "@portal/components/CyberCard.tsx"; -import {StatusBadge} from "@portal/components/StatusBadge"; - -interface ServerHeroProps { - agent: Agent; - latestMetrics: LatestMetrics | null; - onBack: () => void; -} - -/** - * 服务器头部信息组件 - * 显示服务器基本信息、状态和关键指标 - */ -export const ServerHero = ({agent, latestMetrics, onBack}: ServerHeroProps) => { - const displayName = agent?.name?.trim() ? agent.name : '未命名探针'; - const isOnline = agent?.status === 1; - const statusDotStyles = isOnline ? 'bg-emerald-500' : 'bg-rose-500'; - const statusText = isOnline ? '在线' : '离线'; - - const platformDisplay = latestMetrics?.host?.platform - ? `${latestMetrics.host.platform} ${latestMetrics.host.platformVersion || ''}`.trim() - : agent?.os || '-'; - const architectureDisplay = latestMetrics?.host?.kernelArch || agent?.arch || '-'; - const uptimeDisplay = formatUptime(latestMetrics?.host?.uptime); - const lastSeenDisplay = agent ? formatDateTime(agent.lastSeenAt) : '-'; - - const networkSummary = latestMetrics?.network - ? `${formatBytes(latestMetrics.network.totalBytesSentTotal)} ↑ / ${formatBytes( - latestMetrics.network.totalBytesRecvTotal, - )} ↓` - : '—'; - - const heroStats = [ - {label: '运行系统', value: platformDisplay || '-'}, - {label: '硬件架构', value: architectureDisplay || '-'}, - {label: '系统进程', value: latestMetrics?.host?.procs || '-'}, - {label: '运行时长', value: uptimeDisplay}, - ]; - - return ( - -
-
-
- -
-
-
-

{displayName}

- -
-

- {[agent.hostname].filter(Boolean).join(' · ') || '-'} -

-
-
-
- -
- {heroStats.map((stat) => ( - - ))} -
-
-
- 探针 ID:{agent.id} - - 版本:{agent.version || '-'} - - 网络累计:{networkSummary} -
-
-
- ); -}; diff --git a/web/src/portal/components/server/SnapshotGrid.tsx b/web/src/portal/components/server/SnapshotGrid.tsx deleted file mode 100644 index bccbecc..0000000 --- a/web/src/portal/components/server/SnapshotGrid.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import type {ReactNode} from 'react'; -import type {LucideIcon} from 'lucide-react'; -import {cn} from '@/lib/utils'; - -type AccentVariant = 'blue' | 'emerald' | 'purple' | 'amber'; - -export type SnapshotCardData = { - key: string; - icon: LucideIcon; - title: string; - usagePercent: string; - accent: AccentVariant; - metrics: Array<{ label: string; value: ReactNode }>; -}; - -const accentThemes: Record = { - blue: { - icon: 'text-blue-400', - badge: 'text-blue-400', - highlight: 'text-blue-400', - }, - emerald: { - icon: 'text-emerald-400', - badge: 'text-emerald-400', - highlight: 'text-emerald-400', - }, - purple: { - icon: 'text-purple-400', - badge: 'text-purple-400', - highlight: 'text-purple-400', - }, - amber: { - icon: 'text-amber-400', - badge: 'text-amber-400', - highlight: 'text-amber-400', - }, -}; - -interface SnapshotGridProps { - cards: SnapshotCardData[]; -} - -export const SnapshotGrid = ({cards}: SnapshotGridProps) => ( -
- {cards.map((card) => { - const theme = accentThemes[card.accent]; - return ( -
-
-
- - - -

{card.title}

-
- {card.usagePercent} -
-
- {card.metrics.map((metric) => ( -
- {metric.label} - {metric.value} -
- ))} -
-
- ); - })} -
-); diff --git a/web/src/portal/components/server/SnapshotSection.tsx b/web/src/portal/components/server/SnapshotSection.tsx deleted file mode 100644 index 92cf135..0000000 --- a/web/src/portal/components/server/SnapshotSection.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import {SnapshotGrid, type SnapshotCardData} from './SnapshotGrid'; -import CyberCard from "@portal/components/CyberCard.tsx"; - -interface SnapshotSectionProps { - cards: SnapshotCardData[]; -} - -export const SnapshotSection = ({cards}: SnapshotSectionProps) => { - if (cards.length === 0) { - return null; - } - return ( - -
-

资源快照

-
- -
- ); -}; diff --git a/web/src/portal/components/server/SystemInfoSection.tsx b/web/src/portal/components/server/SystemInfoSection.tsx deleted file mode 100644 index cea11ea..0000000 --- a/web/src/portal/components/server/SystemInfoSection.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import {Cpu, HardDrive, MemoryStick, Network} from 'lucide-react'; -import {InfoGrid} from '@portal/components/server/InfoGrid'; -import {SnapshotSection} from '@portal/components/server/SnapshotSection'; -import type {SnapshotCardData} from '@portal/components/server/SnapshotGrid'; -import {formatBytes, formatDateTime, formatPercentValue, formatUptime} from '@/lib/format.ts'; -import type {Agent, LatestMetrics} from '@/types'; -import CyberCard from "@portal/components/CyberCard.tsx"; - -interface SystemInfoSectionProps { - agent: Agent; - latestMetrics: LatestMetrics | null; -} - -/** - * 系统信息区块组件 - * 显示运行环境、运行状态、快照卡片和网络连接统计 - */ -export const SystemInfoSection = ({agent, latestMetrics}: SystemInfoSectionProps) => { - // 环境信息 - const platformDisplay = latestMetrics?.host?.platform - ? `${latestMetrics.host.platform} ${latestMetrics.host.platformVersion || ''}`.trim() - : agent?.os || '-'; - const architectureDisplay = latestMetrics?.host?.kernelArch || agent?.arch || '-'; - - const environmentInfo = [ - {label: '操作系统', value: platformDisplay || '-'}, - {label: '内核版本', value: latestMetrics?.host?.kernelVersion || '-'}, - {label: '硬件架构', value: architectureDisplay || '-'}, - {label: 'CPU 型号', value: latestMetrics?.cpu?.modelName || '-'}, - {label: '逻辑核心', value: latestMetrics?.cpu?.logicalCores ?? '-'}, - {label: '物理核心', value: latestMetrics?.cpu?.physicalCores ?? '-'}, - ]; - - // 状态信息 - const uptimeDisplay = formatUptime(latestMetrics?.host?.uptime); - const bootTimeDisplay = latestMetrics?.host?.bootTime - ? formatDateTime(latestMetrics.host.bootTime * 1000) - : '-'; - const lastSeenDisplay = agent ? formatDateTime(agent.lastSeenAt) : '-'; - - const networkSummary = latestMetrics?.network - ? `${formatBytes(latestMetrics.network.totalBytesSentTotal)} ↑ / ${formatBytes( - latestMetrics.network.totalBytesRecvTotal, - )} ↓` - : '—'; - - const statusInfo = [ - {label: '启动时间', value: bootTimeDisplay}, - {label: '运行时间', value: uptimeDisplay}, - {label: '最近心跳', value: lastSeenDisplay}, - {label: '进程数', value: latestMetrics?.host?.procs ?? '-'}, - {label: '网络累计', value: networkSummary}, - {label: 'Load', value: `${latestMetrics?.host?.load1?.toFixed(2)} / ${latestMetrics?.host?.load5?.toFixed(2)} / ${latestMetrics?.host?.load15?.toFixed(2)}`}, - ]; - - // 快照卡片 - const snapshotCards: SnapshotCardData[] = []; - - if (latestMetrics) { - snapshotCards.push({ - key: 'cpu', - icon: Cpu, - title: 'CPU 使用', - usagePercent: `${formatPercentValue(latestMetrics.cpu?.usagePercent)}%`, - accent: 'blue', - metrics: [ - {label: '当前使用', value: `${formatPercentValue(latestMetrics.cpu?.usagePercent)}%`}, - ], - }); - - snapshotCards.push({ - key: 'memory', - icon: MemoryStick, - title: '内存使用', - usagePercent: `${formatPercentValue(latestMetrics.memory?.usagePercent)}%`, - accent: 'emerald', - metrics: [ - { - label: '已用 / 总量', - value: `${formatBytes(latestMetrics.memory?.used)} / ${formatBytes(latestMetrics.memory?.total)}` - }, - { - label: 'Swap 已用', - value: `${formatBytes(latestMetrics.memory?.swapUsed)} / ${formatBytes(latestMetrics.memory?.swapTotal)}` - }, - ], - }); - - snapshotCards.push({ - key: 'disk', - icon: HardDrive, - title: '磁盘使用', - usagePercent: latestMetrics.disk - ? `${formatPercentValue(latestMetrics.disk.usagePercent)}%` - : '—', - accent: 'purple', - metrics: [ - { - label: '已用 / 总量', - value: `${formatBytes(latestMetrics.disk?.used, 1)} / ${formatBytes(latestMetrics.disk?.total, 1)}` - }, - {label: '磁盘数量', value: latestMetrics.disk?.totalDisks ?? '-'}, - ], - }); - - // 网络流量卡片 - 整合流量统计信息 - const networkMetrics = [ - { - label: '上行 / 下行', - value: `${formatBytes(latestMetrics.network?.totalBytesSentRate, 1)}/s ↑ / ${formatBytes( - latestMetrics.network?.totalBytesRecvRate, 1, - )}/s ↓`, - }, - { - label: '网络累计', - value: `${formatBytes(latestMetrics.network?.totalBytesSentTotal, 1)} ↑ / ${formatBytes( - latestMetrics.network?.totalBytesRecvTotal, 1, - )} ↓`, - }, - ]; - - // 如果配置了流量限额,添加流量统计信息到网络卡片 - if (agent?.trafficStats?.enabled && agent.trafficStats.limit > 0) { - const trafficUsedPercent = (agent.trafficStats.used / agent.trafficStats.limit) * 100; - - networkMetrics.push({ - label: '流量限额', - value: `${formatBytes(agent.trafficStats.used, 1)} / ${formatBytes(agent.trafficStats.limit, 1)} (${formatPercentValue(trafficUsedPercent)}%)`, - }); - - if (agent.trafficStats.resetDay > 0) { - networkMetrics.push({ - label: '重置日期', - value: `每月${agent.trafficStats.resetDay}号`, - }); - } - } - - snapshotCards.push({ - key: 'network', - icon: Network, - title: '网络流量', - usagePercent: latestMetrics.network - ? `${formatBytes(latestMetrics.network.totalBytesSentRate)}/s` - : '—', - accent: 'amber', - metrics: networkMetrics, - }); - } - - return ( -
-
-
- -

运行环境

-

来自最近一次探针上报的硬件与系统信息

-
- -
-
- -

运行状态

-

关键时间与网络指标,帮助快速判断主机健康状况

-
- -
-
-
- -
-
- ); -}; diff --git a/web/src/portal/components/server/TemperatureChart.tsx b/web/src/portal/components/server/TemperatureChart.tsx deleted file mode 100644 index 31b95d9..0000000 --- a/web/src/portal/components/server/TemperatureChart.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import {memo, useEffect, useMemo, useState} from 'react'; -import {Thermometer} from 'lucide-react'; -import {CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -import {ChartPlaceholder} from '@portal/components/ChartPlaceholder'; -import {CustomTooltip} from '@portal/components/CustomTooltip'; -import {useMetricsQuery} from '@portal/hooks/server'; -import {LIVE_INITIAL_RANGE} from '@portal/constants/time'; -import {TEMPERATURE_COLORS} from '@portal/constants/server'; -import {ChartContainer} from './ChartContainer'; -import {formatChartTime} from '@/lib/format.ts'; - -interface TemperatureChartProps { - agentId: string; - timeRange: string; - start?: number; - end?: number; - isLive?: boolean; -} - -/** - * 系统温度图表组件 - * 支持温度类型切换;实时模式下 5s refetch + React.memo 避免随 ServerDetail 1s 重渲染 - */ -const TemperatureChartImpl = ({agentId, timeRange, start, end, isLive}: TemperatureChartProps) => { - const [selectedTempType, setSelectedTempType] = useState('all'); - const rangeMs = start !== undefined && end !== undefined ? end - start : undefined; - const effectiveRange = isLive ? LIVE_INITIAL_RANGE : timeRange; - - // 数据查询:温度采集 5s 一次,实时模式 5s 重查 - const {data: metricsResponse, isLoading} = useMetricsQuery({ - agentId, - type: 'temperature', - range: start !== undefined && end !== undefined ? undefined : effectiveRange, - start, - end, - refetchIntervalMs: isLive ? 5000 : undefined, - }); - - // 数据转换 - const chartData = useMemo(() => { - if (!metricsResponse?.data.series || metricsResponse.data.series?.length === 0) return []; - - const timeMap = new Map(); - - metricsResponse.data.series?.forEach(series => { - const sensorName = series.name; - series.data.forEach(point => { - if (!timeMap.has(point.timestamp)) { - timeMap.set(point.timestamp, {timestamp: point.timestamp}); - } - - const existing = timeMap.get(point.timestamp)!; - existing[sensorName] = Number(point.value.toFixed(2)); - }); - }); - - return Array.from(timeMap.values()); - }, [metricsResponse]); - - // 提取所有唯一的温度类型 - const temperatureTypes = useMemo(() => { - return metricsResponse?.data.series?.map(s => s.name).sort() || []; - }, [metricsResponse]); - - // 根据选中的类型过滤温度数据 - const filteredTemperatureTypes = useMemo(() => { - if (selectedTempType === 'all') { - return temperatureTypes; - } - return temperatureTypes.filter(type => type === selectedTempType); - }, [temperatureTypes, selectedTempType]); - - // 当温度类型列表变化时,如果当前选中的类型不在列表中,重置为 'all' - useEffect(() => { - if (selectedTempType !== 'all' && temperatureTypes.length > 0) { - if (!temperatureTypes.includes(selectedTempType)) { - setSelectedTempType('all'); - } - } - }, [temperatureTypes, selectedTempType]); - - // 温度类型选择器 - const tempTypeSelector = temperatureTypes.length > 1 && ( - - ); - - // 渲染 - if (isLoading) { - return ( - - - - ); - } - - // 如果没有温度数据,不渲染组件 - if (chartData.length === 0 || temperatureTypes.length === 0) { - return null; - } - - return ( - - - - - formatChartTime(Number(value), timeRange, rangeMs)} - stroke="currentColor" - angle={-15} - textAnchor="end" - className="text-xs text-gray-600 dark:text-cyan-500 font-mono" - height={45} - /> - `${value}°C`} - /> - }/> - - {/* 为选中的温度类型渲染线条 */} - {filteredTemperatureTypes.map((type, index) => { - const color = TEMPERATURE_COLORS[type] || `hsl(${(index * 60) % 360}, 70%, 50%)`; - return ( - - ); - })} - - - - ); -}; - -export const TemperatureChart = memo(TemperatureChartImpl); diff --git a/web/src/portal/components/server/TemperatureMonitorSection.tsx b/web/src/portal/components/server/TemperatureMonitorSection.tsx deleted file mode 100644 index 3c9d09a..0000000 --- a/web/src/portal/components/server/TemperatureMonitorSection.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import {Thermometer} from 'lucide-react'; -import {Card} from '@portal/components/Card'; -import type {LatestMetrics} from '@/types'; - -interface TemperatureMonitorSectionProps { - latestMetrics: LatestMetrics | null; -} - -/** - * 温度监控区块组件 - * 显示系统各部件温度传感器数据 - */ -export const TemperatureMonitorSection = ({latestMetrics}: TemperatureMonitorSectionProps) => { - // 如果没有温度数据,不渲染组件 - if (!latestMetrics?.temperature || latestMetrics.temperature.length === 0) { - return null; - } - - return ( - -
- {latestMetrics.temperature.sort((a, b) => a.sensorKey.localeCompare(b.sensorKey)).map((temp) => ( -
-
- -

{temp.type}

-
-

{temp.temperature.toFixed(1)}°C

-
- ))} -
-
- ); -}; diff --git a/web/src/portal/constants/colors.ts b/web/src/portal/constants/colors.ts deleted file mode 100644 index 45b28ae..0000000 --- a/web/src/portal/constants/colors.ts +++ /dev/null @@ -1,15 +0,0 @@ -// 探针颜色方案(用于 MonitorDetail 区分不同探针) -export const AGENT_COLORS = [ - '#3b82f6', // blue - '#10b981', // emerald - '#f59e0b', // amber - '#8b5cf6', // violet - '#ec4899', // pink - '#06b6d4', // cyan - '#f97316', // orange - '#14b8a6', // teal -]; - -// 重新导出其他颜色配置 -export {INTERFACE_COLORS, TEMPERATURE_COLORS, ACCENT_THEMES} from './server'; -export type {TimeRangeOption} from '@/api/property.ts'; diff --git a/web/src/portal/constants/server.ts b/web/src/portal/constants/server.ts deleted file mode 100644 index 950171a..0000000 --- a/web/src/portal/constants/server.ts +++ /dev/null @@ -1,43 +0,0 @@ -// 网卡颜色配置(上行和下行使用不同的色调) -export const INTERFACE_COLORS = [ - {upload: '#6FD598', download: '#2C70F6'}, // 绿/蓝 - {upload: '#f59e0b', download: '#8b5cf6'}, // 橙/紫 - {upload: '#ec4899', download: '#06b6d4'}, // 粉/青 - {upload: '#10b981', download: '#f97316'}, // 翠绿/深橙 - {upload: '#14b8a6', download: '#2563eb'}, // 青绿/深蓝 -]; - -// 温度类型颜色映射 -export const TEMPERATURE_COLORS: Record = { - 'CPU': '#f97316', // 橙色 - 'GPU': '#8b5cf6', // 紫色 - 'DISK': '#06b6d4', // 青色 - 'BATTERY': '#10b981', // 绿色 - 'CHIPSET': '#f59e0b', // 琥珀色 - 'SYSTEM': '#6366f1', // 靛蓝色 - 'PSU': '#ec4899', // 粉色 -}; - -// 主题配色方案 -export const ACCENT_THEMES: Record<'blue' | 'emerald' | 'purple' | 'amber', { icon: string; badge: string; highlight: string }> = { - blue: { - icon: 'text-blue-400', - badge: 'text-blue-400', - highlight: 'text-blue-400', - }, - emerald: { - icon: 'text-emerald-400', - badge: 'text-emerald-400', - highlight: 'text-emerald-400', - }, - purple: { - icon: 'text-purple-400', - badge: 'text-purple-400', - highlight: 'text-purple-400', - }, - amber: { - icon: 'text-amber-400', - badge: 'text-amber-400', - highlight: 'text-amber-400', - }, -}; diff --git a/web/src/portal/constants/time.ts b/web/src/portal/constants/time.ts deleted file mode 100644 index ff49dba..0000000 --- a/web/src/portal/constants/time.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type {TimeRangeOption} from '@/api/property.ts'; - -// 实时模式标识,前端内部值;查询历史时映射到 LIVE_INITIAL_RANGE -export const LIVE_RANGE = 'live'; -// 实时模式下首次拉取的历史窗口 -export const LIVE_INITIAL_RANGE = '2m'; -// 实时模式滑动窗口长度(毫秒)—— 与 LIVE_INITIAL_RANGE 保持一致 -export const LIVE_WINDOW_MS = 2 * 60 * 1000; - -// 服务器详情页时间范围选项 -export const SERVER_TIME_RANGE_OPTIONS: TimeRangeOption[] = [ - {label: '实时', value: LIVE_RANGE}, - {label: '15分钟', value: '15m'}, - {label: '30分钟', value: '30m'}, - {label: '1小时', value: '1h'}, - {label: '3小时', value: '3h'}, - {label: '6小时', value: '6h'}, - {label: '12小时', value: '12h'}, - {label: '1天', value: '1d'}, - {label: '3天', value: '3d'}, - {label: '7天', value: '7d'}, -]; - -// 监控详情页时间范围选项 -export const MONITOR_TIME_RANGE_OPTIONS: TimeRangeOption[] = [ - {label: '12小时', value: '12h'}, - {label: '1天', value: '1d'}, - {label: '3天', value: '3d'}, - {label: '7天', value: '7d'}, -]; diff --git a/web/src/portal/contexts/ThemeContext.tsx b/web/src/portal/contexts/ThemeContext.tsx deleted file mode 100644 index 5fcf4ae..0000000 --- a/web/src/portal/contexts/ThemeContext.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import {createContext, useContext, useEffect, useState, type ReactNode} from 'react'; - -// 主题类型: light(亮色) | dark(暗色) | auto(自动) -export type Theme = 'light' | 'dark' | 'auto'; - -// 实际应用的主题: light 或 dark -export type AppliedTheme = 'light' | 'dark'; - -interface ThemeContextType { - theme: Theme; - appliedTheme: AppliedTheme; - setTheme: (theme: Theme) => void; -} - -const ThemeContext = createContext(undefined); - -interface ThemeProviderProps { - children: ReactNode; -} - -export const ThemeProvider = ({children}: ThemeProviderProps) => { - // 从 localStorage 读取用户偏好,默认为 auto - const [theme, setThemeState] = useState(() => { - const saved = localStorage.getItem('theme'); - return (saved as Theme) || 'auto'; - }); - - // 实际应用的主题 - const [appliedTheme, setAppliedTheme] = useState('dark'); - - // 设置主题 - const setTheme = (newTheme: Theme) => { - setThemeState(newTheme); - localStorage.setItem('theme', newTheme); - }; - - // 根据主题设置更新实际应用的主题 - useEffect(() => { - const updateAppliedTheme = () => { - let applied: AppliedTheme = 'dark'; - - if (theme === 'auto') { - // 自动模式:检测系统主题 - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - applied = prefersDark ? 'dark' : 'light'; - } else { - applied = theme; - } - - setAppliedTheme(applied); - - // 更新 document.documentElement 的 class - if (applied === 'dark') { - document.documentElement.classList.add('dark'); - } else { - document.documentElement.classList.remove('dark'); - } - }; - - updateAppliedTheme(); - - // 如果是自动模式,监听系统主题变化 - if (theme === 'auto') { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const handler = () => updateAppliedTheme(); - - // 使用 addEventListener 替代已废弃的 addListener - mediaQuery.addEventListener('change', handler); - return () => mediaQuery.removeEventListener('change', handler); - } - }, [theme]); - - return ( - - {children} - - ); -}; - -// 自定义 Hook 用于访问主题上下文 -export const useTheme = () => { - const context = useContext(ThemeContext); - if (context === undefined) { - throw new Error('useTheme must be used within a ThemeProvider'); - } - return context; -}; diff --git a/web/src/portal/hooks/server.ts b/web/src/portal/hooks/server.ts deleted file mode 100644 index 3763c60..0000000 --- a/web/src/portal/hooks/server.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {useQuery} from '@tanstack/react-query'; -import { - getAgent, - getAgentLatestMetrics, - getAgentMetrics, - getAvailableNetworkInterfaces, - type GetAgentMetricsRequest, - type MetricsAggregation, -} from '@/api/agent'; - -interface UseMetricsQueryOptions { - agentId: string; - type: GetAgentMetricsRequest['type']; - range?: string; - start?: number; - end?: number; - interfaceName?: string; - aggregation?: MetricsAggregation; - // 自动刷新间隔(毫秒),不传或 0 表示不自动刷新 - refetchIntervalMs?: number; -} - -/** - * 查询 Agent 基础信息 - * @param agentId Agent ID - * @returns Agent 查询结果 - */ -export const useAgentQuery = (agentId?: string) => { - return useQuery({ - queryKey: ['agent', agentId], - queryFn: () => getAgent(agentId!), - enabled: !!agentId, - staleTime: 60000, // 1分钟缓存 - }); -}; - -/** - * 查询 Agent 最新指标 - * @param agentId Agent ID - * @param intervalMs 自动刷新间隔(毫秒),默认 5000 - * @returns 最新指标查询结果 - */ -export const useLatestMetricsQuery = (agentId?: string, intervalMs: number = 5000) => { - return useQuery({ - queryKey: ['agent', agentId, 'metrics', 'latest'], - queryFn: () => getAgentLatestMetrics(agentId!), - enabled: !!agentId, - refetchInterval: intervalMs > 0 ? intervalMs : false, - }); -}; - -/** - * 查询 Agent 历史指标数据 - * @param options 查询选项 - * @returns 历史指标查询结果 - */ -export const useMetricsQuery = ({agentId, type, range, start, end, interfaceName, aggregation, refetchIntervalMs}: UseMetricsQueryOptions) => { - return useQuery({ - queryKey: ['agent', agentId, 'metrics', type, range, start, end, interfaceName, aggregation], - queryFn: () => - getAgentMetrics({ - agentId, - type, - range, - start, - end, - interface: interfaceName, - aggregation, - }), - enabled: !!agentId, - refetchInterval: refetchIntervalMs && refetchIntervalMs > 0 ? refetchIntervalMs : false, - }); -}; - -/** - * 查询 Agent 可用网卡列表 - * @param agentId Agent ID - * @returns 网卡列表查询结果 - */ -export const useNetworkInterfacesQuery = (agentId?: string) => { - return useQuery({ - queryKey: ['agent', agentId, 'network-interfaces'], - queryFn: () => getAvailableNetworkInterfaces(agentId!), - enabled: !!agentId, - }); -}; diff --git a/web/src/portal/hooks/use-mobile.ts b/web/src/portal/hooks/use-mobile.ts deleted file mode 100644 index 08c7d9e..0000000 --- a/web/src/portal/hooks/use-mobile.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from "react" - -const MOBILE_BREAKPOINT = 768 - -export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState(undefined) - - React.useEffect(() => { - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) - } - mql.addEventListener("change", onChange) - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) - return () => mql.removeEventListener("change", onChange) - }, []) - - return !!isMobile -} \ No newline at end of file diff --git a/web/src/portal/hooks/useLiveBuffer.ts b/web/src/portal/hooks/useLiveBuffer.ts deleted file mode 100644 index 86d25f5..0000000 --- a/web/src/portal/hooks/useLiveBuffer.ts +++ /dev/null @@ -1,74 +0,0 @@ -import {useEffect, useRef, useState} from 'react'; - -/** - * 维护一个"实时滑动窗口"数据缓冲。 - * - * - 非实时模式:直接返回 initial。 - * - 实时模式:第一次进入并拿到 initial 后用它播种缓冲;之后只追加 livePoint, - * 不再因为 initial 引用变化(例如父组件重渲染产生新数组)而重置已有数据。 - * - 若 initial 抵达前已经有 livePoint 进来,播种时仅把比已有最早点更早的历史点 prepend, - * 不丢已经追加的实时点。 - * - resetKey 变化时(如切换网卡 / 切换 agent)会强制清空并等待新 initial 重新播种。 - * - 同一时间戳的 livePoint 被忽略,避免轮询重复触发的重复点。 - * - 早于 ts - windowMs 的点会被丢弃,形成滑动窗口。 - */ -export function useLiveBuffer( - initial: T[], - isLive: boolean, - livePoint: T | null, - windowMs: number, - resetKey?: unknown, -): T[] { - const [buffer, setBuffer] = useState([]); - const lastTsRef = useRef(0); - const seededRef = useRef(false); - - // isLive 切换或 resetKey 变化(数据语义变更)时复位播种状态 - useEffect(() => { - seededRef.current = false; - lastTsRef.current = 0; - setBuffer([]); - }, [isLive, resetKey]); - - // 进入实时模式后,等历史数据到位再播种(每次复位后只播种一次) - // 依赖里加 resetKey:万一 RQ 命中旧缓存导致 initial 引用未变也能重新播种 - useEffect(() => { - if (!isLive) return; - if (seededRef.current) return; - if (!initial || initial.length === 0) return; - seededRef.current = true; - - const initLastTs = initial[initial.length - 1].timestamp; - setBuffer(prev => { - if (prev.length === 0) { - return initial.slice(); - } - // livePoint 已经先到了:只把比已有最早点更早的历史点 prepend,避免覆盖实时点 - const earliestExisting = prev[0].timestamp; - const olderHistory = initial.filter(p => p.timestamp < earliestExisting); - return olderHistory.length > 0 ? [...olderHistory, ...prev] : prev; - }); - if (initLastTs > lastTsRef.current) { - lastTsRef.current = initLastTs; - } - }, [isLive, initial, resetKey]); - - // 追加实时点 - useEffect(() => { - if (!isLive || !livePoint) return; - const ts = livePoint.timestamp; - if (!Number.isFinite(ts) || ts <= 0) return; - if (ts <= lastTsRef.current) return; - lastTsRef.current = ts; - - setBuffer(prev => { - const next = prev.length === 0 ? [livePoint] : [...prev, livePoint]; - const cutoff = ts - windowMs; - let drop = 0; - while (drop < next.length && next[drop].timestamp < cutoff) drop++; - return drop > 0 ? next.slice(drop) : next; - }); - }, [livePoint, isLive, windowMs]); - - return isLive ? buffer : initial; -} diff --git a/web/src/portal/pages/MonitorDetail.tsx b/web/src/portal/pages/MonitorDetail.tsx deleted file mode 100644 index 29e17a4..0000000 --- a/web/src/portal/pages/MonitorDetail.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import {useNavigate, useParams} from 'react-router-dom'; -import {useQuery} from '@tanstack/react-query'; -import {getMonitorAgentStats, getMonitorStatsById} from '@/api/monitor.ts'; -import type {AgentMonitorStat, PublicMonitor} from '@/types'; -import {MonitorHero} from '@portal/components/monitor/MonitorHero.tsx'; -import {ResponseTimeChart} from '@portal/components/monitor/ResponseTimeChart.tsx'; -import {AgentStatsTable} from '@portal/components/monitor/AgentStatsTable.tsx'; -import {EmptyState} from '@portal/components/EmptyState.tsx'; -import {LoadingSpinner} from '@portal/components/LoadingSpinner.tsx'; - -/** - * 监控详情页面 - * 显示监控的详细信息、响应时间趋势和各探针统计 - */ -const MonitorDetail = () => { - const navigate = useNavigate(); - const {id} = useParams<{ id: string }>(); - - // 获取监控详情(聚合数据) - const {data: monitorDetail, isLoading} = useQuery({ - queryKey: ['monitorDetail', id], - queryFn: async () => { - if (!id) throw new Error('Monitor ID is required'); - const response = await getMonitorStatsById(id); - return response.data; - }, - refetchInterval: 30000, - enabled: !!id, - }); - - // 获取各探针的统计数据 - const {data: monitorStats = []} = useQuery({ - queryKey: ['monitorAgentStats', id], - queryFn: async () => { - if (!id) return []; - const response = await getMonitorAgentStats(id); - return response.data || []; - }, - refetchInterval: 30000, - enabled: !!id, - }); - - if (isLoading) { - return ; - } - - if (!monitorDetail) { - return ; - } - - return ( -
-
- {/* 头部区域 */} - navigate('/monitors')} - /> - - {/* 主内容区 */} -
- {/* 响应时间趋势图表 */} - - - {/* 各探针详细数据 */} - -
-
-
- ); -}; - -export default MonitorDetail; diff --git a/web/src/portal/pages/MonitorList.tsx b/web/src/portal/pages/MonitorList.tsx deleted file mode 100644 index 9c06add..0000000 --- a/web/src/portal/pages/MonitorList.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import {useEffect, useMemo, useState} from 'react'; -import {Link} from 'react-router-dom'; -import {useQuery} from '@tanstack/react-query'; -import {AlertTriangle, BarChart3, CheckCircle2, Globe, Loader2, Maximize2, Search, Shield, Zap} from 'lucide-react'; -import {getPublicMonitors} from '@/api/monitor.ts'; -import type {PublicMonitor} from '@/types'; -import {cn} from '@/lib/utils.ts'; -import StatBlock from "@portal/components/StatBlock.tsx"; -import MonitorCard, {type DisplayMode} from "@portal/components/monitor/MonitorCard.tsx"; - -const LoadingSpinner = () => ( -
-
- - 加载监控数据中... -
-
-); - -const EmptyState = () => ( -
- -

暂无监控数据

-

请先在管理后台添加监控任务

-
-); - - -interface Stats { - total: number; - online: number; - issues: number; - avgLatency: number; -} - -const MonitorList = () => { - const [searchKeyword, setSearchKeyword] = useState(''); - const [displayMode, setDisplayMode] = useState('max'); - - const {data: monitors = [], isLoading} = useQuery({ - queryKey: ['publicMonitors'], - queryFn: async () => { - const response = await getPublicMonitors(); - return response.data || []; - }, - refetchInterval: 30000, - }); - - let [stats, setStats] = useState(); - - // 过滤和搜索 - const filteredMonitors = useMemo(() => { - let result = [...monitors]; - - // 搜索过滤 - if (searchKeyword.trim()) { - const keyword = searchKeyword.toLowerCase(); - result = result.filter(m => - m.name.toLowerCase().includes(keyword) || - m.target.toLowerCase().includes(keyword) - ); - } - - return result.sort((a, b) => Number(a.status !== 'up') - Number(b.status !== 'up')); - }, [monitors, searchKeyword]); - - // 统计信息 - const calculateStats = (monitors: PublicMonitor[]) => { - const total = monitors.length; - const online = monitors.filter(m => m.status === 'up').length; - const issues = total - online; - const avgLatency = total > 0 - ? Math.round(monitors.reduce((acc, curr) => acc + curr.responseTime, 0) / total) - : 0; - return {total, online, issues, avgLatency}; - } - - useEffect(() => { - let stats = calculateStats(monitors); - setStats(stats); - }, [monitors]); - - if (isLoading) { - return ( -
- -
- ); - } - - return ( -
- {/* 统计卡片 */} -
- - - 0} - /> - -
- - {/* 过滤和搜索 */} -
-
- {/* 显示模式切换 */} -
- 卡片指标: - - -
-
- - {/* 搜索框 */} -
-
-
- - setSearchKeyword(e.target.value)} - className="w-full bg-transparent border-none text-xs text-gray-800 dark:text-cyan-100 p-2.5 focus:ring-0 placeholder-gray-400 dark:placeholder-cyan-600 font-mono focus:outline-none" - /> -
-
-
- - {/* 监控卡片列表 */} - {filteredMonitors.length === 0 ? ( - - ) : ( -
- {filteredMonitors.map(monitor => ( - - - - ))} -
- )} -
- ); -}; - -export default MonitorList; diff --git a/web/src/portal/pages/ServerDetail.tsx b/web/src/portal/pages/ServerDetail.tsx deleted file mode 100644 index d11d91a..0000000 --- a/web/src/portal/pages/ServerDetail.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import {useState} from 'react'; -import {useNavigate, useParams} from 'react-router-dom'; -import {Card} from '@portal/components/Card.tsx'; -import {EmptyState} from '@portal/components/EmptyState.tsx'; -import {LoadingSpinner} from '@portal/components/LoadingSpinner.tsx'; -import {TimeRangeSelector} from '@portal/components/TimeRangeSelector.tsx'; -import {GpuMonitorSection} from '@portal/components/server/GpuMonitorSection.tsx'; -import {NetworkAddressSection} from '@portal/components/server/NetworkAddressSection.tsx'; -import {NetworkConnectionSection} from '@portal/components/server/NetworkConnectionSection.tsx'; -import {ServerHero} from '@portal/components/server/ServerHero.tsx'; -import {SystemInfoSection} from '@portal/components/server/SystemInfoSection.tsx'; -import {TemperatureMonitorSection} from '@portal/components/server/TemperatureMonitorSection.tsx'; -import {CpuChart} from '@portal/components/server/CpuChart.tsx'; -import {DiskIOChart} from '@portal/components/server/DiskIOChart.tsx'; -import {GpuChart} from '@portal/components/server/GpuChart.tsx'; -import {MemoryChart} from '@portal/components/server/MemoryChart.tsx'; -import {MonitorChart} from '@portal/components/server/MonitorChart.tsx'; -import {NetworkChart} from '@portal/components/server/NetworkChart.tsx'; -import {NetworkConnectionChart} from '@portal/components/server/NetworkConnectionChart.tsx'; -import {TemperatureChart} from '@portal/components/server/TemperatureChart.tsx'; -import {useAgentQuery, useLatestMetricsQuery} from '@portal/hooks/server.ts'; -import {LIVE_RANGE, SERVER_TIME_RANGE_OPTIONS} from '@portal/constants/time.ts'; - -/** - * 服务器详情页面 - * 显示服务器的详细信息、实时指标和历史趋势图表 - */ -const ServerDetail = () => { - const {id} = useParams<{ id: string }>(); - const navigate = useNavigate(); - const [timeRange, setTimeRange] = useState(LIVE_RANGE); - const [customRange, setCustomRange] = useState<{ start: number; end: number } | null>(null); - - const handleCustomRangeApply = (range: { start: number; end: number }) => { - setCustomRange(range); - }; - - const isLive = timeRange === LIVE_RANGE; - const customStart = timeRange === 'custom' ? customRange?.start : undefined; - const customEnd = timeRange === 'custom' ? customRange?.end : undefined; - - // 查询基础数据(用于页面头部和系统信息) - // 实时模式 1s 拉取最新指标,其余 5s - const {data: agentResponse, isLoading} = useAgentQuery(id); - const {data: latestMetricsResponse} = useLatestMetricsQuery(id, isLive ? 1000 : 5000); - - const agent = agentResponse?.data; - const latestMetrics = latestMetricsResponse?.data || null; - const formatLoad = (value?: number) => ( - typeof value === 'number' && Number.isFinite(value) ? value.toFixed(2) : '-' - ); - - const deviceIpInterfaces = (latestMetrics?.networkInterfaces || []) - .map((netInterface) => ({ - name: netInterface.interface, - addrs: Array.from(new Set((netInterface.addrs || []).map((addr) => addr.trim()).filter(Boolean))), - })) - .filter((netInterface) => netInterface.addrs.length > 0); - - if (isLoading) { - return ; - } - - if (!agent) { - return ; - } - - return ( -
-
- {/* 头部区域 */} - navigate('/')} - /> - - {/* 主内容区 */} -
- {/* 网络地址信息 */} - {(agent.ipv4 || agent.ipv6 || deviceIpInterfaces?.length > 0) && ( - - )} - - {/* 系统信息 */} - - - {/* 历史趋势图表 */} - - -
- } - > -
- {/* 核心指标:大屏 2 列,小屏 1 列 */} -
- - -
- - {/* 网络相关:大屏 2 列,中屏 1 列 */} -
- - -
- - {/* 进阶指标:单列全宽 */} -
- -
- - {/* 硬件指标:条件渲染,单列全宽 */} -
- - -
- - {/* 监控指标:单列全宽 */} -
- -
-
- - - {/* 网络连接统计 */} - - - {/* GPU 监控 */} - - - {/* 温度监控 */} - - -
-
- ); -}; - -export default ServerDetail; diff --git a/web/src/portal/utils/server.ts b/web/src/portal/utils/server.ts deleted file mode 100644 index 6944c91..0000000 --- a/web/src/portal/utils/server.ts +++ /dev/null @@ -1,4 +0,0 @@ - -export const isExpired = (expireTime?: number) => { - return expireTime && expireTime > 0 && expireTime - Date.now() < 30 * 24 * 60 * 60 * 1000 -} \ No newline at end of file diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx deleted file mode 100644 index 4c28900..0000000 --- a/web/src/router/index.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import {createBrowserRouter, Navigate} from 'react-router-dom'; -import {type ComponentType, lazy, type LazyExoticComponent, Suspense} from 'react'; -import PrivateRoute from '@admin/components/PrivateRoute'; - -const LoginPage = lazy(() => import('@admin/pages/Login')); -const GitHubCallbackPage = lazy(() => import('@admin/pages/Login/GitHubCallback')); -const OIDCCallbackPage = lazy(() => import('@admin/pages/Login/OIDCCallback')); -const PublicLayout = lazy(() => import('@portal/pages/PublicLayout')); -const AdminLayout = lazy(() => import('@admin/pages/AdminLayout')); -const AgentListPage = lazy(() => import('@admin/pages/Agents/AgentList')); -const AgentDetailPage = lazy(() => import('@admin/pages/Agents/AgentDetail')); -const AgentInstallOneClickPage = lazy(() => import('@admin/pages/Agents/AgentInstallOneClick')); -const AgentInstallManualPage = lazy(() => import('@admin/pages/Agents/AgentInstallManual')); -const ApiKeyListPage = lazy(() => import('@admin/pages/ApiKeys/ApiKeyList')); -const ManageApiKeyListPage = lazy(() => import('@admin/pages/ManageApiKeys/ManageApiKeyList')); -const SettingsPage = lazy(() => import('@admin/pages/Settings')); -const ServerListPage = lazy(() => import('@portal/pages/ServerList.tsx')); -const ServerDetailPage = lazy(() => import('@portal/pages/ServerDetail.tsx')); -const PublicMonitorListPage = lazy(() => import('@portal/pages/MonitorList.tsx')); -const PublicMonitorDetailPage = lazy(() => import('@portal/pages/MonitorDetail.tsx')); -const MonitorListPage = lazy(() => import('@admin/pages/Monitors/MonitorList')); -const DDNSPage = lazy(() => import('@admin/pages/DDNS')); -const AlertRecordListPage = lazy(() => import('@admin/pages/AlertRecords')); - -const LoadingFallback = () => ( -
- 页面加载中... -
-); - -const lazyLoad = (Component: LazyExoticComponent>) => ( - }> - - -); - -const router = createBrowserRouter([ - // 登录页面 - { - path: '/login', - element: lazyLoad(LoginPage), - }, - { - path: '/github/callback', - element: lazyLoad(GitHubCallbackPage), - }, - { - path: '/oidc/callback', - element: lazyLoad(OIDCCallbackPage), - }, - // 公开页面 - 不需要登录 - { - element: , - children: [ - { - path: '/', - element: lazyLoad(ServerListPage), - }, - { - path: '/servers/:id', - element: lazyLoad(ServerDetailPage), - }, - { - path: '/monitors', - element: lazyLoad(PublicMonitorListPage), - }, - { - path: '/monitors/:id', - element: lazyLoad(PublicMonitorDetailPage), - }, - ], - }, - // 管理员页面 - 需要登录 - { - path: '/admin', - element: ( - - - - ), - children: [ - { - index: true, - element: , - }, - { - path: 'agents', - element: lazyLoad(AgentListPage), - }, - { - path: 'agents/:id', - element: lazyLoad(AgentDetailPage), - }, - { - path: 'agents-install', - element: , - }, - { - path: 'agents-install/one-click', - element: lazyLoad(AgentInstallOneClickPage), - }, - { - path: 'agents-install/manual', - element: lazyLoad(AgentInstallManualPage), - }, - { - path: 'api-keys', - element: lazyLoad(ApiKeyListPage), - }, - { - path: 'manage-api-keys', - element: lazyLoad(ManageApiKeyListPage), - }, - { - path: 'monitors', - element: lazyLoad(MonitorListPage), - }, - { - path: 'ddns', - element: lazyLoad(DDNSPage), - }, - { - path: 'alert-records', - element: lazyLoad(AlertRecordListPage), - }, - { - path: 'settings', - element: lazyLoad(SettingsPage), - }, - ], - }, -]); - -export default router; diff --git a/web/vite.config.ts b/web/vite.config.ts deleted file mode 100644 index ba970ff..0000000 --- a/web/vite.config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {defineConfig} from 'vite' -import react from '@vitejs/plugin-react' -import tailwindcss from '@tailwindcss/vite' -import path from "path"; - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [ - react(), - tailwindcss(), - ], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - "@portal": path.resolve(__dirname, "./src/portal"), - "@admin": path.resolve(__dirname, "./src/admin"), - }, - }, - server: { - proxy: { - '/api/': { - target: 'http://localhost:8080/', - changeOrigin: true, - }, - }, - }, -}) diff --git a/web/yarn.lock b/web/yarn.lock deleted file mode 100644 index b5dceea..0000000 --- a/web/yarn.lock +++ /dev/null @@ -1,2733 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ant-design/colors@^8.0.1": - version "8.0.1" - resolved "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz" - integrity sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ== - dependencies: - "@ant-design/fast-color" "^3.0.0" - -"@ant-design/cssinjs-utils@^2.1.2": - version "2.1.2" - resolved "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz" - integrity sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA== - dependencies: - "@ant-design/cssinjs" "^2.1.2" - "@babel/runtime" "^7.23.2" - "@rc-component/util" "^1.4.0" - -"@ant-design/cssinjs@^2.1.2": - version "2.1.2" - resolved "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz" - integrity sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ== - dependencies: - "@babel/runtime" "^7.11.1" - "@emotion/hash" "^0.8.0" - "@emotion/unitless" "^0.7.5" - "@rc-component/util" "^1.4.0" - clsx "^2.1.1" - csstype "^3.1.3" - stylis "^4.3.4" - -"@ant-design/fast-color@^3.0.0", "@ant-design/fast-color@^3.0.1": - version "3.0.1" - resolved "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz" - integrity sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw== - -"@ant-design/icons-svg@^4.5.0": - version "4.5.0" - resolved "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.5.0.tgz" - integrity sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA== - -"@ant-design/icons@^6.3.1": - version "6.3.2" - resolved "https://registry.npmjs.org/@ant-design/icons/-/icons-6.3.2.tgz" - integrity sha512-B6O5a5XJ4wjtNOfZejXYwHW5zvKV5gYkjGf11dHGLEbKn0ABDGndo41+gfIiXyTFhvESj4XTotuud33mUFid0g== - dependencies: - "@ant-design/colors" "^8.0.1" - "@ant-design/icons-svg" "^4.5.0" - "@rc-component/util" "^1.11.0" - clsx "^2.1.1" - -"@ant-design/react-slick@~2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz" - integrity sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg== - dependencies: - "@babel/runtime" "^7.28.4" - clsx "^2.1.1" - json2mq "^0.2.0" - throttle-debounce "^5.0.0" - -"@babel/code-frame@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz" - integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw== - dependencies: - "@babel/helper-validator-identifier" "^7.29.7" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz" - integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg== - -"@babel/core@^7.0.0", "@babel/core@^7.24.4": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz" - integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA== - dependencies: - "@babel/code-frame" "^7.29.7" - "@babel/generator" "^7.29.7" - "@babel/helper-compilation-targets" "^7.29.7" - "@babel/helper-module-transforms" "^7.29.7" - "@babel/helpers" "^7.29.7" - "@babel/parser" "^7.29.7" - "@babel/template" "^7.29.7" - "@babel/traverse" "^7.29.7" - "@babel/types" "^7.29.7" - "@jridgewell/remapping" "^2.3.5" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz" - integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ== - dependencies: - "@babel/parser" "^7.29.7" - "@babel/types" "^7.29.7" - "@jridgewell/gen-mapping" "^0.3.12" - "@jridgewell/trace-mapping" "^0.3.28" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz" - integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g== - dependencies: - "@babel/compat-data" "^7.29.7" - "@babel/helper-validator-option" "^7.29.7" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-globals@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz" - integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA== - -"@babel/helper-module-imports@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz" - integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g== - dependencies: - "@babel/traverse" "^7.29.7" - "@babel/types" "^7.29.7" - -"@babel/helper-module-transforms@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz" - integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg== - dependencies: - "@babel/helper-module-imports" "^7.29.7" - "@babel/helper-validator-identifier" "^7.29.7" - "@babel/traverse" "^7.29.7" - -"@babel/helper-string-parser@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz" - integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw== - -"@babel/helper-validator-identifier@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz" - integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== - -"@babel/helper-validator-option@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz" - integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw== - -"@babel/helpers@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz" - integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg== - dependencies: - "@babel/template" "^7.29.7" - "@babel/types" "^7.29.7" - -"@babel/parser@^7.24.4", "@babel/parser@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz" - integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg== - dependencies: - "@babel/types" "^7.29.7" - -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1", "@babel/runtime@^7.18.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.24.4", "@babel/runtime@^7.24.7", "@babel/runtime@^7.28.4", "@babel/runtime@^7.29.2": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz" - integrity sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw== - -"@babel/runtime@^8.0.0": - version "8.0.0" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.0.tgz" - integrity sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw== - -"@babel/template@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz" - integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg== - dependencies: - "@babel/code-frame" "^7.29.7" - "@babel/parser" "^7.29.7" - "@babel/types" "^7.29.7" - -"@babel/traverse@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz" - integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw== - dependencies: - "@babel/code-frame" "^7.29.7" - "@babel/generator" "^7.29.7" - "@babel/helper-globals" "^7.29.7" - "@babel/parser" "^7.29.7" - "@babel/template" "^7.29.7" - "@babel/types" "^7.29.7" - debug "^4.3.1" - -"@babel/types@^7.29.7": - version "7.29.7" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz" - integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA== - dependencies: - "@babel/helper-string-parser" "^7.29.7" - "@babel/helper-validator-identifier" "^7.29.7" - -"@emotion/hash@^0.8.0": - version "0.8.0" - resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz" - integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== - -"@emotion/unitless@^0.7.5": - version "0.7.5" - resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz" - integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== - -"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": - version "4.9.1" - resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz" - integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.12.2": - version "4.12.2" - resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz" - integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== - -"@eslint/config-array@^0.23.5": - version "0.23.5" - resolved "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz" - integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA== - dependencies: - "@eslint/object-schema" "^3.0.5" - debug "^4.3.1" - minimatch "^10.2.4" - -"@eslint/config-helpers@^0.6.0": - version "0.6.0" - resolved "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz" - integrity sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA== - dependencies: - "@eslint/core" "^1.2.1" - -"@eslint/core@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz" - integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/js@^10.0.1": - version "10.0.1" - resolved "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz" - integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA== - -"@eslint/object-schema@^3.0.5": - version "3.0.5" - resolved "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz" - integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw== - -"@eslint/plugin-kit@^0.7.2": - version "0.7.2" - resolved "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz" - integrity sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A== - dependencies: - "@eslint/core" "^1.2.1" - levn "^0.4.1" - -"@floating-ui/core@^1.8.0": - version "1.8.0" - resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz" - integrity sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ== - dependencies: - "@floating-ui/utils" "^0.2.12" - -"@floating-ui/dom@^1.8.0": - version "1.8.0" - resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz" - integrity sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg== - dependencies: - "@floating-ui/core" "^1.8.0" - "@floating-ui/utils" "^0.2.12" - -"@floating-ui/react-dom@^2.0.0": - version "2.1.9" - resolved "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz" - integrity sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg== - dependencies: - "@floating-ui/dom" "^1.8.0" - -"@floating-ui/utils@^0.2.12": - version "0.2.12" - resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz" - integrity sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww== - -"@humanfs/core@^0.19.2": - version "0.19.2" - resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz" - integrity sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA== - dependencies: - "@humanfs/types" "^0.15.0" - -"@humanfs/node@^0.16.6": - version "0.16.8" - resolved "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz" - integrity sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ== - dependencies: - "@humanfs/core" "^0.19.2" - "@humanfs/types" "^0.15.0" - "@humanwhocodes/retry" "^0.4.0" - -"@humanfs/types@^0.15.0": - version "0.15.0" - resolved "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz" - integrity sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q== - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": - version "0.4.3" - resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz" - integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== - -"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": - version "0.3.13" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" - integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/remapping@^2.3.5": - version "2.3.5" - resolved "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz" - integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": - version "1.5.5" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.31" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" - integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@oxc-project/types@=0.139.0": - version "0.139.0" - resolved "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz" - integrity sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw== - -"@radix-ui/number@1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz" - integrity sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig== - -"@radix-ui/primitive@1.1.5": - version "1.1.5" - resolved "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz" - integrity sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg== - -"@radix-ui/react-arrow@1.1.11": - version "1.1.11" - resolved "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.11.tgz" - integrity sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A== - dependencies: - "@radix-ui/react-primitive" "2.1.7" - -"@radix-ui/react-collection@1.1.12": - version "1.1.12" - resolved "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz" - integrity sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q== - dependencies: - "@radix-ui/react-compose-refs" "1.1.3" - "@radix-ui/react-context" "1.2.0" - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-slot" "1.3.0" - -"@radix-ui/react-compose-refs@1.1.3": - version "1.1.3" - resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz" - integrity sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA== - -"@radix-ui/react-context@1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz" - integrity sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg== - -"@radix-ui/react-direction@1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz" - integrity sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA== - -"@radix-ui/react-dismissable-layer@1.1.15": - version "1.1.15" - resolved "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz" - integrity sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA== - dependencies: - "@radix-ui/primitive" "1.1.5" - "@radix-ui/react-compose-refs" "1.1.3" - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-use-callback-ref" "1.1.2" - "@radix-ui/react-use-effect-event" "0.0.3" - -"@radix-ui/react-dropdown-menu@^2.1.20": - version "2.1.20" - resolved "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.20.tgz" - integrity sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw== - dependencies: - "@radix-ui/primitive" "1.1.5" - "@radix-ui/react-compose-refs" "1.1.3" - "@radix-ui/react-context" "1.2.0" - "@radix-ui/react-id" "1.1.2" - "@radix-ui/react-menu" "2.1.20" - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-use-controllable-state" "1.2.3" - -"@radix-ui/react-focus-guards@1.1.4": - version "1.1.4" - resolved "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz" - integrity sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q== - -"@radix-ui/react-focus-scope@1.1.12": - version "1.1.12" - resolved "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz" - integrity sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A== - dependencies: - "@radix-ui/react-compose-refs" "1.1.3" - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-use-callback-ref" "1.1.2" - -"@radix-ui/react-id@1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz" - integrity sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA== - dependencies: - "@radix-ui/react-use-layout-effect" "1.1.2" - -"@radix-ui/react-menu@2.1.20": - version "2.1.20" - resolved "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.20.tgz" - integrity sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w== - dependencies: - "@radix-ui/primitive" "1.1.5" - "@radix-ui/react-collection" "1.1.12" - "@radix-ui/react-compose-refs" "1.1.3" - "@radix-ui/react-context" "1.2.0" - "@radix-ui/react-direction" "1.1.2" - "@radix-ui/react-dismissable-layer" "1.1.15" - "@radix-ui/react-focus-guards" "1.1.4" - "@radix-ui/react-focus-scope" "1.1.12" - "@radix-ui/react-id" "1.1.2" - "@radix-ui/react-popper" "1.3.3" - "@radix-ui/react-portal" "1.1.13" - "@radix-ui/react-presence" "1.1.7" - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-roving-focus" "1.1.15" - "@radix-ui/react-slot" "1.3.0" - "@radix-ui/react-use-callback-ref" "1.1.2" - aria-hidden "^1.2.4" - react-remove-scroll "^2.7.2" - -"@radix-ui/react-popper@1.3.3": - version "1.3.3" - resolved "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.3.tgz" - integrity sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g== - dependencies: - "@floating-ui/react-dom" "^2.0.0" - "@radix-ui/react-arrow" "1.1.11" - "@radix-ui/react-compose-refs" "1.1.3" - "@radix-ui/react-context" "1.2.0" - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-use-callback-ref" "1.1.2" - "@radix-ui/react-use-layout-effect" "1.1.2" - "@radix-ui/react-use-rect" "1.1.2" - "@radix-ui/react-use-size" "1.1.2" - "@radix-ui/rect" "1.1.2" - -"@radix-ui/react-portal@1.1.13": - version "1.1.13" - resolved "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz" - integrity sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA== - dependencies: - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-use-layout-effect" "1.1.2" - -"@radix-ui/react-presence@1.1.7": - version "1.1.7" - resolved "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz" - integrity sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA== - dependencies: - "@radix-ui/react-use-layout-effect" "1.1.2" - -"@radix-ui/react-primitive@2.1.7": - version "2.1.7" - resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz" - integrity sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ== - dependencies: - "@radix-ui/react-slot" "1.3.0" - -"@radix-ui/react-roving-focus@1.1.15": - version "1.1.15" - resolved "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz" - integrity sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg== - dependencies: - "@radix-ui/primitive" "1.1.5" - "@radix-ui/react-collection" "1.1.12" - "@radix-ui/react-compose-refs" "1.1.3" - "@radix-ui/react-context" "1.2.0" - "@radix-ui/react-direction" "1.1.2" - "@radix-ui/react-id" "1.1.2" - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-use-callback-ref" "1.1.2" - "@radix-ui/react-use-controllable-state" "1.2.3" - "@radix-ui/react-use-is-hydrated" "0.1.1" - "@radix-ui/react-use-layout-effect" "1.1.2" - -"@radix-ui/react-scroll-area@^1.2.14": - version "1.2.14" - resolved "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.14.tgz" - integrity sha512-bBODCWZK7JTbQLHs0uIP4f73wIWatakK4OS33UzkR1x897wu0PuO658a3f+6P2GEGyDzGYMuHRatMVoAk9WZTw== - dependencies: - "@radix-ui/number" "1.1.2" - "@radix-ui/primitive" "1.1.5" - "@radix-ui/react-compose-refs" "1.1.3" - "@radix-ui/react-context" "1.2.0" - "@radix-ui/react-direction" "1.1.2" - "@radix-ui/react-presence" "1.1.7" - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-use-callback-ref" "1.1.2" - "@radix-ui/react-use-layout-effect" "1.1.2" - -"@radix-ui/react-slot@^1.3.0", "@radix-ui/react-slot@1.3.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz" - integrity sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA== - dependencies: - "@radix-ui/react-compose-refs" "1.1.3" - -"@radix-ui/react-tooltip@^1.2.12": - version "1.2.12" - resolved "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.12.tgz" - integrity sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA== - dependencies: - "@radix-ui/primitive" "1.1.5" - "@radix-ui/react-compose-refs" "1.1.3" - "@radix-ui/react-context" "1.2.0" - "@radix-ui/react-dismissable-layer" "1.1.15" - "@radix-ui/react-id" "1.1.2" - "@radix-ui/react-popper" "1.3.3" - "@radix-ui/react-portal" "1.1.13" - "@radix-ui/react-presence" "1.1.7" - "@radix-ui/react-primitive" "2.1.7" - "@radix-ui/react-slot" "1.3.0" - "@radix-ui/react-use-controllable-state" "1.2.3" - "@radix-ui/react-visually-hidden" "1.2.7" - -"@radix-ui/react-use-callback-ref@1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz" - integrity sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw== - -"@radix-ui/react-use-controllable-state@1.2.3": - version "1.2.3" - resolved "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz" - integrity sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA== - dependencies: - "@radix-ui/react-use-effect-event" "0.0.3" - "@radix-ui/react-use-layout-effect" "1.1.2" - -"@radix-ui/react-use-effect-event@0.0.3": - version "0.0.3" - resolved "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz" - integrity sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA== - dependencies: - "@radix-ui/react-use-layout-effect" "1.1.2" - -"@radix-ui/react-use-is-hydrated@0.1.1": - version "0.1.1" - resolved "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz" - integrity sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A== - -"@radix-ui/react-use-layout-effect@1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz" - integrity sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA== - -"@radix-ui/react-use-rect@1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz" - integrity sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw== - dependencies: - "@radix-ui/rect" "1.1.2" - -"@radix-ui/react-use-size@1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz" - integrity sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w== - dependencies: - "@radix-ui/react-use-layout-effect" "1.1.2" - -"@radix-ui/react-visually-hidden@1.2.7": - version "1.2.7" - resolved "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.7.tgz" - integrity sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw== - dependencies: - "@radix-ui/react-primitive" "2.1.7" - -"@radix-ui/rect@1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz" - integrity sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA== - -"@rc-component/async-validator@^6.0.0": - version "6.0.0" - resolved "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-6.0.0.tgz" - integrity sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA== - dependencies: - "@babel/runtime" "^7.24.4" - -"@rc-component/cascader@~1.17.0": - version "1.17.0" - resolved "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.17.0.tgz" - integrity sha512-3cVNG0zrQF1PoXq262L3wGCU+/YLEC1mGSVHDl577dQmA0ZKkXFbY6nwyXo+beCcM7buo49t24jkr+QZdL7O8w== - dependencies: - "@rc-component/select" "~1.8.0" - "@rc-component/tree" "~1.3.2" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/checkbox@~2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz" - integrity sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ== - dependencies: - "@rc-component/util" "^1.3.0" - clsx "^2.1.1" - -"@rc-component/collapse@~1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz" - integrity sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw== - dependencies: - "@babel/runtime" "^7.10.1" - "@rc-component/motion" "^1.1.4" - "@rc-component/util" "^1.3.0" - clsx "^2.1.1" - -"@rc-component/color-picker@~3.1.1": - version "3.1.1" - resolved "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.1.1.tgz" - integrity sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg== - dependencies: - "@ant-design/fast-color" "^3.0.1" - "@rc-component/util" "^1.3.0" - clsx "^2.1.1" - -"@rc-component/context@^2.0.1": - version "2.0.2" - resolved "https://registry.npmjs.org/@rc-component/context/-/context-2.0.2.tgz" - integrity sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA== - dependencies: - "@rc-component/util" "^1.11.0" - -"@rc-component/dialog@~1.10.0": - version "1.10.0" - resolved "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.10.0.tgz" - integrity sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ== - dependencies: - "@rc-component/motion" "^1.3.3" - "@rc-component/portal" "^2.1.0" - "@rc-component/util" "^1.9.0" - clsx "^2.1.1" - -"@rc-component/drawer@~1.4.2": - version "1.4.2" - resolved "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.4.2.tgz" - integrity sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q== - dependencies: - "@rc-component/motion" "^1.1.4" - "@rc-component/portal" "^2.1.3" - "@rc-component/util" "^1.9.0" - clsx "^2.1.1" - -"@rc-component/dropdown@~1.0.0", "@rc-component/dropdown@~1.0.2": - version "1.0.3" - resolved "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.3.tgz" - integrity sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g== - dependencies: - "@rc-component/trigger" "^3.0.0" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/form@~1.8.5": - version "1.8.5" - resolved "https://registry.npmjs.org/@rc-component/form/-/form-1.8.5.tgz" - integrity sha512-d24EYtvUOBhxEtSd/EqIu9DaMuqrWF2IRIvAFCTM6NQ/GJIYNr8DvEpUSUlv2uPxEJ0ZPwYQ+wwlGIAaiHvdrw== - dependencies: - "@rc-component/async-validator" "^6.0.0" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/image@~1.9.0": - version "1.9.0" - resolved "https://registry.npmjs.org/@rc-component/image/-/image-1.9.0.tgz" - integrity sha512-khF7w7xkBH5B1bsBcI1FSUZdkyd1aqpl2eYyILCqCzzQH3XdfehGUaZTnptyaJJfs09/R5hv9jXWyazOMFIClQ== - dependencies: - "@rc-component/motion" "^1.0.0" - "@rc-component/portal" "^2.1.2" - "@rc-component/util" "^1.10.1" - clsx "^2.1.1" - -"@rc-component/input-number@~1.6.2": - version "1.6.2" - resolved "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz" - integrity sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w== - dependencies: - "@rc-component/mini-decimal" "^1.0.1" - "@rc-component/util" "^1.4.0" - clsx "^2.1.1" - -"@rc-component/input@~1.3.0", "@rc-component/input@~1.3.1": - version "1.3.1" - resolved "https://registry.npmjs.org/@rc-component/input/-/input-1.3.1.tgz" - integrity sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig== - dependencies: - "@rc-component/resize-observer" "^1.1.1" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/mentions@~1.10.0": - version "1.10.0" - resolved "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.10.0.tgz" - integrity sha512-CI1njYUVY0NjHtLhNoVmXlJyy568Sfep9Wsak6vmGjtT6uazx98djGYlCXz2xkHhEm73g91Y3MTvzUyE5avI7w== - dependencies: - "@rc-component/input" "~1.3.0" - "@rc-component/menu" "~1.4.0" - "@rc-component/trigger" "^3.0.0" - "@rc-component/util" "^1.3.0" - clsx "^2.1.1" - -"@rc-component/menu@~1.4.0", "@rc-component/menu@~1.4.1": - version "1.4.1" - resolved "https://registry.npmjs.org/@rc-component/menu/-/menu-1.4.1.tgz" - integrity sha512-3GsVRoQ4cnF/AoIQ4P+Z1haBfgfBPQfLT1RJY3Nu4DzOnheTslfCiGSPj7bv/cLj5sW5pHqN25dDXGP3JELAlQ== - dependencies: - "@rc-component/motion" "^1.1.4" - "@rc-component/overflow" "^1.0.0" - "@rc-component/trigger" "^3.0.0" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/mini-decimal@^1.0.1": - version "1.1.4" - resolved "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.4.tgz" - integrity sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w== - dependencies: - "@babel/runtime" "^7.18.0" - -"@rc-component/motion@^1.0.0", "@rc-component/motion@^1.1.3", "@rc-component/motion@^1.1.4", "@rc-component/motion@^1.3.3": - version "1.3.3" - resolved "https://registry.npmjs.org/@rc-component/motion/-/motion-1.3.3.tgz" - integrity sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A== - dependencies: - "@rc-component/util" "^1.11.0" - clsx "^2.1.1" - -"@rc-component/mutate-observer@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz" - integrity sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w== - dependencies: - "@rc-component/util" "^1.2.0" - -"@rc-component/notification@~2.0.7": - version "2.0.7" - resolved "https://registry.npmjs.org/@rc-component/notification/-/notification-2.0.7.tgz" - integrity sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ== - dependencies: - "@rc-component/motion" "^1.1.4" - "@rc-component/util" "^1.11.0" - clsx "^2.1.1" - -"@rc-component/overflow@^1.0.0": - version "1.0.1" - resolved "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.1.tgz" - integrity sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA== - dependencies: - "@babel/runtime" "^7.11.1" - "@rc-component/resize-observer" "^1.0.1" - "@rc-component/util" "^1.4.0" - clsx "^2.1.1" - -"@rc-component/pagination@~1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.4.0.tgz" - integrity sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw== - dependencies: - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/picker@~1.11.0": - version "1.11.0" - resolved "https://registry.npmjs.org/@rc-component/picker/-/picker-1.11.0.tgz" - integrity sha512-6qXGKtoJvO8sUd17m5cyNEbEJub0zflCHnaZTBBmj63DPRZYc0WEHN8rp6hFSl+yMCJS/dJY5G+1fQ8bLCuD7A== - dependencies: - "@rc-component/overflow" "^1.0.0" - "@rc-component/resize-observer" "^1.0.0" - "@rc-component/trigger" "^3.6.15" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/portal@^2.1.0", "@rc-component/portal@^2.1.2", "@rc-component/portal@^2.1.3", "@rc-component/portal@^2.2.0", "@rc-component/portal@^2.2.1": - version "2.2.1" - resolved "https://registry.npmjs.org/@rc-component/portal/-/portal-2.2.1.tgz" - integrity sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA== - dependencies: - "@rc-component/util" "^1.11.0" - clsx "^2.1.1" - -"@rc-component/progress@~1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz" - integrity sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ== - dependencies: - "@rc-component/util" "^1.2.1" - clsx "^2.1.1" - -"@rc-component/qrcode@~2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-2.0.0.tgz" - integrity sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw== - dependencies: - "@babel/runtime" "^7.24.7" - -"@rc-component/rate@~1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz" - integrity sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw== - dependencies: - "@rc-component/util" "^1.3.0" - clsx "^2.1.1" - -"@rc-component/resize-observer@^1.0.0", "@rc-component/resize-observer@^1.0.1", "@rc-component/resize-observer@^1.1.1", "@rc-component/resize-observer@^1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz" - integrity sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q== - dependencies: - "@rc-component/util" "^1.2.0" - -"@rc-component/segmented@~1.3.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.3.0.tgz" - integrity sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg== - dependencies: - "@babel/runtime" "^7.11.1" - "@rc-component/motion" "^1.1.4" - "@rc-component/util" "^1.3.0" - clsx "^2.1.1" - -"@rc-component/select@~1.8.0", "@rc-component/select@~1.8.2": - version "1.8.2" - resolved "https://registry.npmjs.org/@rc-component/select/-/select-1.8.2.tgz" - integrity sha512-HQ9zuYqjfZTlcEMWlU1GAPBajd2OHIMVHyjZSGVTCVARwkfCgvXZMTEn0cduy3L+ejAKkaZluOQvxovZoaJaQw== - dependencies: - "@rc-component/overflow" "^1.0.0" - "@rc-component/trigger" "^3.0.0" - "@rc-component/util" "^1.11.1" - "@rc-component/virtual-list" "^1.2.0" - clsx "^2.1.1" - -"@rc-component/slider@~1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@rc-component/slider/-/slider-1.1.1.tgz" - integrity sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig== - dependencies: - "@rc-component/util" "^1.3.0" - clsx "^2.1.1" - -"@rc-component/steps@~1.2.2": - version "1.2.2" - resolved "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz" - integrity sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw== - dependencies: - "@rc-component/util" "^1.2.1" - clsx "^2.1.1" - -"@rc-component/switch@~1.0.3": - version "1.0.3" - resolved "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz" - integrity sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw== - dependencies: - "@rc-component/util" "^1.3.0" - clsx "^2.1.1" - -"@rc-component/table@~1.10.2": - version "1.10.4" - resolved "https://registry.npmjs.org/@rc-component/table/-/table-1.10.4.tgz" - integrity sha512-HwoTnrwc29zeoXkXGhWqzJh8FIibGUxi1jM4LtoSzmR9d5Vv5osUQpZxnXKBP8iOCvyD6BQzZm1nXJRcnrxpAg== - dependencies: - "@rc-component/context" "^2.0.1" - "@rc-component/resize-observer" "^1.0.0" - "@rc-component/util" "^1.11.1" - "@rc-component/virtual-list" "^1.0.1" - clsx "^2.1.1" - -"@rc-component/tabs@~1.11.0": - version "1.11.0" - resolved "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.11.0.tgz" - integrity sha512-hA/drZYOVa/MMIb4M2fWf3yaTyTG4qVuIABmghvEhyfw2nBob5VTH69lMCDjSVKmgODjO6nWlCV+gVn3xBrj5Q== - dependencies: - "@rc-component/dropdown" "~1.0.0" - "@rc-component/menu" "~1.4.0" - "@rc-component/motion" "^1.1.3" - "@rc-component/resize-observer" "^1.0.0" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/tooltip@~1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz" - integrity sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg== - dependencies: - "@rc-component/trigger" "^3.7.1" - "@rc-component/util" "^1.3.0" - clsx "^2.1.1" - -"@rc-component/tour@~2.4.0": - version "2.4.0" - resolved "https://registry.npmjs.org/@rc-component/tour/-/tour-2.4.0.tgz" - integrity sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg== - dependencies: - "@rc-component/portal" "^2.2.0" - "@rc-component/trigger" "^3.0.0" - "@rc-component/util" "^1.7.0" - clsx "^2.1.1" - -"@rc-component/tree-select@~1.11.0": - version "1.11.0" - resolved "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.11.0.tgz" - integrity sha512-EhS0X0wtUhBfK4S5TlpSY3MR9ndPMGgujtt1PJW3Ej+ToAlnS/6ohYURtCoXBYGqazUwHmgQGVUDsfpVwhWPkg== - dependencies: - "@rc-component/select" "~1.8.0" - "@rc-component/tree" "~1.3.2" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/tree@~1.3.2": - version "1.3.2" - resolved "https://registry.npmjs.org/@rc-component/tree/-/tree-1.3.2.tgz" - integrity sha512-bJFj46wEkpBPnWyTm18XmgAgNQ/4YvprxMOPPY2a6rmhGJYxLuNKEFiL5Qej4Qctu9wHJm8WW+v2SYskafE0kA== - dependencies: - "@rc-component/motion" "^1.0.0" - "@rc-component/util" "^1.11.1" - "@rc-component/virtual-list" "^1.2.0" - clsx "^2.1.1" - -"@rc-component/trigger@^3.0.0", "@rc-component/trigger@^3.6.15", "@rc-component/trigger@^3.7.1", "@rc-component/trigger@^3.9.1": - version "3.10.0" - resolved "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.10.0.tgz" - integrity sha512-uC3QSG7Ax3qLOE5Q2jLqJCJc4iBtJEHzNTPhqGvlRvRcU8x8CT5moIavRVe24YSQKCp2/D1GSq7y76SCSheuVA== - dependencies: - "@rc-component/motion" "^1.3.3" - "@rc-component/portal" "^2.2.1" - "@rc-component/resize-observer" "^1.1.2" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/upload@~1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.1.tgz" - integrity sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA== - dependencies: - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - -"@rc-component/util@^1.10.1", "@rc-component/util@^1.11.0", "@rc-component/util@^1.11.1", "@rc-component/util@^1.2.0", "@rc-component/util@^1.2.1", "@rc-component/util@^1.3.0", "@rc-component/util@^1.4.0", "@rc-component/util@^1.7.0", "@rc-component/util@^1.9.0": - version "1.11.1" - resolved "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz" - integrity sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg== - dependencies: - is-mobile "^5.0.0" - react-is "^18.2.0" - -"@rc-component/virtual-list@^1.0.1", "@rc-component/virtual-list@^1.2.0": - version "1.3.2" - resolved "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.3.2.tgz" - integrity sha512-/smuvWBFdP/Is9QuNDKynD0+T3XTXWFyNXXNKJ4sno8CE3bTOK8sfgYmQJtYwLUNX+lv0Ytd+PMshgpmdReq5g== - dependencies: - "@babel/runtime" "^8.0.0" - "@rc-component/resize-observer" "^1.0.1" - "@rc-component/util" "^1.4.0" - clsx "^2.1.1" - -"@reduxjs/toolkit@^1.9.0 || 2.x.x": - version "2.12.0" - resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz" - integrity sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw== - dependencies: - "@standard-schema/spec" "^1.0.0" - "@standard-schema/utils" "^0.3.0" - immer "^11.0.0" - redux "^5.0.1" - redux-thunk "^3.1.0" - reselect "^5.1.0" - -"@rolldown/binding-darwin-arm64@1.1.5": - version "1.1.5" - resolved "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz" - integrity sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw== - -"@rolldown/pluginutils@^1.0.0", "@rolldown/pluginutils@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz" - integrity sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw== - -"@standard-schema/spec@^1.0.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz" - integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== - -"@standard-schema/utils@^0.3.0": - version "0.3.0" - resolved "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz" - integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== - -"@tailwindcss/node@4.3.2": - version "4.3.2" - resolved "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz" - integrity sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg== - dependencies: - "@jridgewell/remapping" "^2.3.5" - enhanced-resolve "5.21.6" - jiti "^2.7.0" - lightningcss "1.32.0" - magic-string "^0.30.21" - source-map-js "^1.2.1" - tailwindcss "4.3.2" - -"@tailwindcss/oxide-darwin-arm64@4.3.2": - version "4.3.2" - resolved "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz" - integrity sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w== - -"@tailwindcss/oxide@4.3.2": - version "4.3.2" - resolved "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz" - integrity sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag== - optionalDependencies: - "@tailwindcss/oxide-android-arm64" "4.3.2" - "@tailwindcss/oxide-darwin-arm64" "4.3.2" - "@tailwindcss/oxide-darwin-x64" "4.3.2" - "@tailwindcss/oxide-freebsd-x64" "4.3.2" - "@tailwindcss/oxide-linux-arm-gnueabihf" "4.3.2" - "@tailwindcss/oxide-linux-arm64-gnu" "4.3.2" - "@tailwindcss/oxide-linux-arm64-musl" "4.3.2" - "@tailwindcss/oxide-linux-x64-gnu" "4.3.2" - "@tailwindcss/oxide-linux-x64-musl" "4.3.2" - "@tailwindcss/oxide-wasm32-wasi" "4.3.2" - "@tailwindcss/oxide-win32-arm64-msvc" "4.3.2" - "@tailwindcss/oxide-win32-x64-msvc" "4.3.2" - -"@tailwindcss/vite@^4.3.2": - version "4.3.2" - resolved "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz" - integrity sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA== - dependencies: - "@tailwindcss/node" "4.3.2" - "@tailwindcss/oxide" "4.3.2" - tailwindcss "4.3.2" - -"@tanstack/query-core@5.101.2": - version "5.101.2" - resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.2.tgz" - integrity sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw== - -"@tanstack/react-query@^5.101.2": - version "5.101.2" - resolved "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.2.tgz" - integrity sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg== - dependencies: - "@tanstack/query-core" "5.101.2" - -"@types/d3-array@^3.0.3": - version "3.2.2" - resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz" - integrity sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw== - -"@types/d3-color@*": - version "3.1.3" - resolved "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz" - integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== - -"@types/d3-ease@^3.0.0": - version "3.0.2" - resolved "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz" - integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== - -"@types/d3-interpolate@^3.0.1": - version "3.0.4" - resolved "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz" - integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== - dependencies: - "@types/d3-color" "*" - -"@types/d3-path@*": - version "3.1.1" - resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz" - integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== - -"@types/d3-scale@^4.0.2": - version "4.0.9" - resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz" - integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== - dependencies: - "@types/d3-time" "*" - -"@types/d3-shape@^3.1.0": - version "3.1.8" - resolved "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz" - integrity sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w== - dependencies: - "@types/d3-path" "*" - -"@types/d3-time@*", "@types/d3-time@^3.0.0": - version "3.0.4" - resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz" - integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== - -"@types/d3-timer@^3.0.0": - version "3.0.2" - resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz" - integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== - -"@types/esrecurse@^4.3.1": - version "4.3.1" - resolved "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz" - integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== - -"@types/estree@^1.0.6", "@types/estree@^1.0.8": - version "1.0.9" - resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz" - integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== - -"@types/js-cookie@^3.0.0": - version "3.0.6" - resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz" - integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ== - -"@types/json-schema@^7.0.15": - version "7.0.15" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/node@^20.19.0 || >=22.12.0", "@types/node@^26.1.1": - version "26.1.1" - resolved "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz" - integrity sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw== - dependencies: - undici-types "~8.3.0" - -"@types/qs@^6.15.1": - version "6.15.1" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz" - integrity sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw== - -"@types/react-dom@*", "@types/react-dom@^19.2.3": - version "19.2.3" - resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz" - integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== - -"@types/react@*", "@types/react@^18.2.25 || ^19", "@types/react@^19.2.0", "@types/react@^19.2.17": - version "19.2.17" - resolved "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz" - integrity sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw== - dependencies: - csstype "^3.2.2" - -"@types/use-sync-external-store@^0.0.6": - version "0.0.6" - resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz" - integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== - -"@types/validator@^13.15.10": - version "13.15.10" - resolved "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz" - integrity sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA== - -"@typescript-eslint/eslint-plugin@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz" - integrity sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ== - dependencies: - "@eslint-community/regexpp" "^4.12.2" - "@typescript-eslint/scope-manager" "8.63.0" - "@typescript-eslint/type-utils" "8.63.0" - "@typescript-eslint/utils" "8.63.0" - "@typescript-eslint/visitor-keys" "8.63.0" - ignore "^7.0.5" - natural-compare "^1.4.0" - ts-api-utils "^2.5.0" - -"@typescript-eslint/parser@^8.63.0", "@typescript-eslint/parser@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz" - integrity sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig== - dependencies: - "@typescript-eslint/scope-manager" "8.63.0" - "@typescript-eslint/types" "8.63.0" - "@typescript-eslint/typescript-estree" "8.63.0" - "@typescript-eslint/visitor-keys" "8.63.0" - debug "^4.4.3" - -"@typescript-eslint/project-service@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz" - integrity sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ== - dependencies: - "@typescript-eslint/tsconfig-utils" "^8.63.0" - "@typescript-eslint/types" "^8.63.0" - debug "^4.4.3" - -"@typescript-eslint/scope-manager@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz" - integrity sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A== - dependencies: - "@typescript-eslint/types" "8.63.0" - "@typescript-eslint/visitor-keys" "8.63.0" - -"@typescript-eslint/tsconfig-utils@^8.63.0", "@typescript-eslint/tsconfig-utils@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz" - integrity sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg== - -"@typescript-eslint/type-utils@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz" - integrity sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA== - dependencies: - "@typescript-eslint/types" "8.63.0" - "@typescript-eslint/typescript-estree" "8.63.0" - "@typescript-eslint/utils" "8.63.0" - debug "^4.4.3" - ts-api-utils "^2.5.0" - -"@typescript-eslint/types@^8.63.0", "@typescript-eslint/types@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz" - integrity sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q== - -"@typescript-eslint/typescript-estree@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz" - integrity sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q== - dependencies: - "@typescript-eslint/project-service" "8.63.0" - "@typescript-eslint/tsconfig-utils" "8.63.0" - "@typescript-eslint/types" "8.63.0" - "@typescript-eslint/visitor-keys" "8.63.0" - debug "^4.4.3" - minimatch "^10.2.2" - semver "^7.7.3" - tinyglobby "^0.2.15" - ts-api-utils "^2.5.0" - -"@typescript-eslint/utils@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz" - integrity sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA== - dependencies: - "@eslint-community/eslint-utils" "^4.9.1" - "@typescript-eslint/scope-manager" "8.63.0" - "@typescript-eslint/types" "8.63.0" - "@typescript-eslint/typescript-estree" "8.63.0" - -"@typescript-eslint/visitor-keys@8.63.0": - version "8.63.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz" - integrity sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw== - dependencies: - "@typescript-eslint/types" "8.63.0" - eslint-visitor-keys "^5.0.0" - -"@vitejs/plugin-react@^6.0.3": - version "6.0.3" - resolved "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz" - integrity sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg== - dependencies: - "@rolldown/pluginutils" "^1.0.1" - -"@xobotyi/scrollbar-width@^1.9.5": - version "1.9.5" - resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz" - integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.16.0: - version "8.17.0" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz" - integrity sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg== - -ajv@^6.14.0: - version "6.15.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz" - integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -antd@^6.5.0: - version "6.5.0" - resolved "https://registry.npmjs.org/antd/-/antd-6.5.0.tgz" - integrity sha512-9zbVc9UukfGuqCvIAov01nlpDQWfARNmZQyt21ZhqLX7ilXmi4cdkp12xA48WEmXRXwZvno8A03qQuGE9JG8fg== - dependencies: - "@ant-design/colors" "^8.0.1" - "@ant-design/cssinjs" "^2.1.2" - "@ant-design/cssinjs-utils" "^2.1.2" - "@ant-design/fast-color" "^3.0.1" - "@ant-design/icons" "^6.3.1" - "@ant-design/react-slick" "~2.0.0" - "@babel/runtime" "^7.29.2" - "@rc-component/cascader" "~1.17.0" - "@rc-component/checkbox" "~2.0.0" - "@rc-component/collapse" "~1.2.0" - "@rc-component/color-picker" "~3.1.1" - "@rc-component/dialog" "~1.10.0" - "@rc-component/drawer" "~1.4.2" - "@rc-component/dropdown" "~1.0.2" - "@rc-component/form" "~1.8.5" - "@rc-component/image" "~1.9.0" - "@rc-component/input" "~1.3.1" - "@rc-component/input-number" "~1.6.2" - "@rc-component/mentions" "~1.10.0" - "@rc-component/menu" "~1.4.1" - "@rc-component/motion" "^1.3.3" - "@rc-component/mutate-observer" "^2.0.1" - "@rc-component/notification" "~2.0.7" - "@rc-component/pagination" "~1.4.0" - "@rc-component/picker" "~1.11.0" - "@rc-component/progress" "~1.0.2" - "@rc-component/qrcode" "~2.0.0" - "@rc-component/rate" "~1.0.1" - "@rc-component/resize-observer" "^1.1.2" - "@rc-component/segmented" "~1.3.0" - "@rc-component/select" "~1.8.2" - "@rc-component/slider" "~1.1.1" - "@rc-component/steps" "~1.2.2" - "@rc-component/switch" "~1.0.3" - "@rc-component/table" "~1.10.2" - "@rc-component/tabs" "~1.11.0" - "@rc-component/tooltip" "~1.4.0" - "@rc-component/tour" "~2.4.0" - "@rc-component/tree" "~1.3.2" - "@rc-component/tree-select" "~1.11.0" - "@rc-component/trigger" "^3.9.1" - "@rc-component/upload" "~1.1.1" - "@rc-component/util" "^1.11.1" - clsx "^2.1.1" - dayjs "^1.11.11" - scroll-into-view-if-needed "^3.1.0" - throttle-debounce "^5.0.2" - -aria-hidden@^1.2.4: - version "1.2.6" - resolved "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz" - integrity sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA== - dependencies: - tslib "^2.0.0" - -balanced-match@^4.0.2: - version "4.0.4" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz" - integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== - -baseline-browser-mapping@^2.10.42, baseline-browser-mapping@^2.10.43: - version "2.10.43" - resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz" - integrity sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ== - -brace-expansion@^5.0.5: - version "5.0.7" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz" - integrity sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA== - dependencies: - balanced-match "^4.0.2" - -browserslist@^4.24.0, "browserslist@>= 4.21.0": - version "4.28.6" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz" - integrity sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw== - dependencies: - baseline-browser-mapping "^2.10.42" - caniuse-lite "^1.0.30001803" - electron-to-chromium "^1.5.389" - node-releases "^2.0.51" - update-browserslist-db "^1.2.3" - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bound@^1.0.2: - version "1.0.4" - resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -caniuse-lite@^1.0.30001803: - version "1.0.30001805" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz" - integrity sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA== - -class-variance-authority@^0.7.1: - version "0.7.1" - resolved "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz" - integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== - dependencies: - clsx "^2.1.1" - -clsx@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" - integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== - -compute-scroll-into-view@^3.0.2: - version "3.1.1" - resolved "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz" - integrity sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cookie@^1.0.1: - version "1.1.1" - resolved "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz" - integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== - -copy-to-clipboard@^3.3.1: - version "3.3.3" - resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz" - integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== - dependencies: - toggle-selection "^1.0.6" - -copy-to-clipboard@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-4.0.2.tgz" - integrity sha512-gklSft7IuhriZKHKpuoA1fpJSLPNgvUMWMo5BlnzAJm0zNKnznoSv23IjtNqclx8eKi6ZcdvFFzYEER/+U1LoQ== - -cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -css-in-js-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz" - integrity sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A== - dependencies: - hyphenate-style-name "^1.0.3" - -css-tree@^1.1.2: - version "1.1.3" - resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -csstype@^3.1.2, csstype@^3.1.3, csstype@^3.2.2: - version "3.2.3" - resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz" - integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== - -d3-array@^3.1.6, "d3-array@2 - 3", "d3-array@2.10.0 - 3": - version "3.2.4" - resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" - integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== - dependencies: - internmap "1 - 2" - -"d3-color@1 - 3": - version "3.1.0" - resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" - integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== - -d3-ease@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz" - integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== - -"d3-format@1 - 3": - version "3.1.2" - resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz" - integrity sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg== - -d3-interpolate@^3.0.1, "d3-interpolate@1.2.0 - 3": - version "3.0.1" - resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz" - integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== - dependencies: - d3-color "1 - 3" - -d3-path@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" - integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== - -d3-scale@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz" - integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== - 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" - -d3-shape@^3.1.0: - version "3.2.0" - resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" - integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== - dependencies: - d3-path "^3.1.0" - -"d3-time-format@2 - 4": - version "4.1.0" - resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" - integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== - dependencies: - d3-time "1 - 3" - -d3-time@^3.0.0, "d3-time@1 - 3", "d3-time@2.1.1 - 3": - version "3.1.0" - resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz" - integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== - dependencies: - d3-array "2 - 3" - -d3-timer@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz" - integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== - -dayjs@^1.11.11, dayjs@^1.11.21, "dayjs@>= 1.x": - version "1.11.21" - resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz" - integrity sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA== - -debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.4.3: - version "4.4.3" - resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -decimal.js-light@^2.5.1: - version "2.5.1" - resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz" - integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -detect-libc@^2.0.3: - version "2.1.2" - resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz" - integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== - -detect-node-es@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz" - integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -electron-to-chromium@^1.5.389: - version "1.5.389" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz" - integrity sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg== - -enhanced-resolve@5.21.6: - version "5.21.6" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz" - integrity sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.3.3" - -error-stack-parser@^2.0.6: - version "2.1.4" - resolved "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz" - integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== - dependencies: - stackframe "^1.3.4" - -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.2" - resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz" - integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw== - dependencies: - es-errors "^1.3.0" - -es-toolkit@^1.39.3: - version "1.49.0" - resolved "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz" - integrity sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g== - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-plugin-react-hooks@^7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz" - integrity sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g== - dependencies: - "@babel/core" "^7.24.4" - "@babel/parser" "^7.24.4" - hermes-parser "^0.25.1" - zod "^3.25.0 || ^4.0.0" - zod-validation-error "^3.5.0 || ^4.0.0" - -eslint-plugin-react-refresh@^0.5.3: - version "0.5.3" - resolved "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz" - integrity sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA== - -eslint-scope@^9.1.2: - version "9.1.2" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz" - integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ== - dependencies: - "@types/esrecurse" "^4.3.1" - "@types/estree" "^1.0.8" - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz" - integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== - -eslint@^10.0.0, eslint@^10.7.0, "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0 || ^10.0.0", "eslint@^9 || ^10": - version "10.7.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz" - integrity sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ== - dependencies: - "@eslint-community/eslint-utils" "^4.8.0" - "@eslint-community/regexpp" "^4.12.2" - "@eslint/config-array" "^0.23.5" - "@eslint/config-helpers" "^0.6.0" - "@eslint/core" "^1.2.1" - "@eslint/plugin-kit" "^0.7.2" - "@humanfs/node" "^0.16.6" - "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.2" - "@types/estree" "^1.0.6" - ajv "^6.14.0" - cross-spawn "^7.0.6" - debug "^4.3.2" - escape-string-regexp "^4.0.0" - eslint-scope "^9.1.2" - eslint-visitor-keys "^5.0.1" - espree "^11.2.0" - esquery "^1.7.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" - find-up "^5.0.0" - glob-parent "^6.0.2" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - json-stable-stringify-without-jsonify "^1.0.1" - minimatch "^10.2.4" - natural-compare "^1.4.0" - optionator "^0.9.3" - -espree@^11.2.0: - version "11.2.0" - resolved "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz" - integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== - dependencies: - acorn "^8.16.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^5.0.1" - -esquery@^1.7.0: - version "1.7.0" - resolved "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz" - integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -eventemitter3@^5.0.1: - version "5.0.4" - resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz" - integrity sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fast-shallow-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz" - integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw== - -fastest-stable-stringify@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz" - integrity sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q== - -fdir@^6.5.0: - version "6.5.0" - resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" - integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== - -file-entry-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz" - integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== - dependencies: - flat-cache "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz" - integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.4" - -flatted@^3.2.9: - version "3.4.2" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz" - integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== - -fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-nonce@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz" - integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -globals@^17.7.0: - version "17.7.0" - resolved "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz" - integrity sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg== - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.2.4: - version "4.2.11" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -hasown@^2.0.2: - version "2.0.4" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz" - integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== - dependencies: - function-bind "^1.1.2" - -hermes-estree@0.25.1: - version "0.25.1" - resolved "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz" - integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== - -hermes-parser@^0.25.1: - version "0.25.1" - resolved "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz" - integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== - dependencies: - hermes-estree "0.25.1" - -hyphenate-style-name@^1.0.3: - version "1.1.0" - resolved "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz" - integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw== - -ignore@^5.2.0: - version "5.3.2" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -ignore@^7.0.5: - version "7.0.6" - resolved "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz" - integrity sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw== - -immer@^11.0.0, immer@^11.1.8: - version "11.1.11" - resolved "https://registry.npmjs.org/immer/-/immer-11.1.11.tgz" - integrity sha512-qzXuyXAkPySAGYkfsAwodDPWT8Zm7/Uo5BNt4BjhMhG5WlWyZZ4wQqnWwdS8kjlQ1Cwu6gjw3A6+0gTQwlyYtw== - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inline-style-prefixer@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz" - integrity sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw== - dependencies: - css-in-js-utils "^3.1.0" - -"internmap@1 - 2": - version "2.0.3" - resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" - integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-glob@^4.0.0, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-mobile@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz" - integrity sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -jiti@*, jiti@^2.7.0, jiti@>=1.21.0: - version "2.7.0" - resolved "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz" - integrity sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ== - -js-cookie@^3.0.0: - version "3.0.8" - resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz" - integrity sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json2mq@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz" - integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA== - dependencies: - string-convert "^0.2.0" - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -keyv@^4.5.4: - version "4.5.4" - resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lightningcss-darwin-arm64@1.32.0: - version "1.32.0" - resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz" - integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== - -lightningcss@^1.32.0, lightningcss@1.32.0: - version "1.32.0" - resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz" - integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== - dependencies: - detect-libc "^2.0.3" - optionalDependencies: - lightningcss-android-arm64 "1.32.0" - lightningcss-darwin-arm64 "1.32.0" - lightningcss-darwin-x64 "1.32.0" - lightningcss-freebsd-x64 "1.32.0" - lightningcss-linux-arm-gnueabihf "1.32.0" - lightningcss-linux-arm64-gnu "1.32.0" - lightningcss-linux-arm64-musl "1.32.0" - lightningcss-linux-x64-gnu "1.32.0" - lightningcss-linux-x64-musl "1.32.0" - lightningcss-win32-arm64-msvc "1.32.0" - lightningcss-win32-x64-msvc "1.32.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lucide-react@^1.24.0: - version "1.24.0" - resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-1.24.0.tgz" - integrity sha512-YT6mBD8lGKkg4nM39enlm94/sfJIiW0YKUT60fBy4YK8tai31ylg1VhGNWxkpSKHo9UagfnZqwIff3HTDQwXeA== - -magic-string@^0.30.21: - version "0.30.21" - resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz" - integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.5" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -minimatch@^10.2.2, minimatch@^10.2.4: - version "10.2.5" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz" - integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== - dependencies: - brace-expansion "^5.0.5" - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nano-css@^5.6.2: - version "5.6.2" - resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.6.2.tgz" - integrity sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw== - dependencies: - "@jridgewell/sourcemap-codec" "^1.4.15" - css-tree "^1.1.2" - csstype "^3.1.2" - fastest-stable-stringify "^2.0.2" - inline-style-prefixer "^7.0.1" - rtl-css-js "^1.16.1" - stacktrace-js "^2.0.2" - stylis "^4.3.0" - -nanoid@^3.3.12: - version "3.3.16" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz" - integrity sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -node-releases@^2.0.51: - version "2.0.51" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz" - integrity sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ== - -object-inspect@^1.13.3, object-inspect@^1.13.4: - version "1.13.4" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -"picomatch@^3 || ^4", picomatch@^4.0.4, picomatch@^4.0.5: - version "4.0.5" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz" - integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A== - -postcss@^8.5.16: - version "8.5.17" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz" - integrity sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w== - dependencies: - nanoid "^3.3.12" - picocolors "^1.1.1" - source-map-js "^1.2.1" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -qs@^6.15.3: - version "6.15.3" - resolved "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz" - integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A== - dependencies: - es-define-property "^1.0.1" - side-channel "^1.1.1" - -react-dom@*, "react-dom@^0.14.0 || ^15.0.1 || ^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", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", react-dom@^19.2.7, react-dom@>=16.0.0, react-dom@>=16.11.0, react-dom@>=16.8.0, react-dom@>=16.9.0, react-dom@>=18, react-dom@>=18.0.0: - version "19.2.7" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz" - integrity sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ== - dependencies: - scheduler "^0.27.0" - -"react-is@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", react-is@^19.2.7: - version "19.2.7" - resolved "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz" - integrity sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A== - -react-is@^18.2.0: - version "18.3.1" - resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - -"react-redux@^7.2.1 || ^8.1.3 || ^9.0.0", "react-redux@8.x.x || 9.x.x": - version "9.3.0" - resolved "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz" - integrity sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g== - dependencies: - "@types/use-sync-external-store" "^0.0.6" - use-sync-external-store "^1.4.0" - -react-remove-scroll-bar@^2.3.7: - version "2.3.8" - resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz" - integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== - dependencies: - react-style-singleton "^2.2.2" - tslib "^2.0.0" - -react-remove-scroll@^2.7.2: - version "2.7.2" - resolved "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz" - integrity sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q== - dependencies: - react-remove-scroll-bar "^2.3.7" - react-style-singleton "^2.2.3" - tslib "^2.1.0" - use-callback-ref "^1.3.3" - use-sidecar "^1.1.3" - -react-router-dom@^7.18.1: - version "7.18.1" - resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz" - integrity sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg== - dependencies: - react-router "7.18.1" - -react-router@7.18.1: - version "7.18.1" - resolved "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz" - integrity sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg== - dependencies: - cookie "^1.0.1" - set-cookie-parser "^2.6.0" - -react-style-singleton@^2.2.2, react-style-singleton@^2.2.3: - version "2.2.3" - resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz" - integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ== - dependencies: - get-nonce "^1.0.0" - tslib "^2.0.0" - -react-universal-interface@^0.6.2: - version "0.6.2" - resolved "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz" - integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== - -react-use@^17.6.1: - version "17.6.1" - resolved "https://registry.npmjs.org/react-use/-/react-use-17.6.1.tgz" - integrity sha512-uibb3pgzV4LFsYPHyXYGu7dD2+pyk/ZJlPH+AizBR3zolqPWyCleKcWWbUQaKSKfydwgnQ3ymGm1Ab3/saGHCA== - dependencies: - "@types/js-cookie" "^3.0.0" - "@xobotyi/scrollbar-width" "^1.9.5" - copy-to-clipboard "^3.3.1" - fast-deep-equal "^3.1.3" - fast-shallow-equal "^1.0.0" - js-cookie "^3.0.0" - nano-css "^5.6.2" - react-universal-interface "^0.6.2" - resize-observer-polyfill "^1.5.1" - screenfull "^5.1.0" - set-harmonic-interval "^1.0.1" - throttle-debounce "^3.0.1" - ts-easing "^0.2.0" - tslib "^2.1.0" - -react@*, "react@^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.9.0 || ^17.0.0 || ^18 || ^19", "react@^18 || ^19", "react@^18.0 || ^19", react@^19.2.7, react@>=16.0.0, react@>=16.11.0, react@>=16.8.0, react@>=16.9.0, react@>=18, react@>=18.0.0: - version "19.2.7" - resolved "https://registry.npmjs.org/react/-/react-19.2.7.tgz" - integrity sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ== - -recharts@^3.9.2: - version "3.9.2" - resolved "https://registry.npmjs.org/recharts/-/recharts-3.9.2.tgz" - integrity sha512-G4fy+Pk46RaXgwWMh+Nzhyo/lbFAVqXo9gtetlyehe6Ehge9CsgDuOTwQDD+i1+llaLktNBiNq4bhnGlDRXFtw== - dependencies: - "@reduxjs/toolkit" "^1.9.0 || 2.x.x" - clsx "^2.1.1" - decimal.js-light "^2.5.1" - es-toolkit "^1.39.3" - eventemitter3 "^5.0.1" - immer "^11.1.8" - react-redux "8.x.x || 9.x.x" - reselect "5.2.0" - tiny-invariant "^1.3.3" - use-sync-external-store "^1.2.2" - victory-vendor "^37.0.2" - -redux-thunk@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz" - integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== - -redux@^5.0.0, redux@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz" - integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== - -reselect@^5.1.0, reselect@5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz" - integrity sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw== - -resize-observer-polyfill@^1.5.1: - version "1.5.1" - resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz" - integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== - -rolldown@~1.1.4: - version "1.1.5" - resolved "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz" - integrity sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA== - dependencies: - "@oxc-project/types" "=0.139.0" - "@rolldown/pluginutils" "^1.0.0" - optionalDependencies: - "@rolldown/binding-android-arm64" "1.1.5" - "@rolldown/binding-darwin-arm64" "1.1.5" - "@rolldown/binding-darwin-x64" "1.1.5" - "@rolldown/binding-freebsd-x64" "1.1.5" - "@rolldown/binding-linux-arm-gnueabihf" "1.1.5" - "@rolldown/binding-linux-arm64-gnu" "1.1.5" - "@rolldown/binding-linux-arm64-musl" "1.1.5" - "@rolldown/binding-linux-ppc64-gnu" "1.1.5" - "@rolldown/binding-linux-s390x-gnu" "1.1.5" - "@rolldown/binding-linux-x64-gnu" "1.1.5" - "@rolldown/binding-linux-x64-musl" "1.1.5" - "@rolldown/binding-openharmony-arm64" "1.1.5" - "@rolldown/binding-wasm32-wasi" "1.1.5" - "@rolldown/binding-win32-arm64-msvc" "1.1.5" - "@rolldown/binding-win32-x64-msvc" "1.1.5" - -rtl-css-js@^1.16.1: - version "1.16.1" - resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz" - integrity sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg== - dependencies: - "@babel/runtime" "^7.1.2" - -scheduler@^0.27.0: - version "0.27.0" - resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz" - integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== - -screenfull@^5.1.0: - version "5.2.0" - resolved "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz" - integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA== - -scroll-into-view-if-needed@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz" - integrity sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ== - dependencies: - compute-scroll-into-view "^3.0.2" - -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.7.3: - version "7.8.5" - resolved "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz" - integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== - -set-cookie-parser@^2.6.0: - version "2.7.2" - resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz" - integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== - -set-harmonic-interval@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz" - integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel-list@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz" - integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.4" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz" - integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.4" - side-channel-list "^1.0.1" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@0.5.6: - version "0.5.6" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== - -stack-generator@^2.0.5: - version "2.0.10" - resolved "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz" - integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ== - dependencies: - stackframe "^1.3.4" - -stackframe@^1.3.4: - version "1.3.4" - resolved "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz" - integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== - -stacktrace-gps@^3.0.4: - version "3.1.2" - resolved "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz" - integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ== - dependencies: - source-map "0.5.6" - stackframe "^1.3.4" - -stacktrace-js@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz" - integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== - dependencies: - error-stack-parser "^2.0.6" - stack-generator "^2.0.5" - stacktrace-gps "^3.0.4" - -string-convert@^0.2.0: - version "0.2.1" - resolved "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz" - integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== - -stylis@^4.3.0, stylis@^4.3.4: - version "4.4.0" - resolved "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz" - integrity sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA== - -tailwind-merge@^3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz" - integrity sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w== - -tailwindcss@^4.3.2, tailwindcss@4.3.2: - version "4.3.2" - resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz" - integrity sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA== - -tapable@^2.3.3: - version "2.3.3" - resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz" - integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A== - -throttle-debounce@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz" - integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== - -throttle-debounce@^5.0.0, throttle-debounce@^5.0.2: - version "5.0.2" - resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz" - integrity sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A== - -tiny-invariant@^1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz" - integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== - -tinyglobby@^0.2.15, tinyglobby@^0.2.17: - version "0.2.17" - resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz" - integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== - dependencies: - fdir "^6.5.0" - picomatch "^4.0.4" - -toggle-selection@^1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz" - integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== - -ts-api-utils@^2.5.0: - version "2.5.0" - resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz" - integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== - -ts-easing@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz" - integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== - -tslib@*, tslib@^2.0.0, tslib@^2.1.0: - version "2.8.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -tw-animate-css@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz" - integrity sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -typescript-eslint@^8.63.0: - version "8.63.0" - resolved "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz" - integrity sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ== - dependencies: - "@typescript-eslint/eslint-plugin" "8.63.0" - "@typescript-eslint/parser" "8.63.0" - "@typescript-eslint/typescript-estree" "8.63.0" - "@typescript-eslint/utils" "8.63.0" - -typescript@>=4.8.4, "typescript@>=4.8.4 <6.1.0", typescript@~6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz" - integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== - -undici-types@~8.3.0: - version "8.3.0" - resolved "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz" - integrity sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ== - -update-browserslist-db@^1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz" - integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -use-callback-ref@^1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz" - integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== - dependencies: - tslib "^2.0.0" - -use-sidecar@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz" - integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ== - dependencies: - detect-node-es "^1.1.0" - tslib "^2.0.0" - -use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0: - version "1.6.0" - resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz" - integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== - -victory-vendor@^37.0.2: - version "37.3.6" - resolved "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz" - integrity sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ== - 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" - -"vite@^5.2.0 || ^6 || ^7 || ^8", vite@^8.0.0, vite@8.1.4: - version "8.1.4" - resolved "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz" - integrity sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ== - dependencies: - lightningcss "^1.32.0" - picomatch "^4.0.5" - postcss "^8.5.16" - rolldown "~1.1.4" - tinyglobby "^0.2.17" - optionalDependencies: - fsevents "~2.3.3" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -"zod-validation-error@^3.5.0 || ^4.0.0": - version "4.0.2" - resolved "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz" - integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ== - -"zod@^3.25.0 || ^4.0.0": - version "4.4.3" - resolved "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz" - integrity sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==