diff --git a/mantis-frontend b/mantis-frontend new file mode 160000 index 0000000..f1fa091 --- /dev/null +++ b/mantis-frontend @@ -0,0 +1 @@ +Subproject commit f1fa091f4c1abc90049b32c36f2d339400090713 diff --git a/mantis-frontend/.gitattributes b/mantis-frontend/.gitattributes deleted file mode 100644 index 94f480d..0000000 --- a/mantis-frontend/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto eol=lf \ No newline at end of file diff --git a/mantis-frontend/.gitignore b/mantis-frontend/.gitignore deleted file mode 100644 index b8a1634..0000000 --- a/mantis-frontend/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.next -out -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/mantis-frontend/README.md b/mantis-frontend/README.md deleted file mode 100644 index 682899b..0000000 --- a/mantis-frontend/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Next.js + Tailwind CSS - -This template provides a minimal setup to quickly get started with Next.js and Tailwind CSS development. - -## Features - -- **Fast Refresh**: Provides instant updates during development. -- **Tailwind CSS**: A powerful and highly customizable CSS framework that helps you quickly build responsive UIs. -- **ESLint**: Provides basic code linting rules to ensure code quality. - -## Getting Started - -1. Install dependencies: - -```bash -npm install -# or -yarn install -``` - -2. Start the development server: -```bash -npm run dev -# or -yarn dev -``` \ No newline at end of file diff --git a/mantis-frontend/next-env.d.ts b/mantis-frontend/next-env.d.ts deleted file mode 100644 index 1970904..0000000 --- a/mantis-frontend/next-env.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// -/// -import "./.next/types/routes.d.ts"; - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/mantis-frontend/next.config.js b/mantis-frontend/next.config.js deleted file mode 100644 index f38deb9..0000000 --- a/mantis-frontend/next.config.js +++ /dev/null @@ -1,29 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, - trailingSlash: false, - output: 'export', - images: { - remotePatterns: [ - // { - // protocol: 'https', - // hostname: 'cdn.discordapp.com', - // pathname: '/avatars/**', - // }, - ], - }, - - // i18n: { - // locales: ['tw', 'cn', 'ja', 'en'], - // defaultLocale: 'tw', - // localeDetection: true, - // }, - // - // allowedDevOrigins: [ - // '192.168.1.118', - // 'localhost', - // '127.0.0.1', - // ], -}; - -export default nextConfig; \ No newline at end of file diff --git a/mantis-frontend/package-lock.json b/mantis-frontend/package-lock.json deleted file mode 100644 index 7c796ef..0000000 --- a/mantis-frontend/package-lock.json +++ /dev/null @@ -1,4902 +0,0 @@ -{ - "name": "netguardia-dashboard", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "netguardia-dashboard", - "version": "1.0.0", - "dependencies": { - "@fortawesome/fontawesome-svg-core": "^7.1.0", - "@fortawesome/free-brands-svg-icons": "^7.1.0", - "@fortawesome/free-regular-svg-icons": "^7.1.0", - "@fortawesome/free-solid-svg-icons": "^7.1.0", - "@fortawesome/react-fontawesome": "^3.1.1", - "@headlessui/react": "^2.2.9", - "@tailwindcss/typography": "^0.5.19", - "bcryptjs": "^3.0.2", - "better-sqlite3": "12.2.0", - "chart.js": "^4.4.1", - "echarts": "6.0.0", - "echarts-for-react": "3.0.4", - "formidable": "^3.5.2", - "framer-motion": "^12.23.12", - "jsonwebtoken": "^9.0.2", - "maplibre-gl": "^5.8.0", - "next": "^16.0.10", - "react": "^19.2.3", - "react-chartjs-2": "^5.2.0", - "react-dom": "^19.2.3", - "react-error-boundary": "^6.0.0", - "react-markdown": "^10.1.0", - "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.1", - "rxjs": "^7.8.2", - "uuid": "^11.1.0" - }, - "devDependencies": { - "@tailwindcss/postcss": "^4.1.17", - "@types/bcryptjs": "^2.4.6", - "@types/jsonwebtoken": "^9.0.6", - "@types/node": "24.10.11", - "@types/react": "19.2.13", - "@types/react-dom": "^19.2.3", - "@types/uuid": "^9.0.8", - "autoprefixer": "^10.4.21", - "postcss": "^8.5.3", - "tailwindcss": "^4.1.17", - "typescript": "5.9.3" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", - "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.4" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.1.0.tgz", - "integrity": "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz", - "integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==", - "license": "MIT", - "dependencies": { - "@fortawesome/fontawesome-common-types": "7.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-brands-svg-icons": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.1.0.tgz", - "integrity": "sha512-9byUd9bgNfthsZAjBl6GxOu1VPHgBuRUP9juI7ZoM98h8xNPTCTagfwUFyYscdZq4Hr7gD1azMfM9s5tIWKZZA==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "7.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-7.1.0.tgz", - "integrity": "sha512-0e2fdEyB4AR+e6kU4yxwA/MonnYcw/CsMEP9lH82ORFi9svA6/RhDyhxIv5mlJaldmaHLLYVTb+3iEr+PDSZuQ==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "7.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.1.0.tgz", - "integrity": "sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "7.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-3.1.1.tgz", - "integrity": "sha512-EDllr9hpodc21odmUywHS1alXNiCd4E8sp5GJ5s7wYINz8vSmMiNWpALTiuYODb865YyQ/NlyiN4mbXp7HCNqg==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~6 || ~7", - "react": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@headlessui/react": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz", - "integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.26.16", - "@react-aria/focus": "^3.20.2", - "@react-aria/interactions": "^3.25.0", - "@tanstack/react-virtual": "^3.13.9", - "use-sync-external-store": "^1.5.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc" - } - }, - "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "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/@kurkle/color": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", - "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", - "license": "MIT" - }, - "node_modules/@mapbox/geojson-rewind": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", - "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", - "license": "ISC", - "dependencies": { - "get-stream": "^6.0.1", - "minimist": "^1.2.6" - }, - "bin": { - "geojson-rewind": "geojson-rewind" - } - }, - "node_modules/@mapbox/jsonlint-lines-primitives": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", - "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@mapbox/point-geometry": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", - "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", - "license": "ISC" - }, - "node_modules/@mapbox/tiny-sdf": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz", - "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==", - "license": "BSD-2-Clause" - }, - "node_modules/@mapbox/unitbezier": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", - "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", - "license": "BSD-2-Clause" - }, - "node_modules/@mapbox/vector-tile": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", - "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", - "license": "BSD-3-Clause", - "dependencies": { - "@mapbox/point-geometry": "~1.1.0", - "@types/geojson": "^7946.0.16", - "pbf": "^4.0.1" - } - }, - "node_modules/@mapbox/whoots-js": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", - "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", - "license": "ISC", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@maplibre/maplibre-gl-style-spec": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.2.0.tgz", - "integrity": "sha512-cE80g83fRcBbZbQC70siOUxUK6YJ/5ZkClDZbmm+hzrUbv+J6yntkMmcpdz9DbOrWOM7FHKR5rruc6Q/hWx5cA==", - "license": "ISC", - "dependencies": { - "@mapbox/jsonlint-lines-primitives": "~2.0.2", - "@mapbox/unitbezier": "^0.0.1", - "json-stringify-pretty-compact": "^4.0.0", - "minimist": "^1.2.8", - "quickselect": "^3.0.0", - "rw": "^1.3.3", - "tinyqueue": "^3.0.0" - }, - "bin": { - "gl-style-format": "dist/gl-style-format.mjs", - "gl-style-migrate": "dist/gl-style-migrate.mjs", - "gl-style-validate": "dist/gl-style-validate.mjs" - } - }, - "node_modules/@maplibre/vt-pbf": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.0.3.tgz", - "integrity": "sha512-YsW99BwnT+ukJRkseBcLuZHfITB4puJoxnqPVjo72rhW/TaawVYsgQHcqWLzTxqknttYoDpgyERzWSa/XrETdA==", - "license": "MIT", - "dependencies": { - "@mapbox/point-geometry": "^1.1.0", - "@mapbox/vector-tile": "^2.0.4", - "@types/geojson-vt": "3.2.5", - "@types/supercluster": "^7.1.3", - "geojson-vt": "^4.0.2", - "pbf": "^4.0.1", - "supercluster": "^8.0.1" - } - }, - "node_modules/@next/env": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", - "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", - "license": "MIT" - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", - "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", - "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", - "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", - "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", - "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", - "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", - "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", - "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", - "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, - "node_modules/@react-aria/focus": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.1.tgz", - "integrity": "sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ==", - "license": "Apache-2.0", - "dependencies": { - "@react-aria/interactions": "^3.25.5", - "@react-aria/utils": "^3.30.1", - "@react-types/shared": "^3.32.0", - "@swc/helpers": "^0.5.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/interactions": { - "version": "3.25.5", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.5.tgz", - "integrity": "sha512-EweYHOEvMwef/wsiEqV73KurX/OqnmbzKQa2fLxdULbec5+yDj6wVGaRHIzM4NiijIDe+bldEl5DG05CAKOAHA==", - "license": "Apache-2.0", - "dependencies": { - "@react-aria/ssr": "^3.9.10", - "@react-aria/utils": "^3.30.1", - "@react-stately/flags": "^3.1.2", - "@react-types/shared": "^3.32.0", - "@swc/helpers": "^0.5.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/ssr": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", - "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/utils": { - "version": "3.30.1", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.30.1.tgz", - "integrity": "sha512-zETcbDd6Vf9GbLndO6RiWJadIZsBU2MMm23rBACXLmpRztkrIqPEb2RVdlLaq1+GklDx0Ii6PfveVjx+8S5U6A==", - "license": "Apache-2.0", - "dependencies": { - "@react-aria/ssr": "^3.9.10", - "@react-stately/flags": "^3.1.2", - "@react-stately/utils": "^3.10.8", - "@react-types/shared": "^3.32.0", - "@swc/helpers": "^0.5.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-stately/flags": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", - "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, - "node_modules/@react-stately/utils": { - "version": "3.10.8", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", - "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-types/shared": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.0.tgz", - "integrity": "sha512-t+cligIJsZYFMSPFMvsJMjzlzde06tZMOIOFa1OV5Z0BcMowrb2g4mB57j/9nP28iJIRYn10xCniQts+qadrqQ==", - "license": "Apache-2.0", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", - "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.18" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", - "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-x64": "4.1.18", - "@tailwindcss/oxide-freebsd-x64": "4.1.18", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-x64-musl": "4.1.18", - "@tailwindcss/oxide-wasm32-wasi": "4.1.18", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", - "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", - "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", - "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", - "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", - "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", - "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.7.1", - "@emnapi/runtime": "^1.7.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.0", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", - "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/postcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", - "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "postcss": "^8.4.41", - "tailwindcss": "4.1.18" - } - }, - "node_modules/@tailwindcss/typography": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", - "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "6.0.10" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" - } - }, - "node_modules/@tanstack/react-virtual": { - "version": "3.13.12", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz", - "integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==", - "license": "MIT", - "dependencies": { - "@tanstack/virtual-core": "3.13.12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@tanstack/virtual-core": { - "version": "3.13.12", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", - "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@types/bcryptjs": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", - "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, - "node_modules/@types/geojson-vt": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", - "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.10.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.11.tgz", - "integrity": "sha512-/Af7O8r1frCVgOz0I62jWUtMohJ0/ZQU/ZoketltOJPZpnb17yoNc9BSoVuV9qlaIXJiPNOpsfq4ByFajSArNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", - "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "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==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@types/supercluster": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", - "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/bcryptjs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", - "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", - "license": "BSD-3-Clause", - "bin": { - "bcrypt": "bin/bcrypt" - } - }, - "node_modules/better-sqlite3": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.2.0.tgz", - "integrity": "sha512-eGbYq2CT+tos1fBwLQ/tkBt9J5M3JEHjku4hbvQUePCckkvVf14xWj+1m7dGoK81M/fOjFT7yM9UMeKT/+vFLQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - }, - "engines": { - "node": "20.x || 22.x || 23.x || 24.x" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", - "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", - "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": { - "caniuse-lite": "^1.0.30001737", - "electron-to-chromium": "^1.5.211", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001737", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", - "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", - "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/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chart.js": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", - "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", - "license": "MIT", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "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/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "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/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/earcut": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", - "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", - "license": "ISC" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/echarts": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", - "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "2.3.0", - "zrender": "6.0.0" - } - }, - "node_modules/echarts-for-react": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.4.tgz", - "integrity": "sha512-rc7SNdr0JoMTkMJspp9ejlZAoipv2mMwbI30ggIgxSZMELdX36C2aJREvJ9OSkyetC/RoO1s7VrefXUrUAMClg==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "size-sensor": "^1.0.1" - }, - "peerDependencies": { - "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", - "react": "^15.0.0 || >=16.0.0" - } - }, - "node_modules/echarts/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.211", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz", - "integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==", - "dev": true, - "license": "ISC" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "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": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "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==", - "license": "MIT" - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, - "node_modules/formidable": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", - "license": "MIT", - "dependencies": { - "@paralleldrive/cuid2": "^2.2.2", - "dezalgo": "^1.0.4", - "once": "^1.4.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/framer-motion": { - "version": "12.23.12", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.12.tgz", - "integrity": "sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==", - "license": "MIT", - "dependencies": { - "motion-dom": "^12.23.12", - "motion-utils": "^12.23.6", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/geojson-vt": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", - "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==", - "license": "ISC" - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, - "node_modules/gl-matrix": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", - "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", - "license": "MIT" - }, - "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/hast-util-from-parse5": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", - "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5/node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/html-url-attributes": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", - "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/inline-style-parser": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", - "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", - "license": "MIT" - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/json-stringify-pretty-compact": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", - "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", - "license": "MIT" - }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "license": "MIT", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", - "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.2", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/kdbush": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", - "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", - "license": "ISC" - }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "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.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "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.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "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.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "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.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "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.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "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.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "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-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "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-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "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-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "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-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "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.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "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/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "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/maplibre-gl": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.8.0.tgz", - "integrity": "sha512-zLblPFK+z5sxeitDF8RL2cnqfRaivNwxbGoQMfwAm9st6d1lRGTxgI7NNNr/U1AEPkp5+X+wjROagiHvJD8aqg==", - "license": "BSD-3-Clause", - "dependencies": { - "@mapbox/geojson-rewind": "^0.5.2", - "@mapbox/jsonlint-lines-primitives": "^2.0.2", - "@mapbox/point-geometry": "^1.1.0", - "@mapbox/tiny-sdf": "^2.0.7", - "@mapbox/unitbezier": "^0.0.1", - "@mapbox/vector-tile": "^2.0.4", - "@mapbox/whoots-js": "^3.1.0", - "@maplibre/maplibre-gl-style-spec": "^24.2.0", - "@maplibre/vt-pbf": "^4.0.3", - "@types/geojson": "^7946.0.16", - "@types/geojson-vt": "3.2.5", - "@types/supercluster": "^7.1.3", - "earcut": "^3.0.2", - "geojson-vt": "^4.0.2", - "gl-matrix": "^3.4.4", - "kdbush": "^4.0.2", - "murmurhash-js": "^1.0.0", - "pbf": "^4.0.1", - "potpack": "^2.1.0", - "quickselect": "^3.0.0", - "supercluster": "^8.0.1", - "tinyqueue": "^3.0.0" - }, - "engines": { - "node": ">=16.14.0", - "npm": ">=8.1.0" - }, - "funding": { - "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/motion-dom": { - "version": "12.23.12", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.12.tgz", - "integrity": "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==", - "license": "MIT", - "dependencies": { - "motion-utils": "^12.23.6" - } - }, - "node_modules/motion-utils": { - "version": "12.23.6", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", - "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", - "license": "MIT" - }, - "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==", - "license": "MIT" - }, - "node_modules/murmurhash-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", - "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "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/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, - "node_modules/next": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", - "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", - "license": "MIT", - "dependencies": { - "@next/env": "16.1.6", - "@swc/helpers": "0.5.15", - "baseline-browser-mapping": "^2.8.3", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=20.9.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "16.1.6", - "@next/swc-darwin-x64": "16.1.6", - "@next/swc-linux-arm64-gnu": "16.1.6", - "@next/swc-linux-arm64-musl": "16.1.6", - "@next/swc-linux-x64-gnu": "16.1.6", - "@next/swc-linux-x64-musl": "16.1.6", - "@next/swc-win32-arm64-msvc": "16.1.6", - "@next/swc-win32-x64-msvc": "16.1.6", - "sharp": "^0.34.4" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.51.1", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/next/node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/next/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "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.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/node-abi": { - "version": "3.75.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", - "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/pbf": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", - "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", - "license": "BSD-3-Clause", - "dependencies": { - "resolve-protobuf-schema": "^2.1.0" - }, - "bin": { - "pbf": "bin/pbf" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "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.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/potpack": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", - "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", - "license": "ISC" - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/protocol-buffers-schema": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", - "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/quickselect": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", - "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", - "license": "ISC" - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-chartjs-2": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", - "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", - "license": "MIT", - "peerDependencies": { - "chart.js": "^4.1.1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, - "node_modules/react-error-boundary": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.0.0.tgz", - "integrity": "sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "peerDependencies": { - "react": ">=16.13.1" - } - }, - "node_modules/react-markdown": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", - "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "html-url-attributes": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "unified": "^11.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=18", - "react": ">=18" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/rehype-raw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/resolve-protobuf-schema": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", - "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", - "license": "MIT", - "dependencies": { - "protocol-buffers-schema": "^3.3.1" - } - }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/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.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.2", - "semver": "^7.7.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" - } - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/size-sensor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.2.tgz", - "integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==", - "license": "ISC" - }, - "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==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/style-to-js": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", - "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", - "license": "MIT", - "dependencies": { - "style-to-object": "1.0.9" - } - }, - "node_modules/style-to-object": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", - "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.4" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/supercluster": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", - "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", - "license": "ISC", - "dependencies": { - "kdbush": "^4.0.2" - } - }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "license": "MIT" - }, - "node_modules/tailwindcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", - "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tinyqueue": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", - "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", - "license": "ISC" - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "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" - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "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" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/zrender": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", - "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", - "license": "BSD-3-Clause", - "dependencies": { - "tslib": "2.3.0" - } - }, - "node_modules/zrender/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/mantis-frontend/package.json b/mantis-frontend/package.json deleted file mode 100644 index c984907..0000000 --- a/mantis-frontend/package.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "netguardia-dashboard", - "version": "1.0.0", - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint", - "start:https": "node server.js", - "dev:https": "node server.js" - }, - "dependencies": { - "@fortawesome/fontawesome-svg-core": "^7.1.0", - "@fortawesome/free-brands-svg-icons": "^7.1.0", - "@fortawesome/free-regular-svg-icons": "^7.1.0", - "@fortawesome/free-solid-svg-icons": "^7.1.0", - "@fortawesome/react-fontawesome": "^3.1.1", - "@headlessui/react": "^2.2.9", - "@tailwindcss/typography": "^0.5.19", - "bcryptjs": "^3.0.2", - "better-sqlite3": "12.2.0", - "chart.js": "^4.4.1", - "echarts": "6.0.0", - "echarts-for-react": "3.0.4", - "formidable": "^3.5.2", - "framer-motion": "^12.23.12", - "jsonwebtoken": "^9.0.2", - "maplibre-gl": "^5.8.0", - "next": "^16.0.10", - "react": "^19.2.3", - "react-chartjs-2": "^5.2.0", - "react-dom": "^19.2.3", - "react-error-boundary": "^6.0.0", - "react-markdown": "^10.1.0", - "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.1", - "rxjs": "^7.8.2", - "uuid": "^11.1.0" - }, - "devDependencies": { - "@tailwindcss/postcss": "^4.1.17", - "@types/bcryptjs": "^2.4.6", - "@types/jsonwebtoken": "^9.0.6", - "@types/node": "24.10.11", - "@types/react": "19.2.13", - "@types/react-dom": "^19.2.3", - "@types/uuid": "^9.0.8", - "autoprefixer": "^10.4.21", - "postcss": "^8.5.3", - "tailwindcss": "^4.1.17", - "typescript": "5.9.3" - } -} diff --git a/mantis-frontend/postcss.config.js b/mantis-frontend/postcss.config.js deleted file mode 100644 index 516702f..0000000 --- a/mantis-frontend/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - '@tailwindcss/postcss': {}, - autoprefixer: {}, - }, -} \ No newline at end of file diff --git a/mantis-frontend/public/favicon.ico b/mantis-frontend/public/favicon.ico deleted file mode 100644 index ce32193..0000000 Binary files a/mantis-frontend/public/favicon.ico and /dev/null differ diff --git a/mantis-frontend/server.js b/mantis-frontend/server.js deleted file mode 100644 index 3a96995..0000000 --- a/mantis-frontend/server.js +++ /dev/null @@ -1,104 +0,0 @@ -// server.js - HTTPS server with English logs -import { createServer } from 'https'; -import { parse } from 'url'; -import next from 'next'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -// ES Module 中取得 __dirname -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -// Determine environment based on npm script -const scriptName = process.env.npm_lifecycle_event; -let dev; - -if (scriptName === 'dev:https') { - dev = true; - console.log('Development mode'); -} else if (scriptName === 'start:https') { - dev = false; - console.log('Production mode'); -} else { - // Default to production mode - dev = false; - console.log('Unknown script, using production mode'); -} - -const hostname = '0.0.0.0'; -const port = 3000; - -const app = next({ dev, hostname, port }); -const handle = app.getRequestHandler(); - -// SSL certificate file paths -const sslFiles = { - ca: path.join(__dirname, 'ssl/root.ca-bundle'), - cert: path.join(__dirname, 'ssl/server.crt'), - key: path.join(__dirname, 'ssl/server.key') -}; - -// Check and load SSL certificates -function loadSSLCertificates() { - try { - // Check if all certificate files exist - for (const [type, filePath] of Object.entries(sslFiles)) { - if (!fs.existsSync(filePath)) { - throw new Error(`SSL ${type} file not found: ${filePath}`); - } - } - - console.log('All SSL certificate files found'); - - return { - ca: fs.readFileSync(sslFiles.ca), - cert: fs.readFileSync(sslFiles.cert), - key: fs.readFileSync(sslFiles.key) - }; - } catch (error) { - console.error('Failed to load SSL certificates:', error.message); - console.error('Please ensure the following files exist:'); - console.error(' - ssl/*.ca-bundle'); - console.error(' - ssl/*.crt'); - console.error(' - ssl/*.key'); - process.exit(1); - } -} - -// Start the application -app.prepare().then(() => { - const httpsOptions = loadSSLCertificates(); - - createServer(httpsOptions, (req, res) => { - const parsedUrl = parse(req.url, true); - handle(req, res, parsedUrl); - }).listen(port, hostname, (err) => { - if (err) { - console.error('Failed to start HTTPS server:', err); - process.exit(1); - } - console.log(`HTTPS Server ready on https://${hostname}:${port}`); - - if (dev) { - console.log('Development mode started with hot reload support'); - } else { - console.log('Production mode started with optimized performance'); - } - }); - -}).catch((ex) => { - console.error('Failed to start Next.js application:', ex.stack); - process.exit(1); -}); - -// Graceful shutdown -process.on('SIGTERM', () => { - console.log('Shutting down server...'); - process.exit(0); -}); - -process.on('SIGINT', () => { - console.log('Shutting down server...'); - process.exit(0); -}); \ No newline at end of file diff --git a/mantis-frontend/src/components/ErrorDialog.tsx b/mantis-frontend/src/components/ErrorDialog.tsx deleted file mode 100644 index 744ee10..0000000 --- a/mantis-frontend/src/components/ErrorDialog.tsx +++ /dev/null @@ -1,55 +0,0 @@ -'use client' - -import React from 'react' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' - -interface ErrorDialogProps { - error: Error - resetErrorBoundary: () => void -} - -const ErrorDialog: React.FC = ({ error, resetErrorBoundary }) => { - const handleClose = () => { - window.location.href = '/' - } - - return ( -
-
-
- -

Oops! Something went wrong

-
- -
-

- We encountered an unexpected error. Here are the details: -

-
- - {error ? error.toString() : 'Something went wrong'} - -
-
- -
- - -
-
-
- ) -} - -export default ErrorDialog \ No newline at end of file diff --git a/mantis-frontend/src/components/Layout.tsx b/mantis-frontend/src/components/Layout.tsx deleted file mode 100644 index 4835afc..0000000 --- a/mantis-frontend/src/components/Layout.tsx +++ /dev/null @@ -1,53 +0,0 @@ -// src/components/Layout.tsx -import React from 'react' -import Head from 'next/head' -import { siteUrl } from '../config' -import Sidebar from './Sidebar' - -interface LayoutProps { - children: React.ReactNode - title?: string - description?: string -} - -const Layout: React.FC = ({ - children, - title = "NetGuardia", - description = "NetGuardia" -}) => { - const imageUrl = `${siteUrl}/logo.png` - - return ( - <> - - {title} - - - - - {/* Open Graph Meta Tags */} - - - - - - - - - - {/* Twitter Card Meta Tags */} - - - - - - - {/* Wrap Sidebar around the content */} - - {children} - - - ) -} - -export default Layout diff --git a/mantis-frontend/src/components/LoadingSpinner.tsx b/mantis-frontend/src/components/LoadingSpinner.tsx deleted file mode 100644 index a35559b..0000000 --- a/mantis-frontend/src/components/LoadingSpinner.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' - -const LoadingSpinner: React.FC = () => { - return ( -
-
-
- ) -} - -export default LoadingSpinner \ No newline at end of file diff --git a/mantis-frontend/src/components/Sidebar.tsx b/mantis-frontend/src/components/Sidebar.tsx deleted file mode 100644 index 626e4be..0000000 --- a/mantis-frontend/src/components/Sidebar.tsx +++ /dev/null @@ -1,196 +0,0 @@ -// src/components/Sidebar.tsx -import React, { useState } from 'react' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { motion } from 'framer-motion' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { - faTachometerAlt, - faChartBar, - faShieldAlt, - faRobot, - faBars, - faHome, - faMapMarkedAlt -} from '@fortawesome/free-solid-svg-icons' -import { useTheme } from '../providers/ThemeProvider' -import ThemeToggle from './ThemeToggle' - -interface MenuItem { - href: string - icon: any - label: string -} - -const menuItems: MenuItem[] = [ - { - href: '/', - icon: faHome, - label: 'Home' - }, - { - href: '/dashboard', - icon: faTachometerAlt, - label: 'Dashboard' - }, - { - href: '/statistics', - icon: faChartBar, - label: 'Statistics' - }, - { - href: '/traffic-map', - icon: faMapMarkedAlt, - label: 'Traffic Map' - }, - { - href: '/access-control', - icon: faShieldAlt, - label: 'Access Control' - }, - { - href: '/detection', - icon: faRobot, - label: 'Detection' - } -] - -interface SidebarProps { - children: React.ReactNode -} - -export const Sidebar: React.FC = ({ children }) => { - const router = useRouter() - const { actualTheme } = useTheme() - const [isCollapsed, setIsCollapsed] = useState(false) - - const toggleSidebar = () => { - setIsCollapsed(!isCollapsed) - } - - // 檢查當前路徑是否為活動狀態 - const isActive = (href: string) => { - if (href === '/') { - return router.pathname === '/' - } - return router.pathname.startsWith(href) - } - - const sidebarVariants = { - expanded: { width: "240px" }, - collapsed: { width: "80px" } - } - - const menuItemVariants = { - expanded: { x: 0, opacity: 1 }, - collapsed: { x: -10, opacity: 0 } - } - - // 深色模式樣式 - const isDark = actualTheme === 'dark' - - return ( -
- {/* Sidebar */} - - {/* Sidebar Header */} -
- {!isCollapsed && ( - - - NetGuardia - - - )} - -
- - {/* Navigation Menu */} - - - {/* Theme Toggle - 放在 Sidebar 底部 */} -
- -
-
- - {/* Main Content */} -
-
-
- {children} -
-
-
-
- ) -} - -export default Sidebar \ No newline at end of file diff --git a/mantis-frontend/src/components/ThemeToggle.tsx b/mantis-frontend/src/components/ThemeToggle.tsx deleted file mode 100644 index 40363d8..0000000 --- a/mantis-frontend/src/components/ThemeToggle.tsx +++ /dev/null @@ -1,173 +0,0 @@ -// src/components/ThemeToggle.tsx -'use client' - -import React from 'react' -import { motion, AnimatePresence } from 'framer-motion' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { - faSun, - faMoon, - faDesktop, - faChevronDown -} from '@fortawesome/free-solid-svg-icons' -import { useTheme } from '../providers/ThemeProvider' - -interface ThemeToggleProps { - isCollapsed?: boolean -} - -const ThemeToggle: React.FC = ({ isCollapsed = false }) => { - const { theme, actualTheme, setTheme, toggleTheme } = useTheme() - const [showOptions, setShowOptions] = React.useState(false) - - const themeOptions = [ - { - value: 'light' as const, - label: 'Light Mode', - icon: faSun, - }, - { - value: 'dark' as const, - label: 'Dark Mode', - icon: faMoon, - }, - { - value: 'system' as const, - label: 'System Default', - icon: faDesktop, - } - ] - - const currentThemeOption = themeOptions.find(option => option.value === theme) || themeOptions[0] - - // 如果 sidebar 收起,只顯示簡單的切換按鈕 - if (isCollapsed) { - return ( -
- -
- ) - } - - return ( -
- {/* 主題切換按鈕 */} - - - {/* 主題選項下拉選單 */} - - {showOptions && ( - - {themeOptions.map((option) => ( - - ))} - - )} - - - {/* 點擊外部關閉 */} - {showOptions && ( -
setShowOptions(false)} - /> - )} -
- ) -} - -export default ThemeToggle \ No newline at end of file diff --git a/mantis-frontend/src/config.ts b/mantis-frontend/src/config.ts deleted file mode 100644 index 0548efa..0000000 --- a/mantis-frontend/src/config.ts +++ /dev/null @@ -1,32 +0,0 @@ -let httpProtocol = "http"; -let websocketProtocol = "ws"; -const hostname = "192.168.1.3"; -const port = 8080; -export const host = `${hostname}:${port}`; -export const siteUrl = `${httpProtocol}://${host}` - -export const urls = { - bootTime: `${httpProtocol}://${host}/misc/boot_time`, - access_control: { - ipv4: { - white_list: `${httpProtocol}://${host}/ebpf/access_control/ipv4/source/white_list`, - black_list: `${httpProtocol}://${host}/ebpf/access_control/ipv4/source/black_list`, - }, - ipv6: { - white_list: `${httpProtocol}://${host}/ebpf/access_control/ipv6/source/white_list`, - black_list: `${httpProtocol}://${host}/ebpf/access_control/ipv6/source/black_list`, - } - } -} as const; - -export const websocketUrl = { - ipv4FlowStats: (direction: string, flow_direction: string, timeType: string) => - `${websocketProtocol}://${host}/ebpf/statistics/websocket/ipv4/${direction}/${flow_direction}/${timeType}`, - ipv6FlowStats: (direction: string, flow_direction: string, timeType: string) => - `${websocketProtocol}://${host}/ebpf/statistics/websocket/ipv6/${direction}/${flow_direction}/${timeType}`, - - systemHealth: `${websocketProtocol}://${host}/health/websocket/metrics`, - detectionAlert: `${websocketProtocol}://${host}/detection/websocket/alert`, -} as const; - -export const REFRESH_INTERVAL = 50000; \ No newline at end of file diff --git a/mantis-frontend/src/pages/404.tsx b/mantis-frontend/src/pages/404.tsx deleted file mode 100644 index d6a4b15..0000000 --- a/mantis-frontend/src/pages/404.tsx +++ /dev/null @@ -1,216 +0,0 @@ -// pages/404.tsx -import React from 'react'; -import Link from 'next/link'; -import Head from 'next/head'; -import { motion } from 'framer-motion'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faHome, - faArrowLeft, - faExclamationTriangle, - faShieldAlt, - faRobot, - faTachometerAlt, - faChartBar -} from '@fortawesome/free-solid-svg-icons'; - -const Custom404: React.FC = () => { - const navigationCards = [ - { - href: "/dashboard", - icon: faTachometerAlt, - title: "Dashboard", - description: "System Dashboard Overview", - color: "blue" - }, - { - href: "/statistics", - icon: faChartBar, - title: "Statistics", - description: "Data Statistics and Analysis", - color: "green" - }, - { - href: "/access_control", - icon: faShieldAlt, - title: "Access Control", - description: "Access Control Management", - color: "red" - }, - { - href: "/detection", - icon: faRobot, - title: "Detection", - description: "Threat Detection System", - color: "purple" - } - ]; - - const getColorClasses = (color: string) => { - const colorMap = { - blue: { - bg: "bg-blue-500", - hover: "hover:bg-blue-600", - border: "border-blue-200", - hoverBorder: "hover:border-blue-300", - hoverBg: "hover:bg-blue-50", - text: "text-blue-600" - }, - purple: { - bg: "bg-purple-500", - hover: "hover:bg-purple-600", - border: "border-purple-200", - hoverBorder: "hover:border-purple-300", - hoverBg: "hover:bg-purple-50", - text: "text-purple-600" - }, - green: { - bg: "bg-green-500", - hover: "hover:bg-green-600", - border: "border-green-200", - hoverBorder: "hover:border-green-300", - hoverBg: "hover:bg-green-50", - text: "text-green-600" - }, - red: { - bg: "bg-red-500", - hover: "hover:bg-red-600", - border: "border-red-200", - hoverBorder: "hover:border-red-300", - hoverBg: "hover:bg-red-50", - text: "text-red-600" - } - }; - return colorMap[color as keyof typeof colorMap]; - }; - - return ( - <> - - 404 - Page Not Found | NetGuardia - - - -
-
-
- - {/* 主要錯誤區域 */} - -

- 404 -

- -

- Page Not Found -

- -

- Sorry, the page you are looking for does not exist or has been moved.
- Please choose from the options below to continue using the NetGuardia system. -

-
- - {/* 導航卡片區域 */} - -

- Quick Navigation to Main Features -

- -
- {navigationCards.map((card, index) => { - const colors = getColorClasses(card.color); - return ( - - -
-
-
- -
-
-

- {card.title} -

-

- {card.description} -

-
-
-
- -
- ); - })} -
-
- - {/* 操作按鈕 */} - - - - - Back to Home - - - - window.history.back()} - className="w-full sm:w-auto bg-gray-600 text-white px-8 py-4 rounded-lg font-semibold hover:bg-gray-700 transition-all duration-300 flex items-center justify-center gap-3 shadow-lg" - > - - Back to Previous Page - - - - {/* 底部信息 */} - -
-

- Need Help? -

-

- If you believe this is a system error, please contact the technical support team or system administrator. -

-
-
-
-
-
- - ); -}; - -export default Custom404; \ No newline at end of file diff --git a/mantis-frontend/src/pages/_app.tsx b/mantis-frontend/src/pages/_app.tsx deleted file mode 100644 index e5065ff..0000000 --- a/mantis-frontend/src/pages/_app.tsx +++ /dev/null @@ -1,22 +0,0 @@ -// src/pages/_app.tsx - 深色模式版本 -import type { AppProps } from 'next/app' -import { ErrorBoundary } from 'react-error-boundary' -import { ThemeProvider } from '../providers/ThemeProvider' -import { WebSocketProvider } from '../providers/WebSocketProvider' -import { AccessControlProvider } from '../providers/AccessControlProvider' -import ErrorDialog from '../components/ErrorDialog' -import '../styles/globals.css' - -export default function App({ Component, pageProps }: AppProps) { - return ( - - - - - - - - - - ) -} \ No newline at end of file diff --git a/mantis-frontend/src/pages/access-control.tsx b/mantis-frontend/src/pages/access-control.tsx deleted file mode 100644 index 5901a2b..0000000 --- a/mantis-frontend/src/pages/access-control.tsx +++ /dev/null @@ -1,848 +0,0 @@ -import React, { useState, useEffect, useContext, useCallback, useMemo } from 'react' -import Head from 'next/head' -import { motion, AnimatePresence } from 'framer-motion' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { - faPlus, - faTrash, - faSearch, - faFilter, - faCheck, - faShieldAlt, - faNetworkWired, - faGlobe, - faInfoCircle, - faTimes, - faExclamationTriangle, - faRefresh, - faClock -} from '@fortawesome/free-solid-svg-icons' -import { AccessControlContext } from '../providers/AccessControlProvider' -import { useTheme } from '../providers/ThemeProvider' -import { putData, deleteData } from '../utils/connectionUtils' -import { urls } from '../config' -import Layout from '../components/Layout' -import LoadingSpinner from '../components/LoadingSpinner' -import { - AccessControlItem, - NotificationProps, - ModalProps, - ToggleButtonProps, - StatsCardProps -} from '../types/AccessControlTypes' - -// 通知組件 -const Notification: React.FC = ({ message, type, onClose }) => { - useEffect(() => { - const timer = setTimeout(() => { - onClose() - }, 4000) - - return () => clearTimeout(timer) - }, [onClose]) - - const typeStyles = { - success: 'bg-green-100 border-green-400 text-green-700', - error: 'bg-red-100 border-red-400 text-red-700', - warning: 'bg-yellow-100 border-yellow-400 text-yellow-700', - info: 'bg-blue-100 border-blue-400 text-blue-700' - } - - return ( - -
- {message} - -
-
- ) -} - -// 模態框組件 -const Modal: React.FC = ({ - title, - children, - onClose, - onSubmit, - submitLabel = 'Submit', - submitDisabled = false - }) => { - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - - return ( - - - e.stopPropagation()} - > -
-
-

{title}

- -
-
- -
- {children} -
- -
- - {onSubmit && ( - - )} -
-
-
-
- ) -} - -// 切換按鈕組件 -const ToggleButton: React.FC = ({ isActive, onClick, children, className = "" }) => { - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - - return ( - - ) -} - -const StatsCard: React.FC = ({ title, value, icon, color }) => { - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - - return ( - -
-
-

{title}

-

{value.toLocaleString()}

-
-
- -
-
-
- ) -} - -// 主要組件 -const AccessControl: React.FC = () => { - const { - currentData, - filteredData, - isLoading, - setFilteredData, - switchTo, - refreshData, - getCacheStatus - } = useContext(AccessControlContext) - - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - - // 狀態管理 - const [isIPv6, setIsIPv6] = useState(false) - const [listType, setListType] = useState<'black_list' | 'white_list'>('black_list') - const [searchTerm, setSearchTerm] = useState('') - const [isModalOpen, setIsModalOpen] = useState(false) - const [newIp, setNewIp] = useState('') - const [newPort, setNewPort] = useState('') - const [blockAllPorts, setBlockAllPorts] = useState(false) - const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false) - const [itemToDelete, setItemToDelete] = useState<{ ip: string; port: string | number } | null>(null) - const [notification, setNotification] = useState<{ message: string; type: 'success' | 'error' | 'warning' | 'info' } | null>(null) - const [isSubmitting, setIsSubmitting] = useState(false) - - // 切换IP版本和列表类型 - 无跳动的平滑切换 - const handleIPVersionChange = useCallback((newIsIPv6: boolean) => { - if (newIsIPv6 !== isIPv6) { - setIsIPv6(newIsIPv6) - switchTo(newIsIPv6, listType) - } - }, [isIPv6, listType, switchTo]) - - const handleListTypeChange = useCallback((newListType: 'black_list' | 'white_list') => { - if (newListType !== listType) { - setListType(newListType) - switchTo(isIPv6, newListType) - } - }, [isIPv6, listType, switchTo]) - - // 其他回调函数 - const handleAddItemClick = useCallback(() => { - setIsModalOpen(true) - setNewIp('') - setNewPort('') - setBlockAllPorts(false) - }, []) - - const handleModalClose = useCallback(() => { - setIsModalOpen(false) - setNewIp('') - setNewPort('') - setBlockAllPorts(false) - }, []) - - // 通知功能 - const showNotification = useCallback((message: string, type: 'success' | 'error' | 'warning' | 'info' = 'success') => { - setNotification({ message, type }) - }, []) - - const closeNotification = useCallback(() => { - setNotification(null) - }, []) - - // 手动更新資料 - const handleRefresh = useCallback(async () => { - try { - await refreshData(isIPv6, listType) - showNotification('Data updated', 'success') - } catch (error) { - showNotification('Update failed', 'error') - } - }, [isIPv6, listType, refreshData, showNotification]) - - // 初始化資料 - 组件挂载时加载默认資料 - useEffect(() => { - switchTo(isIPv6, listType) - }, []) // 只在组件挂载时执行一次 - - // 搜索过滤 - 基于currentData而不是重新获取 - useEffect(() => { - if (!searchTerm.trim()) { - setFilteredData(currentData) - return - } - - const lowerSearchTerm = searchTerm.toLowerCase() - const filtered = currentData.filter(({ ip }) => - ip.toLowerCase().includes(lowerSearchTerm) - ) - setFilteredData(filtered) - }, [searchTerm, currentData, setFilteredData]) - - // 展平資料,便於表格顯示 - const flatData = useMemo(() => - filteredData.flatMap(({ ip, ports }) => - ports.map((port) => ({ ip, port })) - ), - [filteredData] - ) - - // 統計資料 - const stats = useMemo(() => { - const uniqueIPs = new Set(filteredData.map(item => item.ip)).size - const totalEntries = flatData.length - const allPortsEntries = flatData.filter(item => item.port === "0" || item.port === 0).length - - return { - uniqueIPs, - totalEntries, - allPortsEntries, - specificPortEntries: totalEntries - allPortsEntries - } - }, [filteredData, flatData]) - - // IP 驗證 - const validateIP = useCallback((ip: string, isIPv6: boolean): boolean => { - if (isIPv6) { - // 簡單的 IPv6 驗證 - const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$/ - return ipv6Regex.test(ip) || ip.includes('::') - } else { - // IPv4 驗證 - const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/ - if (!ipv4Regex.test(ip)) return false - - const parts = ip.split('.') - return parts.every(part => { - const num = parseInt(part, 10) - return num >= 0 && num <= 255 - }) - } - }, []) - - // 端口驗證 - const validatePort = useCallback((port: string): boolean => { - if (!port.trim()) return false - const portNum = parseInt(port, 10) - return !isNaN(portNum) && portNum >= 1 && portNum <= 65535 - }, []) - - // 添加項目 - const handleAddItemSubmit = useCallback(async () => { - if (!newIp.trim()) { - showNotification('Please enter a valid IP address', 'error') - return - } - - if (!validateIP(newIp.trim(), isIPv6)) { - showNotification(`Please enter a valid ${isIPv6 ? 'IPv6' : 'IPv4'} address`, 'error') - return - } - - if (!blockAllPorts && !validatePort(newPort)) { - showNotification('Please enter a valid port number (1-65535)', 'error') - return - } - - const portToSend = blockAllPorts ? "0" : newPort - const url = isIPv6 - ? urls.access_control.ipv6[listType] - : urls.access_control.ipv4[listType] - - setIsSubmitting(true) - - try { - await new Promise((resolve, reject) => { - putData( - url, - `${newIp.trim()}:${portToSend}`, - () => { - showNotification( - `Successfully added: ${newIp.trim()}:${blockAllPorts ? '*' : portToSend}`, - 'success' - ) - setIsModalOpen(false) - setNewIp('') - setNewPort('') - setBlockAllPorts(false) - // 更新当前資料 - refreshData(isIPv6, listType) - resolve() - }, - (error) => { - showNotification(`Failed to add: ${error.message}`, 'error') - reject(error) - } - ) - }) - } catch (error) { - console.error('Add item error:', error) - } finally { - setIsSubmitting(false) - } - }, [newIp, newPort, blockAllPorts, isIPv6, listType, refreshData, showNotification, validateIP, validatePort]) - - // 刪除處理 - const handleDeleteClick = useCallback((ip: string, port: string | number) => { - setItemToDelete({ ip, port }) - setIsConfirmModalOpen(true) - }, []) - - const handleDeleteCancel = useCallback(() => { - setIsConfirmModalOpen(false) - setItemToDelete(null) - }, []) - - const handleDeleteConfirm = useCallback(async () => { - if (!itemToDelete) return - - const { ip, port } = itemToDelete - const formattedIp = isIPv6 ? `[${ip}]` : ip - const url = isIPv6 - ? urls.access_control.ipv6[listType] - : urls.access_control.ipv4[listType] - - setIsSubmitting(true) - - try { - await new Promise((resolve, reject) => { - deleteData( - url, - `${formattedIp}:${port}`, - () => { - showNotification( - `Successfully deleted: ${formattedIp}:${port === "0" || port === 0 ? "*" : port}`, - 'success' - ) - // 更新当前資料 - refreshData(isIPv6, listType) - setIsConfirmModalOpen(false) - setItemToDelete(null) - resolve() - }, - (error) => { - showNotification(`Failed to delete: ${error.message}`, 'error') - setIsConfirmModalOpen(false) - setItemToDelete(null) - reject(error) - } - ) - }) - } catch (error) { - console.error('Delete item error:', error) - } finally { - setIsSubmitting(false) - } - }, [itemToDelete, isIPv6, listType, refreshData, showNotification]) - - // 搜索處理 - const handleSearchChange = useCallback((e: React.ChangeEvent) => { - setSearchTerm(e.target.value) - }, []) - - // 全部端口勾選處理 - const handleBlockAllPortsChange = useCallback((e: React.ChangeEvent) => { - setBlockAllPorts(e.target.checked) - if (e.target.checked) { - setNewPort('') - } - }, []) - - // 获取缓存状态用于调试 - const cacheStatus = getCacheStatus() - - return ( - <> - - Access Control - NetGuardia - - - - - {/* 通知 */} - - {notification && ( - - )} - - - {/* 頁面標題 */} - -
-
-

- - Access Control Management -

-

- Manage IPv4 and IPv6 network access whitelists and blacklists -

-
-
-
- - {/* 統計卡片 */} -
- - - - -
- - {/* 控制面板 */} - -
- {/* 搜索框 */} -
-
- - setSearchTerm(e.target.value)} - placeholder="Search IP Address..." - className={`w-full pl-8 pr-3 py-1 text-sm border rounded-lg focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 ${ - isDark ? 'bg-gray-700 border-gray-500 text-gray-300 placeholder-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder-gray-400' - }`} - /> -
-
- - {/* IP 版本選擇 */} -
- IP Version: -
- setIsIPv6(false)} - > - IPv4 - - setIsIPv6(true)} - > - IPv6 - -
-
- - {/* 列表類型選擇 */} -
- List Type: -
- handleListTypeChange("black_list")} - > - - Blacklist - - handleListTypeChange("white_list")} - > - - Whitelist - -
-
- - {/* 操作按鈕 */} -
- - - -
-
-
- - {/* 資料表格 */} - -
-
-

- {isIPv6 ? 'IPv6' : 'IPv4'} {listType === 'black_list' ? 'Blacklist' : 'Whitelist'} - {isLoading && ( -
-
-
- )} -

- - {flatData.length} Rules - -
-
- - {/* 简化表格切换,移除奇怪的动画 */} - {flatData.length === 0 ? ( -
- -

- {searchTerm ? 'No matching results found' : 'No data yet'} -

-

- {searchTerm ? 'Please try adjusting your search criteria' : 'Click the button above to add the first rule'} -

- {!searchTerm && ( - - )} -
- ) : ( -
- - - - - - - - - - {flatData.map(({ ip, port }, index) => ( - - - - - - ))} - -
- IP Address - - Port - - Action -
- {ip} - - - {port === "0" || port === 0 ? "All ports (*)" : port} - - - -
-
- )} -
- - {/* 添加規則模態框 */} - {isModalOpen && ( - -
-
- - setNewIp(e.target.value)} - placeholder={isIPv6 ? "2001:db8::" : "192.168.1.1"} - className={`w-full px-3 py-2 border rounded-lg focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 ${ - isDark ? 'bg-gray-700 border-gray-600 text-gray-300 placeholder-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder-gray-400' - }`} - /> -
- -
-
- - -
-
- -
- - setNewPort(e.target.value)} - placeholder="80" - disabled={blockAllPorts} - className={`w-full px-3 py-2 border rounded-lg focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 ${ - blockAllPorts - ? (isDark ? 'bg-gray-600 text-gray-500 border-gray-600' : 'bg-gray-100 text-gray-500 border-gray-300') - : (isDark ? 'bg-gray-700 border-gray-600 text-gray-300' : 'bg-white border-gray-300 text-gray-900') - }`} - /> - {blockAllPorts && ( -

- Block all ports for this IP -

- )} -
- -
-
- -
-

- Add to {listType === 'black_list' ? 'Blacklist' : 'Whitelist'} -

-

- {listType === 'black_list' - ? 'IP addresses in the Blacklist will be denied access' - : 'Only IP addresses in the Whitelist can access' - } - {blockAllPorts && ', all ports will be affected'} -

-
-
-
-
-
- )} - - {/* 確認刪除模態框 */} - {isConfirmModalOpen && ( - -
-
- - Warning -
- -

- Are you sure you want to delete this rule? -

- - {itemToDelete && ( -
-
-
- IP Address: - {itemToDelete.ip} -
-
- Port: - - {itemToDelete.port === "0" || itemToDelete.port === 0 - ? "All Ports (*)" - : itemToDelete.port - } - -
-
- List Type: - - {listType === 'black_list' ? 'Blacklist' : 'Whitelist'} - -
-
-
- )} - -
-
- -

- This action cannot be undone. Once deleted, this rule will take effect immediately. -

-
-
-
-
- )} -
- - ) -} - -export default AccessControl \ No newline at end of file diff --git a/mantis-frontend/src/pages/dashboard.tsx b/mantis-frontend/src/pages/dashboard.tsx deleted file mode 100644 index 0459af0..0000000 --- a/mantis-frontend/src/pages/dashboard.tsx +++ /dev/null @@ -1,1076 +0,0 @@ -import React, { useContext, useState, useEffect, useCallback, useRef } from 'react' -import Head from 'next/head' -import { motion } from 'framer-motion' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { - faNetworkWired, - faGlobe, - faArrowUp, - faArrowDown, - faServer, - faChartLine, - faPause, - faPlay, - faClock, - faShieldAlt -} from '@fortawesome/free-solid-svg-icons' -import { WebsocketContext } from '../providers/WebSocketProvider' -import Layout from '../components/Layout' -import LoadingSpinner from '../components/LoadingSpinner' -import { combineLatest } from 'rxjs' -import { map, catchError } from 'rxjs/operators' -import { of } from 'rxjs' -import ReactEcharts from 'echarts-for-react' -import * as echarts from 'echarts' -import { - TrendData, - TrendDataPoint, - TrafficData, - UpdateControlProps, - EChartsComponentProps -} from '../types/DashboardTypes' -import { useTheme } from "../providers/ThemeProvider" - -// 更新频率選項 (毫秒) -const UPDATE_INTERVALS = [ - { label: "immediate", value: 0 }, - { label: "0.5s", value: 500 }, - { label: "1s", value: 1000 }, - { label: "2s", value: 2000 }, - { label: "3s", value: 3000 }, - { label: "5s", value: 5000 }, - { label: "10s", value: 10000 } -]; - -// 智能格式化位元組大小(返回数值和单位) -const formatBytesWithUnit = (bytes: number): {value: number, unit: string} => { - const units = ["B", "KB", "MB", "GB", "TB"] - if (bytes < 1024) return {value: bytes, unit: "B"} - - let i = 0 - let value = bytes - - while (value >= 1024 && i < units.length - 1) { - value /= 1024 - i++ - } - - return {value: parseFloat(value.toFixed(2)), unit: units[i]} -} - -// 格式化位元組大小(用于显示) -const formatBytes = (bytes: number): string => { - const {value, unit} = formatBytesWithUnit(bytes) - return `${value} ${unit}` -} - -// 获取最适合的图表单位(更保守的切换策略) -const getBestChartUnit = (dataArray: TrendDataPoint[]): {unit: string, divisor: number} => { - if (dataArray.length === 0) return {unit: "B", divisor: 1} - - const maxBytes = Math.max(...dataArray.map(point => point.value)) - - if (maxBytes < 10 * 1024) { - return {unit: "B", divisor: 1} - } else if (maxBytes < 10 * 1024 * 1024) { - return {unit: "KB", divisor: 1024} - } else if (maxBytes < 10 * 1024 * 1024 * 1024) { - return {unit: "MB", divisor: 1024 * 1024} - } else { - return {unit: "GB", divisor: 1024 * 1024 * 1024} - } -} - -// 協議號對應名稱 -const getProtocolName = (protocol: number): string => { - switch (protocol) { - case 1: return 'ICMP' - case 6: return 'TCP' - case 17: return 'UDP' - case 47: return 'GRE' - case 50: return 'ESP' - case 51: return 'AH' - case 58: return 'ICMPv6' - default: return `Proto ${protocol}` - } -} - -// 解析流量資料 -const parseFlowData = (rawData: any): any => { - try { - if (typeof rawData === 'object' && rawData !== null) { - return rawData - } - return JSON.parse(rawData) - } catch (error) { - console.error("Error parsing flow data:", error) - return {} - } -} - -const UpdateControl: React.FC = ({ - updateInterval, - setUpdateInterval, - isPaused, - setIsPaused, - lastUpdateTime, - connectionStatus - }) => { - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - - return ( -
- {/* 更新頻率控制 */} -
- - - -
- - {/* 暫停/恢復按鈕 */} - - - {/* 連接狀態和最後更新時間 */} -
- {lastUpdateTime && ( -
- Last update: {lastUpdateTime.toLocaleTimeString()} -
- )} -
-
- ); -}; - -const EChartsComponent: React.FC = ({ data, type, isPaused }) => { - const chartRef = useRef(null) - const { actualTheme } = useTheme() - - const isDark = actualTheme === 'dark' - - const getOption = useCallback(() => { - if (!data.ingressSource.length || !data.egressSource.length) { - return {} - } - - const validateData = (dataArray: TrendDataPoint[]) => { - return dataArray.every(point => - point && - typeof point.value === 'number' && - !isNaN(point.value) && - point.time - ) - } - - if (!validateData(data.ingressSource) || !validateData(data.egressSource)) { - console.warn(`Invalid chart data detected for ${type}, skipping update`) - return {} - } - - const colors = type === 'ipv4' - ? ['#3B82F6', '#EF4444'] - : ['#10B981', '#F59E0B'] - - const timeLabels = data.ingressSource.map(point => point.time) - - const allData = [...data.ingressSource, ...data.egressSource] - const {unit, divisor} = getBestChartUnit(allData) - - const ingressValues = data.ingressSource.map(point => - typeof point.value === 'number' && !isNaN(point.value) ? - Math.max(0, point.value / divisor) : 0 - ) - const egressValues = data.egressSource.map(point => - typeof point.value === 'number' && !isNaN(point.value) ? - Math.max(0, point.value / divisor) : 0 - ) - - return { - animation: false, - - title: { - text: '', - left: 'center', - textStyle: { - fontSize: 16, - fontWeight: 'bold', - color: isPaused ? '#F59E0B' : (isDark ? '#E5E7EB' : '#374151') - } - }, - tooltip: { - trigger: 'axis', - axisPointer: { - type: 'cross', - label: { - backgroundColor: isDark ? '#4B5563' : '#6a7985' - } - }, - backgroundColor: isDark ? 'rgba(31, 41, 55, 0.95)' : 'rgba(0, 0, 0, 0.8)', - borderColor: isDark ? '#4B5563' : 'transparent', - textStyle: { - color: isDark ? '#F3F4F6' : '#fff' - }, - formatter: function(params: any) { - let result = `Time: ${params[0].name}
` - let totalTraffic = 0 - params.forEach((param: any) => { - const value = typeof param.value === 'number' ? param.value : 0 - result += `${param.seriesName}: ${value.toFixed(2)} ${unit}
` - totalTraffic += value - }) - result += `Total traffic: ${totalTraffic.toFixed(2)} ${unit}` - return result - } - }, - legend: { - data: [ - `${type.toUpperCase()} Ingress Traffic`, - `${type.toUpperCase()} Egress Traffic` - ], - top: 30, - textStyle: { - color: isDark ? '#D1D5DB' : '#6B7280' - } - }, - grid: { - left: '3%', - right: '4%', - bottom: '3%', - top: '60px', - containLabel: true - }, - xAxis: { - type: 'category', - boundaryGap: false, - data: timeLabels, - axisLabel: { - color: isDark ? '#9CA3AF' : '#6B7280', - rotate: 45, - interval: Math.max(1, Math.floor(timeLabels.length / 6)) - }, - axisLine: { - lineStyle: { - color: isDark ? '#F3F4F6' : '#858997', - } - }, - splitLine: { - show: false - } - }, - yAxis: { - type: 'value', - name: `Traffic (${unit})`, - nameTextStyle: { - color: isDark ? '#9CA3AF' : '#6B7280' - }, - axisLabel: { - color: isDark ? '#9CA3AF' : '#6B7280', - formatter: `{value} ${unit}` - }, - axisLine: { - lineStyle: { - color: isDark ? '#4B5563' : '#E5E7EB' - } - }, - splitLine: { - lineStyle: { - color: isDark ? '#F3F4F6' : '#858997' - } - }, - min: 0 - }, - series: [ - { - name: `${type.toUpperCase()} Ingress Traffic`, - type: 'line', - smooth: true, - smoothMonotone: 'x', - symbol: 'circle', - symbolSize: 4, - showSymbol: true, - itemStyle: { color: colors[0] }, - lineStyle: { width: 2.5, color: colors[0], cap: 'round', join: 'round' }, - areaStyle: { - color: { - type: 'linear', x: 0, y: 0, x2: 0, y2: 1, - colorStops: [ - { offset: 0, color: colors[0] + '80' }, - { offset: 1, color: colors[0] + '20' } - ] - } - }, - data: ingressValues, - emphasis: { - focus: 'series', - lineStyle: { width: 3 }, - itemStyle: { shadowBlur: 10, shadowColor: colors[0] + '80' } - }, - animation: false - }, - { - name: `${type.toUpperCase()} Egress Traffic`, - type: 'line', - smooth: true, - smoothMonotone: 'x', - symbol: 'circle', - symbolSize: 4, - showSymbol: true, - itemStyle: { color: colors[1] }, - lineStyle: { width: 2.5, color: colors[1], cap: 'round', join: 'round' }, - areaStyle: { - color: { - type: 'linear', x: 0, y: 0, x2: 0, y2: 1, - colorStops: [ - { offset: 0, color: colors[1] + '80' }, - { offset: 1, color: colors[1] + '20' } - ] - } - }, - data: egressValues, - emphasis: { - focus: 'series', - lineStyle: { width: 3 }, - itemStyle: { shadowBlur: 10, shadowColor: colors[1] + '80' } - }, - animation: false - } - ] - } - }, [data, type, isPaused, isDark]) - - const onChartReady = useCallback((chart: any) => { - console.log(`${type} chart ready`) - }, [type]) - - const onEvents = { - 'click': (params: any) => { - console.log('Chart clicked:', params) - } - } - - return ( - - ) -} - -const DONUT_COLORS: Array<{ start: string; end: string }> = [ - { start: '#EF4444', end: '#DC2626' }, - { start: '#F59E0B', end: '#D97706' }, - { start: '#3B82F6', end: '#2563EB' }, - { start: '#10B981', end: '#059669' }, - { start: '#8B5CF6', end: '#7C3AED' }, - { start: '#EC4899', end: '#DB2777' }, - { start: '#F97316', end: '#EA580C' }, - { start: '#06B6D4', end: '#0891B2' }, - { start: '#84CC16', end: '#65A30D' }, - { start: '#6366F1', end: '#4F46E5' } -] - -const PROTOCOL_COLORS: Array<{ start: string; end: string }> = [ - { start: '#3B82F6', end: '#2563EB' }, - { start: '#10B981', end: '#059669' }, - { start: '#F59E0B', end: '#D97706' }, - { start: '#8B5CF6', end: '#7C3AED' }, - { start: '#EC4899', end: '#DB2777' }, - { start: '#06B6D4', end: '#0891B2' }, - { start: '#EF4444', end: '#DC2626' }, - { start: '#F97316', end: '#EA580C' }, - { start: '#84CC16', end: '#65A30D' }, - { start: '#6366F1', end: '#4F46E5' } -] - -const DonutChart: React.FC<{ - data: Record - colors?: Array<{ start: string; end: string }> - totalLabel?: string -}> = ({ data, colors = DONUT_COLORS, totalLabel = 'Total' }) => { - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - - const chartData = Object.entries(data) - .map(([name, value]) => ({ name, value })) - .sort((a, b) => b.value - a.value) - - const totalAttacks = chartData.reduce((sum, item) => sum + item.value, 0) - - const getOption = () => { - if (chartData.length === 0) { - return { - graphic: { - elements: [{ - type: 'group', - left: 'center', - top: 'center', - children: [ - { - type: 'text', - style: { - text: 'Waiting for Data...', - fontSize: 14, - fill: isDark ? '#6B7280' : '#9CA3AF', - textAlign: 'center' - } - } - ] - }] - } - } - } - - return { - graphic: { elements: [] }, - tooltip: { - trigger: 'item', - backgroundColor: isDark ? 'rgba(31, 41, 55, 0.95)' : 'rgba(255, 255, 255, 0.95)', - borderColor: isDark ? '#4B5563' : '#E5E7EB', - borderWidth: 1, - textStyle: { - color: isDark ? '#F3F4F6' : '#1F2937' - }, - formatter: (params: any) => { - const colorDot = `` - return `${colorDot}${params.name}
Count: ${params.value}
Proportion: ${params.percent}%` - } - }, - series: [ - { - type: 'pie', - radius: ['50%', '78%'], - center: ['50%', '50%'], - silent: true, - avoidLabelOverlap: true, - itemStyle: { - borderRadius: 6, - borderColor: isDark ? '#4B5563' : '#fff', - borderWidth: 3 - }, - label: { - show: true, - position: 'center', - formatter: () => `{total|${totalAttacks}}\n{label|${totalLabel}}`, - rich: { - total: { - fontSize: 28, - fontWeight: 'bold', - color: isDark ? '#F3F4F6' : '#1F2937', - lineHeight: 36 - }, - label: { - fontSize: 13, - color: isDark ? '#9CA3AF' : '#6B7280', - lineHeight: 22 - } - } - }, - labelLine: { - show: false - }, - data: chartData.map((item, index) => { - const colorPair = colors[index % colors.length] - return { - ...item, - itemStyle: { - color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ - { offset: 0, color: colorPair.start }, - { offset: 1, color: colorPair.end } - ]) - } - } - }), - animationType: 'scale', - animationEasing: 'elasticOut', - animationDelay: () => Math.random() * 200 - } - ] - } - } - - return ( -
- - {chartData.length > 0 && ( -
- {chartData.map((item, index) => { - const colorPair = colors[index % colors.length] - const percent = totalAttacks > 0 ? ((item.value / totalAttacks) * 100).toFixed(1) : '0' - return ( -
-
- - - {item.name} - -
-
- - {item.value} - - - {percent}% - -
-
- ) - })} -
- )} -
- ) -} - -const Dashboard: React.FC = () => { - const { actualTheme } = useTheme() - const { getIPv4FlowStream, getIPv6FlowStream, getDetectionAlertStream } = useContext(WebsocketContext) - const [isLoading, setIsLoading] = useState(true) - const [connectionStatus, setConnectionStatus] = useState<'connecting' | 'connected' | 'error'>('connecting') - const [networkData, setNetworkData] = useState({}) - - const [updateInterval, setUpdateInterval] = useState(2000) - const [isPaused, setIsPaused] = useState(false) - const [lastUpdateTime, setLastUpdateTime] = useState(null) - - const [apiTimeRange, setApiTimeRange] = useState("1min") - - const [attackTypeCounts, setAttackTypeCounts] = useState>({}) - const [protocolCounts, setProtocolCounts] = useState>({}) - - const latestDataRef = useRef({}) - const lastUpdateRef = useRef(0) - const updateIntervalRef = useRef(updateInterval) - const isPausedRef = useRef(isPaused) - - const isDark = actualTheme === 'dark' - - useEffect(() => { updateIntervalRef.current = updateInterval }, [updateInterval]) - useEffect(() => { isPausedRef.current = isPaused }, [isPaused]) - - const [trendDataIPv4, setTrendDataIPv4] = useState({ - ingressSource: [], - egressSource: [] - }) - - const [trendDataIPv6, setTrendDataIPv6] = useState({ - ingressSource: [], - egressSource: [] - }) - - const [trafficData, setTrafficData] = useState({ - ipv4: { ingressSource: 0, egressSource: 0 }, - ipv6: { ingressSource: 0, egressSource: 0 } - }) - - const calculateThroughput = useCallback(( - ipVersion: string, - direction: string, - trafficType: string - ): number => { - try { - const key = `${direction}_${trafficType}` - const connections = latestDataRef.current[key]?.[ipVersion] || {} - let totalBytes = 0 - - for (const connection in connections) { - const connectionData = connections[connection] - if (connectionData && typeof connectionData.bytes === "number" && connectionData.bytes >= 0) { - totalBytes += connectionData.bytes - } - } - - return Math.max(0, totalBytes) - } catch (error) { - console.error(`Error calculating throughput for ${ipVersion} ${direction} ${trafficType}:`, error) - return 0 - } - }, []) - - const updateChartData = useCallback(() => { - if (isPausedRef.current) return; - - const ipv4IngressSource = calculateThroughput("ipv4", "ingress", "source") - const ipv4EgressSource = calculateThroughput("ipv4", "egress", "source") - const ipv6IngressSource = calculateThroughput("ipv6", "ingress", "source") - const ipv6EgressSource = calculateThroughput("ipv6", "egress", "source") - - setTrafficData({ - ipv4: { ingressSource: ipv4IngressSource, egressSource: ipv4EgressSource }, - ipv6: { ingressSource: ipv6IngressSource, egressSource: ipv6EgressSource } - }) - - const currentTime = new Date().toLocaleTimeString('en-GB', { - hour12: false, - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }) - - const updateTrendArray = (prevArray: TrendDataPoint[], newValue: number) => { - const maxPoints = 16 - const newPoint = { time: currentTime, value: Math.max(0, newValue) } - - if (prevArray.length < maxPoints) { - return [...prevArray, newPoint] - } - - return [...prevArray.slice(1), newPoint] - } - - setTrendDataIPv4(prev => ({ - ingressSource: updateTrendArray(prev.ingressSource, ipv4IngressSource), - egressSource: updateTrendArray(prev.egressSource, ipv4EgressSource) - })) - - setTrendDataIPv6(prev => ({ - ingressSource: updateTrendArray(prev.ingressSource, ipv6IngressSource), - egressSource: updateTrendArray(prev.egressSource, ipv6EgressSource) - })) - - setLastUpdateTime(new Date()) - }, [calculateThroughput]) - - useEffect(() => { - if (!isPaused && Object.keys(latestDataRef.current).length > 0) { - updateChartData() - lastUpdateRef.current = Date.now() - } - }, [isPaused, updateChartData]) - - useEffect(() => { - if (updateInterval === 0 || isPaused) { - return - } - - const intervalId = setInterval(() => { - const now = Date.now() - if (Object.keys(latestDataRef.current).length > 0 && - (now - lastUpdateRef.current) >= updateInterval && - !isPausedRef.current) { - updateChartData() - lastUpdateRef.current = now - } - }, Math.min(updateInterval / 4, 1000)) - - return () => clearInterval(intervalId) - }, [updateInterval, isPaused, updateChartData]) - - useEffect(() => { - if (!getIPv4FlowStream || !getIPv6FlowStream) { - console.warn("WebSocket streams not available") - setConnectionStatus('error') - setIsLoading(false) - return - } - - return () => { - console.log("Component unmounting - cleaning up WebSocket subscriptions") - } - }, [getIPv4FlowStream, getIPv6FlowStream]) - - useEffect(() => { - if (!getIPv4FlowStream || !getIPv6FlowStream) { - setConnectionStatus('error') - setIsLoading(false) - return - } - - console.log(`[TIME RANGE CHANGE] Switching to ${apiTimeRange}`) - - setTrendDataIPv4({ ingressSource: [], egressSource: [] }) - setTrendDataIPv6({ ingressSource: [], egressSource: [] }) - setNetworkData({}) - latestDataRef.current = {} - setConnectionStatus('connecting') - - const PROTOCOLS = ["ipv4", "ipv6"] as const - const DIRECTIONS = ["ingress", "egress"] as const - const FLOW_DIRECTIONS = ["source", "destination"] as const - const TIME_RANGES = ["1min", "10min", "1hour"] as const - - const streamConfigs: Array<{ - protocol: typeof PROTOCOLS[number] - direction: typeof DIRECTIONS[number] - flowDirection: typeof FLOW_DIRECTIONS[number] - range: typeof TIME_RANGES[number] - }> = [] - - PROTOCOLS.forEach(protocol => { - DIRECTIONS.forEach(direction => { - FLOW_DIRECTIONS.forEach(flowDirection => { - TIME_RANGES.forEach(range => { - streamConfigs.push({ protocol, direction, flowDirection, range }) - }) - }) - }) - }) - - const streams = streamConfigs.map(config => { - const { protocol, direction, flowDirection } = config - const getStream = protocol === "ipv4" ? getIPv4FlowStream : getIPv6FlowStream - - return getStream(direction, flowDirection, apiTimeRange).pipe( - map((rawData: any) => { - if (rawData === null || rawData === undefined) { - return { key: `${direction}_${flowDirection}`, protocol, data: {} } - } - - const parsedData = parseFlowData(rawData) - - if (!parsedData || typeof parsedData !== 'object') { - return { key: `${direction}_${flowDirection}`, protocol, data: {} } - } - - return { key: `${direction}_${flowDirection}`, protocol, data: parsedData } - }), - catchError(error => { - console.error(`${protocol} ${direction}/${flowDirection} (${apiTimeRange}):`, error) - return of({ key: `${direction}_${flowDirection}`, protocol, data: {} }) - }) - ) - }) - - const subscription = combineLatest(streams).subscribe({ - next: (results) => { - const validResults = results.filter(result => result !== null && result.data !== null) - - if (validResults.length === 0) { - setConnectionStatus('connected') - setIsLoading(false) - return - } - - const newData: any = {} - validResults.forEach(result => { - const { key, protocol, data } = result - if (!newData[key]) newData[key] = {} - newData[key][protocol] = data || {} - }) - - if (Object.keys(newData).length > 0) { - latestDataRef.current = { ...latestDataRef.current, ...newData } - - setNetworkData((prevData: any) => { - const updatedData = { ...prevData } - Object.entries(newData).forEach(([key, protocolData]: [string, any]) => { - if (!updatedData[key]) updatedData[key] = {} - Object.entries(protocolData || {}).forEach(([protocol, data]) => { - updatedData[key][protocol] = data - }) - }) - return updatedData - }) - - const now = Date.now() - const currentUpdateInterval = updateIntervalRef.current - const currentIsPaused = isPausedRef.current - - const shouldUpdate = !currentIsPaused && - (currentUpdateInterval === 0 || (now - lastUpdateRef.current) >= currentUpdateInterval) - - if (shouldUpdate) { - updateChartData() - lastUpdateRef.current = now - } - - setConnectionStatus('connected') - setIsLoading(false) - } - }, - error: (error) => { - console.error(`(${apiTimeRange}):`, error) - setConnectionStatus('error') - setIsLoading(false) - } - }) - - return () => { - console.log(`Cleaning up subscription for ${apiTimeRange}`) - subscription.unsubscribe() - } - }, [getIPv4FlowStream, getIPv6FlowStream, apiTimeRange, updateChartData]) - - // Subscribe to detection alerts for attack type / protocol breakdown charts - useEffect(() => { - if (!getDetectionAlertStream) return - - const subscription = getDetectionAlertStream().subscribe({ - next: (rawData: any) => { - try { - const data = typeof rawData === 'string' ? JSON.parse(rawData) : rawData - if (!data || !data.is_attack) return - - if (data.attack_type) { - setAttackTypeCounts(prev => ({ - ...prev, - [data.attack_type]: (prev[data.attack_type] || 0) + 1 - })) - } - - if (data.protocol !== undefined) { - const protoName = getProtocolName(data.protocol) - setProtocolCounts(prev => ({ - ...prev, - [protoName]: (prev[protoName] || 0) + 1 - })) - } - } catch (error) { - console.error("Error parsing detection alert for dashboard:", error) - } - }, - error: (error: any) => { - console.error("Detection alert stream error in dashboard:", error) - } - }) - - return () => subscription.unsubscribe() - }, [getDetectionAlertStream]) - - const statsCards = [ - { - title: 'IPv4 Ingress Traffic', - value: formatBytes(trafficData.ipv4.ingressSource), - icon: faGlobe, - iconColor: 'text-blue-600', - bgColor: 'bg-blue-50' - }, - { - title: 'IPv4 Egress Traffic', - value: formatBytes(trafficData.ipv4.egressSource), - icon: faNetworkWired, - iconColor: 'text-red-600', - bgColor: 'bg-red-50' - }, - { - title: 'IPv6 Ingress Traffic', - value: formatBytes(trafficData.ipv6.ingressSource), - icon: faGlobe, - iconColor: 'text-green-600', - bgColor: 'bg-green-50' - }, - { - title: 'IPv6 Egress Traffic', - value: formatBytes(trafficData.ipv6.egressSource), - icon: faNetworkWired, - iconColor: 'text-yellow-600', - bgColor: 'bg-yellow-50' - } - ] - - if (isLoading) { - return ( - <> - Dashboard - NetGuardia - -
-
- -
-
-
- - ) - } - - if (connectionStatus === 'error') { - return ( - <> - Dashboard - NetGuardia - -
-
-
- -
-

Connection Error

-

Unable to establish WebSocket connection. Please check your network status.

- -
-
-
- - ) - } - - return ( - <> - - Dashboard - NetGuardia - - - - - {/* 頁面標題 */} - -
-
-

- Network Traffic Monitoring -

-

- Real-time monitoring of IPv4 and IPv6 network traffic status -

-
- -
- {/* API 時間範圍控制 */} -
- - - -
- - -
-
-
- - {/* 統計卡片 */} -
- {statsCards.map((card, index) => ( - -
-
-

- {card.title} -

-

- {card.value} -

-
-
- -
-
-
- ))} -
- - {/* 圖表區域 */} -
- -
- -

IPv4 Network Usage Trends

-
- -
- - -
- -

IPv6 Network Usage Trends

-
- -
-
- - {/* 攻擊統計圓餅圖 */} -
- -
- -

Attack Type Distribution

-
- -
- - -
- -

Attack Protocol Distribution

-
- -
-
-
- - ) -} - -export default Dashboard \ No newline at end of file diff --git a/mantis-frontend/src/pages/detection.tsx b/mantis-frontend/src/pages/detection.tsx deleted file mode 100644 index ad7ce42..0000000 --- a/mantis-frontend/src/pages/detection.tsx +++ /dev/null @@ -1,856 +0,0 @@ -import React, { useContext, useEffect, useState, useCallback, useMemo, useRef } from 'react' -import Head from 'next/head' -import { motion, AnimatePresence } from 'framer-motion' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { - faRobot, - faShieldAlt, - faExclamationTriangle, - faFilter, - faCheck, - faGlobe, - faTimes, - faClock, - faArrowRight, - faEye, - faList, - faPause, - faPlay, -} from '@fortawesome/free-solid-svg-icons' -import { WebsocketContext } from '../providers/WebSocketProvider' -import { useTheme } from '../providers/ThemeProvider' -import { putData } from '../utils/connectionUtils' -import { urls } from '../config' -import Layout from '../components/Layout' -import LoadingSpinner from '../components/LoadingSpinner' - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -type AlertSource = 'ml' | 'rule' | 'fusion' -type AlertSeverity = 'high' | 'critical' - -interface UnifiedAlert { - timestamp: number - flow_key: string - src_ip: string - dst_ip: string - src_port: number - dst_port: number - protocol: number - source: AlertSource - severity: AlertSeverity - is_attack: boolean - attack_type: string | null - confidence: number - ae_score: number - rule_sid: number | null - rule_msg: string | null -} - -interface PriorityInfo { - color: string - bgColor: string - label: string -} - -interface NotificationProps { - message: string - type: 'success' | 'error' | 'warning' | 'info' - onClose: () => void -} - -interface AlertDetailsProps { - log: UnifiedAlert - onClose: () => void - showNotification: (message: string, type: 'success' | 'error' | 'warning' | 'info') => void -} - -interface AlertItemProps { - log: UnifiedAlert - index: number - onClick: (index: number) => void -} - -interface FilterButtonProps { - isActive: boolean - onClick: () => void - children: React.ReactNode - className?: string - activeClassName?: string - activeHoverClassName?: string -} - -// --------------------------------------------------------------------------- -// Constants & helpers -// --------------------------------------------------------------------------- - -const UPDATE_INTERVALS = [ - { label: 'immediate', value: 0 }, - { label: '1s', value: 1000 }, - { label: '3s', value: 3000 }, - { label: '5s', value: 5000 }, - { label: '10s', value: 10000 }, -] - -const SOURCE_CONFIG: Record = { - ml: { label: 'ML', color: '#7c3aed', bgColor: 'rgba(124,58,237,0.1)' }, - rule: { label: 'Rule', color: '#0284c7', bgColor: 'rgba(2,132,199,0.1)' }, - fusion: { label: 'Fusion', color: '#0f766e', bgColor: 'rgba(15,118,110,0.1)' }, -} - -const SEVERITY_CONFIG: Record = { - critical: { color: '#e53e3e', bgColor: 'rgba(239,68,68,0.1)', label: 'Critical' }, - high: { color: '#dd6b20', bgColor: 'rgba(251,146,60,0.1)', label: 'High' }, -} - -const getProtocolName = (protocol: number): string => { - switch (protocol) { - case 1: return 'ICMP' - case 6: return 'TCP' - case 17: return 'UDP' - case 47: return 'GRE' - case 50: return 'ESP' - case 51: return 'AH' - case 58: return 'ICMPv6' - default: return `Proto ${protocol}` - } -} - -const formatTimestamp = (timestamp: number): string => { - if (!timestamp) return 'N/A' - return new Date(timestamp * 1000).toLocaleString('en-GB', { - year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', - }) -} - -const parseAlertData = (rawData: any): UnifiedAlert | null => { - try { - const data = typeof rawData === 'string' ? JSON.parse(rawData) : rawData - if (!data || typeof data !== 'object') return null - - const source: AlertSource = data.source === 'ml' || data.source === 'rule' || data.source === 'fusion' - ? data.source : 'ml' - const severity: AlertSeverity = data.severity === 'critical' ? 'critical' : 'high' - - return { - timestamp: data.timestamp ?? 0, - flow_key: data.flow_key ?? '', - src_ip: data.src_ip ?? 'Unknown', - dst_ip: data.dst_ip ?? 'Unknown', - src_port: data.src_port ?? 0, - dst_port: data.dst_port ?? 0, - protocol: data.protocol ?? 0, - source, - severity, - is_attack: data.is_attack ?? false, - attack_type: data.attack_type ?? null, - confidence: data.confidence ?? 0, - ae_score: data.ae_score ?? 0, - rule_sid: data.rule_sid ?? null, - rule_msg: data.rule_msg ?? null, - } - } catch { - return null - } -} - -// --------------------------------------------------------------------------- -// Notification -// --------------------------------------------------------------------------- - -const Notification: React.FC = ({ message, type, onClose }) => { - useEffect(() => { - const timer = setTimeout(onClose, 4000) - return () => clearTimeout(timer) - }, [onClose]) - - const typeStyles = { - success: 'bg-green-100 border-green-400 text-green-700', - error: 'bg-red-100 border-red-400 text-red-700', - warning: 'bg-yellow-100 border-yellow-400 text-yellow-700', - info: 'bg-blue-100 border-blue-400 text-blue-700', - } - - return ( - -
- {message} - -
-
- ) -} - -// --------------------------------------------------------------------------- -// AlertDetails -// --------------------------------------------------------------------------- - -const AlertDetails: React.FC = ({ log, onClose, showNotification }) => { - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - const priorityInfo = SEVERITY_CONFIG[log.severity] - const sourceInfo = SOURCE_CONFIG[log.source] - - const isIPv6Source = log.src_ip.includes(':') && !log.src_ip.includes('.') - const isIPv6Dest = log.dst_ip.includes(':') && !log.dst_ip.includes('.') - - const handleBlockIP = useCallback((ip: string, port: number, isSource: boolean, blockAllPorts = false) => { - const isIPv6 = isSource ? isIPv6Source : isIPv6Dest - const formattedIp = isIPv6 ? `[${ip}]` : ip - const url = isIPv6 ? urls.access_control.ipv6.black_list : urls.access_control.ipv4.black_list - const portToSend = blockAllPorts ? '0' : String(port) - const displayPort = blockAllPorts ? '*' : String(port) - - putData( - url, - `${formattedIp}:${portToSend}`, - () => showNotification(`Successfully added to blacklist: ${ip}:${displayPort}`, 'success'), - (error) => showNotification(`Failed to add to blacklist: ${error.message}`, 'error'), - ) - }, [isIPv6Source, isIPv6Dest, showNotification]) - - const handleAllowIP = useCallback((ip: string, port: number, isSource: boolean, allowAllPorts = false) => { - const isIPv6 = isSource ? isIPv6Source : isIPv6Dest - const formattedIp = isIPv6 ? `[${ip}]` : ip - const url = isIPv6 ? urls.access_control.ipv6.white_list : urls.access_control.ipv4.white_list - const portToSend = allowAllPorts ? '0' : String(port) - const displayPort = allowAllPorts ? '*' : String(port) - - putData( - url, - `${formattedIp}:${portToSend}`, - () => showNotification(`Successfully added to whitelist: ${ip}:${displayPort}`, 'success'), - (error) => showNotification(`Failed to add to whitelist: ${error.message}`, 'error'), - ) - }, [isIPv6Source, isIPv6Dest, showNotification]) - - return ( - - - {/* Header */} -
-

- - Alert Details -

- -
- - {/* Basic info */} -
-
- Timestamp: - {formatTimestamp(log.timestamp)} -
-
- Severity: - - - {priorityInfo.label} - -
-
- Source: - - {sourceInfo.label} - -
-
- Attack Type: - {log.attack_type ?? '—'} -
-
- Protocol: - {getProtocolName(log.protocol)} -
-
- Is Attack: - - {log.is_attack ? 'Yes' : 'No'} - -
-
- - {/* ML scores — only when source is ml or fusion */} - {(log.source === 'ml' || log.source === 'fusion') && ( -
- ML Score Details: -
- {[ - { label: 'Confidence', value: log.confidence }, - { label: 'AE Score', value: log.ae_score }, - ].map(({ label, value }) => ( -
- {label}: - = 1.0 ? 'text-red-500' : - value >= 0.5 ? 'text-orange-500' : - value >= 0.1 ? 'text-yellow-500' : - 'text-green-500' - }`}> - {value.toFixed(4)} - -
- ))} -
-
- )} - - {/* Rule details — only when source is rule or fusion */} - {(log.source === 'rule' || log.source === 'fusion') && (log.rule_sid !== null || log.rule_msg !== null) && ( -
- Rule Match: -
- {log.rule_sid !== null && ( -
- SID: - {log.rule_sid} -
- )} - {log.rule_msg !== null && ( -
- Message: - {log.rule_msg} -
- )} -
-
- )} - - {/* Connection details */} -
-

Connection Details

-
-
-
Source
-
{log.src_ip}
-
Port: {log.src_port}
-
- -
-
Destination
-
{log.dst_ip}
-
Port: {log.dst_port}
-
-
-
- - {/* IP management */} -
-

IP Management

- - {[ - { label: 'Source IP', ip: log.src_ip, port: log.src_port, isSource: true, bgClass: isDark ? 'bg-blue-900/30' : 'bg-blue-50' }, - { label: 'Destination IP', ip: log.dst_ip, port: log.dst_port, isSource: false, bgClass: isDark ? 'bg-red-900/30' : 'bg-red-50' }, - ].map(({ label, ip, port, isSource, bgClass }) => ( -
-
{label}: {ip}
-
- - - - -
-
- ))} -
-
-
- ) -} - -// --------------------------------------------------------------------------- -// AlertItem -// --------------------------------------------------------------------------- - -const AlertItem: React.FC = ({ log, index, onClick }) => { - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - const priorityInfo = SEVERITY_CONFIG[log.severity] - const sourceInfo = SOURCE_CONFIG[log.source] - - const handleClick = useCallback(() => onClick(index), [index, onClick]) - - return ( - -
-
- -
-
-
- - {formatTimestamp(log.timestamp)} - -
- - {sourceInfo.label} - - {log.attack_type && ( - - {log.attack_type} - - )} -
-
-
- {log.src_ip}:{log.src_port} - - {log.dst_ip}:{log.dst_port} - ({getProtocolName(log.protocol)}) -
-
- - {priorityInfo.label} - - {(log.source === 'ml' || log.source === 'fusion') && ( - - {log.confidence.toFixed(4)} - - )} - {(log.source === 'rule' || log.source === 'fusion') && log.rule_sid !== null && ( - - SID {log.rule_sid} - - )} -
-
-
-
- ) -} - -// --------------------------------------------------------------------------- -// FilterButton -// --------------------------------------------------------------------------- - -const FilterButton: React.FC = ({ - isActive, onClick, children, - className = '', activeClassName = '', activeHoverClassName = '', - }) => { - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - - return ( - - ) -} - -// --------------------------------------------------------------------------- -// Main page -// --------------------------------------------------------------------------- - -const Detection: React.FC = () => { - const { getDetectionAlertStream } = useContext(WebsocketContext) - const { actualTheme } = useTheme() - const isDark = actualTheme === 'dark' - - const [logs, setLogs] = useState([]) - const [selectedLogIndex, setSelectedLogIndex] = useState(null) - const [severityFilter, setSeverityFilter] = useState('all') - const [sourceFilter, setSourceFilter] = useState('all') - const [notification, setNotification] = useState<{ message: string; type: 'success' | 'error' | 'warning' | 'info' } | null>(null) - const [isLoading, setIsLoading] = useState(true) - const [isPaused, setIsPaused] = useState(false) - const [updateInterval, setUpdateInterval] = useState(0) - const [lastUpdateTime, setLastUpdateTime] = useState(null) - - const bufferRef = useRef([]) - const updateIntervalRef = useRef(updateInterval) - const isPausedRef = useRef(isPaused) - - useEffect(() => { updateIntervalRef.current = updateInterval }, [updateInterval]) - useEffect(() => { isPausedRef.current = isPaused }, [isPaused]) - - const showNotification = useCallback((message: string, type: 'success' | 'error' | 'warning' | 'info' = 'success') => { - setNotification({ message, type }) - }, []) - - const closeNotification = useCallback(() => setNotification(null), []) - - const flushBuffer = useCallback(() => { - if (bufferRef.current.length === 0) return - const newEntries = [...bufferRef.current] - bufferRef.current = [] - setLogs(prev => [...newEntries, ...prev].slice(0, 200)) - setLastUpdateTime(new Date()) - }, []) - - useEffect(() => { - if (updateInterval === 0 || isPaused) return - const timer = setInterval(flushBuffer, updateInterval) - return () => clearInterval(timer) - }, [updateInterval, isPaused, flushBuffer]) - - // Reads isPaused via ref so this callback never changes, avoiding WS re-subscription on pause toggle - const processNewLogData = useCallback((rawData: any) => { - if (isPausedRef.current) return - const alert = parseAlertData(rawData) - if (!alert) return - - if (updateIntervalRef.current === 0) { - setLogs(prev => [alert, ...prev.slice(0, 199)]) - setLastUpdateTime(new Date()) - } else { - bufferRef.current.push(alert) - } - }, []) - - useEffect(() => { - if (!getDetectionAlertStream) { - setIsLoading(false) - return - } - - const loadingTimeout = setTimeout(() => setIsLoading(false), 3000) - - const subscription = getDetectionAlertStream().subscribe({ - next: (data: any) => { - clearTimeout(loadingTimeout) - processNewLogData(data) - setIsLoading(false) - }, - error: () => { - clearTimeout(loadingTimeout) - setIsLoading(false) - }, - }) - - return () => { - clearTimeout(loadingTimeout) - subscription.unsubscribe() - } - }, [getDetectionAlertStream, processNewLogData]) - - const filteredLogs = useMemo(() => { - return logs.filter(log => { - const severityOk = severityFilter === 'all' || log.severity === severityFilter - const sourceOk = sourceFilter === 'all' || log.source === sourceFilter - return severityOk && sourceOk - }) - }, [logs, severityFilter, sourceFilter]) - - const stats = useMemo(() => ({ - total: logs.length, - critical: logs.filter(l => l.severity === 'critical').length, - high: logs.filter(l => l.severity === 'high').length, - ml: logs.filter(l => l.source === 'ml').length, - rule: logs.filter(l => l.source === 'rule').length, - fusion: logs.filter(l => l.source === 'fusion').length, - uniqueIPs: new Set([...logs.map(l => l.src_ip), ...logs.map(l => l.dst_ip)]).size, - }), [logs]) - - const handleAlertClick = useCallback((index: number) => setSelectedLogIndex(index), []) - const handleCloseDetails = useCallback(() => setSelectedLogIndex(null), []) - const togglePause = useCallback(() => setIsPaused(p => !p), []) - - if (isLoading) { - return ( - <> - Detection - NetGuardia - -
- -
-
- - ) - } - - return ( - <> - - Detection - NetGuardia - - - - - - {notification && ( - - )} - - - {/* Page header */} - -

- - Threat Detection -

-

- Real-time alerts from ML inference, rule matching, and fusion engine -

-
- - {/* Stats cards */} -
- {[ - { label: 'Total', value: stats.total, icon: faList, bg: 'bg-blue-500' }, - { label: 'Critical', value: stats.critical, icon: faExclamationTriangle, bg: 'bg-red-500' }, - { label: 'High', value: stats.high, icon: faExclamationTriangle, bg: 'bg-orange-500' }, - { label: 'ML', value: stats.ml, icon: faRobot, bg: 'bg-purple-500' }, - { label: 'Rule', value: stats.rule, icon: faShieldAlt, bg: 'bg-sky-500' }, - { label: 'Fusion', value: stats.fusion, icon: faShieldAlt, bg: 'bg-teal-500' }, - { label: 'Unique IPs', value: stats.uniqueIPs, icon: faGlobe, bg: 'bg-indigo-500' }, - ].map(({ label, value, icon, bg }, i) => ( - -
-
-

{label}

-

{value}

-
-
- -
-
-
- ))} -
- - {/* Controls & filters */} - - {/* System controls */} -
-
- - Update Interval: - -
- - - - {lastUpdateTime && ( -
- Last Update: {lastUpdateTime.toLocaleTimeString()} -
- )} -
- - {/* Severity filter */} -
- Severity: - { setSeverityFilter('all'); setSelectedLogIndex(null) }}> - All - - { setSeverityFilter('critical'); setSelectedLogIndex(null) }} - activeClassName="bg-red-600 text-white shadow-lg" activeHoverClassName="hover:bg-red-700" - > - Critical ({stats.critical}) - - { setSeverityFilter('high'); setSelectedLogIndex(null) }} - activeClassName="bg-orange-600 text-white shadow-lg" activeHoverClassName="hover:bg-orange-700" - > - High ({stats.high}) - -
- - {/* Source filter */} -
- Source: - { setSourceFilter('all'); setSelectedLogIndex(null) }}> - All - - { setSourceFilter('ml'); setSelectedLogIndex(null) }} - activeClassName="bg-purple-600 text-white shadow-lg" activeHoverClassName="hover:bg-purple-700" - > - ML ({stats.ml}) - - { setSourceFilter('rule'); setSelectedLogIndex(null) }} - activeClassName="bg-sky-600 text-white shadow-lg" activeHoverClassName="hover:bg-sky-700" - > - Rule ({stats.rule}) - - { setSourceFilter('fusion'); setSelectedLogIndex(null) }} - activeClassName="bg-teal-600 text-white shadow-lg" activeHoverClassName="hover:bg-teal-700" - > - Fusion ({stats.fusion}) - -
-
- - {/* Main content */} -
- -
-
-
-

- - Security Alerts -

- - {filteredLogs.length} Alerts - -
-
- -
- {filteredLogs.length === 0 ? ( -
- -

- {logs.length === 0 ? 'No Alerts Detected' : 'No Matching Alerts'} -

-

- {logs.length === 0 - ? 'Alerts will appear here when threats are detected' - : 'Try adjusting filter criteria'} -

-
- ) : ( -
- {filteredLogs.map((log, index) => ( - - ))} -
- )} -
-
-
- - {selectedLogIndex !== null && filteredLogs[selectedLogIndex] && ( - - - - )} -
-
- - ) -} - -export default Detection \ No newline at end of file diff --git a/mantis-frontend/src/pages/index.tsx b/mantis-frontend/src/pages/index.tsx deleted file mode 100644 index 536811f..0000000 --- a/mantis-frontend/src/pages/index.tsx +++ /dev/null @@ -1,1472 +0,0 @@ -// src/pages/index.tsx -import Head from 'next/head' -import {motion, AnimatePresence} from 'framer-motion' -import {useEffect, useState, useContext, useMemo, useCallback} from 'react' -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' -import { - faShieldAlt, - faNetworkWired, - faServer, - faChartLine, - faExclamationTriangle, - faGlobe, - faEye, - faFireAlt, - faRobot, - faClock, - faArrowUp, - faArrowDown, - faBolt, - faUsers, - faDatabase, - faLock, - faPlay, - faMemory, - faThermometerHalf, - faWifi, - faCheckCircle, - faExclamationCircle, - faTimesCircle, - faRefresh, - faPause, - faDesktop, - faWeight, - faTachometerAlt, - faTimes, - faMicrochip, - faHdd, - faFire, - faInfoCircle, -} from '@fortawesome/free-solid-svg-icons' -import Layout from '../components/Layout' -import {WebsocketContext} from '../providers/WebSocketProvider' -import {useTheme} from '../providers/ThemeProvider' -import LoadingSpinner from '../components/LoadingSpinner' -import Link from 'next/link' -import { - SystemHealthData, - HealthMetric, - SystemDetailModalProps, - HealthCardProps, - LoadAverageProps, - NetworkStatsProps -} from '../types/HomeTypes' - -// 工具函數 -const formatBytes = (bytes: number): string => { - const units = ['B', 'KB', 'MB', 'GB', 'TB'] - if (bytes === 0) return '0 B' - - const i = Math.floor(Math.log(bytes) / Math.log(1024)) - const value = bytes / Math.pow(1024, i) - - return `${value.toFixed(1)} ${units[i]}` -} - -const formatUptimeFromSeconds = (seconds: number): string => { - const days = Math.floor(seconds / (24 * 3600)) - const hours = Math.floor((seconds % (24 * 3600)) / 3600) - const minutes = Math.floor((seconds % 3600) / 60) - - if (days > 0) { - return `${days} days ${hours} hours ${minutes} minutes` - } else if (hours > 0) { - return `${hours} hours ${minutes} minutes` - } else { - return `${minutes} minutes` - } -} - -const getStatusFromValue = (value: number, type: 'cpu' | 'memory' | 'temperature' | 'load'): 'good' | 'warning' | 'critical' => { - switch (type) { - case 'cpu': - if (value < 50) return 'good' - if (value < 80) return 'warning' - return 'critical' - case 'memory': - if (value < 60) return 'good' - if (value < 85) return 'warning' - return 'critical' - case 'temperature': - if (value < 45) return 'good' - if (value < 70) return 'warning' - return 'critical' - case 'load': - if (value < 1.0) return 'good' - if (value < 2.0) return 'warning' - return 'critical' - default: - return 'good' - } -} - -const getStatusColor = (status: 'good' | 'warning' | 'critical' | 'unknown') => { - switch (status) { - case 'good': - return { - color: 'text-green-600', - bgColor: 'bg-green-100', - iconColor: 'text-green-500', - dotColor: 'bg-green-500' - } - case 'warning': - return { - color: 'text-yellow-600', - bgColor: 'bg-yellow-100', - iconColor: 'text-yellow-500', - dotColor: 'bg-yellow-500' - } - case 'critical': - return { - color: 'text-red-600', - bgColor: 'bg-red-100', - iconColor: 'text-red-500', - dotColor: 'bg-red-500' - } - default: - return { - color: 'text-gray-600', - bgColor: 'bg-gray-100', - iconColor: 'text-gray-500', - dotColor: 'bg-gray-500' - } - } -} - -// 系統詳情模態框組件 -const SystemDetailModal: React.FC = ({ isOpen, onClose, systemHealth, type }) => { - const {actualTheme} = useTheme() - const isDark = actualTheme === 'dark' - - if (!isOpen || !systemHealth) return null - - const getModalContent = () => { - switch (type) { - case 'cpu': - return { - title: 'CPU Details', - icon: faMicrochip, - color: 'text-blue-600', - content: ( -
- {/* CPU 基本資訊 */} -
-

- - Processor Information -

-
-
- Processor model: -
{systemHealth.cpu_details?.cpu_brand}
-
-
- Number of cores: -
{systemHealth.cpu_details?.core_count} cores -
-
-
- Base frequency: -
{systemHealth.cpu_details?.cpu_frequency} MHz -
-
-
- Overall usage: -
{systemHealth.cpu_details?.cpu_usage.toFixed(1)}% -
-
-
-
- - {/* 各核心使用率 */} - {systemHealth.cpu_details?.cores && ( -
-

- - Usage per core -

-
- {systemHealth.cpu_details.cores.map((core) => { - const usage = core.usage_percent - const status = getStatusFromValue(usage, 'cpu') - const statusColors = getStatusColor(status) - - return ( -
-
- - Core {core.core_id} - - - {usage.toFixed(1)}% - -
- - {/* 進度條 */} -
-
-
- -
- {core.frequency} MHz -
-
- ) - })} -
-
- )} - - {/* 系統負載 */} - {systemHealth.load_average ? ( -
-

- - System load average -

-
- {[ - {label: '1 minute', value: systemHealth.load_average.one_minute}, - {label: '5 minutes', value: systemHealth.load_average.five_minute}, - {label: '15 minutes', value: systemHealth.load_average.fifteen_minute} - ].map((load) => { - const status = getStatusFromValue(load.value, 'load') - const statusColors = getStatusColor(status) - - return ( -
-
{load.label}
-
- {load.value.toFixed(2)} -
-
-
-
-
- {status === 'good' ? 'Normal' : status === 'warning' ? 'Warning' : 'Overloaded'} -
-
- ) - })} -
-
- ) : ( -
-
- -

System load data unavailable

-
-
- )} -
- ) - } - - case 'memory': - const memoryUsed = systemHealth.memory_usage.used - const memoryTotal = systemHealth.memory_usage.total - const memoryAvailable = systemHealth.memory_usage.available - const memoryPercent = systemHealth.memory_usage.usage_percent - const swapUsed = systemHealth.memory_usage.swap_used - const swapTotal = systemHealth.memory_usage.swap_total - const swapPercent = swapTotal > 0 ? (swapUsed / swapTotal) * 100 : 0 - - return { - title: 'Memory Details', - icon: faMemory, - color: 'text-green-600', - content: ( -
- {/* 實體記憶體 */} -
-

- - Physical Memory (RAM) -

- - {/* 記憶體使用率圓餅圖式進度條 */} -
-
- - {/* 背景圓環 */} - - {/* 進度圓環 */} - - -
-
-
{memoryPercent.toFixed(1)}% -
-
Used -
-
-
-
-
- -
-
-
Total Capacity
-
{formatBytes(memoryTotal)}
-
-
-
Used
-
{formatBytes(memoryUsed)}
-
-
-
Available
-
{formatBytes(memoryAvailable)}
-
-
-
Usage
-
- {memoryPercent.toFixed(1)}% -
-
-
-
- - {/* 交換空間 */} - {swapTotal > 0 && ( -
-

- - Swap -

- -
-
- Usage - {swapPercent.toFixed(1)}% - -
-
-
-
-
- -
-
-
Total Capacity -
-
-
-
-
-
Used
-
{formatBytes(swapUsed)} -
-
-
-
- )} -
- ) - } - - case 'temperature': - const temp = systemHealth.temperature - const tempStatus = getStatusFromValue(temp, 'temperature') - const tempColors = getStatusColor(tempStatus) - const tempPercent = Math.min((temp / 100) * 100, 100) - - return { - title: 'Temperature Details', - icon: faThermometerHalf, - color: 'text-orange-600', - content: ( -
- {/* 溫度顯示 */} -
-

- - Current system temperature -

- - {/* 溫度數值顯示 */} -
-
- {temp.toFixed(1)}°C -
-
- - - {tempStatus === 'good' ? 'Normal temperature' : - tempStatus === 'warning' ? 'High temperature' : 'Too high a temperature'} - -
-
- - {/* 漸層溫度條 */} -
- {/* 溫度條容器 */} -
- {/* 漸層背景 */} -
- - {/* 當前溫度進度 */} -
-
- - {/* 溫度刻度 */} -
- {[0, 25, 45, 70, 100].map((value) => ( -
-
- - {value}° - -
- ))} -
-
-
- - {/* 溫度控制建議 */} -
-
-
-
-
-
-
Normal -
-
0-45°C -
-
-
-
-
-
-
Warn -
-
45-70°C -
-
-
-
-
-
-
Danger -
-
70-100°C -
-
-
-
-
- ) - } - default: - return {title: '', icon: faServer, color: 'text-gray-600', content:
No Data
} - } - } - - const modalContent = getModalContent() - - return ( - - - e.stopPropagation()} - > - {/* 模態框標題 */} -
-
-

- - {modalContent.title} -

- -
-
- - {/* 模態框內容 */} -
- {modalContent.content} -
-
-
-
- ) -} - -const HealthCard: React.FC = ({metric, index, systemHealth, onClick}) => { - const {actualTheme} = useTheme() - const statusColors = getStatusColor(metric.status) - const [showTooltip, setShowTooltip] = useState(false) - const isDark = actualTheme === 'dark' - - // 計算進度條百分比 - const getProgressPercentage = (): number => { - if (typeof metric.value !== 'number') return 0 - - switch (metric.name) { - case 'CPU usage': - case 'Memory usage': - return metric.value - case 'System temperature': - // 溫度以100°C為最大值計算百分比 - return Math.min((metric.value / 100) * 100, 100) - default: - return 0 - } - } - - // 獲取詳細資訊 - const getDetailedInfo = (): string => { - if (!systemHealth) return '' - - switch (metric.name) { - case 'CPU usage': - return `Processor Load: ${systemHealth.cpu_details?.cpu_usage.toFixed(1)}%\nStatus: ${metric.status === 'good' ? 'Normal' : metric.status === 'warning' ? 'Warning' : 'Critical'}` - case 'Memory usage': - return `Total Memory: ${formatBytes(systemHealth.memory_usage.total)}\nUsed: ${formatBytes(systemHealth.memory_usage.used)}\nAvailable Memory: ${formatBytes(systemHealth.memory_usage.available)}\nUsage: ${systemHealth.memory_usage.usage_percent.toFixed(1)}%` - case 'System temperature': - return `Current Temperature: ${systemHealth.temperature.toFixed(1)}°C\nStatus: ${metric.status === 'good' ? 'Normal' : metric.status === 'warning' ? 'High' : 'Critical'}` - default: - return metric.description || '' - } - } - - const progressPercentage = getProgressPercentage() - const hasProgressBar = metric.name.includes('usage') || metric.name.includes('temperature') - const isClickable = ['CPU Usage', 'Memory Usage', 'System Temperature'].includes(metric.name) - - return ( - setShowTooltip(true)} - onMouseLeave={() => setShowTooltip(false)} - onClick={isClickable ? onClick : undefined} - > -
-
-
-

- {metric.name} -

-
-
-

- {typeof metric.value === 'number' ? - metric.value.toFixed(metric.name.includes('Usage') || metric.name.includes('Temperature') ? 1 : 0) : - metric.value - } - {metric.unit && {metric.unit}} -

- {metric.description && ( -

{metric.description}

- )} - - {/* 進度條 */} - {hasProgressBar && ( -
-
-
-
-
- )} - -
- - {metric.status === 'good' ? 'Normal' : - metric.status === 'warning' ? 'Warn' : - metric.status === 'critical' ? 'Urgent' : 'Unknown'} - -
-
-
- -
-
- - {/* 懸停提示框 */} - {showTooltip && getDetailedInfo() && !isClickable && ( -
-
{getDetailedInfo()}
- {/* 箭頭 */} -
-
- )} -
- ) -} - -const LoadAverageDetails: React.FC = ({loadAverage}) => { - if (!loadAverage) { - return ( - -

- - System Load Average -

-
- -

System load data is not available

-
-
- ) - } - - const loadMetrics = [ - { - label: '1 minute', - value: loadAverage.one_minute, - color: getStatusFromValue(loadAverage.one_minute, 'load') - }, - { - label: '5 minutes', - value: loadAverage.five_minute, - color: getStatusFromValue(loadAverage.five_minute, 'load') - }, - { - label: '15 minutes', - value: loadAverage.fifteen_minute, - color: getStatusFromValue(loadAverage.fifteen_minute, 'load') - } - ] - - const getLoadDescription = (value: number): string => { - if (value < 0.5) return 'System Idle' - if (value < 1.0) return 'Load Normal' - if (value < 1.5) return 'Load High' - if (value < 2.0) return 'Load Very High' - return 'System Overloaded' - } - - const getBarWidth = (value: number): number => { - // 以 3.0 為最大值來計算進度條寬度 - return Math.min((value / 3.0) * 100, 100) - } - - return ( - -

- - System Load Average -

- -
- {loadMetrics.map((metric, index) => { - const statusColors = getStatusColor(metric.color) - const barWidth = getBarWidth(metric.value) - - return ( - -
- {metric.label} -
- - {metric.value.toFixed(2)} - - - {getLoadDescription(metric.value)} - -
-
- - {/* 進度條 */} -
-
-
- -
- 0.0 - 1.0 - 2.0 - 3.0+ -
-
- ) - })} -
-
- ) -} - -const NetworkStats: React.FC = ({networkStats}) => { - const {actualTheme} = useTheme(); - const isDark = actualTheme === 'dark'; - const interfaces = [ - {key: 'ingress', name: 'Ingress Network', icon: faArrowDown, color: 'text-blue-600'}, - {key: 'egress', name: 'Egress Network', icon: faArrowUp, color: 'text-green-600'}, - // {key: 'management', name: 'Management Network', icon: faDesktop, color: 'text-purple-600'} - ] - - return ( - -

- - Network Interface Status -

- -
- {interfaces.map((iface, index) => { - const stats = networkStats[iface.key as keyof typeof networkStats] - const hasErrors = stats.errors_received > 0 || stats.errors_transmitted > 0 - - return ( - -
-
- - {iface.name} - ({stats.interface}) -
- {hasErrors ? ( - - ) : ( - - )} -
- -
-
-
Received
-
{formatBytes(stats.bytes_received)}
-
{stats.packets_received.toLocaleString()} packets -
-
-
-
Transmitted
-
{formatBytes(stats.bytes_transmitted)}
-
{stats.packets_transmitted.toLocaleString()} packets -
-
-
- - {hasErrors && ( -
-
- Errors: Received {stats.errors_received}, Transmitted {stats.errors_transmitted} -
-
- )} -
- ) - })} -
-
- ) -} - -// 主要組件 -export default function Home() { - const {actualTheme} = useTheme() - const {bootTime, getSystemHealthStream} = useContext(WebsocketContext) - const [currentTime, setCurrentTime] = useState(new Date()) - const [systemHealth, setSystemHealth] = useState(null) - const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState(null) - const [isPaused, setIsPaused] = useState(false) - const [lastUpdateTime, setLastUpdateTime] = useState(null) - const [connectionStatus, setConnectionStatus] = useState<'connecting' | 'connected' | 'error'>('connecting') - - const isDark = actualTheme === 'dark' - - // 模態框狀態 - const [modalState, setModalState] = useState<{ - isOpen: boolean - type: 'cpu' | 'memory' | 'temperature' | null - }>({ - isOpen: false, - type: null - }) - - // 更新當前時間 - useEffect(() => { - const timer = setInterval(() => { - setCurrentTime(new Date()) - }, 1000) - - return () => clearInterval(timer) - }, []) - - const processSystemHealthData = useCallback((rawData: any) => { - if (isPaused) return - - try { - // 解析 WebSocket 資料 - const parsedData = typeof rawData === 'object' ? rawData : JSON.parse(rawData) - - // 驗證必要欄位 - if (parsedData && typeof parsedData === 'object') { - const healthData: SystemHealthData = { - timestamp: parsedData.timestamp || Date.now() / 1000, - boot_time: parsedData.boot_time, - uptime_seconds: parsedData.uptime_seconds, - system_info: { - kernel_version: parsedData.system_info?.kernel_version || 'Unknown', - os_name: parsedData.system_info?.os_name || 'Unknown', - os_version: parsedData.system_info?.os_version || 'Unknown', - architecture: parsedData.system_info?.architecture || 'Unknown', - total_processes: parsedData.system_info?.total_processes || 0 - }, - cpu_details: parsedData.cpu_details ? { - cpu_brand: parsedData.cpu_details.cpu_brand || 'Unknown', - core_count: parsedData.cpu_details.core_count || 0, - cpu_usage: parsedData.cpu_details.cpu_usage || 0, - cpu_frequency: parsedData.cpu_details.cpu_frequency || 0, - cores: parsedData.cpu_details.cores || [] - } : undefined, - memory_usage: { - total: parsedData.memory_usage?.total || 0, - used: parsedData.memory_usage?.used || 0, - available: parsedData.memory_usage?.available || 0, - usage_percent: parsedData.memory_usage?.usage_percent || 0, - swap_total: parsedData.memory_usage?.swap_total || 0, - swap_used: parsedData.memory_usage?.swap_used || 0 - }, - network_stats: { - ingress: parsedData.network_stats?.ingress || { - interface: 'Unknown', - bytes_received: 0, - bytes_transmitted: 0, - packets_received: 0, - packets_transmitted: 0, - errors_received: 0, - errors_transmitted: 0 - }, - egress: parsedData.network_stats?.egress || { - interface: 'Unknown', - bytes_received: 0, - bytes_transmitted: 0, - packets_received: 0, - packets_transmitted: 0, - errors_received: 0, - errors_transmitted: 0 - }, - management: parsedData.network_stats?.management || { - interface: 'Unknown', - bytes_received: 0, - bytes_transmitted: 0, - packets_received: 0, - packets_transmitted: 0, - errors_received: 0, - errors_transmitted: 0 - } - }, - load_average: parsedData.load_average || null, - temperature: parsedData.temperature || 0 - } - - setSystemHealth(healthData) - setLastUpdateTime(new Date()) - } - } catch (error) { - console.error("Error processing system health data:", error) - } - }, [isPaused]) - - // WebSocket 訂閱效果 - useEffect(() => { - if (!getSystemHealthStream) { - setError('WebSocket service unavailable') - setIsLoading(false) - return - } - - console.log("Initializing System Health WebSocket subscription...") - setError(null) - setIsLoading(true) - - // 訂閱系統健康串流 - const subscription = getSystemHealthStream().subscribe({ - next: (data: any) => { - processSystemHealthData(data) - setConnectionStatus('connected') - setIsLoading(false) - }, - error: (error: any) => { - console.error("System health WebSocket error:", error) - setConnectionStatus('error') - setError('WebSocket connection failed') - setIsLoading(false) - } - }) - - // 清理函數:只取消訂閱,不關閉 WebSocket 連線 - return () => { - console.log("Unsubscribing from System Health stream") - subscription.unsubscribe() - } - }, [getSystemHealthStream, processSystemHealthData]) - - // 暫停/恢復處理 - useEffect(() => { - if (!isPaused && systemHealth) { - // 恢復時更新時間戳 - setLastUpdateTime(new Date()) - } - }, [isPaused, systemHealth]) - - // 連接狀態初始化(可選,用於顯示連接狀態) - useEffect(() => { - if (!getSystemHealthStream) { - console.warn("WebSocket streams not available") - setConnectionStatus('error') - setIsLoading(false) - return - } - - console.log("WebSocket service available") - setConnectionStatus('connecting') - }, [getSystemHealthStream]) - - // 計算系統健康指標 - const healthMetrics = useMemo((): HealthMetric[] => { - if (!systemHealth) return [] - - const cpuUsage = systemHealth.cpu_details?.cpu_usage || 0 - const memoryUsage = systemHealth.memory_usage.usage_percent - const temperature = systemHealth.temperature - - const metrics: HealthMetric[] = [ - { - name: 'CPU Usage', - value: cpuUsage, - unit: '%', - status: getStatusFromValue(cpuUsage, 'cpu'), - icon: faMicrochip, - color: 'bg-blue-500', - bgColor: 'bg-blue-50', - description: `Processor Load` - }, - { - name: 'Memory Usage', - value: memoryUsage, - unit: '%', - status: getStatusFromValue(memoryUsage, 'memory'), - icon: faMemory, - color: 'bg-green-500', - bgColor: 'bg-green-50', - description: `Used ${formatBytes(systemHealth.memory_usage.used)} / ${formatBytes(systemHealth.memory_usage.total)}` - }, - { - name: 'System Temperature', - value: temperature, - unit: '°C', - status: getStatusFromValue(temperature, 'temperature'), - icon: faThermometerHalf, - color: 'bg-orange-500', - bgColor: 'bg-orange-50', - description: 'Hardware Temperature' - } - ] - - return metrics - }, [systemHealth]) - - // 處理卡片點擊 - const handleCardClick = useCallback((metricName: string) => { - let type: 'cpu' | 'memory' | 'temperature' | null = null - - switch (metricName) { - case 'CPU Usage': - type = 'cpu' - break - case 'Memory Usage': - type = 'memory' - break - case 'System Temperature': - type = 'temperature' - break - default: - return - } - - setModalState({isOpen: true, type}) - }, []) - - // 關閉模態框 - const closeModal = useCallback(() => { - setModalState({isOpen: false, type: null}) - }, []) - - // 快速導航項目 - const quickNavigation = [ - { - title: 'Network Traffic Monitoring', - description: 'View IPv4/IPv6 traffic statistics and trend analysis', - icon: faChartLine, - color: 'bg-blue-500', - href: '/dashboard', - badge: 'Real-time' - }, - { - title: 'Statistics Analysis', - description: 'In-depth analysis of network usage patterns and performance metrics', - icon: faDatabase, - color: 'bg-green-500', - href: '/statistics', - badge: 'Analysis' - }, - { - title: 'Access Control', - description: 'Manage IP whitelists, blacklists, and firewall rules', - icon: faLock, - color: 'bg-red-500', - href: '/access-control', - badge: 'Security' - }, - // { - // title: 'AI Threat Detection', - // description: 'Intelligent detection of abnormal behavior and potential network threats', - // icon: faRobot, - // color: 'bg-purple-500', - // href: '/ai-detection', - // badge: 'AI' - // } - ] - - if (isLoading && !systemHealth) { - return ( - <> - - Dashboard - NetGuardia - - -
-
- -
-
-
- - ) - } - - return ( - <> - - NetGuardia - Network Security Monitoring System - - - - - - - {/* 系統詳情模態框 */} - - - {/* 歡迎標題 */} - -
-
-

- Welcome to NetGuardia -

-

- Network Security Monitoring System - Protecting your network environment with - comprehensive security monitoring -

-
- - {/* 系統狀態控制 */} -
- {/* 系統狀態指示器 */} -
-
- - {error ? 'System Error' : systemHealth ? 'System Normal' : 'Connecting'} - -
- - {/* 暫停/恢復按鈕 */} - - - {/* 手動更新按鈕 */} - - - {/* 最後更新時間 */} - {lastUpdateTime && ( -
- Last Updated: {lastUpdateTime.toLocaleTimeString()} -
- )} -
-
-
- - {/* 錯誤狀態 */} - {error && ( - -
- - System status loading failed: {error} -
-
- )} - - {/* 系統健康狀態卡片 - 可點擊 */} - {healthMetrics.length > 0 && ( -
- {healthMetrics.map((metric, index) => ( - handleCardClick(metric.name)} - /> - ))} -
- )} - - {/* 系統詳細資訊 */} - {systemHealth && ( -
- {/* 系統資訊 */} - -

- - System Information -

- -
- {/* 基本系統資訊 */} -
-
- System - {systemHealth.system_info.os_name} {systemHealth.system_info.os_version} ({systemHealth.system_info.architecture}, {systemHealth.system_info.kernel_version}) -
- {systemHealth.cpu_details && ( - <> -
- CPU Model - {systemHealth.cpu_details.cpu_brand} -
-
- CPU Cores - {systemHealth.cpu_details.core_count} -
-
- CPU Frequency - {systemHealth.cpu_details.cpu_frequency} MHz -
- - )} -
- RAM Size - {formatBytes(systemHealth.memory_usage.total)} -
-
- System Time - {new Date(systemHealth.timestamp * 1000).toLocaleString('zh-TW')} -
- {systemHealth.uptime_seconds && ( -
- Uptime - {formatUptimeFromSeconds(systemHealth.uptime_seconds)} -
- )} -
-
-
-
- )} - - {/* 網路狀態和系統負載 */} - {systemHealth && ( -
- {/* 網路狀態 - 占滿整行 */} - -
- )} - - {/* 快速導航 */} - -

Quick - Navigation

-
- {quickNavigation.map((item, index) => ( - - -
-
-
- -
- - {item.badge} - -
-

- {item.title} -

-

- {item.description} -

-
- -
- ))} -
-
-
- - ) -} \ No newline at end of file diff --git a/mantis-frontend/src/pages/statistics.tsx b/mantis-frontend/src/pages/statistics.tsx deleted file mode 100644 index f162c5c..0000000 --- a/mantis-frontend/src/pages/statistics.tsx +++ /dev/null @@ -1,943 +0,0 @@ -import React, { useContext, useState, useEffect, useCallback, useMemo, useRef } from "react"; -import Head from 'next/head'; -import { motion } from 'framer-motion'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faChartBar, - faNetworkWired, - faGlobe, - faSearch, - faFilter, - faSortUp, - faSortDown, - faSort, - faRefresh, - faDownload, - faInfoCircle, - faPause, - faPlay, - faClock -} from '@fortawesome/free-solid-svg-icons'; -import { WebsocketContext } from "../providers/WebSocketProvider"; -import { useTheme } from '../providers/ThemeProvider'; -import Layout from '../components/Layout'; -import LoadingSpinner from '../components/LoadingSpinner'; -import { combineLatest, of } from 'rxjs'; -import { map, catchError, throttleTime, distinctUntilChanged } from 'rxjs/operators'; -import { - ToggleButtonProps, - TableHeaderProps, - UpdateControlProps, - ControlPanelProps, - DataTableProps, - NoDataStateProps, - StatsOverviewProps -} from '../types/StatisticsTypes'; - -// 常量定義 -const TRAFFIC_TYPES = ["source", "destination"]; -const DIRECTIONS = ["ingress", "egress"]; -const TIME_RANGES = ["1min", "10min", "1hour"]; - -// 更新频率选項 (毫秒) -const UPDATE_INTERVALS = [ - { label: "immediate", value: 0 }, - { label: "1s", value: 1000 }, - { label: "3s", value: 3000 }, - { label: "5s", value: 5000 }, - { label: "10s", value: 10000 } -]; - -// 格式化時間戳 -const formatTimestamp = (timestamp: number | string, bootTime: number | null): string => { - if (!timestamp || !bootTime) return "Loading..."; - const date = new Date((bootTime + Number(timestamp)) / 1e6); - date.setHours(date.getHours() + 8); // GMT+8 - return date.toISOString().replace("T", " ").split(".")[0]; -}; - -// 解析流量資料 -const parseFlowData = (rawData: any): any => { - try { - if (typeof rawData === 'object' && rawData !== null) { - return rawData; - } - return JSON.parse(rawData); - } catch (error) { - console.error("Error parsing flow data:", error); - return {}; - } -}; - -// 格式化位元組大小 -const formatBytes = (bytes: number): string => { - const units = ["B", "KB", "MB", "GB", "TB"]; - if (bytes < 1024) return `${bytes} B`; - - let i = 0; - let value = bytes; - - while (value >= 1024 && i < units.length - 1) { - value /= 1024; - i++; - } - - return `${value.toFixed(2)} ${units[i]}`; -}; - -// IP 排序輔助函數 -const getIPSortValue = (ip: string, isIPv6: boolean): any => { - if (isIPv6) { - const cleanIPv6 = ip.replace(/\[|\]|:[0-9]+/g, ""); - const expandIPv6 = (ip: string) => { - const parts = ip.split("::"); - const left = (parts[0] || "").split(":"); - const right = (parts[1] || "").split(":"); - const missingZeros = 8 - (left.length + right.length); - const zeros = Array(missingZeros).fill("0"); - return [...left, ...zeros, ...right].map((part) => part || "0"); - }; - - const expanded = expandIPv6(cleanIPv6); - try { - const hexString = expanded.reduce((acc, part) => acc + part.padStart(4, "0"), ""); - return BigInt("0x" + hexString); - } catch (error) { - console.error("Error converting IPv6 to number:", error); - return ip; - } - } else { - try { - return ip.split(".").reduce((acc, octet) => acc * 256 + parseInt(octet, 10), 0); - } catch (error) { - console.error("Error converting IPv4 to number:", error); - return ip; - } - } -}; - -const ToggleButton: React.FC = ({ isActive, onClick, children, className = "" }) => { - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - return ( - - ); -}; - -const TableHeader: React.FC = ({ column, sortConfig, onSort }) => { - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - return ( - onSort(column.key)} - className={`px-4 py-3 text-left text-xs font-medium uppercase tracking-wider cursor-pointer transition-colors ${ - sortConfig.key === column.key - ? (isDark ? 'bg-blue-900/30 text-blue-300' : 'bg-blue-50 text-gray-500') - : (isDark ? 'text-gray-400 hover:bg-gray-700' : 'text-gray-500 hover:bg-gray-100') - }`} - > -
- {column.label} - -
- - ); -}; - -const UpdateControl: React.FC = ({ - updateInterval, - setUpdateInterval, - isPaused, - setIsPaused, - lastUpdateTime - }) => { - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - return ( -
- {/* 更新频率 */} -
- - Update Interval: - -
- - {/* 暂停/恢复按钮 */} - - - {/* 更新資料按钮 */} - - - {/* 最后更新时间 */} - {lastUpdateTime && ( -
- Last Update: {lastUpdateTime.toLocaleTimeString()} -
- )} -
- ); -}; - -const ControlPanel: React.FC = ({ - isIPv6, setIsIPv6, - direction, setDirection, - trafficType, setTrafficType, - timeRange, setTimeRange, - searchTerm, setSearchTerm, - updateInterval, setUpdateInterval, - isPaused, setIsPaused, - lastUpdateTime - }) => { - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - return ( - - {/* 更新控制 */} - - -
- {/* 搜索框 */} -
-
- - setSearchTerm(e.target.value)} - placeholder="Search IP Address..." - className={`w-full pl-8 pr-3 py-1 text-sm border rounded-lg focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 ${ - isDark ? 'bg-gray-700 border-gray-500 text-gray-300 placeholder-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder-gray-400' - }`} - /> -
-
- - {/* IP Version Selection */} -
- IP Version: -
- setIsIPv6(false)} - > - IPv4 - - setIsIPv6(true)} - > - IPv6 - -
-
- - {/* 流量方向 */} -
- Direction: -
- setDirection("ingress")} - > - Ingress - - setDirection("egress")} - > - Egress - -
-
- - {/* 流量類型 */} -
- Traffic Type: -
- setTrafficType("source")} - > - Source - - setTrafficType("destination")} - > - Destination - -
-
- - {/* 時間範圍 */} -
- Time: -
- {TIME_RANGES.map((range) => ( - setTimeRange(range)} - > - {range} - - ))} -
-
-
-
- ); -}; - -const DataTable: React.FC = ({ data, sortConfig, handleSort, bootTime, isUpdating }) => { - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - const columns = [ - { key: "ip", label: "IP Address" }, - { key: "geo", label: "Location" }, - { key: "bytes", label: "Bytes" }, - { key: "packets", label: "Packets" }, - { key: "last_seen", label: "Last Seen (GMT+8)" } - ]; - - const formatGeoLocation = (geo: any) => { - if (!geo) { - return ( - - Unknown - - ); - } - - return ( -
- - {geo.country_code || 'Unknown'} - -
- ); - }; - - return ( - - {/* 表格容器 - 设置最大高度和滚动 */} -
- - {/* 固定表头 */} - - - {columns.map((column) => ( - - ))} - - - - {data.map((row, index) => ( - - - - - - - - ))} - -
- {row.ip} - - {formatGeoLocation(row.geo)} - - {formatBytes(row.bytes)} - - {row.packets.toLocaleString()} - - {formatTimestamp(row.last_seen, bootTime)} -
-
- - {/* 資料条数显示 */} - {data.length > 0 && ( -
-
- Showing {data.length} records -
-
- )} -
- ); -}; - -const NoDataState: React.FC = ({ isSearchFiltered }) => { - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - return ( - - - {isSearchFiltered ? ( - <> -

No matching results found

-

Please try adjusting your search criteria

- - ) : ( - <> -

No data found

-

Please try adjusting your filters or time range

- - )} -
- ); -}; - -const StatsOverview: React.FC = ({ data, isIPv6, direction, trafficType }) => { - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - const totalBytes = data.reduce((sum, item) => sum + item.bytes, 0); - const totalPackets = data.reduce((sum, item) => sum + item.packets, 0); - const uniqueIPs = new Set( - data.map(item => { - // 移除端口部分,只保留IP - if (item.ip.includes('[') && item.ip.includes(']:')) { - // IPv6: [ip]:port -> ip - return item.ip.split(']:')[0] + ']'; - } else if (item.ip.includes(':') && !item.ip.includes('[')) { - // IPv4: ip:port -> ip - return item.ip.split(':').slice(0, -1).join(':'); - } - return item.ip; - }) - ).size; - - const stats = [ - { - title: 'Total Traffic', - value: formatBytes(totalBytes), - icon: faNetworkWired, - color: 'bg-blue-500' - }, - { - title: 'Total Packets', - value: totalPackets.toLocaleString(), - icon: faChartBar, - color: 'bg-green-500' - }, - { - title: 'Unique IPs', - value: uniqueIPs.toLocaleString(), - icon: faGlobe, - color: 'bg-purple-500' - } - ]; - - return ( - - {stats.map((stat, index) => ( - -
-
-

- {stat.title} -

-

- {stat.value} -

-
-
- -
-
-
- ))} -
- ); -}; - -// 主要統計組件 -const Statistics: React.FC = () => { - const { bootTime, getIPv4FlowStream, getIPv6FlowStream } = useContext(WebsocketContext); - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - // 狀態管理 - const [isIPv6, setIsIPv6] = useState(false); - const [trafficType, setTrafficType] = useState("source"); - const [direction, setDirection] = useState("ingress"); - const [timeRange, setTimeRange] = useState("1min"); - const [sortConfig, setSortConfig] = useState({ key: null, direction: "desc" }); - const [searchTerm, setSearchTerm] = useState(""); - const [isLoading, setIsLoading] = useState(true); - const [flowData, setFlowData] = useState({}); - const [error, setError] = useState(null); - - // 更新控制狀態 - const [updateInterval, setUpdateInterval] = useState(3000); - const [isPaused, setIsPaused] = useState(false); - const [lastUpdateTime, setLastUpdateTime] = useState(null); - const [isUpdating, setIsUpdating] = useState(false); - - // 用於存儲最新資料和控制更新的 refs - const latestDataRef = useRef({}); - const lastUpdateRef = useRef(0); - const updateIntervalRef = useRef(updateInterval); - const isPausedRef = useRef(isPaused); - - // 同步 refs 與 state - useEffect(() => { - updateIntervalRef.current = updateInterval; - }, [updateInterval]); - - useEffect(() => { - isPausedRef.current = isPaused; - }, [isPaused]); - - // 解析流量資料 - 使用 useCallback 保持穩定引用 - const parseFlowData = useCallback((rawData: any): any => { - try { - if (typeof rawData === 'object' && rawData !== null) { - return rawData; - } - return JSON.parse(rawData); - } catch (error) { - console.error("Error parsing flow data:", error); - return {}; - } - }, []); - - // 處理 WebSocket 資料 - 使用 useCallback 保持穩定引用 - const processWebSocketData = useCallback((results: any[]) => { - const validResults = results.filter(result => result !== null); - if (validResults.length === 0) return; - - const newData: any = {}; - validResults.forEach(result => { - if (!result) return; - const { key, protocol, data } = result; - if (!newData[key]) { - newData[key] = {}; - } - newData[key][protocol] = data; - }); - - if (Object.keys(newData).length > 0) { - latestDataRef.current = newData; - - // 根據當前狀態決定是否立即更新 UI - const now = Date.now(); - const currentUpdateInterval = updateIntervalRef.current; - const currentIsPaused = isPausedRef.current; - - const shouldUpdate = !currentIsPaused && - (currentUpdateInterval === 0 || (now - lastUpdateRef.current) >= currentUpdateInterval); - - if (shouldUpdate) { - setIsUpdating(true); - setFlowData(newData); - setLastUpdateTime(new Date()); - lastUpdateRef.current = now; - setTimeout(() => setIsUpdating(false), 300); - } - } - }, []); - - useEffect(() => { - if (!getIPv4FlowStream || !getIPv6FlowStream) { - setError("WebSocket service unavailable"); - setIsLoading(false); - return; - } - - console.log("Initializing Statistics WebSocket subscriptions..."); - setError(null); - setIsLoading(true); - - // 使用循環動態生成所有配置 - const DIRECTIONS = ['ingress', 'egress'] as const; - const TRAFFIC_TYPES = ['source', 'destination'] as const; - const TIME_RANGES = ['1min', '10min', '1hour'] as const; - - const streamConfigs: Array<{ - dir: typeof DIRECTIONS[number]; - type: typeof TRAFFIC_TYPES[number]; - range: typeof TIME_RANGES[number]; - protocol: 'ipv4' | 'ipv6'; - }> = []; - - // 生成所有組合 (2 directions × 2 types × 3 ranges × 2 protocols = 24 configs) - DIRECTIONS.forEach(dir => { - TRAFFIC_TYPES.forEach(type => { - TIME_RANGES.forEach(range => { - streamConfigs.push({ dir, type, range, protocol: "ipv4" }); - streamConfigs.push({ dir, type, range, protocol: "ipv6" }); - }); - }); - }); - - console.log(`Total stream configs: ${streamConfigs.length}`); // 應該是 24 - - // 創建所有資料流的 Observable - const streams = streamConfigs.map(config => { - const { dir, type, range, protocol } = config; - const getStream = protocol === "ipv4" ? getIPv4FlowStream : getIPv6FlowStream; - - return getStream(dir, type, range).pipe( - map((rawData: any) => ({ - key: `${dir}_${type}_${range}`, - protocol, - data: parseFlowData(rawData) - })), - catchError(error => { - console.error(`Error in ${protocol} stream (${dir}, ${type}, ${range}):`, error); - return of(null); - }) - ); - }); - - // 訂閱合併的資料流 - const subscription = combineLatest(streams).subscribe({ - next: (results) => { - processWebSocketData(results); - setIsLoading(false); - }, - error: (error) => { - console.error("WebSocket subscription error:", error); - setError("WebSocket connection error"); - setIsLoading(false); - } - }); - - // 清理函數:只取消訂閱,不關閉 WebSocket 連線 - return () => { - console.log("Unsubscribing from Statistics WebSocket streams"); - subscription.unsubscribe(); - }; - }, [getIPv4FlowStream, getIPv6FlowStream, parseFlowData, processWebSocketData]); - - useEffect(() => { - if (!isPaused && Object.keys(latestDataRef.current).length > 0) { - // 恢復時立即更新一次 - setFlowData(latestDataRef.current); - setLastUpdateTime(new Date()); - lastUpdateRef.current = Date.now(); - } - }, [isPaused]); - - useEffect(() => { - // 即時模式或暫停時不需要定時器 - if (updateInterval === 0 || isPaused) { - return; - } - - const intervalId = setInterval(() => { - const now = Date.now(); - if (Object.keys(latestDataRef.current).length > 0 && - (now - lastUpdateRef.current) >= updateInterval && - !isPausedRef.current) { - setIsUpdating(true); - setFlowData(latestDataRef.current); - setLastUpdateTime(new Date()); - lastUpdateRef.current = now; - setTimeout(() => setIsUpdating(false), 300); - } - }, Math.min(updateInterval / 4, 1000)); // 檢查頻率為更新間隔的 1/4,最少 1 秒 - - return () => clearInterval(intervalId); - }, [updateInterval, isPaused]); - - // 提取當前選擇的資料 - const parseCurrentData = useCallback(() => { - const key = `${direction}_${trafficType}_${timeRange}`; - const protocol = isIPv6 ? "ipv6" : "ipv4"; - const data = flowData[key]?.[protocol]; - - if (!data) return null; - - return Object.entries(data).map(([ip, details]: [string, any]) => ({ - ip, - bytes: details.bytes || 0, - geo: details.geo || null, - packets: details.packets || 0, - last_seen: details.last_seen || 0, - })); - }, [flowData, direction, trafficType, timeRange, isIPv6]); - - // 排序和過濾資料 - const sortedData = useMemo(() => { - const data = parseCurrentData(); - if (!data) return null; - - if (!sortConfig.key) { - if (searchTerm.trim()) { - return data.filter((row) => - row.ip.toLowerCase().includes(searchTerm.trim().toLowerCase()) - ); - } - return data; - } - - // 排序 - const sortedData = [...data].sort((a, b) => { - let valA = a[sortConfig.key as keyof typeof a]; - let valB = b[sortConfig.key as keyof typeof b]; - - if (sortConfig.key === "ip") { - valA = getIPSortValue(valA as string, isIPv6); - valB = getIPSortValue(valB as string, isIPv6); - } - - if (valA < valB) { - return sortConfig.direction === "asc" ? -1 : 1; - } - if (valA > valB) { - return sortConfig.direction === "asc" ? 1 : -1; - } - return 0; - }); - - // 過濾 - if (searchTerm.trim()) { - return sortedData.filter((row) => - row.ip.toLowerCase().includes(searchTerm.trim().toLowerCase()) - ); - } - - return sortedData; - }, [parseCurrentData, sortConfig, searchTerm, isIPv6]); - - // 檢查是否由於搜索而過濾 - const isSearchFiltered = useMemo(() => { - return searchTerm.trim() !== "" && parseCurrentData() !== null; - }, [searchTerm, parseCurrentData]); - - // ============ 事件處理函數 ============ - - // 處理排序 - const handleSort = useCallback((key: string) => { - setSortConfig(prevConfig => { - const direction = prevConfig.key === key && prevConfig.direction === "asc" ? "desc" : "asc"; - return { key, direction }; - }); - }, []); - - // 搜索處理 - const handleSearchChange = useCallback((e: React.ChangeEvent) => { - setSearchTerm(e.target.value); - }, []); - - if (isLoading) { - return ( - <> - - Statistics - NetGuardia - - -
-
- -
-
-
- - ); - } - - if (error) { - return ( - <> - - Statistics - NetGuardia - - -
-
-
⚠️
-

Loading Error

-

{error}

- -
-
-
- - ); - } - - return ( - <> - - Statistics - NetGuardia - - - - - {/* 頁面標題 */} - -
-
-

- Network Traffic Statistics -

-

- Analyze and monitor network traffic data, view detailed connection statistics -

-
-
-
- - {/* 控制面板 */} - - - {/* 統計概覽 */} - {sortedData && sortedData.length > 0 && ( - - )} - - {/* 資料表格 */} -
- {sortedData === null ? ( - - ) : sortedData.length === 0 ? ( - - ) : ( - - )} -
-
- - ); -}; - -export default Statistics; \ No newline at end of file diff --git a/mantis-frontend/src/pages/traffic-map.tsx b/mantis-frontend/src/pages/traffic-map.tsx deleted file mode 100644 index d2dac7a..0000000 --- a/mantis-frontend/src/pages/traffic-map.tsx +++ /dev/null @@ -1,793 +0,0 @@ -import React, { useContext, useState, useEffect, useCallback, useMemo, useRef } from 'react'; -import Head from 'next/head'; -import { motion } from 'framer-motion'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faMapMarkedAlt, - faGlobe, - faNetworkWired, - faFilter, - faLayerGroup, - faExpand, - faCompress, - faInfoCircle, - faTimes, - faPause, - faPlay, - faClock, - faRefresh -} from '@fortawesome/free-solid-svg-icons'; -import Layout from '../components/Layout'; -import LoadingSpinner from '../components/LoadingSpinner'; -import { useTheme } from '../providers/ThemeProvider'; -import { WebsocketContext } from '../providers/WebSocketProvider'; -import { combineLatest, of } from 'rxjs'; -import { map, catchError } from 'rxjs/operators'; -import { - MapPoint, - MapConfig, - GeoLocation, - FlowDetails, - FlowDataRecord, - ProtocolData, - FlowDataState -} from "../types/TrafficMapTypes"; - -const INITIAL_MAP_CONFIG: MapConfig = { - center: [0, 20], - zoom: 0, - minZoom: 0, - maxZoom: 8, - scrollZoom: true, - boxZoom: false, - doubleClickZoom: false, - touchZoomRotate: false, - dragRotate: false, - pitchWithRotate: false, - renderWorldCopies: false -}; - -const formatBytes = (bytes: number): string => { - const units = ['B', 'KB', 'MB', 'GB', 'TB']; - if (bytes < 1024) return `${bytes} B`; - - let i = 0; - let value = bytes; - - while (value >= 1024 && i < units.length - 1) { - value /= 1024; - i++; - } - - return `${value.toFixed(2)} ${units[i]}`; -}; - -const ToggleButton: React.FC<{ - isActive: boolean; - onClick: () => void; - children: React.ReactNode; - isDark?: boolean; -}> = ({ isActive, onClick, children, isDark = false }) => ( - -); - -const ControlPanel: React.FC<{ - isIPv6: boolean; - setIsIPv6: (value: boolean) => void; - direction: string; - setDirection: (value: string) => void; - trafficType: string; - setTrafficType: (value: string) => void; - timeRange: string; - setTimeRange: (value: string) => void; - isPaused: boolean; - setIsPaused: (value: boolean) => void; - onRefresh: () => void; - lastUpdateTime: Date | null; -}> = ({ - isIPv6, - setIsIPv6, - direction, - setDirection, - trafficType, - setTrafficType, - timeRange, - setTimeRange, - isPaused, - setIsPaused, - onRefresh, - lastUpdateTime - }) => { - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - const TIME_RANGES = ["1min", "10min", "1hour"]; - - return ( - -
-
- IP Version: -
- setIsIPv6(false)} - isDark={isDark} - > - IPv4 - - setIsIPv6(true)} - isDark={isDark} - > - IPv6 - -
-
- -
- Direction: -
- setDirection('ingress')} - isDark={isDark} - > - Ingress - - setDirection('egress')} - isDark={isDark} - > - Egress - -
-
- -
- Traffic Type: -
- setTrafficType('source')} - isDark={isDark} - > - Source - - setTrafficType('destination')} - isDark={isDark} - > - Destination - -
-
- -
- Time: -
- {TIME_RANGES.map((range) => ( - setTimeRange(range)} - isDark={isDark} - > - {range} - - ))} -
-
- -
- - - - - {lastUpdateTime && ( -
- Last Update: {lastUpdateTime.toLocaleTimeString()} -
- )} -
-
-
- ); -}; - -const MapComponent: React.FC<{ - points: MapPoint[]; - isDark: boolean; -}> = ({ points, isDark }) => { - const mapContainer = useRef(null); - const map = useRef(null); - - useEffect(() => { - if (!mapContainer.current) return; - - const initMap = async () => { - const maplibregl = await import('maplibre-gl'); - - if (map.current) return; - - map.current = new maplibregl.Map({ - container: mapContainer.current!, - style: isDark - ? 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json' - : 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json', - center: INITIAL_MAP_CONFIG.center, - zoom: INITIAL_MAP_CONFIG.zoom, - minZoom: INITIAL_MAP_CONFIG.minZoom, - maxZoom: INITIAL_MAP_CONFIG.maxZoom, - scrollZoom: INITIAL_MAP_CONFIG.scrollZoom, - boxZoom: INITIAL_MAP_CONFIG.boxZoom, - doubleClickZoom: INITIAL_MAP_CONFIG.doubleClickZoom, - touchZoomRotate: INITIAL_MAP_CONFIG.touchZoomRotate, - dragRotate: INITIAL_MAP_CONFIG.dragRotate, - pitchWithRotate: INITIAL_MAP_CONFIG.pitchWithRotate, - renderWorldCopies: INITIAL_MAP_CONFIG.renderWorldCopies - }); - - map.current.on('load', () => { - if (!map.current) return; - - const style = map.current.getStyle(); - if (style && style.layers) { - style.layers.forEach((layer: any) => { - if (layer.type === 'symbol') { - map.current.setLayoutProperty(layer.id, 'visibility', 'none'); - } - }); - } - }); - }; - - initMap().catch(error => { - console.error('Failed to initialize map:', error); - }); - - return () => { - if (map.current) { - map.current.remove(); - map.current = null; - } - }; - }, [isDark]); - - useEffect(() => { - if (!map.current) return; - - const updateMarkers = async () => { - const maplibregl = await import('maplibre-gl'); - - const updateLayers = () => { - if (!map.current) return; - - if (!points.length) { - if (map.current.getSource('traffic-points')) { - const source = map.current.getSource('traffic-points') as any; - source.setData({ - type: 'FeatureCollection', - features: [] - }); - } - return; - } - - const geojsonData = { - type: 'FeatureCollection', - features: points.map(point => ({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [point.longitude, point.latitude] - }, - properties: { - ip: point.ip, - regions: point.regions, - code: point.code, - city: point.city, - bytes: point.bytes, - packets: point.packets, - type: point.type, - traffic: formatBytes(point.bytes) - } - })) - }; - - if (map.current.getSource('traffic-points')) { - const source = map.current.getSource('traffic-points') as any; - source.setData(geojsonData); - return; - } - - map.current.addSource('traffic-points', { - type: 'geojson', - data: geojsonData - }); - - map.current.addLayer({ - id: 'traffic-points-glow', - type: 'circle', - source: 'traffic-points', - paint: { - 'circle-color': [ - 'match', - ['get', 'type'], - 'source', '#3B82F6', - 'destination', '#EF4444', - '#8B5CF6' - ], - 'circle-radius': 16, - 'circle-opacity': 0.3, - 'circle-blur': 1 - } - }); - - map.current.addLayer({ - id: 'traffic-points-layer', - type: 'circle', - source: 'traffic-points', - paint: { - 'circle-color': [ - 'match', - ['get', 'type'], - 'source', '#3B82F6', - 'destination', '#EF4444', - '#8B5CF6' - ], - 'circle-radius': 5, - 'circle-opacity': 0.9, - 'circle-blur': 0.2 - } - }); - - map.current.on('mouseenter', 'traffic-points-layer', (e: any) => { - const coordinates = e.features[0].geometry.coordinates.slice(); - const props = e.features[0].properties; - - const popupContent = ` -
- ${props.ip}
- Regions: ${props.regions}
- Code: ${props.code}
- City: ${props.city}
- Traffic: ${props.traffic}
- Packets: ${props.packets.toLocaleString()}
- Type: ${props.type} -
- `; - - const popup = new maplibregl.Popup({ - closeButton: false, - closeOnClick: false - }) - .setLngLat(coordinates) - .setHTML(popupContent) - .addTo(map.current); - - (map.current as any)._hoverPopup = popup; - map.current.getCanvas().style.cursor = 'pointer'; - }); - - map.current.on('mouseleave', 'traffic-points-layer', () => { - if ((map.current as any)._hoverPopup) { - (map.current as any)._hoverPopup.remove(); - (map.current as any)._hoverPopup = null; - } - map.current.getCanvas().style.cursor = ''; - }); - }; - - if (map.current.isStyleLoaded()) { - updateLayers(); - } else { - map.current.once('load', updateLayers); - } - }; - - updateMarkers(); - }, [points]); - - return ( -
-
-
- ); -}; - -const StatsCard: React.FC<{ - title: string; - value: string | number; - icon: any; - color: string; -}> = ({ title, value, icon, color }) => { - const { actualTheme } = useTheme(); - const isDark = actualTheme === 'dark'; - - return ( - -
-
-

- {title} -

-

- {value} -

-
-
- -
-
-
- ); -}; - -const TrafficMap: React.FC = () => { - const { actualTheme } = useTheme(); - const { getIPv4FlowStream, getIPv6FlowStream, bootTime } = useContext(WebsocketContext); - const isDark = actualTheme === 'dark'; - - // 狀態管理 - const [isIPv6, setIsIPv6] = useState(false); - const [direction, setDirection] = useState('ingress'); - const [trafficType, setTrafficType] = useState('source'); - const [timeRange, setTimeRange] = useState('1min'); - const [isPaused, setIsPaused] = useState(false); - const [lastUpdateTime, setLastUpdateTime] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - const [mapPoints, setMapPoints] = useState([]); - const [flowData, setFlowData] = useState({}); - - const latestDataRef = useRef({}); - const isPausedRef = useRef(isPaused); - - // 同步 isPaused 到 ref - useEffect(() => { - isPausedRef.current = isPaused; - }, [isPaused]); - - // 解析流量資料 - 使用 useCallback 保持穩定引用 - const parseFlowData = useCallback((rawData: any): FlowDataRecord => { - try { - if (typeof rawData === 'object' && rawData !== null) { - return rawData; - } - return JSON.parse(rawData); - } catch (error) { - console.error("Error parsing flow data:", error); - return {}; - } - }, []); - - // 處理 WebSocket 資料 - 使用 useCallback 保持穩定引用 - const processWebSocketData = useCallback((results: any[]) => { - if (isPausedRef.current) return; - - const validResults = results.filter(result => result !== null); - if (validResults.length === 0) return; - - const newData: any = {}; - validResults.forEach(result => { - if (!result) return; - const { key, protocol, data } = result; - if (!newData[key]) { - newData[key] = {}; - } - newData[key][protocol] = data; - }); - - if (Object.keys(newData).length > 0) { - latestDataRef.current = newData; - setFlowData(newData); - setLastUpdateTime(new Date()); - } - }, []); - - useEffect(() => { - if (!getIPv4FlowStream || !getIPv6FlowStream) { - setError("WebSocket service unavailable"); - setIsLoading(false); - return; - } - - console.log("Initializing Traffic Map WebSocket subscriptions..."); - setError(null); - setIsLoading(true); - - // 定義所有需要訂閱的配置 - const DIRECTIONS = ['ingress', 'egress'] as const; - const TRAFFIC_TYPES = ['source', 'destination'] as const; - const TIME_RANGES = ['1min', '10min', '1hour'] as const; - - const streamConfigs: Array<{ - dir: typeof DIRECTIONS[number]; - type: typeof TRAFFIC_TYPES[number]; - range: typeof TIME_RANGES[number]; - protocol: 'ipv4' | 'ipv6'; - }> = []; - - // 生成所有組合 (2 directions × 2 types × 3 ranges × 2 protocols = 24 configs) - DIRECTIONS.forEach(dir => { - TRAFFIC_TYPES.forEach(type => { - TIME_RANGES.forEach(range => { - streamConfigs.push({ dir, type, range, protocol: "ipv4" }); - streamConfigs.push({ dir, type, range, protocol: "ipv6" }); - }); - }); - }); - - console.log(`Total stream configs for Traffic Map: ${streamConfigs.length}`); // 24 - - // 創建所有串流的 Observable - const streams = streamConfigs.map(config => { - const { dir, type, range, protocol } = config; - const getStream = protocol === "ipv4" ? getIPv4FlowStream : getIPv6FlowStream; - - return getStream(dir, type, range).pipe( - map((rawData: any) => ({ - key: `${dir}_${type}_${range}`, - protocol, - data: parseFlowData(rawData) - })), - catchError(error => { - console.error(`Error in ${protocol} stream (${dir}/${type}/${range}):`, error); - return of(null); - }) - ); - }); - - // 訂閱合併的串流 - const subscription = combineLatest(streams).subscribe({ - next: (results) => { - processWebSocketData(results); - setIsLoading(false); - }, - error: (error) => { - console.error("WebSocket subscription error:", error); - setError("WebSocket connection error"); - setIsLoading(false); - } - }); - - // 清理函數:只取消訂閱,不關閉 WebSocket 連線 - return () => { - console.log("Unsubscribing from Traffic Map WebSocket streams"); - subscription.unsubscribe(); - }; - }, [getIPv4FlowStream, getIPv6FlowStream, parseFlowData, processWebSocketData]); - - useEffect(() => { - if (!isPaused && Object.keys(latestDataRef.current).length > 0) { - // 恢復時立即更新一次 - setFlowData(latestDataRef.current); - setLastUpdateTime(new Date()); - } - }, [isPaused]); - - useEffect(() => { - const key = `${direction}_${trafficType}_${timeRange}`; - const protocol = isIPv6 ? "ipv6" : "ipv4"; - const data: FlowDataRecord | undefined = flowData[key]?.[protocol]; - - if (!data) { - setMapPoints([]); - return; - } - - const points: MapPoint[] = []; - const seenLocations: Record = {}; - - Object.entries(data).forEach(([ipPort, details]: [string, FlowDetails]) => { - // 檢查地理位置資料是否有效 - if (!details.geo || - details.geo.latitude === null || - details.geo.longitude === null) { - return; - } - - // 提取 IP 地址(移除端口) - let ip = ipPort; - if (ipPort.includes('[') && ipPort.includes(']:')) { - // IPv6 格式: [ip]:port - ip = ipPort.split(']:')[0].replace('[', '') + ']'; - } else if (ipPort.includes(':')) { - // IPv4 格式: ip:port - const parts = ipPort.split(':'); - ip = parts.slice(0, -1).join(':'); - } - - const locationKey = `${details.geo.latitude},${details.geo.longitude}`; - - // 合併相同位置的多個 IP - if (seenLocations[locationKey]) { - const existingPoint = seenLocations[locationKey]; - existingPoint.bytes += details.bytes || 0; - existingPoint.packets += details.packets || 0; - if (!existingPoint.ip.includes(ip)) { - existingPoint.ip += `, ${ip}`; - } - } else { - const newPoint: MapPoint = { - id: ipPort, - ip: ip, - latitude: details.geo.latitude, - longitude: details.geo.longitude, - bytes: details.bytes || 0, - packets: details.packets || 0, - regions: details.geo.country || 'Unknown', - city: details.geo.city || 'Unknown', - code: details.geo.country_code || 'Unknown', - type: trafficType as 'source' | 'destination' - }; - seenLocations[locationKey] = newPoint; - points.push(newPoint); - } - }); - - setMapPoints(points); - }, [flowData, direction, trafficType, timeRange, isIPv6]); - - const handleRefresh = useCallback(() => { - if (!isPaused && Object.keys(latestDataRef.current).length > 0) { - setFlowData(latestDataRef.current); - setLastUpdateTime(new Date()); - } - }, [isPaused]); - - const stats = useMemo(() => { - const totalBytes = mapPoints.reduce((sum, point) => sum + point.bytes, 0); - const totalPackets = mapPoints.reduce((sum, point) => sum + point.packets, 0); - - const uniqueIPs = new Set(); - mapPoints.forEach(point => { - const ips = point.ip.split(',').map(ip => ip.trim()); - ips.forEach(ip => uniqueIPs.add(ip)); - }); - - const uniqueRegions = new Set(mapPoints.map(point => point.regions)).size; - - return { - totalBytes: formatBytes(totalBytes), - totalPackets: totalPackets.toLocaleString(), - uniqueIPs: uniqueIPs.size, - uniqueRegions - }; - }, [mapPoints]); - - if (isLoading) { - return ( - <> - - Traffic Map - NetGuardia - - -
- -
-
- - ); - } - - return ( - <> - - Traffic Map - NetGuardia - - - - - - -

- - Network Traffic Map -

-

- Visualize network traffic geographic distribution in real-time -

-
- - - -
- - - - -
- - - - -
- - ); -}; - -export default TrafficMap; \ No newline at end of file diff --git a/mantis-frontend/src/providers/AccessControlProvider.tsx b/mantis-frontend/src/providers/AccessControlProvider.tsx deleted file mode 100644 index 9861ce2..0000000 --- a/mantis-frontend/src/providers/AccessControlProvider.tsx +++ /dev/null @@ -1,278 +0,0 @@ -import React, { createContext, useState, useCallback, useMemo, useRef } from 'react' -import { fetchData } from '../utils/connectionUtils' -import { urls } from '../config' - -interface AccessControlItem { - ip: string; - ports: (string | number)[]; -} - -interface CachedData { - [key: string]: { - data: AccessControlItem[]; - timestamp: number; - isLoading: boolean; - } -} - -interface AccessControlContextType { - // 当前显示的数据 - currentData: AccessControlItem[]; - filteredData: AccessControlItem[]; - isLoading: boolean; - - // 数据管理方法 - setFilteredData: (data: AccessControlItem[]) => void; - switchTo: (isIPv6: boolean, listType: string) => void; - refreshData: (isIPv6?: boolean, listType?: string) => Promise; - - // 缓存状态 - getCacheStatus: () => { [key: string]: { lastUpdate: Date; isStale: boolean } }; -} - -export const AccessControlContext = createContext({ - currentData: [], - filteredData: [], - isLoading: false, - setFilteredData: () => {}, - switchTo: () => {}, - refreshData: async () => {}, - getCacheStatus: () => ({}), -}) - -interface AccessControlProviderProps { - children: React.ReactNode -} - -// 缓存过期时间 (10分钟) -const CACHE_EXPIRE_TIME = 10 * 60 * 1000; - -// 生成缓存键 -const getCacheKey = (isIPv6: boolean, listType: string): string => { - return `${isIPv6 ? 'ipv6' : 'ipv4'}_${listType}`; -} - -// 检查缓存是否过期 -const isCacheStale = (timestamp: number): boolean => { - return Date.now() - timestamp > CACHE_EXPIRE_TIME; -} - -export const AccessControlProvider: React.FC = ({ children }) => { - // 缓存所有数据,避免重复请求 - const [cache, setCache] = useState({}) - const [filteredData, setFilteredData] = useState([]) - const [currentKey, setCurrentKey] = useState('') - - // 使用 ref 追踪正在进行的请求,避免重复请求 - const pendingRequests = useRef>(new Set()) - - // 从缓存或 API 获取数据 - const fetchCachedData = useCallback(async ( - isIPv6: boolean, - listType: string, - forceRefresh: boolean = false - ): Promise => { - const key = getCacheKey(isIPv6, listType); - const cachedItem = cache[key]; - - // 如果有有效缓存且不强制刷新,直接返回 - if (!forceRefresh && cachedItem && !isCacheStale(cachedItem.timestamp) && !cachedItem.isLoading) { - return cachedItem.data; - } - - // 如果正在请求中,等待完成 - if (pendingRequests.current.has(key)) { - return new Promise((resolve) => { - const checkInterval = setInterval(() => { - if (!pendingRequests.current.has(key)) { - clearInterval(checkInterval); - const updatedCache = cache[key]; - resolve(updatedCache ? updatedCache.data : []); - } - }, 100); - }); - } - - // 标记正在加载 - pendingRequests.current.add(key); - - // 立即更新缓存状态为加载中 - setCache(prev => ({ - ...prev, - [key]: { - data: cachedItem?.data || [], - timestamp: cachedItem?.timestamp || 0, - isLoading: true - } - })); - - try { - const url = isIPv6 - ? urls.access_control.ipv6[listType as keyof typeof urls.access_control.ipv6] - : urls.access_control.ipv4[listType as keyof typeof urls.access_control.ipv4]; - - const data = await new Promise((resolve, reject) => { - fetchData( - url, - (response) => { - try { - const parsedData = JSON.parse(response); - const dataArray: AccessControlItem[] = Object.entries(parsedData).map(([ip, ports]) => ({ - ip, - ports: ports as (string | number)[], - })); - resolve(dataArray); - } catch (error) { - console.error("Error parsing data:", error); - reject(new Error("Invalid data format")); - } - }, - (error) => { - console.error(`Error fetching from ${url}:`, error); - reject(new Error("Network error: Please check your connection")); - } - ); - }); - - // 更新缓存 - setCache(prev => ({ - ...prev, - [key]: { - data, - timestamp: Date.now(), - isLoading: false - } - })); - - return data; - - } catch (error) { - // 发生错误时,恢复缓存状态 - setCache(prev => ({ - ...prev, - [key]: { - data: cachedItem?.data || [], - timestamp: cachedItem?.timestamp || 0, - isLoading: false - } - })); - - throw error; - } finally { - // 移除请求标记 - pendingRequests.current.delete(key); - } - }, [cache]); - - // 切换到指定的IP版本和列表类型 - const switchTo = useCallback(async (isIPv6: boolean, listType: string) => { - const key = getCacheKey(isIPv6, listType); - setCurrentKey(key); - - try { - const data = await fetchCachedData(isIPv6, listType); - setFilteredData(data); - } catch (error) { - console.error(`Failed to switch to ${key}:`, error); - // 发生错误时显示空数据,但不影响用户体验 - setFilteredData([]); - } - }, [fetchCachedData]); - - // 刷新数据 - const refreshData = useCallback(async (isIPv6?: boolean, listType?: string) => { - // 如果没有指定参数,刷新当前数据 - if (isIPv6 === undefined || listType === undefined) { - if (!currentKey) return; - const [ipVersion, list] = currentKey.split('_'); - const isV6 = ipVersion === 'ipv6'; - await switchTo(isV6, list); - return; - } - - // 刷新指定数据 - try { - const data = await fetchCachedData(isIPv6, listType, true); - const key = getCacheKey(isIPv6, listType); - - // 如果刷新的是当前显示的数据,更新显示 - if (key === currentKey) { - setFilteredData(data); - } - } catch (error) { - console.error(`Failed to refresh data for ${getCacheKey(isIPv6, listType)}:`, error); - throw error; - } - }, [currentKey, fetchCachedData, switchTo]); - - // 获取缓存状态(用于调试和监控) - const getCacheStatus = useCallback(() => { - const status: { [key: string]: { lastUpdate: Date; isStale: boolean } } = {}; - - Object.entries(cache).forEach(([key, item]) => { - status[key] = { - lastUpdate: new Date(item.timestamp), - isStale: isCacheStale(item.timestamp) - }; - }); - - return status; - }, [cache]); - - // 计算当前数据和加载状态 - const currentData = useMemo(() => { - return currentKey ? (cache[currentKey]?.data || []) : []; - }, [cache, currentKey]); - - const isLoading = useMemo(() => { - return currentKey ? (cache[currentKey]?.isLoading || false) : false; - }, [cache, currentKey]); - - // 预加载常用数据(IPv4 黑名单) - React.useEffect(() => { - // 在组件挂载时预加载 IPv4 黑名单,这是最常用的 - const preloadKey = getCacheKey(false, 'black_list'); - if (!cache[preloadKey]) { - fetchCachedData(false, 'black_list').catch(error => { - console.warn('Failed to preload IPv4 blacklist:', error); - }); - } - }, []); // 只在组件挂载时执行一次 - - // 定期清理过期缓存 - React.useEffect(() => { - const cleanupInterval = setInterval(() => { - setCache(prev => { - const now = Date.now(); - const cleaned: CachedData = {}; - - Object.entries(prev).forEach(([key, item]) => { - // 保留最近 30 分钟内的缓存 - if (now - item.timestamp < 30 * 60 * 1000) { - cleaned[key] = item; - } - }); - - return cleaned; - }); - }, 5 * 60 * 1000); // 每5分钟清理一次 - - return () => clearInterval(cleanupInterval); - }, []); - - const contextValue = useMemo((): AccessControlContextType => ({ - currentData, - filteredData, - isLoading, - setFilteredData, - switchTo, - refreshData, - getCacheStatus, - }), [currentData, filteredData, isLoading, switchTo, refreshData, getCacheStatus]); - - return ( - - {children} - - ); -}; \ No newline at end of file diff --git a/mantis-frontend/src/providers/ThemeProvider.tsx b/mantis-frontend/src/providers/ThemeProvider.tsx deleted file mode 100644 index 08fb631..0000000 --- a/mantis-frontend/src/providers/ThemeProvider.tsx +++ /dev/null @@ -1,115 +0,0 @@ -// src/providers/ThemeProvider.tsx -'use client' - -import React, { createContext, useContext, useEffect, useState } from 'react' - -type Theme = 'light' | 'dark' | 'system' - -interface ThemeContextType { - theme: Theme - actualTheme: 'light' | 'dark' - setTheme: (theme: Theme) => void - toggleTheme: () => void -} - -const ThemeContext = createContext(undefined) - -export const useTheme = () => { - const context = useContext(ThemeContext) - if (!context) { - throw new Error('useTheme must be used within a ThemeProvider') - } - return context -} - -export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [theme, setTheme] = useState('system') - const [actualTheme, setActualTheme] = useState<'light' | 'dark'>('light') - - const getSystemTheme = (): 'light' | 'dark' => { - if (typeof window !== 'undefined') { - return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' - } - return 'light' - } - - const calculateActualTheme = (currentTheme: Theme): 'light' | 'dark' => { - if (currentTheme === 'system') { - return getSystemTheme() - } - return currentTheme - } - - useEffect(() => { - const savedTheme = localStorage.getItem('theme') as Theme - if (savedTheme && ['light', 'dark', 'system'].includes(savedTheme)) { - setTheme(savedTheme) - } - }, []) - - useEffect(() => { - const newActualTheme = calculateActualTheme(theme) - setActualTheme(newActualTheme) - - if (typeof window !== 'undefined') { - const root = window.document.documentElement - root.classList.remove('light', 'dark') - root.classList.add(newActualTheme) - - root.setAttribute('data-theme', newActualTheme) - - localStorage.setItem('theme', theme) - } - }, [theme]) - - useEffect(() => { - if (typeof window !== 'undefined') { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') - - const handleChange = () => { - if (theme === 'system') { - const newActualTheme = calculateActualTheme(theme) - setActualTheme(newActualTheme) - - const root = window.document.documentElement - root.classList.remove('light', 'dark') - root.classList.add(newActualTheme) - root.setAttribute('data-theme', newActualTheme) - } - } - - mediaQuery.addEventListener('change', handleChange) - return () => mediaQuery.removeEventListener('change', handleChange) - } - }, [theme]) - - const toggleTheme = () => { - setTheme((currentTheme: Theme) => { - switch (currentTheme) { - case 'light': - return 'dark' - case 'dark': - return 'system' - case 'system': - return 'light' - default: - return 'light' - } - }) - } - - const handleSetTheme = (newTheme: Theme) => { - setTheme(newTheme) - } - - return ( - - {children} - - ) -} \ No newline at end of file diff --git a/mantis-frontend/src/providers/WebSocketProvider.tsx b/mantis-frontend/src/providers/WebSocketProvider.tsx deleted file mode 100644 index 2370645..0000000 --- a/mantis-frontend/src/providers/WebSocketProvider.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import React, { createContext, useState, useEffect, useRef, useCallback } from 'react' -import { websocketUrl, urls } from '../config' -import { createWebSocket, fetchData } from '../utils/connectionUtils' -import { BehaviorSubject } from 'rxjs' -import { share } from 'rxjs/operators' - -interface WebSocketContextType { - bootTime: number | null; - getDetectionAlertStream: () => any; - getIPv4FlowStream: (direction: string, flow_direction: string, timeType: string) => any; - getIPv6FlowStream: (direction: string, flow_direction: string, timeType: string) => any; - getSystemHealthStream: () => any; - getLatestFlowData: (protocol: 'ipv4' | 'ipv6', direction: string, flow_direction: string, timeType: string) => any; - getLatestSystemHealth: () => any; -} - -export const WebsocketContext = createContext({ - bootTime: null, - getDetectionAlertStream: () => null, - getIPv4FlowStream: () => null, - getIPv6FlowStream: () => null, - getSystemHealthStream: () => null, - getLatestFlowData: () => null, - getLatestSystemHealth: () => null, -}) - -export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const wsRefs = useRef<{ [key: string]: WebSocket }>({}) - const [bootTime, setBootTime] = useState(null) - const retryDelays = useRef<{ [key: string]: number }>({}) - const dataSubjects = useRef<{ [key: string]: BehaviorSubject }>({}) - const latestDataCache = useRef<{ [key: string]: any }>({}) - - const fetchBootTime = async () => { - try { - await fetchData( - urls.bootTime, - (data) => setBootTime(Number(data.trim())), - (error) => { - throw new Error(`Failed to fetch boot_time: ${error?.message || 'Unknown error'}`) - } - ) - } catch (error) { - console.error('Boot time fetch error:', error) - } - } - - const getOrCreateSubject = (url: string) => { - if (!dataSubjects.current[url]) { - dataSubjects.current[url] = new BehaviorSubject(null) - } - return dataSubjects.current[url] - } - - const getWebSocketStream = (url: string) => { - const subject = getOrCreateSubject(url) - return subject.asObservable().pipe(share()) - } - - const setupWebSocket = (url: string, retryCount: number = 0) => { - if (wsRefs.current[url] && wsRefs.current[url].readyState === WebSocket.OPEN) { - console.log(`WebSocket already connected: ${url}`) - return wsRefs.current[url] - } - - if (wsRefs.current[url]) { - wsRefs.current[url].close() - } - - const ws = createWebSocket( - url, - (data) => { - latestDataCache.current[url] = data - getOrCreateSubject(url).next(data) - }, - (error) => console.error(`WebSocket error for ${url}:`, error) - ) - - ws.onclose = () => { - console.warn(`WebSocket for ${url} closed. Retrying...`) - delete wsRefs.current[url] - let delay = retryDelays.current[url] || 1000 - retryDelays.current[url] = Math.min(delay * 2, 30000) - setTimeout(() => setupWebSocket(url, retryCount + 1), delay) - } - - ws.onopen = () => { - console.log(`WebSocket connected: ${url}`) - retryDelays.current[url] = 1000 - } - - wsRefs.current[url] = ws - return ws - } - - const getLatestFlowData = useCallback(( - protocol: 'ipv4' | 'ipv6', - direction: string, - flow_direction: string, - timeType: string - ) => { - const urlFunc = protocol === 'ipv4' ? websocketUrl.ipv4FlowStats : websocketUrl.ipv6FlowStats - const url = urlFunc(direction, flow_direction, timeType) - return latestDataCache.current[url] || null - }, []) - - const getLatestSystemHealth = useCallback(() => { - return latestDataCache.current[websocketUrl.systemHealth] || null - }, []) - - const getDetectionAlertStream = useCallback(() => { - return getWebSocketStream(websocketUrl.detectionAlert) - }, []) - - const getIPv4FlowStream = useCallback((direction: string, flow_direction: string, timeType: string) => { - const url = websocketUrl.ipv4FlowStats(direction, flow_direction, timeType) - return getWebSocketStream(url) - }, []) - - const getIPv6FlowStream = useCallback((direction: string, flow_direction: string, timeType: string) => { - const url = websocketUrl.ipv6FlowStats(direction, flow_direction, timeType) - return getWebSocketStream(url) - }, []) - - const getSystemHealthStream = useCallback(() => { - return getWebSocketStream(websocketUrl.systemHealth) - }, []) - - const initializeWebSockets = useCallback(() => { - console.log("Initializing all WebSocket connections...") - - setupWebSocket(websocketUrl.detectionAlert) - setupWebSocket(websocketUrl.systemHealth) - - const directions = ["ingress", "egress"] - const flowDirections = ["source", "destination"] - const timeTypes = ["1min", "10min", "1hour"] - - directions.forEach(direction => { - flowDirections.forEach(flow_direction => { - timeTypes.forEach(timeType => { - setupWebSocket(websocketUrl.ipv4FlowStats(direction, flow_direction, timeType)) - setupWebSocket(websocketUrl.ipv6FlowStats(direction, flow_direction, timeType)) - }) - }) - }) - - console.log(`Total WebSocket connections: ${Object.keys(wsRefs.current).length}`) - }, []) - - const closeWebSockets = useCallback(() => { - console.log("Closing all WebSocket connections...") - - Object.entries(wsRefs.current).forEach(([url, ws]) => { - try { - ws.close() - } catch (error) { - console.error(`Failed to close WebSocket ${url}:`, error) - } - }) - wsRefs.current = {} - - Object.values(dataSubjects.current).forEach(subject => { - subject.complete() - }) - dataSubjects.current = {} - latestDataCache.current = {} - }, []) - - useEffect(() => { - fetchBootTime() - initializeWebSockets() - return () => closeWebSockets() - }, []) - - return ( - - {children} - - ) -} \ No newline at end of file diff --git a/mantis-frontend/src/styles/globals.css b/mantis-frontend/src/styles/globals.css deleted file mode 100644 index 0ef832d..0000000 --- a/mantis-frontend/src/styles/globals.css +++ /dev/null @@ -1,68 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@300;400;500;600&family=Noto+Sans+TC:wght@300;400;500;600&family=Noto+Sans+SC:wght@300;400;500;600&family=Noto+Sans+JP:wght@300;400;500;600&display=swap'); -@import "tailwindcss"; - -svg.svg-inline--fa { - display: inline-block; - height: 1em; - overflow: visible; - vertical-align: -0.125em; -} - -/* 確保圖標尺寸穩定 */ -.svg-inline--fa { - width: 1em; -} - -/* 針對有固定寬高的圖標 */ -.svg-inline--fa.fa-fw { - width: 1.25em; -} - -/* 防止尺寸跳動 */ -.svg-inline--fa { - transition: none !important; -} - -/* 確保初始渲染就有正確尺寸 */ -.svg-inline--fa:not(:root) { - overflow: visible; - box-sizing: content-box; -} - -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-thumb { - background: rgba(59, 130, 246, 0.3); /* 使用 Tailwind blue-500 */ - border-radius: 10px; - transition: background 0.2s ease-in-out; /* 使用完整的 transition 屬性 */ -} - -::-webkit-scrollbar-thumb:hover { - background: rgba(59, 130, 246, 0.6); /* 懸停時加深 */ -} - -::-webkit-scrollbar-track { - background: rgba(243, 244, 246, 0.5); /* 淺灰色背景,提供更好的對比 */ - border-radius: 10px; - transition: background 0.15s ease; /* 軌道也加上過渡效果 */ -} - -/* 可選:為暗色主題準備的樣式 */ -@media (prefers-color-scheme: dark) { - ::-webkit-scrollbar-thumb { - background: rgba(96, 165, 250, 0.4); /* 暗色主題下使用 blue-400 */ - transition: background 0.2s ease-in-out; - } - - ::-webkit-scrollbar-thumb:hover { - background: rgba(96, 165, 250, 0.7); - } - - ::-webkit-scrollbar-track { - background: rgba(31, 41, 55, 0.3); /* 暗色主題下的軌道背景 */ - transition: background 0.15s ease; - } -} \ No newline at end of file diff --git a/mantis-frontend/src/types/AIDetectionTypes.tsx b/mantis-frontend/src/types/AIDetectionTypes.tsx deleted file mode 100644 index d30e428..0000000 --- a/mantis-frontend/src/types/AIDetectionTypes.tsx +++ /dev/null @@ -1,50 +0,0 @@ -// ai detection types -export interface AlertLog { - timestamp: number - flow_key: string - src_ip: string - dst_ip: string - src_port: number - dst_port: number - protocol: number - is_attack: boolean - attack_type: string - confidence: number - ae_score: number - rf_score: number - ensemble_score: number -} - -export interface PriorityInfo { - color: string - bgColor: string - icon: any - label: string -} - -export interface NotificationProps { - message: string - type: 'success' | 'error' | 'warning' | 'info' - onClose: () => void -} - -export interface AlertDetailsProps { - log: AlertLog - onClose: () => void - showNotification: (message: string, type?: 'success' | 'error' | 'warning' | 'info') => void -} - -export interface AlertItemProps { - log: AlertLog - index: number - onClick: (index: number) => void -} - -export interface FilterButtonProps { - isActive: boolean - onClick: () => void - children: React.ReactNode - className?: string - activeClassName?: string - activeHoverClassName?: string -} \ No newline at end of file diff --git a/mantis-frontend/src/types/AccessControlTypes.tsx b/mantis-frontend/src/types/AccessControlTypes.tsx deleted file mode 100644 index 70038a6..0000000 --- a/mantis-frontend/src/types/AccessControlTypes.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// access control types -export interface AccessControlItem { - ip: string - ports: (string | number)[] -} - -export interface NotificationProps { - message: string - type: 'success' | 'error' | 'warning' | 'info' - onClose: () => void -} - -export interface ModalProps { - title: string - children: React.ReactNode - onClose: () => void - onSubmit?: () => void - submitLabel?: string - submitDisabled?: boolean -} - -export interface ToggleButtonProps { - isActive: boolean - onClick: () => void - children: React.ReactNode - className?: string -} - -export interface StatsCardProps { - title: string - value: number - icon: any - color: string -} \ No newline at end of file diff --git a/mantis-frontend/src/types/DashboardTypes.tsx b/mantis-frontend/src/types/DashboardTypes.tsx deleted file mode 100644 index 1afc087..0000000 --- a/mantis-frontend/src/types/DashboardTypes.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// dashboard types -export interface TrendDataPoint { - time: string - value: number -} - -export interface TrendData { - ingressSource: TrendDataPoint[] - egressSource: TrendDataPoint[] -} - -export interface TrafficData { - ipv4: { - ingressSource: number - egressSource: number - } - ipv6: { - ingressSource: number - egressSource: number - } -} - -export interface UpdateControlProps { - updateInterval: number; - setUpdateInterval: (value: number) => void; - isPaused: boolean; - setIsPaused: (value: boolean) => void; - lastUpdateTime: Date | null; - connectionStatus: string; -} - -export interface EChartsComponentProps { - data: TrendData - type: 'ipv4' | 'ipv6' - isPaused: boolean -} diff --git a/mantis-frontend/src/types/HomeTypes.tsx b/mantis-frontend/src/types/HomeTypes.tsx deleted file mode 100644 index b8c7499..0000000 --- a/mantis-frontend/src/types/HomeTypes.tsx +++ /dev/null @@ -1,86 +0,0 @@ -// index types -export interface SystemHealthData { - timestamp: number - boot_time?: number - uptime_seconds?: number - system_info: { - kernel_version: string, - os_name: string, - os_version: string, - architecture: string, - total_processes: number - } - cpu_details?: { - cpu_brand: string - core_count: number - cpu_usage: number - cpu_frequency: number - cores: Array<{ - core_id: number - usage_percent: number - frequency: number - }> - } - memory_usage: { - total: number - used: number - available: number - usage_percent: number - swap_total: number - swap_used: number - } - network_stats: { - ingress: NetworkInterface - egress: NetworkInterface - management: NetworkInterface - } - load_average: { - one_minute: number - five_minute: number - fifteen_minute: number - } | null - temperature: number -} - -export interface NetworkInterface { - interface: string - bytes_received: number - bytes_transmitted: number - packets_received: number - packets_transmitted: number - errors_received: number - errors_transmitted: number -} - -export interface HealthMetric { - name: string - value: number | string - unit?: string - status: 'good' | 'warning' | 'critical' | 'unknown' - icon: any - color: string - bgColor: string - description?: string -} - -export interface SystemDetailModalProps { - isOpen: boolean - onClose: () => void - systemHealth: SystemHealthData | null - type: 'cpu' | 'memory' | 'temperature' -} - -export interface HealthCardProps { - metric: HealthMetric - index: number - systemHealth?: SystemHealthData | null - onClick?: () => void -} - -export interface LoadAverageProps { - loadAverage: SystemHealthData['load_average'] -} - -export interface NetworkStatsProps { - networkStats: SystemHealthData['network_stats'] -} diff --git a/mantis-frontend/src/types/StatisticsTypes.tsx b/mantis-frontend/src/types/StatisticsTypes.tsx deleted file mode 100644 index be882ae..0000000 --- a/mantis-frontend/src/types/StatisticsTypes.tsx +++ /dev/null @@ -1,58 +0,0 @@ -// statistics types -export interface ToggleButtonProps { - isActive: boolean; - onClick: () => void; - children: React.ReactNode; - className?: string; -} - -export interface TableHeaderProps { - column: { key: string; label: string }; - sortConfig: { key: string | null; direction: string }; - onSort: (key: string) => void; -} - -export interface UpdateControlProps { - updateInterval: number; - setUpdateInterval: (value: number) => void; - isPaused: boolean; - setIsPaused: (value: boolean) => void; - lastUpdateTime: Date | null; -} - -export interface ControlPanelProps { - isIPv6: boolean; - setIsIPv6: (value: boolean) => void; - direction: string; - setDirection: (value: string) => void; - trafficType: string; - setTrafficType: (value: string) => void; - timeRange: string; - setTimeRange: (value: string) => void; - searchTerm: string; - setSearchTerm: (value: string) => void; - updateInterval: number; - setUpdateInterval: (value: number) => void; - isPaused: boolean; - setIsPaused: (value: boolean) => void; - lastUpdateTime: Date | null; -} - -export interface DataTableProps { - data: any[]; - sortConfig: { key: string | null; direction: string }; - handleSort: (key: string) => void; - bootTime: number | null; - isUpdating: boolean; -} - -export interface NoDataStateProps { - isSearchFiltered: boolean; -} - -export interface StatsOverviewProps { - data: any[]; - isIPv6: boolean; - direction: string; - trafficType: string; -} \ No newline at end of file diff --git a/mantis-frontend/src/types/TrafficMapTypes.tsx b/mantis-frontend/src/types/TrafficMapTypes.tsx deleted file mode 100644 index 2571dbd..0000000 --- a/mantis-frontend/src/types/TrafficMapTypes.tsx +++ /dev/null @@ -1,55 +0,0 @@ -// traffic map types - -export interface MapPoint { - id: string; - ip: string; - latitude: number; - longitude: number; - bytes: number; - packets: number; - code: string; - city: string; - regions: string; - type: 'source' | 'destination'; -} - -export interface MapConfig { - center: [number, number]; - zoom: number; - minZoom: number, - maxZoom: number, - scrollZoom: boolean, - boxZoom: boolean, - doubleClickZoom: boolean, - touchZoomRotate: boolean, - dragRotate: boolean, - pitchWithRotate: boolean, - renderWorldCopies: boolean -} - -export interface GeoLocation { - latitude: number | null; - longitude: number | null; - city?: string; - country?: string; - country_code?: string; -} - -export interface FlowDetails { - bytes: number; - packets: number; - geo: GeoLocation; -} - -export interface FlowDataRecord { - [ipPort: string]: FlowDetails; -} - -export interface ProtocolData { - ipv4?: FlowDataRecord; - ipv6?: FlowDataRecord; -} - -export interface FlowDataState { - [key: string]: ProtocolData; -} diff --git a/mantis-frontend/src/utils/connectionUtils.ts b/mantis-frontend/src/utils/connectionUtils.ts deleted file mode 100644 index bc26871..0000000 --- a/mantis-frontend/src/utils/connectionUtils.ts +++ /dev/null @@ -1,93 +0,0 @@ -export const createWebSocket = ( - url: string, - onMessage: (data: any) => void, - onError?: (error: Event) => void -): WebSocket => { - const ws = new WebSocket(url) - - ws.onmessage = (event) => { - try { - const data = JSON.parse(event.data) - onMessage(data) - } catch (error) { - console.error("Failed to parse WebSocket message:", error) - } - } - - ws.onerror = (error) => { - console.error("WebSocket Error:", error) - if (onError) onError(error) - } - - return ws -} - -export const fetchData = async ( - url: string, - onSuccess: (data: string) => void, - onError?: (error?: Error) => void -): Promise => { - try { - const response = await fetch(url) - if (!response.ok) throw new Error(`HTTP Error: ${response.status}`) - const data = await response.text() - onSuccess(data) - } catch (error) { - console.error(`Failed to fetch data from URL: ${url}`) - if (onError) onError(error as Error) - } -} - -export const putData = async ( - url: string, - payload: any, - onSuccess?: (data?: any) => void, - onError?: (error: Error) => void -): Promise => { - try { - const response = await fetch(url, { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), - }) - - if (!response.ok) throw new Error(`HTTP Error: ${response.status}`) - - const contentType = response.headers.get("Content-Type") - const isJsonResponse = contentType && contentType.includes("application/json") - - const data = isJsonResponse ? await response.json() : null - - if (onSuccess) onSuccess(data) - } catch (error) { - console.error(`Failed to send PUT request to URL: ${url}`, error) - if (onError) onError(error as Error) - } -} - -export const deleteData = async ( - url: string, - data: any, - onSuccess?: (data: string) => void, - onError?: (error: Error) => void -): Promise => { - try { - const response = await fetch(url, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }) - - if (!response.ok) throw new Error(`HTTP Error: ${response.status}`) - - const responseData = await response.text() - if (onSuccess) onSuccess(responseData) - } catch (error) { - console.error(`Failed to send DELETE request to URL: ${url}`, error) - if (onError) onError(error as Error) - } -} \ No newline at end of file diff --git a/mantis-frontend/start.bat b/mantis-frontend/start.bat deleted file mode 100644 index eb83804..0000000 --- a/mantis-frontend/start.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -title KWL-BOT v2.0 -npm run dev -pause \ No newline at end of file diff --git a/mantis-frontend/start.sh b/mantis-frontend/start.sh deleted file mode 100644 index d68e96c..0000000 --- a/mantis-frontend/start.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -# 切換到工作目錄 -cd /home - -# 檢查是否為首次安裝(通過檢查是否存在特定標記文件) -if [ ! -f ".setup_complete" ]; then - echo "First installation detected, starting system initialization..." - - # 更新系統套件 - apt-get update - apt-get upgrade -y - - # 安裝必要的系統套件 - apt install tzdata -y - apt install -y ca-certificates curl gnupg - - echo "Installing Node.js LTS (v22.x)..." - - # 創建 keyrings 目錄 - sudo mkdir -p /etc/apt/keyrings - - # 下載並添加 NodeSource GPG key - curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg - - # 設定 Node.js 主要版本號 - NODE_MAJOR=22 - - # 添加 NodeSource repository - echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list - - # 更新套件索引 - apt update - - # 安裝 Node.js (包含 npm) - apt install nodejs -y - - # 驗證安裝 - echo "Node.js installation completed!" - echo "Node.js version: $(node --version)" - echo "npm version: $(npm --version)" - - # 創建安裝完成標記 - touch .setup_complete - echo "System initialization completed!" -else - echo "System initialization already completed, skipping installation steps..." - echo "Current Node.js version: $(node --version)" - echo "Current npm version: $(npm --version)" -fi - -# 安裝/更新 npm 依賴套件 -echo "Installing/updating npm dependencies..." -if [ -f "package.json" ]; then - npm install -else - echo "No package.json found, skipping npm install..." -fi - -# 運行應用程式 -echo "Starting application..." -if [ -f "package.json" ]; then - # 檢查是否有 build script - if grep -q '"build"' package.json 2>/dev/null; then - echo "Running build script..." - npm run build - else - echo "No build script found, skipping build step..." - fi - - # 檢查是否有 start script - if grep -q '"start:https"' package.json 2>/dev/null; then - echo "Running start script for https..." - npm run start:https - elif grep -q '"start"' package.json 2>/dev/null; then - echo "Running start script..." - npm run start - else - echo "No start script found, please configure your package.json start script..." - fi -else - echo "No package.json found, please initialize your Node.js project first with 'npm init'" -fi \ No newline at end of file diff --git a/mantis-frontend/tailwind.config.js b/mantis-frontend/tailwind.config.js deleted file mode 100644 index dd77b57..0000000 --- a/mantis-frontend/tailwind.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import typography from '@tailwindcss/typography'; - -/** @type {import('tailwindcss').Config} */ -export default { - content: [ - "./pages/**/*.{js,ts,jsx,tsx}", - "./components/**/*.{js,ts,jsx,tsx}", - "./src/**/*.{js,ts,jsx,tsx}", - ], - theme: { - extend: {}, - }, - plugins: [typography], -} \ No newline at end of file diff --git a/mantis-frontend/tsconfig.json b/mantis-frontend/tsconfig.json deleted file mode 100644 index 55963a4..0000000 --- a/mantis-frontend/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "target": "es2024", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "strict": false, - "noEmit": true, - "incremental": true, - "module": "esnext", - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "react-jsx" - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules" - ] -}