diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 2d473e10..4a7cecd9 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 1 @@ -37,7 +37,7 @@ jobs: network=host - name: Cache npm dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.npm @@ -48,7 +48,7 @@ jobs: ${{ runner.os }}-node- - name: Cache Docker layers - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.ref_name }}-${{ hashFiles('docker/Dockerfile') }} @@ -78,7 +78,7 @@ jobs: echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV - name: Build and Push Multi-Arch Docker Image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: ./docker/Dockerfile diff --git a/docker/Dockerfile b/docker/Dockerfile index c82aa3e6..15f3d81f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Install dependencies and build frontend -FROM node:22-alpine AS deps +FROM node:24-alpine AS deps WORKDIR /app RUN apk add --no-cache python3 make g++ @@ -26,7 +26,7 @@ COPY . . RUN npm run build:backend # Stage 4: Production dependencies -FROM node:22-alpine AS production-deps +FROM node:24-alpine AS production-deps WORKDIR /app COPY package*.json ./ @@ -35,7 +35,7 @@ RUN npm ci --only=production --ignore-scripts --force && \ npm cache clean --force # Stage 5: Build native modules -FROM node:22-alpine AS native-builder +FROM node:24-alpine AS native-builder WORKDIR /app RUN apk add --no-cache python3 make g++ @@ -46,7 +46,7 @@ RUN npm ci --only=production bcryptjs better-sqlite3 --force && \ npm cache clean --force # Stage 6: Final image -FROM node:22-alpine +FROM node:24-alpine ENV DATA_DIR=/app/data \ PORT=8080 \ NODE_ENV=production diff --git a/package-lock.json b/package-lock.json index 7fd1447b..dc2db2ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,24 +9,24 @@ "version": "0.0.0", "dependencies": { "@hookform/resolvers": "^5.1.1", - "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-avatar": "^1.1.10", - "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-progress": "^1.1.7", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.2.8", - "@tailwindcss/vite": "^4.1.12", + "@tailwindcss/vite": "^4.1.11", "@types/bcryptjs": "^2.4.6", "@types/multer": "^2.0.0", "@uiw/codemirror-extensions-hyper-link": "^4.24.1", @@ -48,8 +48,8 @@ "clsx": "^2.1.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", - "dotenv": "^17.2.1", - "drizzle-orm": "^0.44.4", + "dotenv": "^17.2.0", + "drizzle-orm": "^0.44.3", "express": "^5.1.0", "jose": "^5.2.3", "jsonwebtoken": "^9.0.2", @@ -58,10 +58,10 @@ "nanoid": "^5.1.5", "next-themes": "^0.4.6", "node-fetch": "^3.3.2", - "react": "^19.1.1", - "react-dom": "^19.1.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", "react-hook-form": "^7.60.0", - "react-resizable-panels": "^3.0.5", + "react-resizable-panels": "^3.0.3", "react-xtermjs": "^1.0.10", "sonner": "^2.0.7", "ssh2": "^1.16.0", @@ -78,8 +78,8 @@ "@types/express": "^5.0.3", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^24.0.13", - "@types/react": "^19.1.11", - "@types/react-dom": "^19.1.7", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", "@types/ssh2": "^1.15.5", "@types/ws": "^8.18.1", "@vitejs/plugin-react-swc": "^3.10.2", @@ -95,6 +95,19 @@ "vite": "^7.0.4" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.27.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", @@ -1235,25 +1248,15 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1264,15 +1267,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1536,19 +1539,19 @@ "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", "license": "MIT" }, "node_modules/@radix-ui/react-accordion": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", - "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.11.tgz", + "integrity": "sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collapsible": "1.1.11", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", @@ -1623,15 +1626,15 @@ } }, "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", - "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.2.tgz", + "integrity": "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", @@ -1653,16 +1656,16 @@ } }, "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", - "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.11.tgz", + "integrity": "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" @@ -1774,22 +1777,13 @@ } } }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" }, - "node_modules/@radix-ui/react-dismissable-layer": { + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", @@ -1816,17 +1810,98 @@ } } }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz", + "integrity": "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, @@ -1846,9 +1921,9 @@ } }, "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1927,25 +2002,25 @@ } }, "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.15.tgz", + "integrity": "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", @@ -1967,21 +2042,21 @@ } }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", - "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz", + "integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", @@ -2004,9 +2079,9 @@ } }, "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", @@ -2060,9 +2135,9 @@ } }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -2131,12 +2206,12 @@ } }, "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", + "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", @@ -2162,17 +2237,17 @@ } }, "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", - "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz", + "integrity": "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" @@ -2193,22 +2268,22 @@ } }, "node_modules/@radix-ui/react-select": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", - "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz", + "integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", @@ -2259,13 +2334,13 @@ } }, "node_modules/@radix-ui/react-slider": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", - "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.5.tgz", + "integrity": "sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", @@ -2310,12 +2385,12 @@ } }, "node_modules/@radix-ui/react-switch": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", - "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz", + "integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", @@ -2339,18 +2414,18 @@ } }, "node_modules/@radix-ui/react-tabs": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz", + "integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", + "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { @@ -2402,6 +2477,95 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", @@ -3146,24 +3310,24 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz", - "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.5.1", + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.12" + "tailwindcss": "4.1.11" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz", - "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3174,24 +3338,24 @@ "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.12", - "@tailwindcss/oxide-darwin-arm64": "4.1.12", - "@tailwindcss/oxide-darwin-x64": "4.1.12", - "@tailwindcss/oxide-freebsd-x64": "4.1.12", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", - "@tailwindcss/oxide-linux-x64-musl": "4.1.12", - "@tailwindcss/oxide-wasm32-wasi": "4.1.12", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz", - "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", "cpu": [ "arm64" ], @@ -3205,9 +3369,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz", - "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", "cpu": [ "arm64" ], @@ -3221,9 +3385,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz", - "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", "cpu": [ "x64" ], @@ -3237,9 +3401,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz", - "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", "cpu": [ "x64" ], @@ -3253,9 +3417,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz", - "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", "cpu": [ "arm" ], @@ -3269,9 +3433,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz", - "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", "cpu": [ "arm64" ], @@ -3285,9 +3449,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz", - "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", "cpu": [ "arm64" ], @@ -3301,9 +3465,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz", - "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", "cpu": [ "x64" ], @@ -3317,9 +3481,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz", - "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", "cpu": [ "x64" ], @@ -3333,9 +3497,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz", - "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -3350,75 +3514,21 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.5", - "@emnapi/runtime": "^1.4.5", - "@emnapi/wasi-threads": "^1.0.4", - "@napi-rs/wasm-runtime": "^0.2.12", - "@tybys/wasm-util": "^0.10.0", + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.11", + "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.4.5", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.0.4", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.4.5", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.0", - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz", - "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", "cpu": [ "arm64" ], @@ -3432,9 +3542,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz", - "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", "cpu": [ "x64" ], @@ -3448,14 +3558,14 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.12.tgz", - "integrity": "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", + "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.1.12", - "@tailwindcss/oxide": "4.1.12", - "tailwindcss": "4.1.12" + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" @@ -3631,9 +3741,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz", - "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3641,9 +3751,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.1.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz", - "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "devOptional": true, "license": "MIT", "peerDependencies": { @@ -4973,9 +5083,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", - "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz", + "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -4985,9 +5095,9 @@ } }, "node_modules/drizzle-orm": { - "version": "0.44.4", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.4.tgz", - "integrity": "sha512-ZyzKFpTC/Ut3fIqc2c0dPZ6nhchQXriTsqTNs4ayRgl6sZcFlMs9QZKPSHXK4bdOf41GHGWf+FrpcDDYwW+W6Q==", + "version": "0.44.3", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.3.tgz", + "integrity": "sha512-8nIiYQxOpgUicEL04YFojJmvC4DNO4KoyXsEIqN44+g6gNBr6hmVpWk3uyAt4CaTiRGDwoU+alfqNNeonLAFOQ==", "license": "Apache-2.0", "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", @@ -5164,9 +5274,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -6143,9 +6253,9 @@ "license": "ISC" }, "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -6570,12 +6680,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.18", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", - "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-error": { @@ -7314,24 +7424,24 @@ } }, "node_modules/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", - "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { - "react": "^19.1.1" + "react": "^19.1.0" } }, "node_modules/react-hook-form": { @@ -7398,9 +7508,9 @@ } }, "node_modules/react-resizable-panels": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.5.tgz", - "integrity": "sha512-3z1yN25DMTXLg2wfyFrW32r5k4WEcUa3F7cJ2EgtNK07lnOs4mpM8yWLGunCpkhcQRwJX4fqoLcIh/pHPxzlmQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.3.tgz", + "integrity": "sha512-7HA8THVBHTzhDK4ON0tvlGXyMAJN1zBeRpuyyremSikgYh2ku6ltD7tsGQOcXx4NKPrZtYCm/5CBr+dkruTGQw==", "license": "MIT", "peerDependencies": { "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", @@ -7883,22 +7993,18 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", - "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", "license": "MIT" }, "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", "license": "MIT", "engines": { "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" } }, "node_modules/tar": { diff --git a/package.json b/package.json index b36acf2c..e53653c2 100644 --- a/package.json +++ b/package.json @@ -13,24 +13,24 @@ }, "dependencies": { "@hookform/resolvers": "^5.1.1", - "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-avatar": "^1.1.10", - "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-progress": "^1.1.7", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.2.8", - "@tailwindcss/vite": "^4.1.12", + "@tailwindcss/vite": "^4.1.11", "@types/bcryptjs": "^2.4.6", "@types/multer": "^2.0.0", "@uiw/codemirror-extensions-hyper-link": "^4.24.1", @@ -52,8 +52,8 @@ "clsx": "^2.1.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", - "dotenv": "^17.2.1", - "drizzle-orm": "^0.44.4", + "dotenv": "^17.2.0", + "drizzle-orm": "^0.44.3", "express": "^5.1.0", "jose": "^5.2.3", "jsonwebtoken": "^9.0.2", @@ -62,10 +62,10 @@ "nanoid": "^5.1.5", "next-themes": "^0.4.6", "node-fetch": "^3.3.2", - "react": "^19.1.1", - "react-dom": "^19.1.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", "react-hook-form": "^7.60.0", - "react-resizable-panels": "^3.0.5", + "react-resizable-panels": "^3.0.3", "react-xtermjs": "^1.0.10", "sonner": "^2.0.7", "ssh2": "^1.16.0", @@ -82,8 +82,8 @@ "@types/express": "^5.0.3", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^24.0.13", - "@types/react": "^19.1.11", - "@types/react-dom": "^19.1.7", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", "@types/ssh2": "^1.15.5", "@types/ws": "^8.18.1", "@vitejs/plugin-react-swc": "^3.10.2", diff --git a/src/backend/database/routes/users.ts b/src/backend/database/routes/users.ts index 790ac7be..aca004ab 100644 --- a/src/backend/database/routes/users.ts +++ b/src/backend/database/routes/users.ts @@ -79,7 +79,7 @@ async function verifyOIDCToken(idToken: string, issuerUrl: string, clientId: str const key = await importJWK(publicKey); const {payload} = await jwtVerify(idToken, key, { - issuer: issuerUrl, + issuer: [issuerUrl, issuerUrl.replace(/\/application\/o\/[^\/]+$/, '')], audience: clientId, }); diff --git a/src/ui/Homepage/HomepageAuth.tsx b/src/ui/Homepage/HomepageAuth.tsx index 43241e55..8cb5a221 100644 --- a/src/ui/Homepage/HomepageAuth.tsx +++ b/src/ui/Homepage/HomepageAuth.tsx @@ -1,10 +1,21 @@ import React, {useState, useEffect} from "react"; -import {cn} from "@/lib/utils.ts"; -import {Button} from "@/components/ui/button.tsx"; -import {Input} from "@/components/ui/input.tsx"; -import {Label} from "@/components/ui/label.tsx"; -import {Alert, AlertTitle, AlertDescription} from "@/components/ui/alert.tsx"; -import axios from "axios"; +import {cn} from "../../lib/utils.ts"; +import {Button} from "../../components/ui/button.tsx"; +import {Input} from "../../components/ui/input.tsx"; +import {Label} from "../../components/ui/label.tsx"; +import {Alert, AlertTitle, AlertDescription} from "../../components/ui/alert.tsx"; +import { + registerUser, + loginUser, + getUserInfo, + getRegistrationAllowed, + getOIDCConfig, + getUserCount, + initiatePasswordReset, + verifyPasswordResetCode, + completePasswordReset, + getOIDCAuthorizeUrl +} from "../main-axios.ts"; function setCookie(name: string, value: string, days = 7) { const expires = new Date(Date.now() + days * 864e5).toUTCString(); @@ -18,11 +29,7 @@ function getCookie(name: string) { }, ""); } -const apiBase = import.meta.env.DEV ? "http://localhost:8081/users" : "/users"; -const API = axios.create({ - baseURL: apiBase, -}); interface HomepageAuthProps extends React.ComponentProps<"div"> { setLoggedIn: (loggedIn: boolean) => void; @@ -74,14 +81,14 @@ export function HomepageAuth({ }, [loggedIn]); useEffect(() => { - API.get("/registration-allowed").then(res => { - setRegistrationAllowed(res.data.allowed); + getRegistrationAllowed().then(res => { + setRegistrationAllowed(res.allowed); }); }, []); useEffect(() => { - API.get("/oidc-config").then((response) => { - if (response.data) { + getOIDCConfig().then((response) => { + if (response) { setOidcConfigured(true); } else { setOidcConfigured(false); @@ -96,8 +103,8 @@ export function HomepageAuth({ }, []); useEffect(() => { - API.get("/count").then(res => { - if (res.data.count === 0) { + getUserCount().then(res => { + if (res.count === 0) { setFirstUser(true); setTab("signup"); } else { @@ -123,7 +130,7 @@ export function HomepageAuth({ try { let res, meRes; if (tab === "login") { - res = await API.post("/login", {username: localUsername, password}); + res = await loginUser(localUsername, password); } else { if (password !== signupConfirmPassword) { setError("Passwords do not match"); @@ -135,31 +142,37 @@ export function HomepageAuth({ setLoading(false); return; } - await API.post("/create", {username: localUsername, password}); - res = await API.post("/login", {username: localUsername, password}); + + await registerUser(localUsername, password); + res = await loginUser(localUsername, password); } - setCookie("jwt", res.data.token); + + if (!res || !res.token) { + throw new Error('No token received from login'); + } + + setCookie("jwt", res.token); [meRes] = await Promise.all([ - API.get("/me", {headers: {Authorization: `Bearer ${res.data.token}`}}), - API.get("/db-health") + getUserInfo(), ]); + setInternalLoggedIn(true); setLoggedIn(true); - setIsAdmin(!!meRes.data.is_admin); - setUsername(meRes.data.username || null); - setUserId(meRes.data.id || null); + setIsAdmin(!!meRes.is_admin); + setUsername(meRes.username || null); + setUserId(meRes.userId || null); setDbError(null); onAuthSuccess({ - isAdmin: !!meRes.data.is_admin, - username: meRes.data.username || null, - userId: meRes.data.id || null + isAdmin: !!meRes.is_admin, + username: meRes.username || null, + userId: meRes.userId || null }); setInternalLoggedIn(true); if (tab === "signup") { setSignupConfirmPassword(""); } } catch (err: any) { - setError(err?.response?.data?.error || "Unknown error"); + setError(err?.response?.data?.error || err?.message || "Unknown error"); setInternalLoggedIn(false); setLoggedIn(false); setIsAdmin(false); @@ -176,29 +189,26 @@ export function HomepageAuth({ } } - async function initiatePasswordReset() { + async function handleInitiatePasswordReset() { setError(null); setResetLoading(true); try { - await API.post("/initiate-reset", {username: localUsername}); + const result = await initiatePasswordReset(localUsername); setResetStep("verify"); setError(null); } catch (err: any) { - setError(err?.response?.data?.error || "Failed to initiate password reset"); + setError(err?.response?.data?.error || err?.message || "Failed to initiate password reset"); } finally { setResetLoading(false); } } - async function verifyResetCode() { + async function handleVerifyResetCode() { setError(null); setResetLoading(true); try { - const response = await API.post("/verify-reset-code", { - username: localUsername, - resetCode: resetCode - }); - setTempToken(response.data.tempToken); + const response = await verifyPasswordResetCode(localUsername, resetCode); + setTempToken(response.tempToken); setResetStep("newPassword"); setError(null); } catch (err: any) { @@ -208,7 +218,7 @@ export function HomepageAuth({ } } - async function completePasswordReset() { + async function handleCompletePasswordReset() { setError(null); setResetLoading(true); @@ -225,11 +235,7 @@ export function HomepageAuth({ } try { - await API.post("/complete-reset", { - username: localUsername, - tempToken: tempToken, - newPassword: newPassword - }); + await completePasswordReset(localUsername, tempToken, newPassword); setResetStep("initiate"); setResetCode(""); @@ -267,8 +273,8 @@ export function HomepageAuth({ setError(null); setOidcLoading(true); try { - const authResponse = await API.get("/oidc/authorize"); - const {auth_url: authUrl} = authResponse.data; + const authResponse = await getOIDCAuthorizeUrl(); + const {auth_url: authUrl} = authResponse; if (!authUrl || authUrl === 'undefined') { throw new Error('Invalid authorization URL received from backend'); @@ -299,18 +305,18 @@ export function HomepageAuth({ setError(null); setCookie("jwt", token); - API.get("/me", {headers: {Authorization: `Bearer ${token}`}}) + getUserInfo() .then(meRes => { setInternalLoggedIn(true); setLoggedIn(true); - setIsAdmin(!!meRes.data.is_admin); - setUsername(meRes.data.username || null); - setUserId(meRes.data.id || null); + setIsAdmin(!!meRes.is_admin); + setUsername(meRes.username || null); + setUserId(meRes.id || null); setDbError(null); onAuthSuccess({ - isAdmin: !!meRes.data.is_admin, - username: meRes.data.username || null, - userId: meRes.data.id || null + isAdmin: !!meRes.is_admin, + username: meRes.username || null, + userId: meRes.id || null }); setInternalLoggedIn(true); window.history.replaceState({}, document.title, window.location.pathname); @@ -486,7 +492,7 @@ export function HomepageAuth({ type="button" className="w-full h-11 text-base font-semibold" disabled={resetLoading || !localUsername.trim()} - onClick={initiatePasswordReset} + onClick={handleInitiatePasswordReset} > {resetLoading ? Spinner : "Send Reset Code"} @@ -519,7 +525,7 @@ export function HomepageAuth({ type="button" className="w-full h-11 text-base font-semibold" disabled={resetLoading || resetCode.length !== 6} - onClick={verifyResetCode} + onClick={handleVerifyResetCode} > {resetLoading ? Spinner : "Verify Code"} @@ -598,7 +604,7 @@ export function HomepageAuth({ type="button" className="w-full h-11 text-base font-semibold" disabled={resetLoading || !newPassword || !confirmPassword} - onClick={completePasswordReset} + onClick={handleCompletePasswordReset} > {resetLoading ? Spinner : "Reset Password"} diff --git a/src/ui/apps/Terminal/TerminalComponent.tsx b/src/ui/apps/Terminal/TerminalComponent.tsx index d77ac606..322afa41 100644 --- a/src/ui/apps/Terminal/TerminalComponent.tsx +++ b/src/ui/apps/Terminal/TerminalComponent.tsx @@ -226,7 +226,9 @@ export const TerminalComponent = forwardRef(function SSHT const cols = terminal.cols; const rows = terminal.rows; - const wsUrl = window.location.hostname === 'localhost' ? 'ws://localhost:8082' : `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/ssh/websocket/`; + const wsUrl = import.meta.env.DEV + ? 'ws://localhost:8082' + : `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/ssh/websocket/`; const ws = new WebSocket(wsUrl); webSocketRef.current = ws; diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index c1192d85..85766006 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -1,4 +1,8 @@ -import axios from 'axios'; +import axios, { AxiosError, AxiosInstance } from 'axios'; + +// ============================================================================ +// TYPES & INTERFACES +// ============================================================================ interface SSHHostData { name?: string; @@ -93,47 +97,70 @@ interface FileManagerShortcut { path: string; } +interface FileManagerOperation { + name: string; + path: string; + isSSH: boolean; + sshSessionId?: string; + hostId: number; +} + export type ServerStatus = { status: 'online' | 'offline'; lastChecked: string; }; +interface CpuMetrics { + percent: number | null; + cores: number | null; + load: [number, number, number] | null; +} + +interface MemoryMetrics { + percent: number | null; + usedGiB: number | null; + totalGiB: number | null; +} + +interface DiskMetrics { + percent: number | null; + usedHuman: string | null; + totalHuman: string | null; +} + export type ServerMetrics = { - cpu: { percent: number | null; cores: number | null; load: [number, number, number] | null }; - memory: { percent: number | null; usedGiB: number | null; totalGiB: number | null }; - disk: { percent: number | null; usedHuman: string | null; totalHuman: string | null }; + cpu: CpuMetrics; + memory: MemoryMetrics; + disk: DiskMetrics; lastChecked: string; }; -const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; +interface AuthResponse { + token: string; +} -const sshHostApi = axios.create({ - baseURL: isLocalhost ? 'http://localhost:8081' : '', - headers: { - 'Content-Type': 'application/json', - }, -}); +interface UserInfo { + id: string; + username: string; + is_admin: boolean; +} -const tunnelApi = axios.create({ - baseURL: isLocalhost ? 'http://localhost:8083' : '', - headers: { - 'Content-Type': 'application/json', - }, -}); +interface UserCount { + count: number; +} -const fileManagerApi = axios.create({ - baseURL: isLocalhost ? 'http://localhost:8084' : '', - headers: { - 'Content-Type': 'application/json', - } -}) +interface OIDCAuthorize { + auth_url: string; +} -const statsApi = axios.create({ - baseURL: isLocalhost ? 'http://localhost:8085' : '', - headers: { - 'Content-Type': 'application/json', - } -}) +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ + +function setCookie(name: string, value: string, days = 7): void { + const expires = new Date(Date.now() + days * 864e5).toUTCString(); + document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/`; +} function getCookie(name: string): string | undefined { const value = `; ${document.cookie}`; @@ -141,44 +168,116 @@ function getCookie(name: string): string | undefined { if (parts.length === 2) return parts.pop()?.split(';').shift(); } -sshHostApi.interceptors.request.use((config) => { - const token = getCookie('jwt'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; -}); +function createApiInstance(baseURL: string): AxiosInstance { + const instance = axios.create({ + baseURL, + headers: { 'Content-Type': 'application/json' }, + timeout: 30000, + }); -statsApi.interceptors.request.use((config) => { - const token = getCookie('jwt'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; -}); + instance.interceptors.request.use((config) => { + const token = getCookie('jwt'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }); -tunnelApi.interceptors.request.use((config) => { - const token = getCookie('jwt'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; -}); + instance.interceptors.response.use( + (response) => response, + (error: AxiosError) => { + if (error.response?.status === 401) { + // Token expired or invalid - clear cookie + document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + } + return Promise.reject(error); + } + ); -fileManagerApi.interceptors.request.use((config) => { - const token = getCookie('jwt'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; + return instance; +} + +// ============================================================================ +// API INSTANCES +// ============================================================================ + +const isDev = process.env.NODE_ENV === 'development' || window.location.hostname === 'localhost'; + +// SSH Host Management API (port 8081) +export const sshHostApi = createApiInstance( + isDev ? 'http://localhost:8081/ssh' : '/ssh' +); + +// Tunnel Management API (port 8083) +export const tunnelApi = createApiInstance( + isDev ? 'http://localhost:8083/ssh' : '/ssh' +); + +// File Manager Operations API (port 8084) - SSH file operations +export const fileManagerApi = createApiInstance( + isDev ? 'http://localhost:8084/ssh/file_manager' : '/ssh/file_manager' +); + +// Server Statistics API (port 8085) +export const statsApi = createApiInstance( + isDev ? 'http://localhost:8085' : '' +); + +// Authentication API (port 8081) +export const authApi = createApiInstance( + isDev ? 'http://localhost:8081/users' : '/users' +); + +// ============================================================================ +// ERROR HANDLING +// ============================================================================ + +class ApiError extends Error { + constructor( + message: string, + public status?: number, + public code?: string + ) { + super(message); + this.name = 'ApiError'; } - return config; -}); +} + +function handleApiError(error: unknown, operation: string): never { + if (axios.isAxiosError(error)) { + const status = error.response?.status; + const message = error.response?.data?.error || error.message; + + if (status === 401) { + throw new ApiError('Authentication required', 401); + } else if (status === 403) { + throw new ApiError('Access denied', 403); + } else if (status === 404) { + throw new ApiError('Resource not found', 404); + } else if (status && status >= 500) { + throw new ApiError('Server error occurred', status); + } else { + throw new ApiError(message || `Failed to ${operation}`, status); + } + } + + if (error instanceof ApiError) { + throw error; + } + + throw new ApiError(`Unexpected error during ${operation}: ${error instanceof Error ? error.message : 'Unknown error'}`); +} + +// ============================================================================ +// SSH HOST MANAGEMENT +// ============================================================================ export async function getSSHHosts(): Promise { try { - const response = await sshHostApi.get('/ssh/db/host'); + const response = await sshHostApi.get('/db/host'); return response.data; } catch (error) { - throw error; + handleApiError(error, 'fetch SSH hosts'); } } @@ -216,23 +315,20 @@ export async function createSSHHost(hostData: SSHHostData): Promise { const formData = new FormData(); formData.append('key', hostData.key); - const dataWithoutFile = {...submitData}; + const dataWithoutFile = { ...submitData }; delete dataWithoutFile.key; formData.append('data', JSON.stringify(dataWithoutFile)); - const response = await sshHostApi.post('/ssh/db/host', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, + const response = await sshHostApi.post('/db/host', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, }); - return response.data; } else { - const response = await sshHostApi.post('/ssh/db/host', submitData); + const response = await sshHostApi.post('/db/host', submitData); return response.data; } } catch (error) { - throw error; + handleApiError(error, 'create SSH host'); } } @@ -269,23 +365,20 @@ export async function updateSSHHost(hostId: number, hostData: SSHHostData): Prom const formData = new FormData(); formData.append('key', hostData.key); - const dataWithoutFile = {...submitData}; + const dataWithoutFile = { ...submitData }; delete dataWithoutFile.key; formData.append('data', JSON.stringify(dataWithoutFile)); - const response = await sshHostApi.put(`/ssh/db/host/${hostId}`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, + const response = await sshHostApi.put(`/db/host/${hostId}`, formData, { + headers: { 'Content-Type': 'multipart/form-data' }, }); - return response.data; } else { - const response = await sshHostApi.put(`/ssh/db/host/${hostId}`, submitData); + const response = await sshHostApi.put(`/db/host/${hostId}`, submitData); return response.data; } } catch (error) { - throw error; + handleApiError(error, 'update SSH host'); } } @@ -296,37 +389,41 @@ export async function bulkImportSSHHosts(hosts: SSHHostData[]): Promise<{ errors: string[]; }> { try { - const response = await sshHostApi.post('/ssh/bulk-import', {hosts}); + const response = await sshHostApi.post('/bulk-import', { hosts }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'bulk import SSH hosts'); } } export async function deleteSSHHost(hostId: number): Promise { try { - const response = await sshHostApi.delete(`/ssh/db/host/${hostId}`); + const response = await sshHostApi.delete(`/db/host/${hostId}`); return response.data; } catch (error) { - throw error; + handleApiError(error, 'delete SSH host'); } } export async function getSSHHostById(hostId: number): Promise { try { - const response = await sshHostApi.get(`/ssh/db/host/${hostId}`); + const response = await sshHostApi.get(`/db/host/${hostId}`); return response.data; } catch (error) { - throw error; + handleApiError(error, 'fetch SSH host'); } } +// ============================================================================ +// TUNNEL MANAGEMENT +// ============================================================================ + export async function getTunnelStatuses(): Promise> { try { - const response = await tunnelApi.get('/ssh/tunnel/status'); + const response = await tunnelApi.get('/tunnel/status'); return response.data || {}; } catch (error) { - throw error; + handleApiError(error, 'fetch tunnel statuses'); } } @@ -337,148 +434,121 @@ export async function getTunnelStatusByName(tunnelName: string): Promise { try { - const response = await tunnelApi.post('/ssh/tunnel/connect', tunnelConfig); + const response = await tunnelApi.post('/tunnel/connect', tunnelConfig); return response.data; } catch (error) { - throw error; + handleApiError(error, 'connect tunnel'); } } export async function disconnectTunnel(tunnelName: string): Promise { try { - const response = await tunnelApi.post('/ssh/tunnel/disconnect', {tunnelName}); + const response = await tunnelApi.post('/tunnel/disconnect', { tunnelName }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'disconnect tunnel'); } } export async function cancelTunnel(tunnelName: string): Promise { try { - const response = await tunnelApi.post('/ssh/tunnel/cancel', {tunnelName}); + const response = await tunnelApi.post('/tunnel/cancel', { tunnelName }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'cancel tunnel'); } } +// ============================================================================ +// FILE MANAGER METADATA (Recent, Pinned, Shortcuts) +// ============================================================================ + export async function getFileManagerRecent(hostId: number): Promise { try { - const response = await sshHostApi.get(`/ssh/file_manager/recent?hostId=${hostId}`); + const response = await sshHostApi.get(`/file_manager/recent?hostId=${hostId}`); return response.data || []; } catch (error) { + // Don't throw for file manager metadata - return empty array return []; } } -export async function addFileManagerRecent(file: { - name: string; - path: string; - isSSH: boolean; - sshSessionId?: string; - hostId: number -}): Promise { +export async function addFileManagerRecent(file: FileManagerOperation): Promise { try { - const response = await sshHostApi.post('/ssh/file_manager/recent', file); + const response = await sshHostApi.post('/file_manager/recent', file); return response.data; } catch (error) { - throw error; + handleApiError(error, 'add recent file'); } } -export async function removeFileManagerRecent(file: { - name: string; - path: string; - isSSH: boolean; - sshSessionId?: string; - hostId: number -}): Promise { +export async function removeFileManagerRecent(file: FileManagerOperation): Promise { try { - const response = await sshHostApi.delete('/ssh/file_manager/recent', {data: file}); + const response = await sshHostApi.delete('/file_manager/recent', { data: file }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'remove recent file'); } } export async function getFileManagerPinned(hostId: number): Promise { try { - const response = await sshHostApi.get(`/ssh/file_manager/pinned?hostId=${hostId}`); + const response = await sshHostApi.get(`/file_manager/pinned?hostId=${hostId}`); return response.data || []; } catch (error) { return []; } } -export async function addFileManagerPinned(file: { - name: string; - path: string; - isSSH: boolean; - sshSessionId?: string; - hostId: number -}): Promise { +export async function addFileManagerPinned(file: FileManagerOperation): Promise { try { - const response = await sshHostApi.post('/ssh/file_manager/pinned', file); + const response = await sshHostApi.post('/file_manager/pinned', file); return response.data; } catch (error) { - throw error; + handleApiError(error, 'add pinned file'); } } -export async function removeFileManagerPinned(file: { - name: string; - path: string; - isSSH: boolean; - sshSessionId?: string; - hostId: number -}): Promise { +export async function removeFileManagerPinned(file: FileManagerOperation): Promise { try { - const response = await sshHostApi.delete('/ssh/file_manager/pinned', {data: file}); + const response = await sshHostApi.delete('/file_manager/pinned', { data: file }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'remove pinned file'); } } export async function getFileManagerShortcuts(hostId: number): Promise { try { - const response = await sshHostApi.get(`/ssh/file_manager/shortcuts?hostId=${hostId}`); + const response = await sshHostApi.get(`/file_manager/shortcuts?hostId=${hostId}`); return response.data || []; } catch (error) { return []; } } -export async function addFileManagerShortcut(shortcut: { - name: string; - path: string; - isSSH: boolean; - sshSessionId?: string; - hostId: number -}): Promise { +export async function addFileManagerShortcut(shortcut: FileManagerOperation): Promise { try { - const response = await sshHostApi.post('/ssh/file_manager/shortcuts', shortcut); + const response = await sshHostApi.post('/file_manager/shortcuts', shortcut); return response.data; } catch (error) { - throw error; + handleApiError(error, 'add shortcut'); } } -export async function removeFileManagerShortcut(shortcut: { - name: string; - path: string; - isSSH: boolean; - sshSessionId?: string; - hostId: number -}): Promise { +export async function removeFileManagerShortcut(shortcut: FileManagerOperation): Promise { try { - const response = await sshHostApi.delete('/ssh/file_manager/shortcuts', {data: shortcut}); + const response = await sshHostApi.delete('/file_manager/shortcuts', { data: shortcut }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'remove shortcut'); } } +// ============================================================================ +// SSH FILE OPERATIONS +// ============================================================================ + export async function connectSSH(sessionId: string, config: { ip: string; port: number; @@ -488,61 +558,61 @@ export async function connectSSH(sessionId: string, config: { keyPassword?: string; }): Promise { try { - const response = await fileManagerApi.post('/ssh/file_manager/ssh/connect', { + const response = await fileManagerApi.post('/ssh/connect', { sessionId, ...config }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'connect SSH'); } } export async function disconnectSSH(sessionId: string): Promise { try { - const response = await fileManagerApi.post('/ssh/file_manager/ssh/disconnect', {sessionId}); + const response = await fileManagerApi.post('/ssh/disconnect', { sessionId }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'disconnect SSH'); } } export async function getSSHStatus(sessionId: string): Promise<{ connected: boolean }> { try { - const response = await fileManagerApi.get('/ssh/file_manager/ssh/status', { - params: {sessionId} + const response = await fileManagerApi.get('/ssh/status', { + params: { sessionId } }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'get SSH status'); } } export async function listSSHFiles(sessionId: string, path: string): Promise { try { - const response = await fileManagerApi.get('/ssh/file_manager/ssh/listFiles', { - params: {sessionId, path} + const response = await fileManagerApi.get('/ssh/listFiles', { + params: { sessionId, path } }); return response.data || []; } catch (error) { - throw error; + handleApiError(error, 'list SSH files'); } } export async function readSSHFile(sessionId: string, path: string): Promise<{ content: string; path: string }> { try { - const response = await fileManagerApi.get('/ssh/file_manager/ssh/readFile', { - params: {sessionId, path} + const response = await fileManagerApi.get('/ssh/readFile', { + params: { sessionId, path } }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'read SSH file'); } } export async function writeSSHFile(sessionId: string, path: string, content: string): Promise { try { - const response = await fileManagerApi.post('/ssh/file_manager/ssh/writeFile', { + const response = await fileManagerApi.post('/ssh/writeFile', { sessionId, path, content @@ -554,13 +624,13 @@ export async function writeSSHFile(sessionId: string, path: string, content: str throw new Error('File write operation did not return success status'); } } catch (error) { - throw error; + handleApiError(error, 'write SSH file'); } } export async function uploadSSHFile(sessionId: string, path: string, fileName: string, content: string): Promise { try { - const response = await fileManagerApi.post('/ssh/file_manager/ssh/uploadFile', { + const response = await fileManagerApi.post('/ssh/uploadFile', { sessionId, path, fileName, @@ -568,13 +638,13 @@ export async function uploadSSHFile(sessionId: string, path: string, fileName: s }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'upload SSH file'); } } export async function createSSHFile(sessionId: string, path: string, fileName: string, content: string = ''): Promise { try { - const response = await fileManagerApi.post('/ssh/file_manager/ssh/createFile', { + const response = await fileManagerApi.post('/ssh/createFile', { sessionId, path, fileName, @@ -582,26 +652,26 @@ export async function createSSHFile(sessionId: string, path: string, fileName: s }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'create SSH file'); } } export async function createSSHFolder(sessionId: string, path: string, folderName: string): Promise { try { - const response = await fileManagerApi.post('/ssh/file_manager/ssh/createFolder', { + const response = await fileManagerApi.post('/ssh/createFolder', { sessionId, path, folderName }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'create SSH folder'); } } export async function deleteSSHItem(sessionId: string, path: string, isDirectory: boolean): Promise { try { - const response = await fileManagerApi.delete('/ssh/file_manager/ssh/deleteItem', { + const response = await fileManagerApi.delete('/ssh/deleteItem', { data: { sessionId, path, @@ -610,31 +680,33 @@ export async function deleteSSHItem(sessionId: string, path: string, isDirectory }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'delete SSH item'); } } export async function renameSSHItem(sessionId: string, oldPath: string, newName: string): Promise { try { - const response = await fileManagerApi.put('/ssh/file_manager/ssh/renameItem', { + const response = await fileManagerApi.put('/ssh/renameItem', { sessionId, oldPath, newName }); return response.data; } catch (error) { - throw error; + handleApiError(error, 'rename SSH item'); } } -export {sshHostApi, tunnelApi, fileManagerApi}; +// ============================================================================ +// SERVER STATISTICS +// ============================================================================ export async function getAllServerStatuses(): Promise> { try { const response = await statsApi.get('/status'); return response.data || {}; } catch (error) { - throw error; + handleApiError(error, 'fetch server statuses'); } } @@ -643,7 +715,7 @@ export async function getServerStatusById(id: number): Promise { const response = await statsApi.get(`/status/${id}`); return response.data; } catch (error) { - throw error; + handleApiError(error, 'fetch server status'); } } @@ -652,6 +724,100 @@ export async function getServerMetricsById(id: number): Promise { const response = await statsApi.get(`/metrics/${id}`); return response.data; } catch (error) { - throw error; + handleApiError(error, 'fetch server metrics'); + } +} + +// ============================================================================ +// AUTHENTICATION +// ============================================================================ + +export async function registerUser(username: string, password: string): Promise { + try { + const response = await authApi.post('/create', { username, password }); + return response.data; + } catch (error) { + handleApiError(error, 'register user'); + } +} + +export async function loginUser(username: string, password: string): Promise { + try { + const response = await authApi.post('/login', { username, password }); + return response.data; + } catch (error) { + handleApiError(error, 'login user'); + } +} + +export async function getUserInfo(): Promise { + try { + const response = await authApi.get('/me'); + return response.data; + } catch (error) { + handleApiError(error, 'fetch user info'); + } +} + +export async function getRegistrationAllowed(): Promise<{ allowed: boolean }> { + try { + const response = await authApi.get('/registration-allowed'); + return response.data; + } catch (error) { + handleApiError(error, 'check registration status'); + } +} + +export async function getOIDCConfig(): Promise { + try { + const response = await authApi.get('/oidc-config'); + return response.data; + } catch (error) { + handleApiError(error, 'fetch OIDC config'); + } +} + +export async function getUserCount(): Promise { + try { + const response = await authApi.get('/count'); + return response.data; + } catch (error) { + handleApiError(error, 'fetch user count'); + } +} + +export async function initiatePasswordReset(username: string): Promise { + try { + const response = await authApi.post('/initiate-reset', { username }); + return response.data; + } catch (error) { + handleApiError(error, 'initiate password reset'); + } +} + +export async function verifyPasswordResetCode(username: string, resetCode: string): Promise { + try { + const response = await authApi.post('/verify-reset-code', { username, resetCode }); + return response.data; + } catch (error) { + handleApiError(error, 'verify reset code'); + } +} + +export async function completePasswordReset(username: string, tempToken: string, newPassword: string): Promise { + try { + const response = await authApi.post('/complete-reset', { username, tempToken, newPassword }); + return response.data; + } catch (error) { + handleApiError(error, 'complete password reset'); + } +} + +export async function getOIDCAuthorizeUrl(): Promise { + try { + const response = await authApi.get('/oidc/authorize'); + return response.data; + } catch (error) { + handleApiError(error, 'get OIDC authorize URL'); } } \ No newline at end of file