diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 774dc59..ffe013d 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -32,10 +32,6 @@ services: timeout: 3s volumes: - heady_redis_data:/data - # Expose to localhost so the Heady dev server (running on the host) can - # use the same Redis instance Authentik uses for its session store. - ports: - - '127.0.0.1:6389:6379' networks: [authentik-internal] heady-authentik-server: diff --git a/package.json b/package.json index 1398f27..5add468 100644 --- a/package.json +++ b/package.json @@ -27,27 +27,16 @@ "@astrojs/tailwind": "^5.1.2", "@kubernetes/client-node": "^1.3.0", "@libsql/client": "0.15.12", - "@radix-ui/react-avatar": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.16", - "@radix-ui/react-dropdown-menu": "^2.1.17", - "@radix-ui/react-select": "^2.3.0", - "@radix-ui/react-separator": "^1.1.9", - "@radix-ui/react-slot": "^1.2.5", - "@radix-ui/react-switch": "^1.3.0", - "@radix-ui/react-tabs": "^1.1.14", - "@radix-ui/react-tooltip": "^1.2.9", "@shopify/lang-jsonc": "^1.0.1", "alpinejs": "^3.14.1", "arktype": "^2.1.20", "astro": "^4.16.18", "chart.js": "^4.4.7", - "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", "dotenv": "17.2.1", "drizzle-orm": "0.44.4", "guacamole-lite": "^1.2.0", - "ioredis": "^5.11.1", "ip-address": "^9.0.5", "is-cidr": "^5.0.3", "jose": "6.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02bdc0f..5f540d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,33 +26,6 @@ importers: '@libsql/client': specifier: 0.15.12 version: 0.15.12(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@radix-ui/react-avatar': - specifier: ^1.1.12 - version: 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-dialog': - specifier: ^1.1.16 - version: 1.1.16(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-dropdown-menu': - specifier: ^2.1.17 - version: 2.1.17(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-select': - specifier: ^2.3.0 - version: 2.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-separator': - specifier: ^1.1.9 - version: 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': - specifier: ^1.2.5 - version: 1.2.5(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-switch': - specifier: ^1.3.0 - version: 1.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-tabs': - specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-tooltip': - specifier: ^1.2.9 - version: 1.2.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@shopify/lang-jsonc': specifier: ^1.0.1 version: 1.0.1 @@ -68,9 +41,6 @@ importers: chart.js: specifier: ^4.4.7 version: 4.5.0 - class-variance-authority: - specifier: ^0.7.1 - version: 0.7.1 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -86,9 +56,6 @@ importers: guacamole-lite: specifier: ^1.2.0 version: 1.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - ioredis: - specifier: ^5.11.1 - version: 5.11.1 ip-address: specifier: ^9.0.5 version: 9.0.5 @@ -901,21 +868,6 @@ packages: cpu: [x64] os: [win32] - '@floating-ui/core@1.7.5': - resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - - '@floating-ui/dom@1.7.6': - resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - - '@floating-ui/react-dom@2.1.8': - resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.11': - resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - '@iconify-json/simple-icons@1.2.48': resolution: {integrity: sha512-EACOtZMoPJtERiAbX1De0asrrCtlwI27+03c9OJlYWsly9w1O5vcD8rTzh+kDPjo+K8FOVnq2Qy+h/CzljSKDA==} @@ -1027,9 +979,6 @@ packages: cpu: [x64] os: [win32] - '@ioredis/commands@1.10.0': - resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1184,397 +1133,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@radix-ui/number@1.1.2': - resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} - - '@radix-ui/primitive@1.1.4': - resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==} - - '@radix-ui/react-arrow@1.1.9': - resolution: {integrity: sha512-yqHW5WQ/cTpU/un7dqqIKNy2iRU8BC0JB78PEzTfCCYvZu1U6W9KwObAniMk9nhSfyotKPQTYaUD/HB0f5muig==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-avatar@1.1.12': - resolution: {integrity: sha512-NQCQyWC7QrDPhjMn8hUqFeU0lUrprIgm1AyMgLbzuQJibNnatdc3SSMo3/UGFu/eUkJUU1cEcKCnyhXTQzq6tA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collection@1.1.9': - resolution: {integrity: sha512-zuSVi7ziP7uQRqc+yGxsKJfNkdyHv3ZKDaHe0gzg4dRgws96TPKWIiz84tVHP4GEcEl8bC0mdt17NkcxaJHmaQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-compose-refs@1.1.3': - resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context@1.1.4': - resolution: {integrity: sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dialog@1.1.16': - resolution: {integrity: sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-direction@1.1.2': - resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.12': - resolution: {integrity: sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-dropdown-menu@2.1.17': - resolution: {integrity: sha512-S6b3Jm57sY5EdDyOMLkacbB0qMnKhy1RCKZCt795ZkmtUOAvojYIZ5p7dXHIh5Cyr3jCLLI5/g64V3FKLudZmw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-guards@1.1.4': - resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-scope@1.1.9': - resolution: {integrity: sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-id@1.1.2': - resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-menu@2.1.17': - resolution: {integrity: sha512-fmbNnFyf+JYCN0DhhWnEdUTDnZD1mXaPQWivdsPIb8oOSbARfD3LIQJbLCG8a8QLCwoMxiJ7GVPIFcC8Dw8v2Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-popper@1.3.0': - resolution: {integrity: sha512-9PB589e1aWZbrlFUHdz6WiPCL+xLZHQFX7oibqG/6Q0SwOkxDyQX9W/cyPa+sAPPKuC8cpLCpRczE5a/1DiwVQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-portal@1.1.11': - resolution: {integrity: sha512-UEytdjgEh2tJGgD/gZK4FUx6t1rNIlM3U0DENhSrG7I75FGm1DnaDuVUWF1pWAWUwGmn1sCJ1VGHn8LhN1aTOw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-presence@1.1.6': - resolution: {integrity: sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.5': - resolution: {integrity: sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-roving-focus@1.1.12': - resolution: {integrity: sha512-FvgPt1bRmg8Xt2QpF7NUZW3dE0ZQHGm41dAdgT2J2GJPoIXz+9Em3NobAxf4fupcxhgHu03E5CRiU2MWvObXyg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-select@2.3.0': - resolution: {integrity: sha512-mENc7WpJvJcW8hlMpzfFcHcEhTvYS5JMBmi9HVC1Q00uhBwML086MHYUV8QQdQv6lcu0Wg8dzd1RB8AFADcG/g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-separator@1.1.9': - resolution: {integrity: sha512-gvgW+JV/Mbjj6darztTetnmElpQEzZrXpJvfj+dOxNAxiyHEAyUvEjjl4zxblvmjmKmi3jfPoy7ZdxzCuUBJSA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.2.5': - resolution: {integrity: sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-switch@1.3.0': - resolution: {integrity: sha512-GP1EZwhoZO/GGnhM1P5/2Vpm8iN8EnngyU0oezn2l78kN8tj25pyrvjIaT7azBhK615KSt+P2w39y57YV5jVkA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-tabs@1.1.14': - resolution: {integrity: sha512-D5jwp9JNuwDeCw3CYD2Fz+sSHo0droQjC8u75dJHe4aWr5q6yBiXZU+hurXnKudRgEpUkD5TsI6bjHPo5ThUxA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-tooltip@1.2.9': - resolution: {integrity: sha512-u6F9MmTtBSLkiXNVDrtB/yPCZarM9smNswC24YYLV/M+bth6J3Gs3vlJezEoFwKZvPvxhCpUYdUnOsNG/0XOlA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-use-callback-ref@1.1.2': - resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.3': - resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-effect-event@0.0.3': - resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-escape-keydown@1.1.2': - resolution: {integrity: sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-is-hydrated@0.1.1': - resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.2': - resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-previous@1.1.2': - resolution: {integrity: sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-rect@1.1.2': - resolution: {integrity: sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-size@1.1.2': - resolution: {integrity: sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-visually-hidden@1.2.5': - resolution: {integrity: sha512-tPcHNI3FajdDBFpl/Ez1m2WL0ufJqBKyHxMDBvKitopamK36WwBGOMicuMEZKkM5Wce41QxUyv6BsiqfrWBiGg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/rect@1.1.2': - resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==} - '@rolldown/binding-android-arm64@1.0.0-beta.33': resolution: {integrity: sha512-xhDQXKftRkEULIxCddrKMR8y0YO/Y+6BKk/XrQP2B29YjV2wr8DByoEz+AHX9BfLHb2srfpdN46UquBW2QXWpQ==} cpu: [arm64] @@ -2223,10 +1781,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-hidden@1.2.6: - resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} - engines: {node: '>=10'} - aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -2417,9 +1971,6 @@ packages: resolution: {integrity: sha512-86M1y3ZeQvpZkZejQCcS+IaSWjlDUC+ORP0peScQ4uEUFCZ8bEQVz7NlJHqysoUb6w3zCjx4Mq/8/2RHhMwHYw==} engines: {node: '>=14'} - class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -2440,10 +1991,6 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - cluster-key-slot@1.1.1: - resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} - engines: {node: '>=0.10.0'} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -2534,15 +2081,6 @@ packages: supports-color: optional: true - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -2562,10 +2100,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -2586,9 +2120,6 @@ packages: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - deterministic-object-hash@2.0.2: resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} engines: {node: '>=18'} @@ -2954,10 +2485,6 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -3082,10 +2609,6 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - ioredis@5.11.1: - resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} - engines: {node: '>=12.22.0'} - ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -3936,36 +3459,6 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react-remove-scroll-bar@2.3.8: - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.7.2: - resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-style-singleton@2.2.3: - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -3985,14 +3478,6 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - redis-errors@1.2.0: - resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} - engines: {node: '>=4'} - - redis-parser@3.0.0: - resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} - engines: {node: '>=4'} - regex-recursion@5.1.1: resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} @@ -4241,9 +3726,6 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - standard-as-callback@2.1.0: - resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -4514,26 +3996,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - use-callback-ref@1.3.3: - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - utf-8-validate@5.0.10: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} @@ -5496,23 +4958,6 @@ snapshots: '@esbuild/win32-x64@0.25.9': optional: true - '@floating-ui/core@1.7.5': - dependencies: - '@floating-ui/utils': 0.2.11 - - '@floating-ui/dom@1.7.6': - dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 - - '@floating-ui/react-dom@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/dom': 1.7.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@floating-ui/utils@0.2.11': {} - '@iconify-json/simple-icons@1.2.48': dependencies: '@iconify/types': 2.0.0 @@ -5594,8 +5039,6 @@ snapshots: '@img/sharp-win32-x64@0.33.5': optional: true - '@ioredis/commands@1.10.0': {} - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -5775,393 +5218,6 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@radix-ui/number@1.1.2': {} - - '@radix-ui/primitive@1.1.4': {} - - '@radix-ui/react-arrow@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-avatar@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-collection@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.5(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-compose-refs@1.1.3(@types/react@18.3.31)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-context@1.1.4(@types/react@18.3.31)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-dialog@1.1.16(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.5(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.31)(react@18.3.1) - aria-hidden: 1.2.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.2(@types/react@18.3.31)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-direction@1.1.2(@types/react@18.3.31)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-dismissable-layer@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-dropdown-menu@2.1.17(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-id': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-menu': 2.1.17(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-focus-guards@1.1.4(@types/react@18.3.31)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-focus-scope@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-id@1.1.2(@types/react@18.3.31)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-menu@2.1.17(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-direction': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-popper': 1.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.5(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.31)(react@18.3.1) - aria-hidden: 1.2.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.2(@types/react@18.3.31)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-popper@1.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-rect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/rect': 1.1.2 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-portal@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-presence@1.1.6(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-primitive@2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-slot': 1.2.5(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-roving-focus@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-direction': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-id': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-select@2.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/number': 1.1.2 - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-direction': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-popper': 1.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.5(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.2.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - aria-hidden: 1.2.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.2(@types/react@18.3.31)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-separator@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-slot@1.2.5(@types/react@18.3.31)(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-switch@1.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-tabs@1.1.14(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-direction': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-id': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-tooltip@1.2.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-context': 1.1.4(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.2(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-popper': 1.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.5(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.2.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/react-use-callback-ref@1.1.2(@types/react@18.3.31)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-use-controllable-state@1.2.3(@types/react@18.3.31)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-effect-event': 0.0.3(@types/react@18.3.31)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-use-effect-event@0.0.3(@types/react@18.3.31)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-use-escape-keydown@1.1.2(@types/react@18.3.31)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@18.3.31)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-use-layout-effect@1.1.2(@types/react@18.3.31)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-use-previous@1.1.2(@types/react@18.3.31)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-use-rect@1.1.2(@types/react@18.3.31)(react@18.3.1)': - dependencies: - '@radix-ui/rect': 1.1.2 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-use-size@1.1.2(@types/react@18.3.31)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.31)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.31 - - '@radix-ui/react-visually-hidden@1.2.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - '@types/react-dom': 18.3.7(@types/react@18.3.31) - - '@radix-ui/rect@1.1.2': {} - '@rolldown/binding-android-arm64@1.0.0-beta.33': optional: true @@ -6810,10 +5866,6 @@ snapshots: argparse@2.0.1: {} - aria-hidden@1.2.6: - dependencies: - tslib: 2.8.1 - aria-query@5.3.2: {} arktype@2.1.20: @@ -7079,10 +6131,6 @@ snapshots: dependencies: ip-regex: 5.0.0 - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - cli-boxes@3.0.0: {} cli-cursor@5.0.0: @@ -7099,8 +6147,6 @@ snapshots: clsx@2.1.1: {} - cluster-key-slot@1.1.1: {} - color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -7168,10 +6214,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.3: - dependencies: - ms: 2.1.3 - decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -7187,8 +6229,6 @@ snapshots: delayed-stream@1.0.0: {} - denque@2.1.0: {} - depd@2.0.0: {} dequal@2.0.3: {} @@ -7199,8 +6239,6 @@ snapshots: detect-libc@2.0.4: {} - detect-node-es@1.1.0: {} - deterministic-object-hash@2.0.2: dependencies: base-64: 1.0.0 @@ -7517,8 +6555,6 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 - get-nonce@1.0.1: {} - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -7700,18 +6736,6 @@ snapshots: ini@1.3.8: optional: true - ioredis@5.11.1: - dependencies: - '@ioredis/commands': 1.10.0 - cluster-key-slot: 1.1.1 - debug: 4.4.3 - denque: 2.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -8648,33 +7672,6 @@ snapshots: react-refresh@0.17.0: {} - react-remove-scroll-bar@2.3.8(@types/react@18.3.31)(react@18.3.1): - dependencies: - react: 18.3.1 - react-style-singleton: 2.2.3(@types/react@18.3.31)(react@18.3.1) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.31 - - react-remove-scroll@2.7.2(@types/react@18.3.31)(react@18.3.1): - dependencies: - react: 18.3.1 - react-remove-scroll-bar: 2.3.8(@types/react@18.3.31)(react@18.3.1) - react-style-singleton: 2.2.3(@types/react@18.3.31)(react@18.3.1) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@18.3.31)(react@18.3.1) - use-sidecar: 1.1.3(@types/react@18.3.31)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.31 - - react-style-singleton@2.2.3(@types/react@18.3.31)(react@18.3.1): - dependencies: - get-nonce: 1.0.1 - react: 18.3.1 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.31 - react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -8696,12 +7693,6 @@ snapshots: readdirp@4.1.2: {} - redis-errors@1.2.0: {} - - redis-parser@3.0.0: - dependencies: - redis-errors: 1.2.0 - regex-recursion@5.1.1: dependencies: regex: 5.1.1 @@ -9056,8 +8047,6 @@ snapshots: stackback@0.0.2: {} - standard-as-callback@2.1.0: {} - statuses@2.0.1: {} std-env@3.9.0: {} @@ -9282,7 +8271,8 @@ snapshots: optionalDependencies: typescript: 5.9.2 - tslib@2.8.1: {} + tslib@2.8.1: + optional: true tsx@4.20.4: dependencies: @@ -9377,21 +8367,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - use-callback-ref@1.3.3(@types/react@18.3.31)(react@18.3.1): - dependencies: - react: 18.3.1 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.31 - - use-sidecar@1.1.3(@types/react@18.3.31)(react@18.3.1): - dependencies: - detect-node-es: 1.1.0 - react: 18.3.1 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.31 - utf-8-validate@5.0.10: dependencies: node-gyp-build: 4.8.4 diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx deleted file mode 100644 index 291931b..0000000 --- a/src/components/dashboard/Dashboard.tsx +++ /dev/null @@ -1,406 +0,0 @@ -// 🤠 Heady Dashboard — React replacement for the Alpine-driven home page. - -import { - Activity, - ArrowRight, - CheckCircle2, - CircleDot, - Database, - Globe, - HardDrive, - KeyRound, - Network, - Plus, - Server, - Shield, - Terminal, - Users, - Wifi, - XCircle, -} from 'lucide-react'; -import * as React from 'react'; -import { AppShell, type SessionUser } from '@/components/shell/AppShell'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; - -export interface Machine { - id: string; - name: string; - ip_address: string; - os: string; - online: boolean; -} - -export interface Session { - id: string; - node_name: string; - user_email: string; - protocol: 'ssh' | 'rdp' | 'vnc' | 'telnet' | 'kubernetes'; - status: string; -} - -export interface DashboardProps { - machines: Machine[]; - activeSessions: Session[]; -} - -export function DashboardPage(props: DashboardProps) { - return ( - - {(user) => } - - ); -} - -function DashboardBody({ - user, - machines, - activeSessions, -}: DashboardProps & { user: SessionUser }) { - const onlineMachines = machines.filter((m) => m.online).length; - const systemHealth = { - headscale: true, - database: true, - oidc: true, - remoteAccess: true, - }; - const everythingHealthy = Object.values(systemHealth).every((v) => v); - - return ( -
- {/* ─── Welcome header ─────────────────────────────── */} -
-
-

- Welcome back, {user.name.split(' ')[0]} -

-

- Strategic VPN management that's actually pleasant to use. -

-
-
- - -
-
- - {/* ─── Stats grid ─────────────────────────────────── */} -
- } - label="Online Machines" - value={`${onlineMachines}`} - sub={`of ${machines.length}`} - tone="emerald" - /> - } - label="Active Sessions" - value={`${activeSessions.length}`} - tone="sky" - /> - } - label="System Health" - value={everythingHealthy ? 'Healthy' : 'Degraded'} - tone={everythingHealthy ? 'emerald' : 'amber'} - /> - } - label="Network" - value="Online" - tone="violet" - /> -
- - {/* ─── Main grid: machines + sidebar ─────────────── */} -
- -
- - - -
-
-
- ); -} - -/* ────────────────────────────────────────── Stat tile ──── */ - -function StatCard({ - icon, - label, - value, - sub, - tone, -}: { - icon: React.ReactNode; - label: string; - value: string; - sub?: string; - tone: 'emerald' | 'sky' | 'amber' | 'violet'; -}) { - const toneClasses: Record = { - emerald: 'bg-emerald-500/10 text-emerald-400', - sky: 'bg-sky-500/10 text-sky-400', - amber: 'bg-amber-500/10 text-amber-400', - violet: 'bg-violet-500/10 text-violet-400', - }; - return ( - - -
- {icon} -
-
-

- {label} -

-

- {value} - {sub && ( - - {sub} - - )} -

-
-
-
- ); -} - -/* ────────────────────────────────────────── Machines ──── */ - -function MachinesOverview({ - machines, - className, -}: { - machines: Machine[]; - className?: string; -}) { - const visible = machines.slice(0, 5); - const overflow = machines.length - visible.length; - return ( - - -
- - - Machines - - Connected devices in your tailnet -
- -
- - {visible.length === 0 ? ( - } - title="No machines yet" - description="Connect your first device to see it here." - /> - ) : ( -
    - {visible.map((m) => ( -
  • - -
    -

    {m.name}

    -

    - {m.ip_address} -

    -
    - - {m.os} - - - {m.online ? 'Online' : 'Offline'} - -
  • - ))} -
- )} - {overflow > 0 && ( -

- and {overflow} more… -

- )} -
-
- ); -} - -/* ────────────────────────────────────────── Sessions ──── */ - -function ActiveSessionsCard({ sessions }: { sessions: Session[] }) { - return ( - - - - - Active Sessions - - - - {sessions.length === 0 ? ( -

No active sessions.

- ) : ( - sessions.slice(0, 4).map((s) => ( -
-
- - {s.protocol} - - {s.node_name} -
- - {s.user_email.split('@')[0]} - -
- )) - )} -
-
- ); -} - -/* ────────────────────────────────────── System health ──── */ - -function SystemHealthCard({ - health, -}: { - health: { headscale: boolean; database: boolean; oidc: boolean; remoteAccess: boolean }; -}) { - const rows: { label: string; ok: boolean; icon: React.ReactNode }[] = [ - { label: 'Headscale', ok: health.headscale, icon: }, - { label: 'Database', ok: health.database, icon: }, - { label: 'OIDC Auth', ok: health.oidc, icon: }, - { label: 'Remote Access', ok: health.remoteAccess, icon: }, - ]; - return ( - - - - - System Health - - - - {rows.map((r) => ( -
- - {r.icon} - {r.label} - - {r.ok ? ( - - - Healthy - - ) : ( - - - Degraded - - )} -
- ))} -
-
- ); -} - -/* ────────────────────────────────────── Quick actions ──── */ - -function QuickActionsCard() { - const items: { href: string; label: string; icon: React.ReactNode }[] = [ - { href: '/machines/add', label: 'Add Machine', icon: }, - { href: '/acls', label: 'Configure ACLs', icon: }, - { href: '/dns', label: 'Manage DNS', icon: }, - { href: '/users', label: 'User Management', icon: }, - ]; - return ( - - - Quick Actions - - - {items.map((it) => ( - - ))} - - - ); -} - -/* ────────────────────────────────────── Empty state ──── */ - -function EmptyState({ - icon, - title, - description, -}: { - icon: React.ReactNode; - title: string; - description: string; -}) { - return ( -
- {icon} -

{title}

-

{description}

-
- ); -} - diff --git a/src/components/dns/DnsPage.tsx b/src/components/dns/DnsPage.tsx deleted file mode 100644 index 739b8aa..0000000 --- a/src/components/dns/DnsPage.tsx +++ /dev/null @@ -1,512 +0,0 @@ -// 🤠 Heady DNS page — React + shadcn rebuild. - -import { - Globe, - Network, - Plus, - RotateCcw, - Save, - Server, - Trash2, -} from 'lucide-react'; -import * as React from 'react'; -import { AppShell } from '@/components/shell/AppShell'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; -import { Input } from '@/components/ui/input'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { Switch } from '@/components/ui/switch'; - -export type DnsRecordType = 'A' | 'AAAA' | 'CNAME' | 'TXT' | 'MX'; - -export interface DnsRecord { - name: string; - type: DnsRecordType; - value: string; -} - -export interface DnsConfig { - prefixes: string[]; - magicDns: boolean; - baseDomain: string; - nameservers: string[]; - splitDns: Record; - searchDomains: string[]; - overrideDns: boolean; - extraRecords: DnsRecord[]; -} - -export interface DnsPageProps { - initial: DnsConfig; - canEdit?: boolean; -} - -export function DnsPage(props: DnsPageProps) { - return ( - - {() => } - - ); -} - -function DnsBody({ initial, canEdit = true }: DnsPageProps) { - const [config, setConfig] = React.useState(initial); - const [dirty, setDirty] = React.useState(false); - - const update = (key: K, value: DnsConfig[K]) => { - setConfig((c) => ({ ...c, [key]: value })); - setDirty(true); - }; - - const reset = () => { - setConfig(initial); - setDirty(false); - }; - - const save = () => { - // TODO: PATCH /api/dns once the backend lands. - console.log('Would save DNS config', config); - setDirty(false); - }; - - return ( -
-
-
-

DNS

-

- Magic DNS, nameservers, search domains, and custom records for your - tailnet. -

-
-
- - -
-
- - {/* Magic DNS toggle row */} - - -
-

Magic DNS

-

- Auto-resolve devices by name on{' '} - - {config.baseDomain || ''} - - . -

-
- update('magicDns', v)} - disabled={!canEdit} - /> -
-
- - {/* Base domain + override toggle */} - - - Base domain - - Suffix appended to every Magic DNS hostname. - - - -
- - update('baseDomain', e.target.value)} - placeholder="heady.local" - disabled={!canEdit} - /> -
-
-
-

Override local DNS

-

- Force clients to use these nameservers. -

-
- update('overrideDns', v)} - disabled={!canEdit} - /> -
-
-
- - {/* Tailnet prefixes (read-only) */} - - - - - Tailnet prefixes - - - Allocated by Headscale. Read-only. - - - - {config.prefixes.map((p) => ( - - {p} - - ))} - - - - {/* String list cards */} -
- } - values={config.nameservers} - placeholder="1.1.1.1" - onChange={(v) => update('nameservers', v)} - disabled={!canEdit} - /> - } - values={config.searchDomains} - placeholder="company.com" - onChange={(v) => update('searchDomains', v)} - disabled={!canEdit} - /> -
- - {/* Split DNS */} - update('splitDns', v)} - disabled={!canEdit} - /> - - {/* Extra records */} - update('extraRecords', v)} - disabled={!canEdit} - /> -
- ); -} - -/* ───── String list editor (nameservers, search domains) ───── */ - -function StringListCard({ - title, - description, - icon, - values, - placeholder, - onChange, - disabled, -}: { - title: string; - description: string; - icon: React.ReactNode; - values: string[]; - placeholder: string; - onChange: (v: string[]) => void; - disabled?: boolean; -}) { - const [draft, setDraft] = React.useState(''); - const add = () => { - const v = draft.trim(); - if (!v || values.includes(v)) return; - onChange([...values, v]); - setDraft(''); - }; - const remove = (i: number) => { - onChange(values.filter((_, idx) => idx !== i)); - }; - return ( - - - {icon}{title} - {description} - - - {values.length === 0 && ( -

No entries yet.

- )} -
    - {values.map((v, i) => ( -
  • - {v} - -
  • - ))} -
-
- setDraft(e.target.value)} - placeholder={placeholder} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - add(); - } - }} - disabled={disabled} - /> - -
-
-
- ); -} - -/* ───── Split DNS ───── */ - -function SplitDnsCard({ - rules, - onChange, - disabled, -}: { - rules: Record; - onChange: (v: Record) => void; - disabled?: boolean; -}) { - const [domain, setDomain] = React.useState(''); - const [servers, setServers] = React.useState(''); - - const add = () => { - const d = domain.trim(); - const s = servers - .split(',') - .map((x) => x.trim()) - .filter(Boolean); - if (!d || s.length === 0) return; - onChange({ ...rules, [d]: s }); - setDomain(''); - setServers(''); - }; - - const remove = (key: string) => { - const next = { ...rules }; - delete next[key]; - onChange(next); - }; - - const entries = Object.entries(rules); - return ( - - - Split DNS - - Per-domain resolvers. Queries for these domains skip the global list. - - - - {entries.length === 0 ? ( -

No split DNS rules.

- ) : ( -
    - {entries.map(([d, ips]) => ( -
  • -
    -

    {d}

    -

    - {ips.join(', ')} -

    -
    - -
  • - ))} -
- )} -
- setDomain(e.target.value)} - placeholder="corp.example.com" - disabled={disabled} - /> - setServers(e.target.value)} - placeholder="10.0.0.1, 10.0.0.2" - disabled={disabled} - /> - -
-
-
- ); -} - -/* ───── Extra records ───── */ - -function ExtraRecordsCard({ - records, - onChange, - disabled, -}: { - records: DnsRecord[]; - onChange: (v: DnsRecord[]) => void; - disabled?: boolean; -}) { - const [name, setName] = React.useState(''); - const [type, setType] = React.useState('A'); - const [value, setValue] = React.useState(''); - - const add = () => { - if (!name.trim() || !value.trim()) return; - onChange([...records, { name: name.trim(), type, value: value.trim() }]); - setName(''); - setValue(''); - }; - const remove = (i: number) => onChange(records.filter((_, idx) => idx !== i)); - - return ( - - - Custom records - - Extra A/AAAA/CNAME/TXT/MX records served alongside Magic DNS. - - - - {records.length === 0 ? ( -

- No custom records. -

- ) : ( -
- - - - - - - - - - {records.map((r, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: stable position - - - - - - - ))} - -
NameTypeValue -
{r.name} - - {r.type} - - - {r.value} - - -
-
- )} -
- setName(e.target.value)} - placeholder="api.heady.local" - disabled={disabled} - /> - - setValue(e.target.value)} - placeholder="100.64.0.10" - disabled={disabled} - /> - -
-
-
- ); -} diff --git a/src/components/machines/MachinesPage.tsx b/src/components/machines/MachinesPage.tsx deleted file mode 100644 index 229c596..0000000 --- a/src/components/machines/MachinesPage.tsx +++ /dev/null @@ -1,356 +0,0 @@ -// 🤠 Heady Machines page — React + shadcn rebuild. - -import { - CircleDot, - Filter, - HardDrive, - MoreHorizontal, - Plus, - RefreshCw, - Search, - Server, - Tag, -} from 'lucide-react'; -import * as React from 'react'; -import { AppShell, type SessionUser } from '@/components/shell/AppShell'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { Input } from '@/components/ui/input'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; - -export interface MachineRow { - id: string; - name: string; - hostname?: string; - ip_address: string; - online: boolean; - expired?: boolean; - os: 'linux' | 'windows' | 'macos' | 'android' | 'ios' | 'unknown'; - user?: string; - tags?: string[]; - last_seen?: string; -} - -export interface MachinesPageProps { - machines: MachineRow[]; -} - -export function MachinesPage(props: MachinesPageProps) { - return ( - - {(user) => } - - ); -} - -function MachinesBody({ - user, - machines, -}: MachinesPageProps & { user: SessionUser }) { - const [search, setSearch] = React.useState(''); - const [statusFilter, setStatusFilter] = React.useState('all'); - const [osFilter, setOsFilter] = React.useState('all'); - - const allOsValues = React.useMemo(() => { - const set = new Set(); - machines.forEach((m) => set.add(m.os)); - return Array.from(set).sort(); - }, [machines]); - - const filtered = React.useMemo(() => { - const needle = search.trim().toLowerCase(); - return machines.filter((m) => { - if (statusFilter === 'online' && !m.online) return false; - if (statusFilter === 'offline' && m.online) return false; - if (osFilter !== 'all' && m.os !== osFilter) return false; - if (!needle) return true; - return ( - m.name.toLowerCase().includes(needle) || - m.ip_address.toLowerCase().includes(needle) || - (m.user ?? '').toLowerCase().includes(needle) || - (m.tags ?? []).some((t) => t.toLowerCase().includes(needle)) - ); - }); - }, [machines, search, statusFilter, osFilter]); - - const onlineCount = machines.filter((m) => m.online).length; - const expiredCount = machines.filter((m) => m.expired).length; - - return ( -
-
-
-

Machines

-

- Every device connected to your Headscale tailnet. -

-
-
- - -
-
- - {/* Stat strip */} -
- } /> - } - /> - } - /> - 0 ? 'amber' : 'muted'} - icon={} - /> -
- - {/* Filter bar */} - - -
- - setSearch(e.target.value)} - className="pl-8" - /> -
- - -
-
- - {/* Results */} - - -
- Devices - - {filtered.length} of {machines.length} shown - -
- -
- - {filtered.length === 0 ? ( - - ) : ( - - )} - -
-
- ); -} - -/* ───── stat ───── */ -function Stat({ - label, - value, - icon, - tone = 'sky', -}: { - label: string; - value: number; - icon: React.ReactNode; - tone?: 'sky' | 'emerald' | 'amber' | 'muted'; -}) { - const tones: Record = { - sky: 'bg-sky-500/10 text-sky-400', - emerald: 'bg-emerald-500/10 text-emerald-400', - amber: 'bg-amber-500/10 text-amber-400', - muted: 'bg-muted text-muted-foreground', - }; - return ( - - -
- {icon} -
-
-

- {label} -

-

{value}

-
-
-
- ); -} - -/* ───── table ───── */ -function MachinesTable({ machines }: { machines: MachineRow[] }) { - return ( -
- - - - - - - - - - - - - {machines.map((m) => ( - - - - - - - - - - ))} - -
StatusNameIPOSUserTags -
- - {m.online ? 'Online' : 'Offline'} - - {m.expired && ( - - expired - - )} - -
{m.name}
- {m.hostname && m.hostname !== m.name && ( -
- {m.hostname} -
- )} -
{m.ip_address} - {m.os} - - {m.user ?? '—'} - - {m.tags && m.tags.length > 0 ? ( -
- {m.tags.map((t) => ( - - - {t} - - ))} -
- ) : ( - - )} -
- -
-
- ); -} - -function MachineRowMenu({ machine }: { machine: MachineRow }) { - return ( - - - - - - {machine.name} - - { - navigator.clipboard?.writeText(machine.ip_address).catch(() => {}); - }} - > - Copy IP - - - Details - - - Open terminal - - - - Disconnect - - - - ); -} - -function EmptyResult() { - return ( -
- -

No matching machines

-

Try adjusting your search or filters.

-
- ); -} diff --git a/src/components/settings/SettingsPage.tsx b/src/components/settings/SettingsPage.tsx deleted file mode 100644 index e688b3f..0000000 --- a/src/components/settings/SettingsPage.tsx +++ /dev/null @@ -1,648 +0,0 @@ -// ⚙️ Heady Settings page — React + shadcn rebuild with tabs. - -import { - CheckCircle2, - FileText, - KeyRound, - Plus, - Server, - Settings, - Shield, - Trash2, - UserCheck, - XCircle, -} from 'lucide-react'; -import * as React from 'react'; -import { AppShell, type SessionUser } from '@/components/shell/AppShell'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; -import { Input } from '@/components/ui/input'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { Switch } from '@/components/ui/switch'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; - -export interface AuthKey { - id: string; - keyPrefix: string; - reusable: boolean; - ephemeral: boolean; - used: boolean; - expiration: string; - createdAt: string; - user: { id: string; name: string; email: string }; -} - -export interface SettingsData { - server: { - version: string; - url: string; - publicUrl: string; - grpcListenAddr: string; - metricsListenAddr: string; - }; - oidc: { - enabled: boolean; - issuer: string; - clientId: string; - stripEmailDomain: boolean; - scope: string[]; - allowedDomains: string[]; - allowedGroups: string[]; - }; - database: { type: string; sqlite: { path: string } }; - tls: { letsencrypt: { hostname: string; challengeType: string } }; - log: { level: string; format: string }; -} - -export interface Permissions { - canGenerateAuthKeys: boolean; - canModifyConfig: boolean; - canViewLogs: boolean; -} - -/** Heady runtime info — pulled from process env + the session store. */ -export interface HeadyRuntime { - sessionStore: 'redis' | 'in-memory'; - sessionLifetime: string; - sessionCookieName: string; - cookieSecure: boolean; - activeSessions: number; - nodeVersion: string; - processUptime: number; -} - -export interface SettingsPageProps { - settings: SettingsData; - authKeys: AuthKey[]; - permissions: Permissions; - heady: HeadyRuntime; -} - -export function SettingsPage(props: SettingsPageProps) { - return ( - - {(user) => } - - ); -} - -function SettingsBody({ - me, - settings, - authKeys, - permissions, - heady, -}: SettingsPageProps & { me: SessionUser }) { - return ( -
-
-

Settings

-

- Configure your Heady deployment, manage pre-auth keys, and review - identity restrictions. -

-
- - {/* Headline strip */} -
- } - label="Headscale" - value={ - settings.server.version.includes('connect') - ? 'not connected' - : `v${settings.server.version}` - } - tone={settings.server.version.includes('connect') ? 'amber' : 'sky'} - /> - } - label="OIDC" - value={settings.oidc.enabled ? 'Enabled' : 'Disabled'} - tone={settings.oidc.enabled ? 'emerald' : 'amber'} - /> - } - label="Sessions" - value={`${heady.activeSessions} active`} - tone="sky" - /> - } - label="Session store" - value={heady.sessionStore} - tone={heady.sessionStore === 'redis' ? 'emerald' : 'amber'} - /> -
- - - - - - Overview - - - - Auth Keys - - - - OIDC - - - - Advanced - - - - - - - - - - - - - - - - -
- ); -} - -function HeadlineStat({ - icon, - label, - value, - tone = 'sky', -}: { - icon: React.ReactNode; - label: string; - value: string; - tone?: 'sky' | 'emerald' | 'amber'; -}) { - const tones = { - sky: 'bg-sky-500/10 text-sky-400', - emerald: 'bg-emerald-500/10 text-emerald-400', - amber: 'bg-amber-500/10 text-amber-400', - }; - return ( - - -
- {icon} -
-
-

- {label} -

-

{value}

-
-
-
- ); -} - -/* ───── Overview tab ───── */ - -function OverviewTab({ - settings, - heady, -}: { - settings: SettingsData; - heady: HeadyRuntime; -}) { - // Heady-side rows — these we know for certain. - const headyRows: { label: string; value: string }[] = [ - { label: 'Public URL', value: settings.server.publicUrl }, - { label: 'Session store', value: heady.sessionStore }, - { label: 'Session lifetime', value: heady.sessionLifetime }, - { label: 'Session cookie', value: heady.sessionCookieName }, - { - label: 'Cookie secure', - value: heady.cookieSecure ? 'true' : 'false (dev)', - }, - { label: 'Active sessions', value: String(heady.activeSessions) }, - { label: 'Node.js', value: heady.nodeVersion }, - { label: 'Process uptime', value: `${heady.processUptime}s` }, - ]; - - // Headscale-side rows — placeholders until the integration lands. - const headscaleRows: { label: string; value: string }[] = [ - { label: 'Version', value: settings.server.version }, - { label: 'URL', value: settings.server.url }, - { label: 'gRPC listen', value: settings.server.grpcListenAddr }, - { label: 'Metrics listen', value: settings.server.metricsListenAddr }, - { - label: 'Database', - value: - settings.database.type.includes('connect') - ? settings.database.type - : `${settings.database.type} (${settings.database.sqlite.path})`, - }, - { label: 'TLS hostname', value: settings.tls.letsencrypt.hostname }, - { label: 'TLS challenge', value: settings.tls.letsencrypt.challengeType }, - ]; - - return ( -
- - - Heady runtime - - Live state of this Heady instance. - - - -
- {headyRows.map((r) => ( -
-
{r.label}
-
- {r.value} -
-
- ))} -
-
-
- - - - Headscale configuration - - Populated from the Headscale API once it's wired. Until then, - placeholder values are shown in dimmed text. - - - -
- {headscaleRows.map((r) => { - const isPlaceholder = r.value.includes('connect'); - return ( -
-
{r.label}
-
- {r.value} -
-
- ); - })} -
-
-
-
- ); -} - -/* ───── Auth keys tab ───── */ - -function AuthKeysTab({ - authKeys, - canCreate, -}: { - authKeys: AuthKey[]; - canCreate: boolean; -}) { - const now = Date.now(); - return ( - - -
- Pre-auth keys - - Issue tokens devices can use to join the tailnet without OIDC. - -
- -
- - {authKeys.length === 0 ? ( -
- No auth keys yet. -
- ) : ( -
- - - - - - - - - - - - {authKeys.map((k) => { - const expired = new Date(k.expiration).getTime() < now; - return ( - - - - - - - - - ); - })} - -
KeyUserFlagsExpiresStatus -
- {k.keyPrefix}… - -
{k.user.name}
-
{k.user.email}
-
-
- {k.reusable && reusable} - {k.ephemeral && ephemeral} - {!k.reusable && !k.ephemeral && ( - - )} -
-
- {new Date(k.expiration).toLocaleDateString()} - - {expired ? ( - expired - ) : k.used ? ( - used - ) : ( - active - )} - - -
-
- )} -
-
- ); -} - -/* ───── OIDC tab ───── */ - -function OidcTab({ - oidc, - canEdit, -}: { - oidc: SettingsData['oidc']; - canEdit: boolean; -}) { - const [enabled, setEnabled] = React.useState(oidc.enabled); - const [issuer, setIssuer] = React.useState(oidc.issuer); - const [clientId, setClientId] = React.useState(oidc.clientId); - const [strip, setStrip] = React.useState(oidc.stripEmailDomain); - - return ( -
- - -
-

OIDC authentication

-

- Identity provider for tailnet user accounts. -

-
- -
-
- - - - Provider - - OIDC issuer + client. Secrets are managed via environment. - - - - - setIssuer(e.target.value)} - disabled={!canEdit || !enabled} - /> - - - setClientId(e.target.value)} - disabled={!canEdit || !enabled} - /> - - - - - -
- -
-
-
-
- - - - Restrictions - - Limit who can log in via OIDC. - - - - - - - -
- ); -} - -function RestrictionList({ - title, - values, - hint, -}: { - title: string; - values: string[]; - hint: string; -}) { - return ( -
-
-

{title}

-

{values.length}

-
-

{hint}

-
- {values.length === 0 ? ( - none - ) : ( - values.map((v) => ( - - {v} - - )) - )} -
-
- ); -} - -function Field({ - label, - children, -}: { - label: string; - children: React.ReactNode; -}) { - return ( -
- - {children} -
- ); -} - -/* ───── Advanced tab ───── */ - -function AdvancedTab({ - settings, - canEdit, -}: { - settings: SettingsData; - canEdit: boolean; -}) { - const [logLevel, setLogLevel] = React.useState(settings.log.level); - const [logFormat, setLogFormat] = React.useState(settings.log.format); - - return ( -
- - - Logging - Verbosity and format of Headscale logs. - - - - - - - - - - - - - - - - Health checks - - Runtime probes on the Headscale instance. - - - - - - - - -
- ); -} - -function HealthRow({ ok, label }: { ok: boolean; label: string }) { - return ( -
- {label} - {ok ? ( - - - OK - - ) : ( - - - Degraded - - )} -
- ); -} diff --git a/src/components/shell/AppShell.tsx b/src/components/shell/AppShell.tsx deleted file mode 100644 index 4eb4a6f..0000000 --- a/src/components/shell/AppShell.tsx +++ /dev/null @@ -1,317 +0,0 @@ -// 🤠 Heady App Shell — React replacement for the Alpine-driven Layout chrome. -// -// One component owns: top nav, user menu, auth gating, and the page slot. -// Fetches /api/auth/profile on mount, gates content behind authorization, -// passes the loaded user down to the page via render prop. - -import { - Activity, - Bell, - GitBranch, - Globe, - LayoutDashboard, - LogOut, - Network, - Server, - Settings, - Shield, - Terminal, - Users, -} from 'lucide-react'; -import * as React from 'react'; -import { - Avatar, - AvatarFallback, - AvatarImage, -} from '@/components/ui/avatar'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { cn } from '@/lib/utils'; - -export interface SessionUser { - email: string; - name: string; - role: string; - role_description: string; - picture?: string; - groups: string[]; - capabilities: string[]; -} - -type AuthState = - | { status: 'loading' } - | { status: 'unauthenticated' } - | { status: 'unauthorized'; user: SessionUser } - | { status: 'authenticated'; user: SessionUser }; - -const NAV_ITEMS: { href: string; label: string; icon: React.ReactNode }[] = [ - { href: '/', label: 'Dashboard', icon: }, - { href: '/machines', label: 'Machines', icon: }, - { href: '/terminal', label: 'Terminal', icon: }, - { href: '/acls', label: 'ACLs', icon: }, - { href: '/dns', label: 'DNS', icon: }, - { href: '/users', label: 'Users', icon: }, - { href: '/settings', label: 'Settings', icon: }, -]; - -interface AppShellProps { - currentPath?: string; - requiredRole?: string; - /** Render-prop receives the authenticated user. */ - children: (user: SessionUser) => React.ReactNode; -} - -export function AppShell({ - currentPath = typeof window !== 'undefined' ? window.location.pathname : '/', - requiredRole = 'member', - children, -}: AppShellProps) { - const [auth, setAuth] = React.useState({ status: 'loading' }); - - React.useEffect(() => { - (async () => { - try { - const r = await fetch('/api/auth/profile'); - const data = await r.json(); - if (!data.authenticated || !data.user) { - setAuth({ status: 'unauthenticated' }); - return; - } - const user = data.user as SessionUser; - const hierarchy = [ - 'owner', - 'admin', - 'network_admin', - 'it_admin', - 'auditor', - 'member', - ]; - const userIdx = hierarchy.indexOf(user.role); - const reqIdx = hierarchy.indexOf(requiredRole); - if (userIdx < 0 || reqIdx < 0 || userIdx > reqIdx) { - setAuth({ status: 'unauthorized', user }); - return; - } - setAuth({ status: 'authenticated', user }); - } catch (err) { - console.error('Auth check failed', err); - setAuth({ status: 'unauthenticated' }); - } - })(); - }, [requiredRole]); - - async function logout() { - try { - await fetch('/api/auth/logout', { method: 'POST' }); - } finally { - window.location.href = '/login'; - } - } - - return ( -
- -
- {auth.status === 'loading' && } - {auth.status === 'unauthenticated' && } - {auth.status === 'unauthorized' && ( - - )} - {auth.status === 'authenticated' && children(auth.user)} -
-
- ); -} - -/* ─────────────────────────────────────────── top nav ─────────── */ - -function TopNav({ - currentPath, - user, - onLogout, -}: { - currentPath: string; - user: SessionUser | null; - onLogout: () => void; -}) { - return ( -
-
- - 🤠 - Heady - - -
- - {user && } -
-
-
- ); -} - -function UserMenu({ - user, - onLogout, -}: { - user: SessionUser; - onLogout: () => void; -}) { - const initials = user.name - .split(' ') - .map((p) => p[0]) - .filter(Boolean) - .slice(0, 2) - .join('') - .toUpperCase(); - return ( - - - - - - -
- {user.name} - - {user.email} - -
-
- - - - - Settings - - - - - - Users - - - - - - Sign out - -
-
- ); -} - -/* ─────────────────────────────────────────── auth states ─────── */ - -function AuthLoading() { - return ( -
- -

Loading Heady…

-
- ); -} - -function AuthRequired() { - return ( -
- -

Authentication required

-

- Sign in with Authentik to manage your Headscale deployment. -

- -
- ); -} - -function AccessDenied({ - user, - requiredRole, - onLogout, -}: { - user: SessionUser; - requiredRole: string; - onLogout: () => void; -}) { - return ( -
- -

Access denied

-

- This page requires the {requiredRole}{' '} - role. You currently have{' '} - {user.role}. -

-
- - -
-
- ); -} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx deleted file mode 100644 index c40978b..0000000 --- a/src/components/ui/avatar.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as AvatarPrimitive from '@radix-ui/react-avatar'; -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const Avatar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Avatar.displayName = AvatarPrimitive.Root.displayName; - -const AvatarImage = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarImage.displayName = AvatarPrimitive.Image.displayName; - -const AvatarFallback = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; - -export { Avatar, AvatarImage, AvatarFallback }; diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx deleted file mode 100644 index 8986dd0..0000000 --- a/src/components/ui/badge.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { type VariantProps, cva } from 'class-variance-authority'; -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const badgeVariants = cva( - 'inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', - { - variants: { - variant: { - default: - 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80', - secondary: - 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', - destructive: - 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80', - outline: 'text-foreground', - success: - 'border-transparent bg-emerald-500/15 text-emerald-400 hover:bg-emerald-500/20', - warning: - 'border-transparent bg-amber-500/15 text-amber-400 hover:bg-amber-500/20', - }, - }, - defaultVariants: { variant: 'default' }, - }, -); - -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ); -} - -export { Badge, badgeVariants }; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx deleted file mode 100644 index 4fb6173..0000000 --- a/src/components/ui/button.tsx +++ /dev/null @@ -1,53 +0,0 @@ -// shadcn-ui (new-york) Button primitive — verbatim from the registry. -import { Slot } from '@radix-ui/react-slot'; -import { type VariantProps, cva } from 'class-variance-authority'; -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const buttonVariants = cva( - 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', - { - variants: { - variant: { - default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', - destructive: - 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', - outline: - 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', - secondary: - 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', - ghost: 'hover:bg-accent hover:text-accent-foreground', - link: 'text-primary underline-offset-4 hover:underline', - }, - size: { - default: 'h-9 px-4 py-2', - sm: 'h-8 rounded-md px-3 text-xs', - lg: 'h-10 rounded-md px-8', - icon: 'h-9 w-9', - }, - }, - defaultVariants: { variant: 'default', size: 'default' }, - }, -); - -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean; -} - -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : 'button'; - return ( - - ); - }, -); -Button.displayName = 'Button'; - -export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx deleted file mode 100644 index 3a078c4..0000000 --- a/src/components/ui/card.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -Card.displayName = 'Card'; - -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardHeader.displayName = 'CardHeader'; - -const CardTitle = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardTitle.displayName = 'CardTitle'; - -const CardDescription = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardDescription.displayName = 'CardDescription'; - -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardContent.displayName = 'CardContent'; - -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardFooter.displayName = 'CardFooter'; - -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardDescription, - CardContent, -}; diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx deleted file mode 100644 index ab99df7..0000000 --- a/src/components/ui/dropdown-menu.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; -import { Check, ChevronRight, Circle } from 'lucide-react'; -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const DropdownMenu = DropdownMenuPrimitive.Root; -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; -const DropdownMenuGroup = DropdownMenuPrimitive.Group; -const DropdownMenuPortal = DropdownMenuPrimitive.Portal; -const DropdownMenuSub = DropdownMenuPrimitive.Sub; -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; - -const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } ->(({ className, inset, children, ...props }, ref) => ( - - {children} - - -)); -DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName; - -const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName; - -const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - - -)); -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; - -const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } ->(({ className, inset, ...props }, ref) => ( - svg]:size-4 [&>svg]:shrink-0", - inset && 'pl-8', - className, - )} - {...props} - /> -)); -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; - -const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } ->(({ className, inset, ...props }, ref) => ( - -)); -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; - -const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DropdownMenuSeparator.displayName = - DropdownMenuPrimitive.Separator.displayName; - -const DropdownMenuShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => ( - -); -DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; - -export { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuRadioGroup, -}; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx deleted file mode 100644 index b9f3cd3..0000000 --- a/src/components/ui/input.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const Input = React.forwardRef>( - ({ className, type, ...props }, ref) => { - return ( - - ); - }, -); -Input.displayName = 'Input'; - -export { Input }; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx deleted file mode 100644 index a481391..0000000 --- a/src/components/ui/select.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import * as SelectPrimitive from '@radix-ui/react-select'; -import { Check, ChevronDown, ChevronUp } from 'lucide-react'; -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const Select = SelectPrimitive.Root; -const SelectGroup = SelectPrimitive.Group; -const SelectValue = SelectPrimitive.Value; - -const SelectTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - span]:line-clamp-1", - className, - )} - {...props} - > - {children} - - - - -)); -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; - -const SelectScrollUpButton = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)); -SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; - -const SelectScrollDownButton = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)); -SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; - -const SelectContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, position = 'popper', ...props }, ref) => ( - - - - - {children} - - - - -)); -SelectContent.displayName = SelectPrimitive.Content.displayName; - -const SelectLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -SelectLabel.displayName = SelectPrimitive.Label.displayName; - -const SelectItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - - - - {children} - -)); -SelectItem.displayName = SelectPrimitive.Item.displayName; - -const SelectSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -SelectSeparator.displayName = SelectPrimitive.Separator.displayName; - -export { - Select, - SelectGroup, - SelectValue, - SelectTrigger, - SelectContent, - SelectLabel, - SelectItem, - SelectSeparator, - SelectScrollUpButton, - SelectScrollDownButton, -}; diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx deleted file mode 100644 index b09907f..0000000 --- a/src/components/ui/separator.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as SeparatorPrimitive from '@radix-ui/react-separator'; -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const Separator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->( - ( - { className, orientation = 'horizontal', decorative = true, ...props }, - ref, - ) => ( - - ), -); -Separator.displayName = SeparatorPrimitive.Root.displayName; - -export { Separator }; diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx deleted file mode 100644 index d86f458..0000000 --- a/src/components/ui/switch.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as SwitchPrimitives from '@radix-ui/react-switch'; -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const Switch = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)); -Switch.displayName = SwitchPrimitives.Root.displayName; - -export { Switch }; diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx deleted file mode 100644 index cbc9726..0000000 --- a/src/components/ui/tabs.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as TabsPrimitive from '@radix-ui/react-tabs'; -import * as React from 'react'; -import { cn } from '@/lib/utils'; - -const Tabs = TabsPrimitive.Root; - -const TabsList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -TabsList.displayName = TabsPrimitive.List.displayName; - -const TabsTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; - -const TabsContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -TabsContent.displayName = TabsPrimitive.Content.displayName; - -export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/src/components/users/UsersPage.tsx b/src/components/users/UsersPage.tsx deleted file mode 100644 index 1722a11..0000000 --- a/src/components/users/UsersPage.tsx +++ /dev/null @@ -1,303 +0,0 @@ -// 🤠 Heady Users page — React + shadcn rebuild. - -import { - Mail, - MoreHorizontal, - Search, - Shield, - UserPlus, - Users as UsersIcon, -} from 'lucide-react'; -import * as React from 'react'; -import { AppShell, type SessionUser } from '@/components/shell/AppShell'; -import { - Avatar, - AvatarFallback, - AvatarImage, -} from '@/components/ui/avatar'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { Input } from '@/components/ui/input'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; - -type Role = - | 'owner' - | 'admin' - | 'network_admin' - | 'it_admin' - | 'auditor' - | 'member'; - -export interface UserRow { - id: string; - email: string; - name?: string; - preferred_username: string; - role: Role; - groups: string[]; - picture?: string; - last_login?: string; -} - -export interface UsersPageProps { - users: UserRow[]; -} - -export function UsersPage({ users }: UsersPageProps) { - return ( - - {(me) => } - - ); -} - -function UsersBody({ users, me }: UsersPageProps & { me: SessionUser }) { - const [search, setSearch] = React.useState(''); - const [roleFilter, setRoleFilter] = React.useState('all'); - - const filtered = React.useMemo(() => { - const needle = search.trim().toLowerCase(); - return users.filter((u) => { - if (roleFilter !== 'all' && u.role !== roleFilter) return false; - if (!needle) return true; - return ( - u.email.toLowerCase().includes(needle) || - (u.name ?? '').toLowerCase().includes(needle) || - u.preferred_username.toLowerCase().includes(needle) || - u.groups.some((g) => g.toLowerCase().includes(needle)) - ); - }); - }, [users, search, roleFilter]); - - const roleBreakdown = React.useMemo(() => { - const acc: Partial> = {}; - users.forEach((u) => { - acc[u.role] = (acc[u.role] ?? 0) + 1; - }); - return acc; - }, [users]); - - return ( -
-
-
-

Users

-

- People in your tailnet, with roles mapped from Authentik groups. -

-
- -
- - {/* Role summary cards */} -
- {( - [ - { role: 'owner', label: 'Owners' }, - { role: 'admin', label: 'Admins' }, - { role: 'network_admin', label: 'Network' }, - { role: 'it_admin', label: 'IT' }, - { role: 'auditor', label: 'Auditors' }, - { role: 'member', label: 'Members' }, - ] as { role: Role; label: string }[] - ).map(({ role, label }) => ( - - -

- {label} -

-

- {roleBreakdown[role] ?? 0} -

-
-
- ))} -
- - {/* Filter bar */} - - -
- - setSearch(e.target.value)} - className="pl-8" - /> -
- -
-
- - {/* Results card */} - - -
- Directory - - {filtered.length} of {users.length} shown - -
- -
- - {filtered.length === 0 ? ( - - ) : ( -
    - {filtered.map((u) => ( - - ))} -
- )} -
-
-
- ); -} - -function UserRowItem({ user, isMe }: { user: UserRow; isMe: boolean }) { - const initials = (user.name ?? user.preferred_username ?? user.email) - .split(/[\s.@]/) - .map((p) => p[0]) - .filter(Boolean) - .slice(0, 2) - .join('') - .toUpperCase(); - - return ( -
  • - - {user.picture && } - {initials || '?'} - -
    -
    -

    {user.name ?? user.preferred_username}

    - {isMe && ( - - you - - )} -
    -

    - - {user.email} -

    -
    - -
    - {user.groups.slice(0, 2).map((g) => ( - - {g} - - ))} - {user.groups.length > 2 && ( - - +{user.groups.length - 2} - - )} -
    - -
  • - ); -} - -function RoleBadge({ role }: { role: Role }) { - const variants: Record< - Role, - { label: string; className: string } - > = { - owner: { label: 'Owner', className: 'bg-violet-500/15 text-violet-300' }, - admin: { label: 'Admin', className: 'bg-rose-500/15 text-rose-300' }, - network_admin: { - label: 'Network', - className: 'bg-sky-500/15 text-sky-300', - }, - it_admin: { label: 'IT', className: 'bg-emerald-500/15 text-emerald-300' }, - auditor: { label: 'Auditor', className: 'bg-amber-500/15 text-amber-300' }, - member: { label: 'Member', className: 'bg-muted text-muted-foreground' }, - }; - const v = variants[role]; - return ( - - - {v.label} - - ); -} - -function UserRowMenu({ user }: { user: UserRow }) { - return ( - - - - - - {user.email} - - - Details - - Change role - - - Disable - - - - ); -} - -function EmptyResult() { - return ( -
    - -

    No matching users

    -

    Try adjusting your search or role filter.

    -
    - ); -} diff --git a/src/env.d.ts b/src/env.d.ts index 2b7470a..e16c13c 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,11 +1 @@ /// - -import type { SessionUser } from './lib/auth/session-manager.js'; - -declare global { - namespace App { - interface Locals { - user?: SessionUser; - } - } -} diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 124079b..3ff6424 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -10,18 +10,11 @@ import '../styles/global.css'; export interface Props { title: string; description?: string; - /** - * Pages built on the React shadcn shell should set `hideChrome={true}` so - * the legacy Alpine nav/footer/toast container doesn't double up over the - * React top nav. Pages still using Alpine keep the default (false). - */ - hideChrome?: boolean; } const { title, description = "Strategic VPN management that's actually awesome to use!", - hideChrome = false, } = Astro.props; --- @@ -223,11 +216,7 @@ const { - - {hideChrome ? ( - - ) : ( - +
    - + \ No newline at end of file diff --git a/src/pages/settings.astro b/src/pages/settings.astro index b40f2af..6ed5bb1 100644 --- a/src/pages/settings.astro +++ b/src/pages/settings.astro @@ -1,99 +1,933 @@ --- -// ⚙️ Heady Settings — Astro shell, React island. -// -// Pulls live config from the modules that already read it (OIDC + session -// + role mapping). Headscale-specific fields are flagged as "connect -// Headscale" placeholders until we wire up its API. +import { getCollection } from 'astro:content'; +// Heady Settings - Alpine.js/Astro Settings & Configuration 🤠 +import Layout from '../layouts/Layout.astro'; -import { getSessionManager } from '@/lib/auth/session-manager'; -import { - getRoleMappingConfig, - getSessionConfig, - loadAuthentikConfig, -} from '@/lib/config/authentik'; -import { SettingsPage } from '@/components/settings/SettingsPage'; -import Layout from '@/layouts/Layout.astro'; - -// Force SSR — config reads env vars at request time. -export const prerender = false; - -const authentik = loadAuthentikConfig(); -const session = getSessionConfig(); -const roleMapping = getRoleMappingConfig(); -const activeSessions = await getSessionManager().getActiveCount(); - -// Combine env-derived and Headscale-derived fields. Anything we can't -// resolve yet shows a clearly-flagged placeholder so the operator can tell -// what's real vs. pending integration. -const HEADSCALE_PLACEHOLDER = 'connect Headscale to populate'; - -const settings = { +// Mock settings data (in production, this would come from Headscale config) +const settingsData = { + // Server configuration server: { - version: HEADSCALE_PLACEHOLDER, - url: HEADSCALE_PLACEHOLDER, - publicUrl: authentik.redirectUri.replace('/api/auth/callback', ''), - grpcListenAddr: HEADSCALE_PLACEHOLDER, - metricsListenAddr: HEADSCALE_PLACEHOLDER, + version: '0.23.0', + url: 'https://headscale.company.com', + publicUrl: 'https://vpn.company.com', + grpcListenAddr: '0.0.0.0:50443', + metricsListenAddr: '127.0.0.1:9090', }, + + // Configuration permissions + config: { + writable: true, // Can modify configuration + readable: true, // Can read configuration + }, + + // OIDC configuration oidc: { enabled: true, - issuer: authentik.issuer, - clientId: authentik.clientId, - stripEmailDomain: false, // Heady doesn't strip — we use full email as session id - scope: authentik.scopes, - allowedDomains: [] as string[], // not modeled yet — managed in Authentik - allowedGroups: [ - ...roleMapping.ownerGroups, - ...roleMapping.adminGroups, - ...roleMapping.networkGroups, - ...roleMapping.itGroups, - ...roleMapping.auditorGroups, - ], + issuer: 'https://auth.company.com', + clientId: 'heady-app', + clientSecret: '***hidden***', + stripEmailDomain: true, + scope: ['openid', 'profile', 'email'], + allowedDomains: ['company.com', 'contractor.com'], + allowedGroups: ['engineering', 'admins', 'network-team'], + adminGroup: 'admins', }, + + // Database configuration database: { - type: HEADSCALE_PLACEHOLDER, - sqlite: { path: HEADSCALE_PLACEHOLDER }, - }, - tls: { - letsencrypt: { - hostname: HEADSCALE_PLACEHOLDER, - challengeType: HEADSCALE_PLACEHOLDER, + type: 'sqlite', + sqlite: { + path: '/var/lib/headscale/db.sqlite', }, }, + + // TLS configuration + tls: { + letsencrypt: { + hostname: 'vpn.company.com', + listen: ':https', + challengeType: 'TLS-ALPN-01', + }, + }, + + // Log configuration log: { - level: process.env.NODE_ENV === 'development' ? 'debug' : 'info', + level: 'info', format: 'text', }, }; -// Heady-specific runtime info — these we DO know. -const heady = { - sessionStore: process.env.REDIS_URL ? 'redis' : 'in-memory', - sessionLifetime: `${Math.round(session.lifetime / 1000 / 60 / 60)}h`, - sessionCookieName: session.cookieName, - cookieSecure: session.secure, - activeSessions, - nodeVersion: process.version, - processUptime: Math.round(process.uptime()), -}; - -// Auth keys still aren't reachable without Headscale; leave the list empty -// and let the UI render its empty state. -const authKeys: never[] = []; +// Mock auth keys data +const authKeysData = [ + { + id: '1', + key: 'authkey-abc123def456...', + keyPrefix: 'authkey-abc123', + reusable: false, + ephemeral: false, + used: false, + expiration: '2024-02-15T10:30:00Z', + createdAt: '2024-01-15T10:30:00Z', + user: { + id: 'user-1', + name: 'alice', + email: 'alice@company.com', + }, + }, + { + id: '2', + key: 'authkey-xyz789uvw012...', + keyPrefix: 'authkey-xyz789', + reusable: true, + ephemeral: false, + used: true, + expiration: '2024-12-31T23:59:59Z', + createdAt: '2024-01-10T14:20:00Z', + user: { + id: 'user-2', + name: 'system', + email: 'system@company.com', + }, + }, +]; +// User permissions const permissions = { canGenerateAuthKeys: true, canModifyConfig: true, canViewLogs: true, }; + +// Get recent settings-related activity +const recentActivity = await getCollection('activity', ({ data }) => + ['system', 'auth'].includes(data.resource_type), +).then((items) => + items + .slice(0, 5) + .sort( + (a, b) => + new Date(b.data.timestamp).getTime() - + new Date(a.data.timestamp).getTime(), + ), +); --- - - - + +
    +
    + + +
    +

    + ⚙️ Settings +

    +

    + Configure your Heady installation, manage authentication keys, and control access restrictions. +

    +
    + + +
    +
    +
    + 🖥️ +
    +
    Server
    +
    +
    +
    +
    + +
    +
    + 🔐 +
    +
    OIDC
    +
    +
    +
    +
    +
    + +
    +
    + 🗝️ +
    +
    Auth Keys
    +
    +
    +
    +
    +
    + + +
    + +
    + + +
    + + +
    +
    + + +
    +

    🖥️ Server Information

    + +
    +

    Configuration

    +
    +
    + Version: + +
    +
    + URL: + +
    +
    + Public URL: + +
    +
    + gRPC Listen: + +
    +
    +
    + +
    +

    Database

    +
    +
    + Type: + +
    +
    + Path: + +
    +
    +
    +
    + + +
    +

    🚀 Quick Actions

    + +
    + + + + + +
    +
    +
    +
    + + +
    +
    +
    +

    🗝️ Pre-Authentication Keys

    +

    + Pre-auth keys allow devices to join your network without manual approval. + + Learn more + +

    +
    + + +
    + + +
    + + + + + +
    + + +
    + + + +
    +
    + + +
    +

    🔐 OIDC Configuration

    + +
    + + +
    +
    +

    Authentication Provider

    +
    +
    + OIDC Enabled +
    +
    +
    +
    + +
    + + +
    + +
    + + +
    +
    +
    +
    + + +
    +
    +

    Access Control

    +
    +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    +
    +
    +
    +
    +
    + + +
    +

    🛡️ Authentication Restrictions

    +
    + 🚧 +

    Feature Coming Soon

    +

    + Advanced authentication restrictions will be available in a future update. +

    +
    +
    + + +
    +

    🔧 Advanced Settings

    + +
    +
    +
    + ⚠️ +
    +

    Advanced Configuration

    +

    + These settings can affect the security and functionality of your network. + Only modify them if you understand the implications. +

    +
    +
    +
    + +
    +
    +

    TLS Configuration

    +
    +
    + Hostname: + +
    +
    + Challenge: + +
    +
    +
    + +
    +

    Logging

    +
    +
    + Level: + +
    +
    + Format: + +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +

    🗝️ Generate Auth Key

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    +
    +
    +
    + + +
    \ No newline at end of file diff --git a/src/pages/terminal.astro b/src/pages/terminal.astro index 8d922c1..4107e27 100644 --- a/src/pages/terminal.astro +++ b/src/pages/terminal.astro @@ -414,7 +414,7 @@ const availableProtocols = Object.entries(userPermissions)
    - + \ No newline at end of file diff --git a/src/styles/global.css b/src/styles/global.css index 6df7077..b5c61c9 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,75 +1,3 @@ @tailwind base; @tailwind components; @tailwind utilities; - -/* shadcn-ui (new-york) theme tokens. - We default to dark for Heady's identity; `:root` and `.dark` carry the - same palette so anywhere we toggle theme later, the dark scheme is - consistent. Swap the .dark block back to the light defaults if you want - theme switching back. */ - -@layer base { - :root { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 47.4% 11.2%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 217.2 91.2% 59.8%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 224.3 76.3% 48%; - - --radius: 0.5rem; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - --card: 222.2 47.4% 11.2%; - --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - --primary: 217.2 91.2% 59.8%; - --primary-foreground: 222.2 47.4% 11.2%; - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 224.3 76.3% 48%; - } -} - -@layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - font-feature-settings: 'rlig' 1, 'calt' 1; - } -} diff --git a/tailwind.config.mjs b/tailwind.config.mjs index b095d1e..684a316 100644 --- a/tailwind.config.mjs +++ b/tailwind.config.mjs @@ -1,59 +1,15 @@ /** @type {import('tailwindcss').Config} */ -import tailwindcssAnimate from 'tailwindcss-animate'; - export default { - darkMode: 'class', content: [ - './src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}', + './src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', './src/pages/**/*.astro', './src/layouts/**/*.astro', - './src/components/**/*.{astro,tsx}', + './src/components/**/*.astro', ], theme: { - container: { - center: true, - padding: '2rem', - screens: { '2xl': '1400px' }, - }, extend: { - // shadcn-ui (new-york) reads colors from CSS variables defined in - // src/styles/global.css. Everything below maps Tailwind utility - // classes to those variables. colors: { - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))', - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))', - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))', - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))', - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))', - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))', - }, - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))', - }, - // Brand accents kept from the original Heady config. + // Heady brand colors heady: { 50: '#f0f9ff', 100: '#e0f2fe', @@ -67,30 +23,21 @@ export default { 900: '#0c4a6e', }, }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)', - }, fontFamily: { - mono: ['Fira Code', 'Monaco', 'Cascadia Code', 'monospace'], - }, - keyframes: { - 'accordion-down': { - from: { height: '0' }, - to: { height: 'var(--radix-accordion-content-height)' }, - }, - 'accordion-up': { - from: { height: 'var(--radix-accordion-content-height)' }, - to: { height: '0' }, - }, + mono: [ + 'Fira Code', + 'Monaco', + 'Cascadia Code', + 'Roboto Mono', + 'monospace', + ], }, animation: { - 'accordion-down': 'accordion-down 0.2s ease-out', - 'accordion-up': 'accordion-up 0.2s ease-out', 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite', }, }, }, - plugins: [tailwindcssAnimate], + plugins: [ + // Add any Tailwind plugins you need + ], }; diff --git a/tsconfig.json b/tsconfig.json index 27a93b5..3fc47aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,11 +21,9 @@ "allowJs": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "baseUrl": ".", "paths": { "~/*": ["./app/*"], - "~server/*": ["./server/*"], - "@/*": ["./src/*"] + "~server/*": ["./server/*"] }, // Vite takes care of building everything, not tsc.