From c8fd2ef47e0ed9c5cd2db4d695e340d3ca1bc353 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Thu, 26 Sep 2024 08:06:14 +0200 Subject: [PATCH 001/241] chore: remove healthcheck on dev compose --- docker-compose.dev.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a1de5bf842..c4da13c1c4 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,8 +2,7 @@ services: runtipi-reverse-proxy: container_name: runtipi-reverse-proxy depends_on: - runtipi: - condition: service_healthy + - runtipi image: traefik:v3.1.4 restart: unless-stopped ports: From 4e7ef6e7c31e3a64d97ebe27b9776d2c792029a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:25:17 +0000 Subject: [PATCH 002/241] chore(deps): bump the minor-patch group across 1 directory with 33 updates Bumps the minor-patch group with 33 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@radix-ui/react-context-menu](https://github.com/radix-ui/primitives) | `2.2.1` | `2.2.2` | | [@radix-ui/react-dialog](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` | | [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives) | `2.1.1` | `2.1.2` | | [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives) | `1.1.0` | `1.2.0` | | [@radix-ui/react-select](https://github.com/radix-ui/primitives) | `2.1.1` | `2.1.2` | | [@radix-ui/react-switch](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` | | [@radix-ui/react-tabs](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` | | [@sentry/nextjs](https://github.com/getsentry/sentry-javascript) | `8.31.0` | `8.34.0` | | [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) | `3.17.0` | `3.19.0` | | [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.56.2` | `5.59.9` | | [bullmq](https://github.com/taskforcesh/bullmq) | `5.13.2` | `5.18.0` | | [dompurify](https://github.com/cure53/DOMPurify) | `3.1.6` | `3.1.7` | | [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) | `0.33.0` | `0.34.1` | | [next](https://github.com/vercel/next.js) | `14.2.13` | `14.2.15` | | [next-intl](https://github.com/amannn/next-intl) | `3.19.4` | `3.21.1` | | [sass](https://github.com/sass/dart-sass) | `1.79.3` | `1.79.4` | | [winston](https://github.com/winstonjs/winston) | `3.14.2` | `3.15.0` | | [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) | `7.25.2` | `7.25.8` | | [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `1.9.2` | `1.9.3` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.47.2` | `1.48.0` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.6.0` | `22.7.5` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `18.3.8` | `18.3.11` | | [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `4.3.1` | `4.3.2` | | [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `2.1.1` | `2.1.2` | | [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `2.1.1` | `2.1.2` | | [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `5.30.5` | `5.33.3` | | [memfs](https://github.com/streamich/memfs) | `4.12.0` | `4.13.0` | | [typescript](https://github.com/microsoft/TypeScript) | `5.6.2` | `5.6.3` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `2.1.1` | `2.1.2` | | [@sentry/types](https://github.com/getsentry/sentry-javascript) | `8.31.0` | `8.34.0` | | [@sentry/node](https://github.com/getsentry/sentry-javascript) | `8.31.0` | `8.34.0` | | [hono](https://github.com/honojs/hono) | `4.6.2` | `4.6.3` | | [@sentry/esbuild-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) | `2.22.4` | `2.22.5` | Updates `@radix-ui/react-context-menu` from 2.2.1 to 2.2.2 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@radix-ui/react-dialog` from 1.1.1 to 1.1.2 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@radix-ui/react-dropdown-menu` from 2.1.1 to 2.1.2 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@radix-ui/react-scroll-area` from 1.1.0 to 1.2.0 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@radix-ui/react-select` from 2.1.1 to 2.1.2 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@radix-ui/react-switch` from 1.1.0 to 1.1.1 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@radix-ui/react-tabs` from 1.1.0 to 1.1.1 - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) Updates `@sentry/nextjs` from 8.31.0 to 8.34.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/8.34.0/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.31.0...8.34.0) Updates `@tabler/icons-react` from 3.17.0 to 3.19.0 - [Release notes](https://github.com/tabler/tabler-icons/releases) - [Commits](https://github.com/tabler/tabler-icons/commits/v3.19.0/packages/icons-react) Updates `@tanstack/react-query` from 5.56.2 to 5.59.9 - [Release notes](https://github.com/TanStack/query/releases) - [Commits](https://github.com/TanStack/query/commits/v5.59.9/packages/react-query) Updates `bullmq` from 5.13.2 to 5.18.0 - [Release notes](https://github.com/taskforcesh/bullmq/releases) - [Commits](https://github.com/taskforcesh/bullmq/compare/v5.13.2...v5.18.0) Updates `dompurify` from 3.1.6 to 3.1.7 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.1.6...3.1.7) Updates `drizzle-orm` from 0.33.0 to 0.34.1 - [Release notes](https://github.com/drizzle-team/drizzle-orm/releases) - [Commits](https://github.com/drizzle-team/drizzle-orm/compare/0.33.0...0.34.1) Updates `next` from 14.2.13 to 14.2.15 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v14.2.13...v14.2.15) Updates `next-intl` from 3.19.4 to 3.21.1 - [Release notes](https://github.com/amannn/next-intl/releases) - [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md) - [Commits](https://github.com/amannn/next-intl/compare/v3.19.4...v3.21.1) Updates `sass` from 1.79.3 to 1.79.4 - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.79.3...1.79.4) Updates `winston` from 3.14.2 to 3.15.0 - [Release notes](https://github.com/winstonjs/winston/releases) - [Changelog](https://github.com/winstonjs/winston/blob/master/CHANGELOG.md) - [Commits](https://github.com/winstonjs/winston/compare/v3.14.2...v3.15.0) Updates `@babel/core` from 7.25.2 to 7.25.8 - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.25.8/packages/babel-core) Updates `@biomejs/biome` from 1.9.2 to 1.9.3 - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/cli/v1.9.3/packages/@biomejs/biome) Updates `@playwright/test` from 1.47.2 to 1.48.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.47.2...v1.48.0) Updates `@types/node` from 22.6.0 to 22.7.5 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@types/react` from 18.3.8 to 18.3.11 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `@vitejs/plugin-react` from 4.3.1 to 4.3.2 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.2/packages/plugin-react) Updates `@vitest/coverage-v8` from 2.1.1 to 2.1.2 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.2/packages/coverage-v8) Updates `@vitest/ui` from 2.1.1 to 2.1.2 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.2/packages/ui) Updates `knip` from 5.30.5 to 5.33.3 - [Release notes](https://github.com/webpro-nl/knip/releases) - [Changelog](https://github.com/webpro-nl/knip/blob/main/packages/knip/.release-it.json) - [Commits](https://github.com/webpro-nl/knip/commits/5.33.3/packages/knip) Updates `memfs` from 4.12.0 to 4.13.0 - [Release notes](https://github.com/streamich/memfs/releases) - [Changelog](https://github.com/streamich/memfs/blob/master/CHANGELOG.md) - [Commits](https://github.com/streamich/memfs/compare/v4.12.0...v4.13.0) Updates `typescript` from 5.6.2 to 5.6.3 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.6.2...v5.6.3) Updates `vitest` from 2.1.1 to 2.1.2 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.2/packages/vitest) Updates `@sentry/types` from 8.31.0 to 8.34.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/8.34.0/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.31.0...8.34.0) Updates `@sentry/node` from 8.31.0 to 8.34.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/8.34.0/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.31.0...8.34.0) Updates `hono` from 4.6.2 to 4.6.3 - [Release notes](https://github.com/honojs/hono/releases) - [Commits](https://github.com/honojs/hono/compare/v4.6.2...v4.6.3) Updates `@sentry/esbuild-plugin` from 2.22.4 to 2.22.5 - [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases) - [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/2.22.4...2.22.5) --- updated-dependencies: - dependency-name: "@radix-ui/react-context-menu" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@radix-ui/react-dialog" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@radix-ui/react-dropdown-menu" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@radix-ui/react-scroll-area" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@radix-ui/react-select" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@radix-ui/react-switch" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@radix-ui/react-tabs" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@sentry/nextjs" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@tabler/icons-react" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@tanstack/react-query" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: bullmq dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: dompurify dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: drizzle-orm dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: next dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: next-intl dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: sass dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: winston dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@biomejs/biome" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@playwright/test" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@types/react" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@vitejs/plugin-react" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@vitest/coverage-v8" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@vitest/ui" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: knip dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: memfs dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: vitest dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@sentry/types" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@sentry/node" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: hono dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@sentry/esbuild-plugin" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch ... Signed-off-by: dependabot[bot] --- package.json | 58 +- packages/cache/package.json | 2 +- packages/db/package.json | 2 +- packages/shared/package.json | 4 +- packages/worker/package.json | 18 +- pnpm-lock.yaml | 2246 +++++++++++++++++----------------- 6 files changed, 1184 insertions(+), 1146 deletions(-) diff --git a/package.json b/package.json index 2c3204da9c..fd0246bf9d 100644 --- a/package.json +++ b/package.json @@ -27,29 +27,29 @@ "@otplib/core": "^12.0.1", "@otplib/plugin-crypto": "^12.0.1", "@otplib/plugin-thirty-two": "^12.0.1", - "@radix-ui/react-context-menu": "^2.2.1", - "@radix-ui/react-dialog": "^1.1.1", - "@radix-ui/react-dropdown-menu": "^2.1.1", - "@radix-ui/react-scroll-area": "^1.1.0", - "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-context-menu": "^2.2.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-scroll-area": "^1.2.0", + "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-switch": "^1.1.0", - "@radix-ui/react-tabs": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", + "@radix-ui/react-tabs": "^1.1.1", "@runtipi/cache": "workspace:^", "@runtipi/db": "workspace:^", "@runtipi/postgres-migrations": "^5.3.0", "@runtipi/shared": "workspace:^", - "@sentry/nextjs": "^8.31.0", + "@sentry/nextjs": "^8.34.0", "@tabler/core": "1.0.0-beta21", - "@tabler/icons-react": "^3.17.0", - "@tanstack/react-query": "^5.56.2", + "@tabler/icons-react": "^3.19.0", + "@tanstack/react-query": "^5.59.9", "@uidotdev/usehooks": "^2.4.1", "argon2": "^0.41.1", - "bullmq": "^5.13.2", + "bullmq": "^5.18.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", - "dompurify": "^3.1.6", - "drizzle-orm": "^0.33.0", + "dompurify": "^3.1.7", + "drizzle-orm": "^0.34.1", "fs-extra": "^11.2.0", "geist": "^1.3.1", "inversify": "^6.0.2", @@ -58,9 +58,9 @@ "let-it-go": "^1.0.0", "lodash.merge": "^4.6.2", "minisearch": "^7.1.0", - "next": "14.2.13", + "next": "14.2.15", "next-client-cookies": "^1.1.1", - "next-intl": "^3.19.4", + "next-intl": "^3.21.1", "next-safe-action": "7.9.3", "pg": "^8.13.0", "qrcode.react": "^4.0.1", @@ -76,21 +76,21 @@ "rehype-raw": "^7.0.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.0", - "sass": "^1.79.3", + "sass": "^1.79.4", "semver": "^7.6.3", "sharp": "0.33.5", "socket.io-client": "^4.8.0", "uuid": "^10.0.0", "validator": "^13.12.0", - "winston": "^3.14.2", + "winston": "^3.15.0", "zod": "^3.23.8", "zustand": "^4.5.5" }, "devDependencies": { - "@babel/core": "^7.25.2", - "@biomejs/biome": "1.9.2", + "@babel/core": "^7.25.8", + "@biomejs/biome": "1.9.3", "@faker-js/faker": "^8.4.1", - "@playwright/test": "^1.47.2", + "@playwright/test": "^1.48.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.5.0", "@testing-library/react": "^16.0.1", @@ -101,25 +101,25 @@ "@types/fs-extra": "^11.0.4", "@types/jsonwebtoken": "^9.0.7", "@types/lodash.merge": "^4.6.9", - "@types/node": "22.6.0", + "@types/node": "22.7.5", "@types/pg": "^8.11.10", - "@types/react": "18.3.8", + "@types/react": "18.3.11", "@types/react-dom": "18.3.0", "@types/semver": "^7.5.8", "@types/uuid": "^10.0.0", "@types/validator": "^13.12.2", - "@vitejs/plugin-react": "^4.3.1", - "@vitest/coverage-v8": "^2.1.1", - "@vitest/ui": "^2.1.1", + "@vitejs/plugin-react": "^4.3.2", + "@vitest/coverage-v8": "^2.1.2", + "@vitest/ui": "^2.1.2", "dotenv-cli": "^7.4.1", "jsdom": "^25.0.1", - "knip": "^5.30.5", - "memfs": "^4.11.2", + "knip": "^5.33.3", + "memfs": "^4.13.0", "msw": "^2.4.9", "next-router-mock": "^0.9.13", - "typescript": "5.6.2", + "typescript": "5.6.3", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^2.1.1", + "vitest": "^2.1.2", "vitest-mock-extended": "^2.0.2", "wait-for-expect": "^3.0.2" }, diff --git a/packages/cache/package.json b/packages/cache/package.json index 2aeab06c94..7dd226bfec 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -15,6 +15,6 @@ "ioredis": "^5.4.1" }, "devDependencies": { - "vitest": "^2.1.1" + "vitest": "^2.1.2" } } diff --git a/packages/db/package.json b/packages/db/package.json index e94d44b600..7ed64326ce 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -13,7 +13,7 @@ "dependencies": { "@runtipi/postgres-migrations": "^5.3.0", "@runtipi/shared": "workspace:^", - "drizzle-orm": "^0.33.0", + "drizzle-orm": "^0.34.1", "pg": "^8.13.0" } } diff --git a/packages/shared/package.json b/packages/shared/package.json index a9015cd41c..cf2ecf2bf4 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -29,11 +29,11 @@ "lodash.clonedeep": "^4.5.0", "tar-fs": "^3.0.6", "validator": "^13.12.0", - "winston": "^3.14.2", + "winston": "^3.15.0", "zod": "^3.23.8" }, "devDependencies": { - "@sentry/types": "^8.31.0", + "@sentry/types": "^8.34.0", "@types/gunzip-maybe": "^1.4.2", "@types/lodash.clonedeep": "^4.5.9", "@types/tar-fs": "^2.0.4" diff --git a/packages/worker/package.json b/packages/worker/package.json index 527147e529..15f48da529 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -16,16 +16,16 @@ "license": "ISC", "devDependencies": { "@faker-js/faker": "^8.4.1", - "@sentry/esbuild-plugin": "^2.22.4", + "@sentry/esbuild-plugin": "^2.22.5", "@types/web-push": "^3.6.3", "dotenv-cli": "^7.4.2", "esbuild": "^0.23.1", - "knip": "^5.30.5", - "memfs": "^4.11.2", + "knip": "^5.33.3", + "memfs": "^4.13.0", "nodemon": "^3.1.7", - "typescript": "^5.6.2", + "typescript": "^5.6.3", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^2.1.1" + "vitest": "^2.1.2" }, "dependencies": { "@hono/node-server": "^1.13.1", @@ -33,12 +33,12 @@ "@runtipi/db": "workspace:^", "@runtipi/shared": "workspace:^", "@sentry/integrations": "^7.114.0", - "@sentry/node": "^8.31.0", + "@sentry/node": "^8.34.0", "ansi-to-html": "^0.7.2", - "bullmq": "^5.13.2", + "bullmq": "^5.18.0", "dotenv": "^16.4.5", - "drizzle-orm": "^0.33.0", - "hono": "^4.6.2", + "drizzle-orm": "^0.34.1", + "hono": "^4.6.3", "inversify": "^6.0.2", "reflect-metadata": "^0.2.2", "socket.io": "^4.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b782dd8714..1b3fe2447e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,29 +21,29 @@ importers: specifier: ^12.0.1 version: 12.0.1 '@radix-ui/react-context-menu': - specifier: ^2.2.1 - version: 2.2.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^2.2.2 + version: 2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': - specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dropdown-menu': - specifier: ^2.1.1 - version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^2.1.2 + version: 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-scroll-area': - specifier: ^1.1.0 - version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.2.0 + version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-select': - specifier: ^2.1.1 - version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^2.1.2 + version: 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.1.0 - version: 1.1.0(@types/react@18.3.8)(react@18.3.1) + version: 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-switch': - specifier: ^1.1.0 - version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-tabs': - specifier: ^1.1.0 - version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@runtipi/cache': specifier: workspace:^ version: link:packages/cache @@ -57,17 +57,17 @@ importers: specifier: workspace:^ version: link:packages/shared '@sentry/nextjs': - specifier: ^8.31.0 - version: 8.31.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react@18.3.1)(webpack@5.94.0) + specifier: ^8.34.0 + version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1)(webpack@5.94.0) '@tabler/core': specifier: 1.0.0-beta21 version: 1.0.0-beta21 '@tabler/icons-react': - specifier: ^3.17.0 - version: 3.17.0(react@18.3.1) + specifier: ^3.19.0 + version: 3.19.0(react@18.3.1) '@tanstack/react-query': - specifier: ^5.56.2 - version: 5.56.2(react@18.3.1) + specifier: ^5.59.9 + version: 5.59.9(react@18.3.1) '@uidotdev/usehooks': specifier: ^2.4.1 version: 2.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -75,8 +75,8 @@ importers: specifier: ^0.41.1 version: 0.41.1 bullmq: - specifier: ^5.13.2 - version: 5.13.2 + specifier: ^5.18.0 + version: 5.18.0 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -84,17 +84,17 @@ importers: specifier: ^2.1.0 version: 2.1.1 dompurify: - specifier: ^3.1.6 - version: 3.1.6 + specifier: ^3.1.7 + version: 3.1.7 drizzle-orm: - specifier: ^0.33.0 - version: 0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.8)(pg@8.13.0)(react@18.3.1) + specifier: ^0.34.1 + version: 0.34.1(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) fs-extra: specifier: ^11.2.0 version: 11.2.0 geist: specifier: ^1.3.1 - version: 1.3.1(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3)) + version: 1.3.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4)) inversify: specifier: ^6.0.2 version: 6.0.2 @@ -114,17 +114,17 @@ importers: specifier: ^7.1.0 version: 7.1.0 next: - specifier: 14.2.13 - version: 14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3) + specifier: 14.2.15 + version: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) next-client-cookies: specifier: ^1.1.1 - version: 1.1.1(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react@18.3.1) + version: 1.1.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1) next-intl: - specifier: ^3.19.4 - version: 3.19.4(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react@18.3.1) + specifier: ^3.21.1 + version: 3.21.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1) next-safe-action: specifier: 7.9.3 - version: 7.9.3(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8) + version: 7.9.3(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8) pg: specifier: ^8.13.0 version: 8.13.0 @@ -145,10 +145,10 @@ importers: version: 2.4.1(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-markdown: specifier: ^9.0.1 - version: 9.0.1(@types/react@18.3.8)(react@18.3.1) + version: 9.0.1(@types/react@18.3.11)(react@18.3.1) react-timezone-select: specifier: ^3.2.8 - version: 3.2.8(react-dom@18.3.1(react@18.3.1))(react-select@5.8.0(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 3.2.8(react-dom@18.3.1(react@18.3.1))(react-select@5.8.0(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) react-tooltip: specifier: ^5.28.0 version: 5.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -168,8 +168,8 @@ importers: specifier: ^4.0.0 version: 4.0.0 sass: - specifier: ^1.79.3 - version: 1.79.3 + specifier: ^1.79.4 + version: 1.79.4 semver: specifier: ^7.6.3 version: 7.6.3 @@ -186,27 +186,27 @@ importers: specifier: ^13.12.0 version: 13.12.0 winston: - specifier: ^3.14.2 - version: 3.14.2 + specifier: ^3.15.0 + version: 3.15.0 zod: specifier: ^3.23.8 version: 3.23.8 zustand: specifier: ^4.5.5 - version: 4.5.5(@types/react@18.3.8)(react@18.3.1) + version: 4.5.5(@types/react@18.3.11)(react@18.3.1) devDependencies: '@babel/core': - specifier: ^7.25.2 - version: 7.25.2 + specifier: ^7.25.8 + version: 7.25.8 '@biomejs/biome': - specifier: 1.9.2 - version: 1.9.2 + specifier: 1.9.3 + version: 1.9.3 '@faker-js/faker': specifier: ^8.4.1 version: 8.4.1 '@playwright/test': - specifier: ^1.47.2 - version: 1.47.2 + specifier: ^1.48.0 + version: 1.48.0 '@testing-library/dom': specifier: ^10.4.0 version: 10.4.0 @@ -215,7 +215,7 @@ importers: version: 6.5.0 '@testing-library/react': specifier: ^16.0.1 - version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@testing-library/user-event': specifier: ^14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) @@ -238,14 +238,14 @@ importers: specifier: ^4.6.9 version: 4.6.9 '@types/node': - specifier: 22.6.0 - version: 22.6.0 + specifier: 22.7.5 + version: 22.7.5 '@types/pg': specifier: ^8.11.10 version: 8.11.10 '@types/react': - specifier: 18.3.8 - version: 18.3.8 + specifier: 18.3.11 + version: 18.3.11 '@types/react-dom': specifier: 18.3.0 version: 18.3.0 @@ -259,14 +259,14 @@ importers: specifier: ^13.12.2 version: 13.12.2 '@vitejs/plugin-react': - specifier: ^4.3.1 - version: 4.3.1(vite@5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0)) + specifier: ^4.3.2 + version: 4.3.2(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)) '@vitest/coverage-v8': - specifier: ^2.1.1 - version: 2.1.1(vitest@2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0)) + specifier: ^2.1.2 + version: 2.1.2(vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1)) '@vitest/ui': - specifier: ^2.1.1 - version: 2.1.1(vitest@2.1.1) + specifier: ^2.1.2 + version: 2.1.2(vitest@2.1.2) dotenv-cli: specifier: ^7.4.1 version: 7.4.2 @@ -274,29 +274,29 @@ importers: specifier: ^25.0.1 version: 25.0.1 knip: - specifier: ^5.30.5 - version: 5.30.5(@types/node@22.6.0)(typescript@5.6.2) + specifier: ^5.33.3 + version: 5.33.3(@types/node@22.7.5)(typescript@5.6.3) memfs: - specifier: ^4.11.2 - version: 4.12.0 + specifier: ^4.13.0 + version: 4.13.0 msw: specifier: ^2.4.9 - version: 2.4.9(typescript@5.6.2) + version: 2.4.9(typescript@5.6.3) next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react@18.3.1) + version: 0.9.13(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1) typescript: - specifier: 5.6.2 - version: 5.6.2 + specifier: 5.6.3 + version: 5.6.3 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.2)(vite@5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)) vitest: - specifier: ^2.1.1 - version: 2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0) + specifier: ^2.1.2 + version: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) vitest-mock-extended: specifier: ^2.0.2 - version: 2.0.2(typescript@5.6.2)(vitest@2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0)) + version: 2.0.2(typescript@5.6.3)(vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1)) wait-for-expect: specifier: ^3.0.2 version: 3.0.2 @@ -311,8 +311,8 @@ importers: version: 5.4.1 devDependencies: vitest: - specifier: ^2.1.1 - version: 2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0) + specifier: ^2.1.2 + version: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) packages/db: dependencies: @@ -323,8 +323,8 @@ importers: specifier: workspace:^ version: link:../shared drizzle-orm: - specifier: ^0.33.0 - version: 0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.8)(pg@8.13.0)(react@18.3.1) + specifier: ^0.34.1 + version: 0.34.1(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) pg: specifier: ^8.13.0 version: 8.13.0 @@ -347,15 +347,15 @@ importers: specifier: ^13.12.0 version: 13.12.0 winston: - specifier: ^3.14.2 - version: 3.14.2 + specifier: ^3.15.0 + version: 3.15.0 zod: specifier: ^3.23.8 version: 3.23.8 devDependencies: '@sentry/types': - specifier: ^8.31.0 - version: 8.31.0 + specifier: ^8.34.0 + version: 8.34.0 '@types/gunzip-maybe': specifier: ^1.4.2 version: 1.4.2 @@ -372,7 +372,7 @@ importers: dependencies: '@hono/node-server': specifier: ^1.13.1 - version: 1.13.1(hono@4.6.2) + version: 1.13.1(hono@4.6.3) '@runtipi/cache': specifier: workspace:^ version: link:../cache @@ -386,23 +386,23 @@ importers: specifier: ^7.114.0 version: 7.114.0 '@sentry/node': - specifier: ^8.31.0 - version: 8.31.0 + specifier: ^8.34.0 + version: 8.34.0 ansi-to-html: specifier: ^0.7.2 version: 0.7.2 bullmq: - specifier: ^5.13.2 - version: 5.13.2 + specifier: ^5.18.0 + version: 5.18.0 dotenv: specifier: ^16.4.5 version: 16.4.5 drizzle-orm: - specifier: ^0.33.0 - version: 0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.8)(pg@8.13.0)(react@18.3.1) + specifier: ^0.34.1 + version: 0.34.1(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) hono: - specifier: ^4.6.2 - version: 4.6.2 + specifier: ^4.6.3 + version: 4.6.3 inversify: specifier: ^6.0.2 version: 6.0.2 @@ -432,8 +432,8 @@ importers: specifier: ^8.4.1 version: 8.4.1 '@sentry/esbuild-plugin': - specifier: ^2.22.4 - version: 2.22.4 + specifier: ^2.22.5 + version: 2.22.5 '@types/web-push': specifier: ^3.6.3 version: 3.6.3 @@ -444,23 +444,23 @@ importers: specifier: ^0.23.1 version: 0.23.1 knip: - specifier: ^5.30.5 - version: 5.30.5(@types/node@22.6.0)(typescript@5.6.2) + specifier: ^5.33.3 + version: 5.33.3(@types/node@22.7.5)(typescript@5.6.3) memfs: - specifier: ^4.11.2 - version: 4.12.0 + specifier: ^4.13.0 + version: 4.13.0 nodemon: specifier: ^3.1.7 version: 3.1.7 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.2)(vite@5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)) vitest: - specifier: ^2.1.1 - version: 2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0) + specifier: ^2.1.2 + version: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) packages: @@ -475,73 +475,85 @@ packages: resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.25.4': - resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} + '@babel/code-frame@7.25.7': + resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} engines: {node: '>=6.9.0'} - '@babel/core@7.25.2': - resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} + '@babel/compat-data@7.25.8': + resolution: {integrity: sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.25.6': - resolution: {integrity: sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==} + '@babel/core@7.25.8': + resolution: {integrity: sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.2': - resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} + '@babel/generator@7.25.7': + resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + '@babel/helper-compilation-targets@7.25.7': + resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.25.2': - resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} + '@babel/helper-module-imports@7.25.7': + resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.25.7': + resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.24.8': - resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} + '@babel/helper-plugin-utils@7.25.7': + resolution: {integrity: sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==} engines: {node: '>=6.9.0'} - '@babel/helper-simple-access@7.24.7': - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + '@babel/helper-simple-access@7.25.7': + resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.8': - resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + '@babel/helper-string-parser@7.25.7': + resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} engines: {node: '>=6.9.0'} '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.8': - resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} + '@babel/helper-validator-identifier@7.25.7': + resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.25.6': - resolution: {integrity: sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==} + '@babel/helper-validator-option@7.25.7': + resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.25.7': + resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==} engines: {node: '>=6.9.0'} '@babel/highlight@7.24.7': resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.25.6': - resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} + '@babel/highlight@7.25.7': + resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.25.8': + resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.24.7': - resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} + '@babel/plugin-transform-react-jsx-self@7.25.7': + resolution: {integrity: sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.24.7': - resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} + '@babel/plugin-transform-react-jsx-source@7.25.7': + resolution: {integrity: sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -550,70 +562,74 @@ packages: resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} engines: {node: '>=6.9.0'} - '@babel/template@7.25.0': - resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} + '@babel/runtime@7.25.7': + resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.25.7': + resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.25.6': - resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} + '@babel/traverse@7.25.7': + resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==} engines: {node: '>=6.9.0'} - '@babel/types@7.25.6': - resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} + '@babel/types@7.25.8': + resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@biomejs/biome@1.9.2': - resolution: {integrity: sha512-4j2Gfwft8Jqp1X0qLYvK4TEy4xhTo4o6rlvJPsjPeEame8gsmbGQfOPBkw7ur+7/Z/f0HZmCZKqbMvR7vTXQYQ==} + '@biomejs/biome@1.9.3': + resolution: {integrity: sha512-POjAPz0APAmX33WOQFGQrwLvlu7WLV4CFJMlB12b6ZSg+2q6fYu9kZwLCOA+x83zXfcPd1RpuWOKJW0GbBwLIQ==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@1.9.2': - resolution: {integrity: sha512-rbs9uJHFmhqB3Td0Ro+1wmeZOHhAPTL3WHr8NtaVczUmDhXkRDWScaxicG9+vhSLj1iLrW47itiK6xiIJy6vaA==} + '@biomejs/cli-darwin-arm64@1.9.3': + resolution: {integrity: sha512-QZzD2XrjJDUyIZK+aR2i5DDxCJfdwiYbUKu9GzkCUJpL78uSelAHAPy7m0GuPMVtF/Uo+OKv97W3P9nuWZangQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@1.9.2': - resolution: {integrity: sha512-BlfULKijNaMigQ9GH9fqJVt+3JTDOSiZeWOQtG/1S1sa8Lp046JHG3wRJVOvekTPL9q/CNFW1NVG8J0JN+L1OA==} + '@biomejs/cli-darwin-x64@1.9.3': + resolution: {integrity: sha512-vSCoIBJE0BN3SWDFuAY/tRavpUtNoqiceJ5PrU3xDfsLcm/U6N93JSM0M9OAiC/X7mPPfejtr6Yc9vSgWlEgVw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@1.9.2': - resolution: {integrity: sha512-ZATvbUWhNxegSALUnCKWqetTZqrK72r2RsFD19OK5jXDj/7o1hzI1KzDNG78LloZxftrwr3uI9SqCLh06shSZw==} + '@biomejs/cli-linux-arm64-musl@1.9.3': + resolution: {integrity: sha512-VBzyhaqqqwP3bAkkBrhVq50i3Uj9+RWuj+pYmXrMDgjS5+SKYGE56BwNw4l8hR3SmYbLSbEo15GcV043CDSk+Q==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@1.9.2': - resolution: {integrity: sha512-T8TJuSxuBDeQCQzxZu2o3OU4eyLumTofhCxxFd3+aH2AEWVMnH7Z/c3QP1lHI5RRMBP9xIJeMORqDQ5j+gVZzw==} + '@biomejs/cli-linux-arm64@1.9.3': + resolution: {integrity: sha512-vJkAimD2+sVviNTbaWOGqEBy31cW0ZB52KtpVIbkuma7PlfII3tsLhFa+cwbRAcRBkobBBhqZ06hXoZAN8NODQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@1.9.2': - resolution: {integrity: sha512-CjPM6jT1miV5pry9C7qv8YJk0FIZvZd86QRD3atvDgfgeh9WQU0k2Aoo0xUcPdTnoz0WNwRtDicHxwik63MmSg==} + '@biomejs/cli-linux-x64-musl@1.9.3': + resolution: {integrity: sha512-TJmnOG2+NOGM72mlczEsNki9UT+XAsMFAOo8J0me/N47EJ/vkLXxf481evfHLlxMejTY6IN8SdRSiPVLv6AHlA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@1.9.2': - resolution: {integrity: sha512-T0cPk3C3Jr2pVlsuQVTBqk2qPjTm8cYcTD9p/wmR9MeVqui1C/xTVfOIwd3miRODFMrJaVQ8MYSXnVIhV9jTjg==} + '@biomejs/cli-linux-x64@1.9.3': + resolution: {integrity: sha512-x220V4c+romd26Mu1ptU+EudMXVS4xmzKxPVb9mgnfYlN4Yx9vD5NZraSx/onJnd3Gh/y8iPUdU5CDZJKg9COA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@1.9.2': - resolution: {integrity: sha512-2x7gSty75bNIeD23ZRPXyox6Z/V0M71ObeJtvQBhi1fgrvPdtkEuw7/0wEHg6buNCubzOFuN9WYJm6FKoUHfhg==} + '@biomejs/cli-win32-arm64@1.9.3': + resolution: {integrity: sha512-lg/yZis2HdQGsycUvHWSzo9kOvnGgvtrYRgoCEwPBwwAL8/6crOp3+f47tPwI/LI1dZrhSji7PNsGKGHbwyAhw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@1.9.2': - resolution: {integrity: sha512-JC3XvdYcjmu1FmAehVwVV0SebLpeNTnO2ZaMdGCSOdS7f8O9Fq14T2P1gTG1Q29Q8Dt1S03hh0IdVpIZykOL8g==} + '@biomejs/cli-win32-x64@1.9.3': + resolution: {integrity: sha512-cQMy2zanBkVLpmmxXdK6YePzmZx0s5Z7KEnwmrW54rcXK3myCNbQa09SwGZ8i/8sLw0H9F3X7K4rxVNGU8/D4Q==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -979,17 +995,17 @@ packages: '@floating-ui/utils@0.2.8': resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} - '@formatjs/ecma402-abstract@2.0.0': - resolution: {integrity: sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==} + '@formatjs/ecma402-abstract@2.1.0': + resolution: {integrity: sha512-SE2V2PE03K9U/YQZ3nxEOysRkQ/CfSwLHR789Uk9N0PTiWT6I+17UTDI97zYEwC1mbnjefqmtjbL8nunjPwGjw==} '@formatjs/fast-memoize@2.2.0': resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==} - '@formatjs/icu-messageformat-parser@2.7.8': - resolution: {integrity: sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==} + '@formatjs/icu-messageformat-parser@2.7.9': + resolution: {integrity: sha512-9Z5buDRMsTbplXknvRlDmnpWhZrayNVcVvkH0+SSz8Ll4XD/7Tcn8m1IjxM3iBJSwQbxwxb7/g0Fkx3d4j2osw==} - '@formatjs/icu-skeleton-parser@1.8.2': - resolution: {integrity: sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==} + '@formatjs/icu-skeleton-parser@1.8.3': + resolution: {integrity: sha512-TsKAP013ayZFbWWR2KWy+f9QVZh0yDFTPK3yE4OqU2gnzafvmKTodRtJLVpfZmpXWJ5y7BWD1AsyT14mcbLzig==} '@formatjs/intl-localematcher@0.5.4': resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} @@ -1174,8 +1190,8 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/util@1.3.0': - resolution: {integrity: sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==} + '@jsonjoy.com/util@1.5.0': + resolution: {integrity: sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -1214,59 +1230,59 @@ packages: resolution: {integrity: sha512-PFfqpHplKa7KMdoQdj5td03uG05VK2Ng1dG0sP4pT9h0dGSX2v9txYt/AnrzPb/vAmfyBBC0NQV7VaBEX+efgQ==} engines: {node: '>=18'} - '@next/env@14.2.13': - resolution: {integrity: sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==} + '@next/env@14.2.15': + resolution: {integrity: sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==} - '@next/swc-darwin-arm64@14.2.13': - resolution: {integrity: sha512-IkAmQEa2Htq+wHACBxOsslt+jMoV3msvxCn0WFSfJSkv/scy+i/EukBKNad36grRxywaXUYJc9mxEGkeIs8Bzg==} + '@next/swc-darwin-arm64@14.2.15': + resolution: {integrity: sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.13': - resolution: {integrity: sha512-Dv1RBGs2TTjkwEnFMVL5XIfJEavnLqqwYSD6LXgTPdEy/u6FlSrLBSSfe1pcfqhFEXRAgVL3Wpjibe5wXJzWog==} + '@next/swc-darwin-x64@14.2.15': + resolution: {integrity: sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.13': - resolution: {integrity: sha512-yB1tYEFFqo4ZNWkwrJultbsw7NPAAxlPXURXioRl9SdW6aIefOLS+0TEsKrWBtbJ9moTDgU3HRILL6QBQnMevg==} + '@next/swc-linux-arm64-gnu@14.2.15': + resolution: {integrity: sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.13': - resolution: {integrity: sha512-v5jZ/FV/eHGoWhMKYrsAweQ7CWb8xsWGM/8m1mwwZQ/sutJjoFaXchwK4pX8NqwImILEvQmZWyb8pPTcP7htWg==} + '@next/swc-linux-arm64-musl@14.2.15': + resolution: {integrity: sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.13': - resolution: {integrity: sha512-aVc7m4YL7ViiRv7SOXK3RplXzOEe/qQzRA5R2vpXboHABs3w8vtFslGTz+5tKiQzWUmTmBNVW0UQdhkKRORmGA==} + '@next/swc-linux-x64-gnu@14.2.15': + resolution: {integrity: sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.13': - resolution: {integrity: sha512-4wWY7/OsSaJOOKvMsu1Teylku7vKyTuocvDLTZQq0TYv9OjiYYWt63PiE1nTuZnqQ4RPvME7Xai+9enoiN0Wrg==} + '@next/swc-linux-x64-musl@14.2.15': + resolution: {integrity: sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.13': - resolution: {integrity: sha512-uP1XkqCqV2NVH9+g2sC7qIw+w2tRbcMiXFEbMihkQ8B1+V6m28sshBwAB0SDmOe0u44ne1vFU66+gx/28RsBVQ==} + '@next/swc-win32-arm64-msvc@14.2.15': + resolution: {integrity: sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.13': - resolution: {integrity: sha512-V26ezyjPqQpDBV4lcWIh8B/QICQ4v+M5Bo9ykLN+sqeKKBxJVDpEc6biDVyluTXTC40f5IqCU0ttth7Es2ZuMw==} + '@next/swc-win32-ia32-msvc@14.2.15': + resolution: {integrity: sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.13': - resolution: {integrity: sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==} + '@next/swc-win32-x64-msvc@14.2.15': + resolution: {integrity: sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1316,6 +1332,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/instrumentation-amqplib@0.42.0': + resolution: {integrity: sha512-fiuU6OKsqHJiydHWgTRQ7MnIrJ2lEqsdgFtNIH4LbAUJl/5XmrIeoDzDnox+hfkgWK65jsleFuQDtYb5hW1koQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-connect@0.39.0': resolution: {integrity: sha512-pGBiKevLq7NNglMgqzmeKczF4XQMTOUOTkK8afRHMZMnrK3fcETyTH7lVaSozwiOM3Ws+SuEmXZT7DYrrhxGlg==} engines: {node: '>=14'} @@ -1388,6 +1410,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-lru-memoizer@0.40.0': + resolution: {integrity: sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mongodb@0.47.0': resolution: {integrity: sha512-yqyXRx2SulEURjgOQyJzhCECSh5i1uM49NUaq9TqLd6fA7g26OahyJfsr9NE38HFqGRHpi4loyrnfYGdrsoVjQ==} engines: {node: '>=14'} @@ -1497,13 +1525,13 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.47.2': - resolution: {integrity: sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==} + '@playwright/test@1.48.0': + resolution: {integrity: sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==} engines: {node: '>=18'} hasBin: true - '@polka/url@1.0.0-next.27': - resolution: {integrity: sha512-MU0SYgcrBdSVLu7Tfow3VY4z1odzlaTYRjt3WQ0z8XbjDWReuy+EALt2HdjhrwD2HPiW2GY+KTSw4HLv4C/EOA==} + '@polka/url@1.0.0-next.28': + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -1552,8 +1580,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-context-menu@2.2.1': - resolution: {integrity: sha512-wvMKKIeb3eOrkJ96s722vcidZ+2ZNfcYZWBPRHIB1VWrF+fiF851Io6LX0kmK5wTDQFKdulCCKJk2c3SBaQHvA==} + '@radix-ui/react-context-menu@2.2.2': + resolution: {integrity: sha512-99EatSTpW+hRYHt7m8wdDlLtkmTovEe8Z/hnxUPV+SKuuNL5HWNhQI4QSdjZqNSgXHay2z4M3Dym73j9p2Gx5Q==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1574,8 +1602,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.1': - resolution: {integrity: sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==} + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + 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.2': + resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1596,8 +1633,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.0': - resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==} + '@radix-ui/react-dismissable-layer@1.1.1': + resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1609,8 +1646,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.1': - resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==} + '@radix-ui/react-dropdown-menu@2.1.2': + resolution: {integrity: sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1622,8 +1659,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.1.0': - resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} + '@radix-ui/react-focus-guards@1.1.1': + resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1653,8 +1690,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-menu@2.1.1': - resolution: {integrity: sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==} + '@radix-ui/react-menu@2.1.2': + resolution: {integrity: sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1679,8 +1716,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.1': - resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} + '@radix-ui/react-portal@1.1.2': + resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1692,8 +1729,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-presence@1.1.0': - resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==} + '@radix-ui/react-presence@1.1.1': + resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1731,8 +1768,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.1.0': - resolution: {integrity: sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==} + '@radix-ui/react-scroll-area@1.2.0': + resolution: {integrity: sha512-q2jMBdsJ9zB7QG6ngQNzNwlvxLQqONyL58QbEGwuyRZZb/ARQwk3uQVbCF7GvQVOtV6EU/pDxAw3zRzJZI3rpQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1744,8 +1781,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-select@2.1.1': - resolution: {integrity: sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==} + '@radix-ui/react-select@2.1.2': + resolution: {integrity: sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1766,8 +1803,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-switch@1.1.0': - resolution: {integrity: sha512-OBzy5WAj641k0AOSpKQtreDMe+isX0MQJ1IVyF03ucdF3DunOnROVrjWs8zsXUxC3zfZ6JL9HFVCUlMghz9dJw==} + '@radix-ui/react-switch@1.1.1': + resolution: {integrity: sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1779,8 +1816,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-tabs@1.1.0': - resolution: {integrity: sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==} + '@radix-ui/react-tabs@1.1.1': + resolution: {integrity: sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1889,83 +1926,83 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.22.4': - resolution: {integrity: sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==} + '@rollup/rollup-android-arm-eabi@4.24.0': + resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.22.4': - resolution: {integrity: sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==} + '@rollup/rollup-android-arm64@4.24.0': + resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.22.4': - resolution: {integrity: sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==} + '@rollup/rollup-darwin-arm64@4.24.0': + resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.22.4': - resolution: {integrity: sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==} + '@rollup/rollup-darwin-x64@4.24.0': + resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.22.4': - resolution: {integrity: sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.22.4': - resolution: {integrity: sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==} + '@rollup/rollup-linux-arm-musleabihf@4.24.0': + resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.22.4': - resolution: {integrity: sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==} + '@rollup/rollup-linux-arm64-gnu@4.24.0': + resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.22.4': - resolution: {integrity: sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==} + '@rollup/rollup-linux-arm64-musl@4.24.0': + resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.22.4': - resolution: {integrity: sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==} + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.22.4': - resolution: {integrity: sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==} + '@rollup/rollup-linux-riscv64-gnu@4.24.0': + resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.22.4': - resolution: {integrity: sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==} + '@rollup/rollup-linux-s390x-gnu@4.24.0': + resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.22.4': - resolution: {integrity: sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==} + '@rollup/rollup-linux-x64-gnu@4.24.0': + resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.22.4': - resolution: {integrity: sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==} + '@rollup/rollup-linux-x64-musl@4.24.0': + resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.22.4': - resolution: {integrity: sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==} + '@rollup/rollup-win32-arm64-msvc@4.24.0': + resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.22.4': - resolution: {integrity: sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==} + '@rollup/rollup-win32-ia32-msvc@4.24.0': + resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.22.4': - resolution: {integrity: sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==} + '@rollup/rollup-win32-x64-msvc@4.24.0': + resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} cpu: [x64] os: [win32] @@ -1974,131 +2011,85 @@ packages: engines: {node: '>10.17.0'} hasBin: true - '@sentry-internal/browser-utils@8.31.0': - resolution: {integrity: sha512-Bq7TFMhPr1PixRGYkB/6ar9ws7sj224XzQ+hgpz6OxGEc9fQakvD8t/Nn7dp14k3FI/hcBRA6BBvpOKUUuPgGA==} + '@sentry-internal/browser-utils@8.34.0': + resolution: {integrity: sha512-4AcYOzPzD1tL5eSRQ/GpKv5enquZf4dMVUez99/Bh3va8qiJrNP55AcM7UzZ7WZLTqKygIYruJTU5Zu2SpEAPQ==} engines: {node: '>=14.18'} - '@sentry-internal/feedback@8.31.0': - resolution: {integrity: sha512-R3LcC2IaTe8lgi5AU9h0rMgyVPpaTiMSLRhRlVeQPVmAKCz8pSG/um13q37t0BsXpTaImW9yYQ71Aj6h6IrShQ==} + '@sentry-internal/feedback@8.34.0': + resolution: {integrity: sha512-aYSM2KPUs0FLPxxbJCFSwCYG70VMzlT04xepD1Y/tTlPPOja/02tSv2tyOdZbv8Uw7xslZs3/8Lhj74oYcTBxw==} engines: {node: '>=14.18'} - '@sentry-internal/replay-canvas@8.31.0': - resolution: {integrity: sha512-ConyrhWozx4HluRj0+9teN4XTC1ndXjxMdJQvDnbLFsQhCCEdwUfaZVshV1CFe9T08Bfyjruaw33yR7pDXYktw==} + '@sentry-internal/replay-canvas@8.34.0': + resolution: {integrity: sha512-x8KhZcCDpbKHqFOykYXiamX6x0LRxv6N1OJHoH+XCrMtiDBZr4Yo30d/MaS6rjmKGMtSRij30v+Uq+YWIgxUrg==} engines: {node: '>=14.18'} - '@sentry-internal/replay@8.31.0': - resolution: {integrity: sha512-r8hmFDwWxeAxpdzBCRWTKQ/QHl8QanFw8XfM0fvFes/H1d/b43Vwc/IiUnsYoMOdooIP8hJFGDKlfq+Y5uVVGA==} + '@sentry-internal/replay@8.34.0': + resolution: {integrity: sha512-EoMh9NYljNewZK1quY23YILgtNdGgrkzJ9TPsj6jXUG0LZ0Q7N7eFWd0xOEDBvFxrmI3cSXF1i4d1sBb+eyKRw==} engines: {node: '>=14.18'} '@sentry/babel-plugin-component-annotate@2.22.3': resolution: {integrity: sha512-OlHA+i+vnQHRIdry4glpiS/xTOtgjmpXOt6IBOUqynx5Jd/iK1+fj+t8CckqOx9wRacO/hru2wfW/jFq0iViLg==} engines: {node: '>= 14'} - '@sentry/babel-plugin-component-annotate@2.22.4': - resolution: {integrity: sha512-hbSq067KwmeKIEkmyzkTNJbmbtx2KRqvpiy9Q/DynI5Z46Nko/ppvgIfyFXK9DelwvEPOqZic4WXTIhO4iv3DA==} + '@sentry/babel-plugin-component-annotate@2.22.5': + resolution: {integrity: sha512-+93qwB9vTX1nj4hD8AMWowXZsZVkvmP9OwTqSh5d4kOeiJ+dZftUk4+FKeKkAX9lvY2reyHV8Gms5mo67c27RQ==} engines: {node: '>= 14'} - '@sentry/browser@8.31.0': - resolution: {integrity: sha512-LZK0uLPGB4Al+qWc1eaad+H/1SR6CY9a0V2XWpUbNAT3+VkEo0Z/78bW1kb43N0cok87hNPOe+c66SfwdxphVQ==} + '@sentry/browser@8.34.0': + resolution: {integrity: sha512-3HHG2NXxzHq1lVmDy2uRjYjGNf9NsJsTPlOC70vbQdOb+S49EdH/XMPy+J3ruIoyv6Cu0LwvA6bMOM6rHZOgNQ==} engines: {node: '>=14.18'} '@sentry/bundler-plugin-core@2.22.3': resolution: {integrity: sha512-DeoUl0WffcqZZRl5Wy9aHvX4WfZbbWt0QbJ7NJrcEViq+dRAI2FQTYECFLwdZi5Gtb3oyqZICO+P7k8wDnzsjQ==} engines: {node: '>= 14'} - '@sentry/bundler-plugin-core@2.22.4': - resolution: {integrity: sha512-25NiyV3v6mdqOXlpzbbJnq0FHdAu1uTEDr+DU8CzNLjIXlq2Sr2CFZ/mhRcR6daM8OAretJdQ34lu0yHUVeE4Q==} + '@sentry/bundler-plugin-core@2.22.5': + resolution: {integrity: sha512-nfvTthV0aNM9/MwgnCi1WjAlCtau1I4kw6+oZIDOwJRDqGNziz517mYRXSsvCUebtGxDZtPcF7hSEBMSHjpncA==} engines: {node: '>= 14'} - '@sentry/cli-darwin@2.36.1': - resolution: {integrity: sha512-JOHQjVD8Kqxm1jUKioAP5ohLOITf+Dh6+DBz4gQjCNdotsvNW5m63TKROwq2oB811p+Jzv5304ujmN4cAqW1Ww==} - engines: {node: '>=10'} - os: [darwin] - - '@sentry/cli-darwin@2.36.2': - resolution: {integrity: sha512-To64Pq+pcmecEr+gFXiqaZy8oKhyLQLXO/SVDdf16CUL2qpuahE3bO5h9kFacMxPPxOWcgc2btF+4gYa1+bQTA==} + '@sentry/cli-darwin@2.37.0': + resolution: {integrity: sha512-CsusyMvO0eCPSN7H+sKHXS1pf637PWbS4rZak/7giz/z31/6qiXmeMlcL3f9lLZKtFPJmXVFO9uprn1wbBVF8A==} engines: {node: '>=10'} os: [darwin] - '@sentry/cli-linux-arm64@2.36.1': - resolution: {integrity: sha512-R//3ZEkbzvoStr3IA7nxBZNiBYyxOljOqAhgiTnejXHmnuwDzM3TBA2n5vKPE/kBFxboEBEw0UTzTIRb1T0bgw==} + '@sentry/cli-linux-arm64@2.37.0': + resolution: {integrity: sha512-2vzUWHLZ3Ct5gpcIlfd/2Qsha+y9M8LXvbZE26VxzYrIkRoLAWcnClBv8m4XsHLMURYvz3J9QSZHMZHSO7kAzw==} engines: {node: '>=10'} cpu: [arm64] os: [linux, freebsd] - '@sentry/cli-linux-arm64@2.36.2': - resolution: {integrity: sha512-g+FFmj1oJ2iRMsfs1ORz6THOO6MiAR55K9YxdZUBvqfoHLjSMt7Jst43sbZ3O0u55hnfixSKLNzDaTGaM/jxIQ==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux, freebsd] - - '@sentry/cli-linux-arm@2.36.1': - resolution: {integrity: sha512-gvEOKN0fWL2AVqUBKHNXPRZfJNvKTs8kQhS8cQqahZGgZHiPCI4BqW45cKMq+ZTt1UUbLmt6khx5Dz7wi+0i5A==} + '@sentry/cli-linux-arm@2.37.0': + resolution: {integrity: sha512-Dz0qH4Yt+gGUgoVsqVt72oDj4VQynRF1QB1/Sr8g76Vbi+WxWZmUh0iFwivYVwWxdQGu/OQrE0tx946HToCRyA==} engines: {node: '>=10'} cpu: [arm] os: [linux, freebsd] - '@sentry/cli-linux-arm@2.36.2': - resolution: {integrity: sha512-cRSvOQK97WM0m03k/c+LVAWT042Qz887WP/2Gy64eUi/PfArwb+QZZnsu4FCygxK9jnzgLTo4+ewoJVi17xaLQ==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux, freebsd] - - '@sentry/cli-linux-i686@2.36.1': - resolution: {integrity: sha512-R7sW5Vk/HacVy2YgQoQB+PwccvFYf2CZVPSFSFm2z7MEfNe77UYHWUq+sjJ4vxWG6HDWGVmaX0fjxyDkE01JRA==} + '@sentry/cli-linux-i686@2.37.0': + resolution: {integrity: sha512-MHRLGs4t/CQE1pG+mZBQixyWL6xDZfNalCjO8GMcTTbZFm44S3XRHfYJZNVCgdtnUP7b6OHGcu1v3SWE10LcwQ==} engines: {node: '>=10'} cpu: [x86, ia32] os: [linux, freebsd] - '@sentry/cli-linux-i686@2.36.2': - resolution: {integrity: sha512-rjxTw/CMd0Q7qlOb7gWFiwn3hJIxNkhbn1bOU54xj9CZvQSCvh10l7l4Y9o8znJLl41c5kMXVq8yuYws9A7AGQ==} - engines: {node: '>=10'} - cpu: [x86, ia32] - os: [linux, freebsd] - - '@sentry/cli-linux-x64@2.36.1': - resolution: {integrity: sha512-UMr3ik8ksA7zQfbzsfwCb+ztenLnaeAbX94Gp+bzANZwPfi/vVHf2bLyqsBs4OyVt9SPlN1bbM/RTGfMjZ3JOw==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux, freebsd] - - '@sentry/cli-linux-x64@2.36.2': - resolution: {integrity: sha512-cF8IPFTlwiC7JgVvSW4rS99sxb1W1N//iANxuzqaDswUnmJLi0AJy/jES87qE5GRB6ljaPVMvH7Kq0OCp3bvPA==} + '@sentry/cli-linux-x64@2.37.0': + resolution: {integrity: sha512-k76ClefKZaDNJZU/H3mGeR8uAzAGPzDRG/A7grzKfBeyhP3JW09L7Nz9IQcSjCK+xr399qLhM2HFCaPWQ6dlMw==} engines: {node: '>=10'} cpu: [x64] os: [linux, freebsd] - '@sentry/cli-win32-i686@2.36.1': - resolution: {integrity: sha512-CflvhnvxPEs5GWQuuDtYSLqPrBaPbcSJFlBuUIb+8WNzRxvVfjgw1qzfZmkQqABqiy/YEsEekllOoMFZAvCcVA==} + '@sentry/cli-win32-i686@2.37.0': + resolution: {integrity: sha512-FFyi5RNYQQkEg4GkP2f3BJcgQn0F4fjFDMiWkjCkftNPXQG+HFUEtrGsWr6mnHPdFouwbYg3tEPUWNxAoypvTw==} engines: {node: '>=10'} cpu: [x86, ia32] os: [win32] - '@sentry/cli-win32-i686@2.36.2': - resolution: {integrity: sha512-YDH/Kcd8JAo1Bg4jtSwF8dr7FZZ8QbYLMx8q/5eenHpq6VdOgPENsTvayLW3cAjWLcm44u8Ed/gcEK0z1IxQmQ==} - engines: {node: '>=10'} - cpu: [x86, ia32] - os: [win32] - - '@sentry/cli-win32-x64@2.36.1': - resolution: {integrity: sha512-wWqht2xghcK3TGnooHZSzA3WSjdtno/xFjZLvWmw38rblGwgKMxLZnlxV6uDyS+OJ6CbfDTlCRay/0TIqA0N8g==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - - '@sentry/cli-win32-x64@2.36.2': - resolution: {integrity: sha512-Kac8WPbkFSVAJqPAVRBiW0uij9PVoXo0owf+EDeIIDLs9yxZat0d1xgyQPlUWrCGdxowMSbDvaSUz1YnE7MUmg==} + '@sentry/cli-win32-x64@2.37.0': + resolution: {integrity: sha512-nSMj4OcfQmyL+Tu/jWCJwhKCXFsCZW1MUk6wjjQlRt9SDLfgeapaMlK1ZvT1eZv5ZH6bj3qJfefwj4U8160uOA==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@sentry/cli@2.36.1': - resolution: {integrity: sha512-gzK5uQKDWKhyH5udoB5+oaUNrS//urWyaXgKvHKz4gFfl744HuKY9dpLPP2nMnf0zLGmGM6xJnMXLqILq0mtxw==} - engines: {node: '>= 10'} - hasBin: true - - '@sentry/cli@2.36.2': - resolution: {integrity: sha512-QoijP9TnO1UVNnRKtH718jlu/F9bBki6ffrOfmcjxkvLT6Q3nBMmqhYNH/AJV/RcgqLd6noWss4fbDMXZLzgIQ==} + '@sentry/cli@2.37.0': + resolution: {integrity: sha512-fM3V4gZRJR/s8lafc3O07hhOYRnvkySdPkvL/0e0XW0r+xRwqIAgQ5ECbsZO16A5weUiXVSf03ztDL1FcmbJCQ==} engines: {node: '>= 10'} hasBin: true @@ -2106,34 +2097,34 @@ packages: resolution: {integrity: sha512-YnanVlmulkjgZiVZ9BfY9k6I082n+C+LbZo52MTvx3FY6RE5iyiPMpaOh67oXEZRWcYQEGm+bKruRxLVP6RlbA==} engines: {node: '>=8'} - '@sentry/core@8.31.0': - resolution: {integrity: sha512-5zsMBOML18e5a/ZoR5XpcYF59e2kSxb6lTg13u52f/+NA27EPgxKgXim5dz6L/6+0cizgwwmFaZFGJiFc2qoAA==} + '@sentry/core@8.34.0': + resolution: {integrity: sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA==} engines: {node: '>=14.18'} - '@sentry/esbuild-plugin@2.22.4': - resolution: {integrity: sha512-/5HwkDtOru7hTZwKRYX8ucVgj4BRxhl2Iq5zsT5GqlzDKIZhpIwugwSQKChW1G7HP27/YN7XuGotgeIApF9hew==} + '@sentry/esbuild-plugin@2.22.5': + resolution: {integrity: sha512-I9fgTbydbZA5cwFflTLRRwEtkee0tvajjW3gh3Lb2ziMA4tnX1VU0iwoWMdiLHVDn3mLUB++L0puYa+QHLY6+Q==} engines: {node: '>= 14'} '@sentry/integrations@7.114.0': resolution: {integrity: sha512-BJIBWXGKeIH0ifd7goxOS29fBA8BkEgVVCahs6xIOXBjX1IRS6PmX0zYx/GP23nQTfhJiubv2XPzoYOlZZmDxg==} engines: {node: '>=8'} - '@sentry/nextjs@8.31.0': - resolution: {integrity: sha512-ad/ygwjJd9pAZLLmVRVoCM39RzbNA1O9RBHyVG5hru/NI4cQnUmWE6vjmgeHR3AGMTVYgtk7IxQ5/NuY11GQ+Q==} + '@sentry/nextjs@8.34.0': + resolution: {integrity: sha512-REHE3E21Mnm92B3BfJz3GTMsaZM8vaDJAe7RlAMDltESRECv+ELJ5qVRLgAp8Bd6w4mG8IRNINmK2UwHrAIi9g==} engines: {node: '>=14.18'} peerDependencies: next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0 - webpack: 5.94.0 + webpack: '>=5.0.0' peerDependenciesMeta: webpack: optional: true - '@sentry/node@8.31.0': - resolution: {integrity: sha512-S4UFpomNruEkBhPgAdHeFrtKfIJp3s4VbIvWIuKsft+SoA3J19a4ozCqijoKu+y6sa++osAYi4S9M7fA7nO0bg==} + '@sentry/node@8.34.0': + resolution: {integrity: sha512-Q7BPp7Y8yCcwD620xoziWSOuPi/PCIdttkczvB0BGzBRYh2s702h+qNusRijRpVNZmzmYOo9m1x7Y1O/b8/v2A==} engines: {node: '>=14.18'} - '@sentry/opentelemetry@8.31.0': - resolution: {integrity: sha512-aAbUMlyZ6EMc3IRcRcr2d5nuNevUgpXpSfhzo9pJbSEfhMe4drJEBnhyAYgPm0HeZtKomWnlXAyrjwSU8weTXg==} + '@sentry/opentelemetry@8.34.0': + resolution: {integrity: sha512-WS91L+HVKGVIzOgt0szGp+24iKOs86BZsAHGt0HWnMR4kqWP6Ak+TLvqWDCxnuzniZMxdewDGA8p5hrBAPsmsA==} engines: {node: '>=14.18'} peerDependencies: '@opentelemetry/api': ^1.9.0 @@ -2142,8 +2133,8 @@ packages: '@opentelemetry/sdk-trace-base': ^1.26.0 '@opentelemetry/semantic-conventions': ^1.27.0 - '@sentry/react@8.31.0': - resolution: {integrity: sha512-geMQNbkJMGREC1TpSWn1Yr+hGOERO13gPqh3aQBpTF0GEDXbmVwX2U/+6wqXCVICGbKujDroReRBRLqk3fmWSA==} + '@sentry/react@8.34.0': + resolution: {integrity: sha512-gIgzhj7h67C+Sdq2ul4fOSK142Gf0uV99bqHRdtIiUlXw9yjzZQY5TKTtzbOaevn7qBJ0xrRKtIRUbOBMl0clw==} engines: {node: '>=14.18'} peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x @@ -2152,20 +2143,20 @@ packages: resolution: {integrity: sha512-tsqkkyL3eJtptmPtT0m9W/bPLkU7ILY7nvwpi1hahA5jrM7ppoU0IMaQWAgTD+U3rzFH40IdXNBFb8Gnqcva4w==} engines: {node: '>=8'} - '@sentry/types@8.31.0': - resolution: {integrity: sha512-prRM/n5nlP+xQZSpdEkSR8BwwZtgsLk0NbI8eCjTMu2isVlrlggop8pVaJb7y9HmElVtDA1Q6y4u8TD2htQKFQ==} + '@sentry/types@8.34.0': + resolution: {integrity: sha512-zLRc60CzohGCo6zNsNeQ9JF3SiEeRE4aDCP9fDDdIVCOKovS+mn1rtSip0qd0Vp2fidOu0+2yY0ALCz1A3PJSQ==} engines: {node: '>=14.18'} '@sentry/utils@7.114.0': resolution: {integrity: sha512-319N90McVpupQ6vws4+tfCy/03AdtsU0MurIE4+W5cubHME08HtiEWlfacvAxX+yuKFhvdsO4K4BB/dj54ideg==} engines: {node: '>=8'} - '@sentry/utils@8.31.0': - resolution: {integrity: sha512-9W2LZ9QIHKc0HSyH/7UmTolc01Q4vX/qMSZk7i1noinlkQtnRUmTP39r1DSITjKCrDHj6zvB/J1RPDUoRcTXxQ==} + '@sentry/utils@8.34.0': + resolution: {integrity: sha512-W1KoRlFUjprlh3t86DZPFxLfM6mzjRzshVfMY7vRlJFymBelJsnJ3A1lPeBZM9nCraOSiw6GtOWu6k5BAkiGIg==} engines: {node: '>=14.18'} - '@sentry/vercel-edge@8.31.0': - resolution: {integrity: sha512-Fj7/ahkdeZKm9PhXjG7CrcAz6fNT7vXHWONPUoX+gg+CZPmzw3D9w6ZDPr++OK5PjKlG65xfbpXd7+kgjZ+98w==} + '@sentry/vercel-edge@8.34.0': + resolution: {integrity: sha512-yF6043FcVO9GqPawCJZp0psEL8iF9+5bOlAdQydCyaj2BtDgFvAeBVI19qlDeAHhqsXNfTD0JsIox2aJPNupwg==} engines: {node: '>=14.18'} '@sentry/webpack-plugin@2.22.3': @@ -2245,19 +2236,22 @@ packages: tom-select: optional: true - '@tabler/icons-react@3.17.0': - resolution: {integrity: sha512-Ndm9Htv7KpIU1PYYrzs5EMhyA3aZGcgaxUp9Q1XOxcRZ+I0X+Ub2WS5f4bkRyDdL1s0++k2T9XRgmg2pG113sw==} + '@tabler/icons-react@3.19.0': + resolution: {integrity: sha512-AqEWGI0tQWgqo6ZjMO5yJ9sYT8oXLuAM/up0hN9iENS6IdtNZryKrkNSiMgpwweNTpl8wFFG/dAZ959S91A/uQ==} peerDependencies: react: '>= 16' '@tabler/icons@3.17.0': resolution: {integrity: sha512-sCSfAQ0w93KSnSL7tS08n73CdIKpuHP8foeLMWgDKiZaCs8ZE//N3ytazCk651ZtruTtByI3b+ZDj7nRf+hHvA==} - '@tanstack/query-core@5.56.2': - resolution: {integrity: sha512-gor0RI3/R5rVV3gXfddh1MM+hgl0Z4G7tj6Xxpq6p2I03NGPaJ8dITY9Gz05zYYb/EJq9vPas/T4wn9EaDPd4Q==} + '@tabler/icons@3.19.0': + resolution: {integrity: sha512-A4WEWqpdbTfnpFEtwXqwAe9qf9sp1yRPvzppqAuwcoF0q5YInqB+JkJtSFToCyBpPVeLxJUxxkapLvt2qQgnag==} + + '@tanstack/query-core@5.59.9': + resolution: {integrity: sha512-vFGnblfJOKlOPyTR5M0ohWKb/03eGubh5KuGyzsDfc7VQ6F0nsB75kQIoLpwp3Wfj6fKv0wGoTUX8BsIfhxDfw==} - '@tanstack/react-query@5.56.2': - resolution: {integrity: sha512-SR0GzHVo6yzhN72pnRhkEFRAHMsUo5ZPzAxfTMvUxFIDVS6W9LYUp6nXW3fcHVdg0ZJl8opSH85jqahvm6DSVg==} + '@tanstack/react-query@5.59.9': + resolution: {integrity: sha512-g2cbiw/ZIIrnUaQqhGtarTAsuLdKDNLtY5HNfRHVWY9kHDj96M4qs4ygJxHc119tPQpzZe4i9W7d2Gc2Gvng2A==} peerDependencies: react: ^18 || ^19 @@ -2332,9 +2326,6 @@ packages: '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -2377,8 +2368,8 @@ packages: '@types/mysql@2.15.26': resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/node@22.6.0': - resolution: {integrity: sha512-QyR8d5bmq+eR72TwQDfujwShHMcIrWIYsaQFtXRE58MHPTEKUNxjxvl0yS0qPMds5xbSDWtp7ZpvGFtd7dfMdQ==} + '@types/node@22.7.5': + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -2401,8 +2392,8 @@ packages: '@types/react-transition-group@4.4.11': resolution: {integrity: sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==} - '@types/react@18.3.8': - resolution: {integrity: sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==} + '@types/react@18.3.11': + resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -2456,28 +2447,28 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@vitejs/plugin-react@4.3.1': - resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} + '@vitejs/plugin-react@4.3.2': + resolution: {integrity: sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitest/coverage-v8@2.1.1': - resolution: {integrity: sha512-md/A7A3c42oTT8JUHSqjP5uKTWJejzUW4jalpvs+rZ27gsURsMU8DEb+8Jf8C6Kj2gwfSHJqobDNBuoqlm0cFw==} + '@vitest/coverage-v8@2.1.2': + resolution: {integrity: sha512-b7kHrFrs2urS0cOk5N10lttI8UdJ/yP3nB4JYTREvR5o18cR99yPpK4gK8oQgI42BVv0ILWYUSYB7AXkAUDc0g==} peerDependencies: - '@vitest/browser': 2.1.1 - vitest: 2.1.1 + '@vitest/browser': 2.1.2 + vitest: 2.1.2 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@2.1.1': - resolution: {integrity: sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==} + '@vitest/expect@2.1.2': + resolution: {integrity: sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==} - '@vitest/mocker@2.1.1': - resolution: {integrity: sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==} + '@vitest/mocker@2.1.2': + resolution: {integrity: sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==} peerDependencies: - '@vitest/spy': 2.1.1 + '@vitest/spy': 2.1.2 msw: ^2.3.5 vite: ^5.0.0 peerDependenciesMeta: @@ -2486,25 +2477,25 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.1': - resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==} + '@vitest/pretty-format@2.1.2': + resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==} - '@vitest/runner@2.1.1': - resolution: {integrity: sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==} + '@vitest/runner@2.1.2': + resolution: {integrity: sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==} - '@vitest/snapshot@2.1.1': - resolution: {integrity: sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==} + '@vitest/snapshot@2.1.2': + resolution: {integrity: sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==} - '@vitest/spy@2.1.1': - resolution: {integrity: sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==} + '@vitest/spy@2.1.2': + resolution: {integrity: sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==} - '@vitest/ui@2.1.1': - resolution: {integrity: sha512-IIxo2LkQDA+1TZdPLYPclzsXukBWd5dX2CKpGqH8CCt8Wh0ZuDn4+vuQ9qlppEju6/igDGzjWF/zyorfsf+nHg==} + '@vitest/ui@2.1.2': + resolution: {integrity: sha512-92gcNzkDnmxOxyHzQrQYRsoV9Q0Aay0r4QMLnV+B+lbqlUWa8nDg9ivyLV5mMVTtGirHsYUGGh/zbIA55gBZqA==} peerDependencies: - vitest: 2.1.1 + vitest: 2.1.2 - '@vitest/utils@2.1.1': - resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} + '@vitest/utils@2.1.2': + resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==} '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -2557,6 +2548,10 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -2687,6 +2682,9 @@ packages: bare-stream@2.3.0: resolution: {integrity: sha512-pVRWciewGUeCyKEuRxwv06M079r+fRjAQjBEK2P6OYGrO43O+Z0LrPZZEjlc4mB6C2RpZ9AxJ1s7NLEtOHO6eA==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + base64id@2.0.0: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} @@ -2716,8 +2714,8 @@ packages: browserify-zlib@0.1.4: resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} - browserslist@4.23.3: - resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} + browserslist@4.24.0: + resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2727,8 +2725,11 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - bullmq@5.13.2: - resolution: {integrity: sha512-McGE8k3mrCvdUHdU0sHkTKDS1xr4pff+hbEKBY51wk5S6Za0gkuejYA620VQTo3Zz37E/NVWMgumwiXPQ3yZcA==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bullmq@5.18.0: + resolution: {integrity: sha512-j7gMwvbe/un51W/JeS2V26s1q3whFrEaH98EMjlJWaJDXGgrBTcTx/iVK6Rte63dv3ZpPBhsxol/h6oPujMe6g==} busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -2742,8 +2743,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001663: - resolution: {integrity: sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==} + caniuse-lite@1.0.30001667: + resolution: {integrity: sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -2978,8 +2979,8 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dompurify@3.1.6: - resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==} + dompurify@3.1.7: + resolution: {integrity: sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==} dotenv-cli@7.4.2: resolution: {integrity: sha512-SbUj8l61zIbzyhIbg0FwPJq6+wjbzdn9oEtozQpZ6kW2ihCcapKVZj49oCT3oPM+mgQm+itgvUQcG5szxVrZTA==} @@ -2993,13 +2994,13 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} - drizzle-orm@0.33.0: - resolution: {integrity: sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==} + drizzle-orm@0.34.1: + resolution: {integrity: sha512-t+zCwyWWt8xTqtYV4doE/xYmT7hpv1L8pQ66zddEz+3VWyedBBtctjMAp22mAJPfyWurRQXUJ1nrTtqLq+DqNA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' '@electric-sql/pglite': '>=0.1.1' - '@libsql/client': '*' + '@libsql/client': '>=0.10.0' '@neondatabase/serverless': '>=0.1' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 @@ -3094,8 +3095,8 @@ packages: ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - electron-to-chromium@1.5.25: - resolution: {integrity: sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==} + electron-to-chromium@1.5.36: + resolution: {integrity: sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3188,6 +3189,10 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -3211,8 +3216,8 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - fdir@6.3.0: - resolution: {integrity: sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==} + fdir@6.4.0: + resolution: {integrity: sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -3283,9 +3288,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -3367,8 +3369,8 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - hono@4.6.2: - resolution: {integrity: sha512-v+39817TgAhetmHUEli8O0uHDmxp2Up3DnhS4oUZXOl5IQ9np9tYtldd42e5zgdLVS0wsOoXQNZ6mx+BGmEvCA==} + hono@4.6.3: + resolution: {integrity: sha512-0LeEuBNFeSHGqZ9sNVVgZjB1V5fmhkBSB0hZrpqStSMLOWgfLy0dHOvrjbJh0H2khsjet6rbHfWTHY0kpYThKQ==} engines: {node: '>=16.9.0'} html-encoding-sniffer@4.0.0: @@ -3408,6 +3410,9 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore-by-default@1.0.1: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} @@ -3425,8 +3430,8 @@ packages: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} - import-in-the-middle@1.11.0: - resolution: {integrity: sha512-5DimNQGoe0pLUHbR9qK84iWaWjjbsxiqXnw6Qz64+azRgleqv9k2kTt5fw7QsOpmaGYtuxxursnPPsnTKEx10Q==} + import-in-the-middle@1.11.2: + resolution: {integrity: sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA==} indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} @@ -3438,8 +3443,8 @@ packages: inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} - intl-messageformat@10.5.14: - resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==} + intl-messageformat@10.6.0: + resolution: {integrity: sha512-AYKl/DY1nl75pJU8EK681JOVL40uQTNJe3yEMXKfydDFoz+5hNrM/PqjchueSMKGKCZKBVgeexqZwy3uC2B36Q==} invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -3550,8 +3555,8 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - jiti@1.21.6: - resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + jiti@2.3.3: + resolution: {integrity: sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==} hasBin: true js-cookie@3.0.5: @@ -3574,9 +3579,9 @@ packages: canvas: optional: true - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} hasBin: true json-parse-even-better-errors@2.3.1: @@ -3609,8 +3614,8 @@ packages: jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} - knip@5.30.5: - resolution: {integrity: sha512-opta1VVKAfIzhvj1iyOr/3SgSDC6jYPoUaYkvjftNqMTeURppYY5VqrAa5DOcJnIsdcAdyoIKHUFg9NRiFaM5w==} + knip@5.33.3: + resolution: {integrity: sha512-saUxedVDCa/8p3w445at66vLmYKretzYsX7+elMJ5ROWGzU+1aTRm3EmKELTaho1ue7BlwJB5BxLJROy43+LtQ==} engines: {node: '>=18.6.0'} hasBin: true peerDependencies: @@ -3687,8 +3692,8 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@3.1.1: - resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3769,8 +3774,8 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - memfs@4.12.0: - resolution: {integrity: sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA==} + memfs@4.13.0: + resolution: {integrity: sha512-dIs5KGy24fbdDhIAg0RxXpFqQp3RwL6wgSMRF9OSuphL/Uc9a4u2/SDJKPLj/zUgtOGKuHrRMrj563+IErj4Cg==} engines: {node: '>= 4.0.0'} memoize-one@6.0.0: @@ -3960,8 +3965,8 @@ packages: next: '>= 13.0.0' react: '>= 16.8.0' - next-intl@3.19.4: - resolution: {integrity: sha512-ywJZS+i+CEBYrZq5RXhoBLpjIQBzcVGWclb/CLUO6Lua+ECPM+uJVTb9WascBDT44bSaPcJT11+ZyaEuWEtlOA==} + next-intl@3.21.1: + resolution: {integrity: sha512-hQm4Wgq5i1lfOHAWmXBVl5d2/XAeddcjsrUmjotXEESzPSvW5j2t0Pr8AV8WorTILgqU748aXuenBhz5P78tdw==} peerDependencies: next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -3993,8 +3998,8 @@ packages: zod: optional: true - next@14.2.13: - resolution: {integrity: sha512-BseY9YNw8QJSwLYD7hlZzl6QVDoSFHL/URN5K64kVEVpCsSOWeyjbIGK+dZUaRViHTaMQX8aqmnn0PHBbGZezg==} + next@14.2.15: + resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -4078,8 +4083,8 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} - package-json-from-dist@1.0.0: - resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} @@ -4187,13 +4192,13 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - playwright-core@1.47.2: - resolution: {integrity: sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==} + playwright-core@1.48.0: + resolution: {integrity: sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==} engines: {node: '>=18'} hasBin: true - playwright@1.47.2: - resolution: {integrity: sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==} + playwright@1.48.0: + resolution: {integrity: sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==} engines: {node: '>=18'} hasBin: true @@ -4251,6 +4256,10 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -4344,8 +4353,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.5.7: - resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==} + react-remove-scroll@2.6.0: + resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==} engines: {node: '>=10'} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -4400,12 +4409,16 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@4.0.1: - resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==} + readdirp@4.0.2: + resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} engines: {node: '>= 14.16.0'} redaxios@0.5.1: @@ -4470,13 +4483,13 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@3.29.4: - resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} + rollup@3.29.5: + resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true - rollup@4.22.4: - resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==} + rollup@4.24.0: + resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4499,8 +4512,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.79.3: - resolution: {integrity: sha512-m7dZxh0W9EZ3cw50Me5GOuYm/tVAJAn91SUnohLRo9cXBixGUOdvmryN+dXpwR831bhoY3Zv7rEFt85PUwTmzA==} + sass@1.79.4: + resolution: {integrity: sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==} engines: {node: '>=14.0.0'} hasBin: true @@ -4747,8 +4760,8 @@ packages: uglify-js: optional: true - terser@5.33.0: - resolution: {integrity: sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==} + terser@5.34.1: + resolution: {integrity: sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==} engines: {node: '>=10'} hasBin: true @@ -4784,8 +4797,8 @@ packages: tinyexec@0.3.0: resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} - tinyglobby@0.2.6: - resolution: {integrity: sha512-NbBoFBpqfcgd1tCiO8Lkfdk+xrA7mlLR9zgvZcZWQQwU63XAfUePyd6wZBaU93Hqw347lHnwFzttAkemHzzz4g==} + tinyglobby@0.2.9: + resolution: {integrity: sha512-8or1+BGEdk1Zkkw2ii16qSS7uVrQJPre5A9o/XkWPATkk23FZh/15BKFxPnlTy6vkljZxLqYCzzBMj30ZrSvjw==} engines: {node: '>=12.0.0'} tinypool@1.0.1: @@ -4887,8 +4900,8 @@ packages: resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} engines: {node: '>=16'} - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true @@ -4927,8 +4940,8 @@ packages: unplugin@1.0.1: resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} - update-browserslist-db@1.1.0: - resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -4949,8 +4962,8 @@ packages: '@types/react': optional: true - use-intl@3.19.4: - resolution: {integrity: sha512-mMUlie/zWiKfO8erR+17dCBL8tBsMWCadqfu/3fipzxVJ8XzZV477k6EBllXR/WfbamwwDFmb6RgYKhHDxGzYw==} + use-intl@3.21.1: + resolution: {integrity: sha512-52kYgcydYkG9SX0ZZGt7W6WD2Va01hwe15bDgkXuaTdSxrF9fDu6hHTV5DxIuSmSSf/FEcBo/nodpw3ZhY31Lw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5006,8 +5019,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@2.1.1: - resolution: {integrity: sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==} + vite-node@2.1.2: + resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -5019,8 +5032,8 @@ packages: vite: optional: true - vite@5.4.6: - resolution: {integrity: sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==} + vite@5.4.8: + resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5056,15 +5069,15 @@ packages: typescript: 3.x || 4.x || 5.x vitest: '>=2.0.0' - vitest@2.1.1: - resolution: {integrity: sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==} + vitest@2.1.2: + resolution: {integrity: sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.1 - '@vitest/ui': 2.1.1 + '@vitest/browser': 2.1.2 + '@vitest/ui': 2.1.2 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -5152,12 +5165,12 @@ packages: engines: {node: '>=8'} hasBin: true - winston-transport@4.7.1: - resolution: {integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==} + winston-transport@4.8.0: + resolution: {integrity: sha512-qxSTKswC6llEMZKgCQdaWgDuMJQnhuvF5f2Nk3SNXc4byfQ+voo2mX1Px9dkNOuR8p0KAjfPG29PuYUSIb+vSA==} engines: {node: '>= 12.0.0'} - winston@3.14.2: - resolution: {integrity: sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==} + winston@3.15.0: + resolution: {integrity: sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==} engines: {node: '>= 12.0.0'} wrap-ansi@6.2.0: @@ -5287,20 +5300,25 @@ snapshots: '@babel/highlight': 7.24.7 picocolors: 1.1.0 - '@babel/compat-data@7.25.4': {} + '@babel/code-frame@7.25.7': + dependencies: + '@babel/highlight': 7.25.7 + picocolors: 1.1.0 + + '@babel/compat-data@7.25.8': {} - '@babel/core@7.25.2': + '@babel/core@7.25.8': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.6 - '@babel/helper-compilation-targets': 7.25.2 - '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) - '@babel/helpers': 7.25.6 - '@babel/parser': 7.25.6 - '@babel/template': 7.25.0 - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 + '@babel/code-frame': 7.25.7 + '@babel/generator': 7.25.7 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helpers': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/template': 7.25.7 + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 convert-source-map: 2.0.0 debug: 4.3.7(supports-color@5.5.0) gensync: 1.0.0-beta.2 @@ -5309,57 +5327,59 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.25.6': + '@babel/generator@7.25.7': dependencies: - '@babel/types': 7.25.6 + '@babel/types': 7.25.8 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 + jsesc: 3.0.2 - '@babel/helper-compilation-targets@7.25.2': + '@babel/helper-compilation-targets@7.25.7': dependencies: - '@babel/compat-data': 7.25.4 - '@babel/helper-validator-option': 7.24.8 - browserslist: 4.23.3 + '@babel/compat-data': 7.25.8 + '@babel/helper-validator-option': 7.25.7 + browserslist: 4.24.0 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-module-imports@7.24.7': + '@babel/helper-module-imports@7.25.7': dependencies: - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': + '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - '@babel/traverse': 7.25.6 + '@babel/core': 7.25.8 + '@babel/helper-module-imports': 7.25.7 + '@babel/helper-simple-access': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + '@babel/traverse': 7.25.7 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.24.8': {} + '@babel/helper-plugin-utils@7.25.7': {} - '@babel/helper-simple-access@7.24.7': + '@babel/helper-simple-access@7.25.7': dependencies: - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 transitivePeerDependencies: - supports-color - '@babel/helper-string-parser@7.24.8': {} + '@babel/helper-string-parser@7.25.7': {} '@babel/helper-validator-identifier@7.24.7': {} - '@babel/helper-validator-option@7.24.8': {} + '@babel/helper-validator-identifier@7.25.7': {} - '@babel/helpers@7.25.6': + '@babel/helper-validator-option@7.25.7': {} + + '@babel/helpers@7.25.7': dependencies: - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 + '@babel/template': 7.25.7 + '@babel/types': 7.25.8 '@babel/highlight@7.24.7': dependencies: @@ -5368,83 +5388,94 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.0 - '@babel/parser@7.25.6': + '@babel/highlight@7.25.7': + dependencies: + '@babel/helper-validator-identifier': 7.25.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.0 + + '@babel/parser@7.25.8': dependencies: - '@babel/types': 7.25.6 + '@babel/types': 7.25.8 - '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-react-jsx-self@7.25.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-react-jsx-source@7.25.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 '@babel/runtime@7.25.6': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.25.0': + '@babel/runtime@7.25.7': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + regenerator-runtime: 0.14.1 - '@babel/traverse@7.25.6': + '@babel/template@7.25.7': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.6 - '@babel/parser': 7.25.6 - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 + '@babel/code-frame': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 + + '@babel/traverse@7.25.7': + dependencies: + '@babel/code-frame': 7.25.7 + '@babel/generator': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/template': 7.25.7 + '@babel/types': 7.25.8 debug: 4.3.7(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.25.6': + '@babel/types@7.25.8': dependencies: - '@babel/helper-string-parser': 7.24.8 - '@babel/helper-validator-identifier': 7.24.7 + '@babel/helper-string-parser': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 to-fast-properties: 2.0.0 '@bcoe/v8-coverage@0.2.3': {} - '@biomejs/biome@1.9.2': + '@biomejs/biome@1.9.3': optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.2 - '@biomejs/cli-darwin-x64': 1.9.2 - '@biomejs/cli-linux-arm64': 1.9.2 - '@biomejs/cli-linux-arm64-musl': 1.9.2 - '@biomejs/cli-linux-x64': 1.9.2 - '@biomejs/cli-linux-x64-musl': 1.9.2 - '@biomejs/cli-win32-arm64': 1.9.2 - '@biomejs/cli-win32-x64': 1.9.2 - - '@biomejs/cli-darwin-arm64@1.9.2': + '@biomejs/cli-darwin-arm64': 1.9.3 + '@biomejs/cli-darwin-x64': 1.9.3 + '@biomejs/cli-linux-arm64': 1.9.3 + '@biomejs/cli-linux-arm64-musl': 1.9.3 + '@biomejs/cli-linux-x64': 1.9.3 + '@biomejs/cli-linux-x64-musl': 1.9.3 + '@biomejs/cli-win32-arm64': 1.9.3 + '@biomejs/cli-win32-x64': 1.9.3 + + '@biomejs/cli-darwin-arm64@1.9.3': optional: true - '@biomejs/cli-darwin-x64@1.9.2': + '@biomejs/cli-darwin-x64@1.9.3': optional: true - '@biomejs/cli-linux-arm64-musl@1.9.2': + '@biomejs/cli-linux-arm64-musl@1.9.3': optional: true - '@biomejs/cli-linux-arm64@1.9.2': + '@biomejs/cli-linux-arm64@1.9.3': optional: true - '@biomejs/cli-linux-x64-musl@1.9.2': + '@biomejs/cli-linux-x64-musl@1.9.3': optional: true - '@biomejs/cli-linux-x64@1.9.2': + '@biomejs/cli-linux-x64@1.9.3': optional: true - '@biomejs/cli-win32-arm64@1.9.2': + '@biomejs/cli-win32-arm64@1.9.3': optional: true - '@biomejs/cli-win32-x64@1.9.2': + '@biomejs/cli-win32-x64@1.9.3': optional: true '@bundled-es-modules/cookie@2.0.0': @@ -5475,8 +5506,8 @@ snapshots: '@emotion/babel-plugin@11.12.0': dependencies: - '@babel/helper-module-imports': 7.24.7 - '@babel/runtime': 7.25.6 + '@babel/helper-module-imports': 7.25.7 + '@babel/runtime': 7.25.7 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.2 @@ -5501,9 +5532,9 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.13.3(@types/react@18.3.8)(react@18.3.1)': + '@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 '@emotion/babel-plugin': 11.12.0 '@emotion/cache': 11.13.1 '@emotion/serialize': 1.3.2 @@ -5513,7 +5544,7 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 transitivePeerDependencies: - supports-color @@ -5697,8 +5728,9 @@ snapshots: '@floating-ui/utils@0.2.8': {} - '@formatjs/ecma402-abstract@2.0.0': + '@formatjs/ecma402-abstract@2.1.0': dependencies: + '@formatjs/fast-memoize': 2.2.0 '@formatjs/intl-localematcher': 0.5.4 tslib: 2.7.0 @@ -5706,24 +5738,24 @@ snapshots: dependencies: tslib: 2.7.0 - '@formatjs/icu-messageformat-parser@2.7.8': + '@formatjs/icu-messageformat-parser@2.7.9': dependencies: - '@formatjs/ecma402-abstract': 2.0.0 - '@formatjs/icu-skeleton-parser': 1.8.2 + '@formatjs/ecma402-abstract': 2.1.0 + '@formatjs/icu-skeleton-parser': 1.8.3 tslib: 2.7.0 - '@formatjs/icu-skeleton-parser@1.8.2': + '@formatjs/icu-skeleton-parser@1.8.3': dependencies: - '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/ecma402-abstract': 2.1.0 tslib: 2.7.0 '@formatjs/intl-localematcher@0.5.4': dependencies: tslib: 2.7.0 - '@hono/node-server@1.13.1(hono@4.6.2)': + '@hono/node-server@1.13.1(hono@4.6.3)': dependencies: - hono: 4.6.2 + hono: 4.6.3 '@hookform/resolvers@3.9.0(react-hook-form@7.53.0(react@18.3.1))': dependencies: @@ -5814,7 +5846,7 @@ snapshots: '@inquirer/figures': 1.0.6 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -5876,12 +5908,12 @@ snapshots: '@jsonjoy.com/json-pack@1.1.0(tslib@2.7.0)': dependencies: '@jsonjoy.com/base64': 1.1.2(tslib@2.7.0) - '@jsonjoy.com/util': 1.3.0(tslib@2.7.0) + '@jsonjoy.com/util': 1.5.0(tslib@2.7.0) hyperdyperid: 1.2.0 thingies: 1.21.0(tslib@2.7.0) tslib: 2.7.0 - '@jsonjoy.com/util@1.3.0(tslib@2.7.0)': + '@jsonjoy.com/util@1.5.0(tslib@2.7.0)': dependencies: tslib: 2.7.0 @@ -5912,33 +5944,33 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@next/env@14.2.13': {} + '@next/env@14.2.15': {} - '@next/swc-darwin-arm64@14.2.13': + '@next/swc-darwin-arm64@14.2.15': optional: true - '@next/swc-darwin-x64@14.2.13': + '@next/swc-darwin-x64@14.2.15': optional: true - '@next/swc-linux-arm64-gnu@14.2.13': + '@next/swc-linux-arm64-gnu@14.2.15': optional: true - '@next/swc-linux-arm64-musl@14.2.13': + '@next/swc-linux-arm64-musl@14.2.15': optional: true - '@next/swc-linux-x64-gnu@14.2.13': + '@next/swc-linux-x64-gnu@14.2.15': optional: true - '@next/swc-linux-x64-musl@14.2.13': + '@next/swc-linux-x64-musl@14.2.15': optional: true - '@next/swc-win32-arm64-msvc@14.2.13': + '@next/swc-win32-arm64-msvc@14.2.15': optional: true - '@next/swc-win32-ia32-msvc@14.2.13': + '@next/swc-win32-ia32-msvc@14.2.15': optional: true - '@next/swc-win32-x64-msvc@14.2.13': + '@next/swc-win32-x64-msvc@14.2.15': optional: true '@nodelib/fs.scandir@2.1.5': @@ -5981,6 +6013,15 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/instrumentation-amqplib@0.42.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-connect@0.39.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -6083,6 +6124,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-lru-memoizer@0.40.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mongodb@0.47.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -6160,7 +6208,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.52.1 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.11.0 + import-in-the-middle: 1.11.2 require-in-the-middle: 7.4.0 semver: 7.6.3 shimmer: 1.2.1 @@ -6172,7 +6220,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.53.0 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.11.0 + import-in-the-middle: 1.11.2 require-in-the-middle: 7.4.0 semver: 7.6.3 shimmer: 1.2.1 @@ -6223,11 +6271,11 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.47.2': + '@playwright/test@1.48.0': dependencies: - playwright: 1.47.2 + playwright: 1.48.0 - '@polka/url@1.0.0-next.27': {} + '@polka/url@1.0.0-next.28': {} '@popperjs/core@2.11.8': {} @@ -6243,429 +6291,435 @@ snapshots: '@radix-ui/primitive@1.1.0': {} - '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(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.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-context-menu@2.2.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-context-menu@2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-menu': 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-context@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-context@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-context@1.1.1(@types/react@18.3.11)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.11 + + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.7(@types/react@18.3.8)(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-direction@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-direction@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-dropdown-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dropdown-menu@2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-menu': 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.11)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-id@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-id@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-menu@2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.7(@types/react@18.3.8)(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/rect': 1.1.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-scroll-area@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-scroll-area@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.0 '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-select@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-select@2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.0 '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.7(@types/react@18.3.8)(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-slot@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-slot@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-switch@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-switch@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-tabs@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-tabs@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.8)(react@18.3.1) - '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: '@radix-ui/rect': 1.1.0 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-use-size@1.1.0(@types/react@18.3.8)(react@18.3.1)': + '@radix-ui/react-use-size@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(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.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 '@radix-ui/rect@1.1.0': {} - '@rollup/plugin-commonjs@26.0.1(rollup@3.29.4)': + '@rollup/plugin-commonjs@26.0.1(rollup@3.29.5)': dependencies: - '@rollup/pluginutils': 5.1.2(rollup@3.29.4) + '@rollup/pluginutils': 5.1.2(rollup@3.29.5) commondir: 1.0.1 estree-walker: 2.0.2 glob: 10.4.5 is-reference: 1.2.1 magic-string: 0.30.11 optionalDependencies: - rollup: 3.29.4 + rollup: 3.29.5 - '@rollup/pluginutils@5.1.2(rollup@3.29.4)': + '@rollup/pluginutils@5.1.2(rollup@3.29.5)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 3.29.4 + rollup: 3.29.5 - '@rollup/rollup-android-arm-eabi@4.22.4': + '@rollup/rollup-android-arm-eabi@4.24.0': optional: true - '@rollup/rollup-android-arm64@4.22.4': + '@rollup/rollup-android-arm64@4.24.0': optional: true - '@rollup/rollup-darwin-arm64@4.22.4': + '@rollup/rollup-darwin-arm64@4.24.0': optional: true - '@rollup/rollup-darwin-x64@4.22.4': + '@rollup/rollup-darwin-x64@4.24.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.22.4': + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.22.4': + '@rollup/rollup-linux-arm-musleabihf@4.24.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.22.4': + '@rollup/rollup-linux-arm64-gnu@4.24.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.22.4': + '@rollup/rollup-linux-arm64-musl@4.24.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.22.4': + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.22.4': + '@rollup/rollup-linux-riscv64-gnu@4.24.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.22.4': + '@rollup/rollup-linux-s390x-gnu@4.24.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.22.4': + '@rollup/rollup-linux-x64-gnu@4.24.0': optional: true - '@rollup/rollup-linux-x64-musl@4.22.4': + '@rollup/rollup-linux-x64-musl@4.24.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.22.4': + '@rollup/rollup-win32-arm64-msvc@4.24.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.22.4': + '@rollup/rollup-win32-ia32-msvc@4.24.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.22.4': + '@rollup/rollup-win32-x64-msvc@4.24.0': optional: true '@runtipi/postgres-migrations@5.3.0': @@ -6675,51 +6729,51 @@ snapshots: transitivePeerDependencies: - pg-native - '@sentry-internal/browser-utils@8.31.0': + '@sentry-internal/browser-utils@8.34.0': dependencies: - '@sentry/core': 8.31.0 - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 + '@sentry/core': 8.34.0 + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 - '@sentry-internal/feedback@8.31.0': + '@sentry-internal/feedback@8.34.0': dependencies: - '@sentry/core': 8.31.0 - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 + '@sentry/core': 8.34.0 + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 - '@sentry-internal/replay-canvas@8.31.0': + '@sentry-internal/replay-canvas@8.34.0': dependencies: - '@sentry-internal/replay': 8.31.0 - '@sentry/core': 8.31.0 - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 + '@sentry-internal/replay': 8.34.0 + '@sentry/core': 8.34.0 + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 - '@sentry-internal/replay@8.31.0': + '@sentry-internal/replay@8.34.0': dependencies: - '@sentry-internal/browser-utils': 8.31.0 - '@sentry/core': 8.31.0 - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 + '@sentry-internal/browser-utils': 8.34.0 + '@sentry/core': 8.34.0 + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 '@sentry/babel-plugin-component-annotate@2.22.3': {} - '@sentry/babel-plugin-component-annotate@2.22.4': {} + '@sentry/babel-plugin-component-annotate@2.22.5': {} - '@sentry/browser@8.31.0': + '@sentry/browser@8.34.0': dependencies: - '@sentry-internal/browser-utils': 8.31.0 - '@sentry-internal/feedback': 8.31.0 - '@sentry-internal/replay': 8.31.0 - '@sentry-internal/replay-canvas': 8.31.0 - '@sentry/core': 8.31.0 - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 + '@sentry-internal/browser-utils': 8.34.0 + '@sentry-internal/feedback': 8.34.0 + '@sentry-internal/replay': 8.34.0 + '@sentry-internal/replay-canvas': 8.34.0 + '@sentry/core': 8.34.0 + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 '@sentry/bundler-plugin-core@2.22.3': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@sentry/babel-plugin-component-annotate': 2.22.3 - '@sentry/cli': 2.36.2 + '@sentry/cli': 2.37.0 dotenv: 16.4.5 find-up: 5.0.0 glob: 9.3.5 @@ -6729,11 +6783,11 @@ snapshots: - encoding - supports-color - '@sentry/bundler-plugin-core@2.22.4': + '@sentry/bundler-plugin-core@2.22.5': dependencies: - '@babel/core': 7.25.2 - '@sentry/babel-plugin-component-annotate': 2.22.4 - '@sentry/cli': 2.36.1 + '@babel/core': 7.25.8 + '@sentry/babel-plugin-component-annotate': 2.22.5 + '@sentry/cli': 2.37.0 dotenv: 16.4.5 find-up: 5.0.0 glob: 9.3.5 @@ -6743,49 +6797,28 @@ snapshots: - encoding - supports-color - '@sentry/cli-darwin@2.36.1': - optional: true - - '@sentry/cli-darwin@2.36.2': - optional: true - - '@sentry/cli-linux-arm64@2.36.1': - optional: true - - '@sentry/cli-linux-arm64@2.36.2': - optional: true - - '@sentry/cli-linux-arm@2.36.1': + '@sentry/cli-darwin@2.37.0': optional: true - '@sentry/cli-linux-arm@2.36.2': + '@sentry/cli-linux-arm64@2.37.0': optional: true - '@sentry/cli-linux-i686@2.36.1': + '@sentry/cli-linux-arm@2.37.0': optional: true - '@sentry/cli-linux-i686@2.36.2': + '@sentry/cli-linux-i686@2.37.0': optional: true - '@sentry/cli-linux-x64@2.36.1': + '@sentry/cli-linux-x64@2.37.0': optional: true - '@sentry/cli-linux-x64@2.36.2': + '@sentry/cli-win32-i686@2.37.0': optional: true - '@sentry/cli-win32-i686@2.36.1': + '@sentry/cli-win32-x64@2.37.0': optional: true - '@sentry/cli-win32-i686@2.36.2': - optional: true - - '@sentry/cli-win32-x64@2.36.1': - optional: true - - '@sentry/cli-win32-x64@2.36.2': - optional: true - - '@sentry/cli@2.36.1': + '@sentry/cli@2.37.0': dependencies: https-proxy-agent: 5.0.1 node-fetch: 2.7.0 @@ -6793,32 +6826,13 @@ snapshots: proxy-from-env: 1.1.0 which: 2.0.2 optionalDependencies: - '@sentry/cli-darwin': 2.36.1 - '@sentry/cli-linux-arm': 2.36.1 - '@sentry/cli-linux-arm64': 2.36.1 - '@sentry/cli-linux-i686': 2.36.1 - '@sentry/cli-linux-x64': 2.36.1 - '@sentry/cli-win32-i686': 2.36.1 - '@sentry/cli-win32-x64': 2.36.1 - transitivePeerDependencies: - - encoding - - supports-color - - '@sentry/cli@2.36.2': - dependencies: - https-proxy-agent: 5.0.1 - node-fetch: 2.7.0 - progress: 2.0.3 - proxy-from-env: 1.1.0 - which: 2.0.2 - optionalDependencies: - '@sentry/cli-darwin': 2.36.2 - '@sentry/cli-linux-arm': 2.36.2 - '@sentry/cli-linux-arm64': 2.36.2 - '@sentry/cli-linux-i686': 2.36.2 - '@sentry/cli-linux-x64': 2.36.2 - '@sentry/cli-win32-i686': 2.36.2 - '@sentry/cli-win32-x64': 2.36.2 + '@sentry/cli-darwin': 2.37.0 + '@sentry/cli-linux-arm': 2.37.0 + '@sentry/cli-linux-arm64': 2.37.0 + '@sentry/cli-linux-i686': 2.37.0 + '@sentry/cli-linux-x64': 2.37.0 + '@sentry/cli-win32-i686': 2.37.0 + '@sentry/cli-win32-x64': 2.37.0 transitivePeerDependencies: - encoding - supports-color @@ -6828,14 +6842,14 @@ snapshots: '@sentry/types': 7.114.0 '@sentry/utils': 7.114.0 - '@sentry/core@8.31.0': + '@sentry/core@8.34.0': dependencies: - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 - '@sentry/esbuild-plugin@2.22.4': + '@sentry/esbuild-plugin@2.22.5': dependencies: - '@sentry/bundler-plugin-core': 2.22.4 + '@sentry/bundler-plugin-core': 2.22.5 unplugin: 1.0.1 uuid: 9.0.1 transitivePeerDependencies: @@ -6849,23 +6863,24 @@ snapshots: '@sentry/utils': 7.114.0 localforage: 1.10.0 - '@sentry/nextjs@8.31.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react@18.3.1)(webpack@5.94.0)': + '@sentry/nextjs@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1)(webpack@5.94.0)': dependencies: '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 - '@rollup/plugin-commonjs': 26.0.1(rollup@3.29.4) - '@sentry/core': 8.31.0 - '@sentry/node': 8.31.0 - '@sentry/opentelemetry': 8.31.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0) - '@sentry/react': 8.31.0(react@18.3.1) - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 - '@sentry/vercel-edge': 8.31.0 + '@rollup/plugin-commonjs': 26.0.1(rollup@3.29.5) + '@sentry-internal/browser-utils': 8.34.0 + '@sentry/core': 8.34.0 + '@sentry/node': 8.34.0 + '@sentry/opentelemetry': 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0) + '@sentry/react': 8.34.0(react@18.3.1) + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 + '@sentry/vercel-edge': 8.34.0 '@sentry/webpack-plugin': 2.22.3(webpack@5.94.0) chalk: 3.0.0 - next: 14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) resolve: 1.22.8 - rollup: 3.29.4 + rollup: 3.29.5 stacktrace-parser: 0.1.10 optionalDependencies: webpack: 5.94.0 @@ -6878,12 +6893,13 @@ snapshots: - react - supports-color - '@sentry/node@8.31.0': + '@sentry/node@8.34.0': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 1.26.0(@opentelemetry/api@1.9.0) '@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.42.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-connect': 0.39.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-dataloader': 0.12.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-express': 0.42.0(@opentelemetry/api@1.9.0) @@ -6896,6 +6912,7 @@ snapshots: '@opentelemetry/instrumentation-ioredis': 0.43.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-kafkajs': 0.3.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-koa': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.40.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-mongodb': 0.47.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-mongoose': 0.42.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-mysql': 0.41.0(@opentelemetry/api@1.9.0) @@ -6908,51 +6925,51 @@ snapshots: '@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 '@prisma/instrumentation': 5.19.1 - '@sentry/core': 8.31.0 - '@sentry/opentelemetry': 8.31.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0) - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 - import-in-the-middle: 1.11.0 + '@sentry/core': 8.34.0 + '@sentry/opentelemetry': 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0) + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 + import-in-the-middle: 1.11.2 transitivePeerDependencies: - supports-color - '@sentry/opentelemetry@8.31.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0)': + '@sentry/opentelemetry@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 - '@sentry/core': 8.31.0 - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 + '@sentry/core': 8.34.0 + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 - '@sentry/react@8.31.0(react@18.3.1)': + '@sentry/react@8.34.0(react@18.3.1)': dependencies: - '@sentry/browser': 8.31.0 - '@sentry/core': 8.31.0 - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 + '@sentry/browser': 8.34.0 + '@sentry/core': 8.34.0 + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 hoist-non-react-statics: 3.3.2 react: 18.3.1 '@sentry/types@7.114.0': {} - '@sentry/types@8.31.0': {} + '@sentry/types@8.34.0': {} '@sentry/utils@7.114.0': dependencies: '@sentry/types': 7.114.0 - '@sentry/utils@8.31.0': + '@sentry/utils@8.34.0': dependencies: - '@sentry/types': 8.31.0 + '@sentry/types': 8.34.0 - '@sentry/vercel-edge@8.31.0': + '@sentry/vercel-edge@8.34.0': dependencies: - '@sentry/core': 8.31.0 - '@sentry/types': 8.31.0 - '@sentry/utils': 8.31.0 + '@sentry/core': 8.34.0 + '@sentry/types': 8.34.0 + '@sentry/utils': 8.34.0 '@sentry/webpack-plugin@2.22.3(webpack@5.94.0)': dependencies: @@ -6985,18 +7002,20 @@ snapshots: '@tabler/icons': 3.17.0 bootstrap: 5.3.3(@popperjs/core@2.11.8) - '@tabler/icons-react@3.17.0(react@18.3.1)': + '@tabler/icons-react@3.19.0(react@18.3.1)': dependencies: - '@tabler/icons': 3.17.0 + '@tabler/icons': 3.19.0 react: 18.3.1 '@tabler/icons@3.17.0': {} - '@tanstack/query-core@5.56.2': {} + '@tabler/icons@3.19.0': {} + + '@tanstack/query-core@5.59.9': {} - '@tanstack/react-query@5.56.2(react@18.3.1)': + '@tanstack/react-query@5.59.9(react@18.3.1)': dependencies: - '@tanstack/query-core': 5.56.2 + '@tanstack/query-core': 5.59.9 react: 18.3.1 '@testing-library/dom@10.4.0': @@ -7020,14 +7039,14 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.6 '@testing-library/dom': 10.4.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-dom': 18.3.0 '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': @@ -7042,28 +7061,28 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.25.6 + '@babel/types': 7.25.8 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.25.6 + '@babel/types': 7.25.8 '@types/connect@3.4.36': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/cookie@0.4.1': {} @@ -7071,7 +7090,7 @@ snapshots: '@types/cors@2.8.17': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/debug@4.1.12': dependencies: @@ -7085,18 +7104,16 @@ snapshots: dependencies: '@types/estree': 1.0.6 - '@types/estree@1.0.5': {} - '@types/estree@1.0.6': {} '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/gunzip-maybe@1.4.2': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/hast@3.0.4': dependencies: @@ -7106,11 +7123,11 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/jsonwebtoken@9.0.7': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/lodash.clonedeep@4.5.9': dependencies: @@ -7130,13 +7147,13 @@ snapshots: '@types/mute-stream@0.0.4': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/mysql@2.15.26': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 - '@types/node@22.6.0': + '@types/node@22.7.5': dependencies: undici-types: 6.19.8 @@ -7148,13 +7165,13 @@ snapshots: '@types/pg@8.11.10': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 pg-protocol: 1.7.0 pg-types: 4.0.2 '@types/pg@8.6.1': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 pg-protocol: 1.7.0 pg-types: 2.2.0 @@ -7162,13 +7179,13 @@ snapshots: '@types/react-dom@18.3.0': dependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 '@types/react-transition-group@4.4.11': dependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - '@types/react@18.3.8': + '@types/react@18.3.11': dependencies: '@types/prop-types': 15.7.13 csstype: 3.1.3 @@ -7181,12 +7198,12 @@ snapshots: '@types/tar-fs@2.0.4': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/tar-stream': 3.1.3 '@types/tar-stream@3.1.3': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/tough-cookie@4.0.5': {} @@ -7204,7 +7221,7 @@ snapshots: '@types/web-push@3.6.3': dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 '@types/wrap-ansi@3.0.0': {} @@ -7215,18 +7232,18 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react@4.3.1(vite@5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0))': + '@vitejs/plugin-react@4.3.2(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1))': dependencies: - '@babel/core': 7.25.2 - '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) + '@babel/core': 7.25.8 + '@babel/plugin-transform-react-jsx-self': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx-source': 7.25.7(@babel/core@7.25.8) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.1.1(vitest@2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0))': + '@vitest/coverage-v8@2.1.2(vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -7240,60 +7257,60 @@ snapshots: std-env: 3.7.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0) + vitest: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.1': + '@vitest/expect@2.1.2': dependencies: - '@vitest/spy': 2.1.1 - '@vitest/utils': 2.1.1 + '@vitest/spy': 2.1.2 + '@vitest/utils': 2.1.2 chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(msw@2.4.9(typescript@5.6.2))(vite@5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0))': + '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(msw@2.4.9(typescript@5.6.3))(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1))': dependencies: - '@vitest/spy': 2.1.1 + '@vitest/spy': 2.1.2 estree-walker: 3.0.3 magic-string: 0.30.11 optionalDependencies: - msw: 2.4.9(typescript@5.6.2) - vite: 5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0) + msw: 2.4.9(typescript@5.6.3) + vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) - '@vitest/pretty-format@2.1.1': + '@vitest/pretty-format@2.1.2': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.1': + '@vitest/runner@2.1.2': dependencies: - '@vitest/utils': 2.1.1 + '@vitest/utils': 2.1.2 pathe: 1.1.2 - '@vitest/snapshot@2.1.1': + '@vitest/snapshot@2.1.2': dependencies: - '@vitest/pretty-format': 2.1.1 + '@vitest/pretty-format': 2.1.2 magic-string: 0.30.11 pathe: 1.1.2 - '@vitest/spy@2.1.1': + '@vitest/spy@2.1.2': dependencies: tinyspy: 3.0.2 - '@vitest/ui@2.1.1(vitest@2.1.1)': + '@vitest/ui@2.1.2(vitest@2.1.2)': dependencies: - '@vitest/utils': 2.1.1 + '@vitest/utils': 2.1.2 fflate: 0.8.2 flatted: 3.3.1 pathe: 1.1.2 sirv: 2.0.4 - tinyglobby: 0.2.6 + tinyglobby: 0.2.9 tinyrainbow: 1.2.0 - vitest: 2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0) + vitest: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) - '@vitest/utils@2.1.1': + '@vitest/utils@2.1.2': dependencies: - '@vitest/pretty-format': 2.1.1 - loupe: 3.1.1 + '@vitest/pretty-format': 2.1.2 + loupe: 3.1.2 tinyrainbow: 1.2.0 '@webassemblyjs/ast@1.12.1': @@ -7376,6 +7393,10 @@ snapshots: '@xtuc/long@4.2.2': {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -7479,7 +7500,7 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 cosmiconfig: 7.1.0 resolve: 1.22.8 @@ -7511,6 +7532,8 @@ snapshots: streamx: 2.20.1 optional: true + base64-js@1.5.1: {} + base64id@2.0.0: {} binary-extensions@2.3.0: {} @@ -7538,18 +7561,23 @@ snapshots: dependencies: pako: 0.2.9 - browserslist@4.23.3: + browserslist@4.24.0: dependencies: - caniuse-lite: 1.0.30001663 - electron-to-chromium: 1.5.25 + caniuse-lite: 1.0.30001667 + electron-to-chromium: 1.5.36 node-releases: 2.0.18 - update-browserslist-db: 1.1.0(browserslist@4.23.3) + update-browserslist-db: 1.1.1(browserslist@4.24.0) buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} - bullmq@5.13.2: + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bullmq@5.18.0: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -7569,7 +7597,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001663: {} + caniuse-lite@1.0.30001667: {} ccount@2.0.1: {} @@ -7578,7 +7606,7 @@ snapshots: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.1 + loupe: 3.1.2 pathval: 2.0.0 chalk@2.4.2: @@ -7621,7 +7649,7 @@ snapshots: chokidar@4.0.1: dependencies: - readdirp: 4.0.1 + readdirp: 4.0.2 chrome-trace-event@1.0.4: {} @@ -7785,10 +7813,10 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 csstype: 3.1.3 - dompurify@3.1.6: {} + dompurify@3.1.7: {} dotenv-cli@7.4.2: dependencies: @@ -7801,11 +7829,11 @@ snapshots: dotenv@16.4.5: {} - drizzle-orm@0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.8)(pg@8.13.0)(react@18.3.1): + drizzle-orm@0.34.1(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1): optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/pg': 8.11.10 - '@types/react': 18.3.8 + '@types/react': 18.3.11 pg: 8.13.0 react: 18.3.1 @@ -7828,7 +7856,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - electron-to-chromium@1.5.25: {} + electron-to-chromium@1.5.36: {} emoji-regex@8.0.0: {} @@ -7858,7 +7886,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 22.6.0 + '@types/node': 22.7.5 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -7968,6 +7996,8 @@ snapshots: dependencies: '@types/estree': 1.0.6 + event-target-shim@5.0.1: {} + events@3.3.0: {} extend@3.0.2: {} @@ -7990,7 +8020,7 @@ snapshots: dependencies: reusify: 1.0.4 - fdir@6.3.0(picomatch@4.0.2): + fdir@6.4.0(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -8040,16 +8070,14 @@ snapshots: function-bind@1.1.2: {} - geist@1.3.1(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3)): + geist@1.3.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4)): dependencies: - next: 14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} - get-func-name@2.0.2: {} - get-nonce@1.0.1: {} glob-parent@5.1.2: @@ -8064,7 +8092,7 @@ snapshots: jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 - package-json-from-dist: 1.0.0 + package-json-from-dist: 1.0.1 path-scurry: 1.11.1 glob@9.3.5: @@ -8182,7 +8210,7 @@ snapshots: dependencies: react-is: 16.13.1 - hono@4.6.2: {} + hono@4.6.3: {} html-encoding-sniffer@4.0.0: dependencies: @@ -8223,6 +8251,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore-by-default@1.0.1: {} ignore@5.3.2: {} @@ -8236,7 +8266,7 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@1.11.0: + import-in-the-middle@1.11.2: dependencies: acorn: 8.12.1 acorn-import-attributes: 1.9.5(acorn@8.12.1) @@ -8249,11 +8279,11 @@ snapshots: inline-style-parser@0.2.4: {} - intl-messageformat@10.5.14: + intl-messageformat@10.6.0: dependencies: - '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/ecma402-abstract': 2.1.0 '@formatjs/fast-memoize': 2.2.0 - '@formatjs/icu-messageformat-parser': 2.7.8 + '@formatjs/icu-messageformat-parser': 2.7.9 tslib: 2.7.0 invariant@2.2.4: @@ -8360,11 +8390,11 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 merge-stream: 2.0.0 supports-color: 8.1.1 - jiti@1.21.6: {} + jiti@2.3.3: {} js-cookie@3.0.5: {} @@ -8402,7 +8432,7 @@ snapshots: - supports-color - utf-8-validate - jsesc@2.5.2: {} + jsesc@3.0.2: {} json-parse-even-better-errors@2.3.1: {} @@ -8451,15 +8481,15 @@ snapshots: jwa: 2.0.0 safe-buffer: 5.2.1 - knip@5.30.5(@types/node@22.6.0)(typescript@5.6.2): + knip@5.33.3(@types/node@22.7.5)(typescript@5.6.3): dependencies: '@nodelib/fs.walk': 1.2.8 '@snyk/github-codeowners': 1.1.0 - '@types/node': 22.6.0 + '@types/node': 22.7.5 easy-table: 1.2.0 enhanced-resolve: 5.17.1 fast-glob: 3.3.2 - jiti: 1.21.6 + jiti: 2.3.3 js-yaml: 4.1.0 minimist: 1.2.8 picocolors: 1.1.0 @@ -8468,7 +8498,7 @@ snapshots: smol-toml: 1.3.0 strip-json-comments: 5.0.1 summary: 2.1.0 - typescript: 5.6.2 + typescript: 5.6.3 zod: 3.23.8 zod-validation-error: 3.4.0(zod@3.23.8) @@ -8531,9 +8561,7 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@3.1.1: - dependencies: - get-func-name: 2.0.2 + loupe@3.1.2: {} lru-cache@10.4.3: {} @@ -8555,8 +8583,8 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 source-map-js: 1.2.1 make-dir@4.0.0: @@ -8722,10 +8750,10 @@ snapshots: dependencies: '@types/mdast': 4.0.4 - memfs@4.12.0: + memfs@4.13.0: dependencies: '@jsonjoy.com/json-pack': 1.1.0(tslib@2.7.0) - '@jsonjoy.com/util': 1.3.0(tslib@2.7.0) + '@jsonjoy.com/util': 1.5.0(tslib@2.7.0) tree-dump: 1.0.2(tslib@2.7.0) tslib: 2.7.0 @@ -8983,7 +9011,7 @@ snapshots: optionalDependencies: msgpackr-extract: 3.0.3 - msw@2.4.9(typescript@5.6.2): + msw@2.4.9(typescript@5.6.3): dependencies: '@bundled-es-modules/cookie': 2.0.0 '@bundled-es-modules/statuses': 1.0.1 @@ -9003,7 +9031,7 @@ snapshots: type-fest: 4.26.1 yargs: 17.7.2 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 mute-stream@1.0.0: {} @@ -9013,57 +9041,57 @@ snapshots: neo-async@2.6.2: {} - next-client-cookies@1.1.1(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react@18.3.1): + next-client-cookies@1.1.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1): dependencies: js-cookie: 3.0.5 - next: 14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) react: 18.3.1 - next-intl@3.19.4(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react@18.3.1): + next-intl@3.21.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) react: 18.3.1 - use-intl: 3.19.4(react@18.3.1) + use-intl: 3.21.1(react@18.3.1) - next-router-mock@0.9.13(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react@18.3.1): + next-router-mock@0.9.13(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1): dependencies: - next: 14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) react: 18.3.1 - next-safe-action@7.9.3(next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8): + next-safe-action@7.9.3(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8): dependencies: - next: 14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: zod: 3.23.8 - next@14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3): + next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4): dependencies: - '@next/env': 14.2.13 + '@next/env': 14.2.15 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001663 + caniuse-lite: 1.0.30001667 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.25.8)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.13 - '@next/swc-darwin-x64': 14.2.13 - '@next/swc-linux-arm64-gnu': 14.2.13 - '@next/swc-linux-arm64-musl': 14.2.13 - '@next/swc-linux-x64-gnu': 14.2.13 - '@next/swc-linux-x64-musl': 14.2.13 - '@next/swc-win32-arm64-msvc': 14.2.13 - '@next/swc-win32-ia32-msvc': 14.2.13 - '@next/swc-win32-x64-msvc': 14.2.13 + '@next/swc-darwin-arm64': 14.2.15 + '@next/swc-darwin-x64': 14.2.15 + '@next/swc-linux-arm64-gnu': 14.2.15 + '@next/swc-linux-arm64-musl': 14.2.15 + '@next/swc-linux-x64-gnu': 14.2.15 + '@next/swc-linux-x64-musl': 14.2.15 + '@next/swc-win32-arm64-msvc': 14.2.15 + '@next/swc-win32-ia32-msvc': 14.2.15 + '@next/swc-win32-x64-msvc': 14.2.15 '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.47.2 - sass: 1.79.3 + '@playwright/test': 1.48.0 + sass: 1.79.4 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -9128,7 +9156,7 @@ snapshots: dependencies: aggregate-error: 3.1.0 - package-json-from-dist@1.0.0: {} + package-json-from-dist@1.0.1: {} pako@0.2.9: {} @@ -9149,7 +9177,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.25.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -9238,11 +9266,11 @@ snapshots: picomatch@4.0.2: {} - playwright-core@1.47.2: {} + playwright-core@1.48.0: {} - playwright@1.47.2: + playwright@1.48.0: dependencies: - playwright-core: 1.47.2 + playwright-core: 1.48.0 optionalDependencies: fsevents: 2.3.2 @@ -9292,6 +9320,8 @@ snapshots: process-nextick-args@2.0.1: {} + process@0.11.10: {} + progress@2.0.3: {} prop-types@15.8.1: @@ -9362,10 +9392,10 @@ snapshots: react-is@17.0.2: {} - react-markdown@9.0.1(@types/react@18.3.8)(react@18.3.1): + react-markdown@9.0.1(@types/react@18.3.11)(react@18.3.1): dependencies: '@types/hast': 3.0.4 - '@types/react': 18.3.8 + '@types/react': 18.3.11 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.0 html-url-attributes: 3.0.0 @@ -9381,30 +9411,30 @@ snapshots: react-refresh@0.14.2: {} - react-remove-scroll-bar@2.3.6(@types/react@18.3.8)(react@18.3.1): + react-remove-scroll-bar@2.3.6(@types/react@18.3.11)(react@18.3.1): dependencies: react: 18.3.1 - react-style-singleton: 2.2.1(@types/react@18.3.8)(react@18.3.1) + react-style-singleton: 2.2.1(@types/react@18.3.11)(react@18.3.1) tslib: 2.7.0 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - react-remove-scroll@2.5.7(@types/react@18.3.8)(react@18.3.1): + react-remove-scroll@2.6.0(@types/react@18.3.11)(react@18.3.1): dependencies: react: 18.3.1 - react-remove-scroll-bar: 2.3.6(@types/react@18.3.8)(react@18.3.1) - react-style-singleton: 2.2.1(@types/react@18.3.8)(react@18.3.1) + react-remove-scroll-bar: 2.3.6(@types/react@18.3.11)(react@18.3.1) + react-style-singleton: 2.2.1(@types/react@18.3.11)(react@18.3.1) tslib: 2.7.0 - use-callback-ref: 1.3.2(@types/react@18.3.8)(react@18.3.1) - use-sidecar: 1.1.2(@types/react@18.3.8)(react@18.3.1) + use-callback-ref: 1.3.2(@types/react@18.3.11)(react@18.3.1) + use-sidecar: 1.1.2(@types/react@18.3.11)(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - react-select@5.8.0(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-select@5.8.0(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 '@emotion/cache': 11.13.1 - '@emotion/react': 11.13.3(@types/react@18.3.8)(react@18.3.1) + '@emotion/react': 11.13.3(@types/react@18.3.11)(react@18.3.1) '@floating-ui/dom': 1.6.11 '@types/react-transition-group': 4.4.11 memoize-one: 6.0.0 @@ -9412,25 +9442,25 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - use-isomorphic-layout-effect: 1.1.2(@types/react@18.3.8)(react@18.3.1) + use-isomorphic-layout-effect: 1.1.2(@types/react@18.3.11)(react@18.3.1) transitivePeerDependencies: - '@types/react' - supports-color - react-style-singleton@2.2.1(@types/react@18.3.8)(react@18.3.1): + react-style-singleton@2.2.1(@types/react@18.3.11)(react@18.3.1): dependencies: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 tslib: 2.7.0 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - react-timezone-select@3.2.8(react-dom@18.3.1(react@18.3.1))(react-select@5.8.0(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + react-timezone-select@3.2.8(react-dom@18.3.1(react@18.3.1))(react-select@5.8.0(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-select: 5.8.0(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-select: 5.8.0(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) spacetime: 7.6.1 timezone-soft: 1.5.2 @@ -9443,7 +9473,7 @@ snapshots: react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -9470,11 +9500,19 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.5.2: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 - readdirp@4.0.1: {} + readdirp@4.0.2: {} redaxios@0.5.1: {} @@ -9561,30 +9599,30 @@ snapshots: reusify@1.0.4: {} - rollup@3.29.4: + rollup@3.29.5: optionalDependencies: fsevents: 2.3.3 - rollup@4.22.4: + rollup@4.24.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.22.4 - '@rollup/rollup-android-arm64': 4.22.4 - '@rollup/rollup-darwin-arm64': 4.22.4 - '@rollup/rollup-darwin-x64': 4.22.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.22.4 - '@rollup/rollup-linux-arm-musleabihf': 4.22.4 - '@rollup/rollup-linux-arm64-gnu': 4.22.4 - '@rollup/rollup-linux-arm64-musl': 4.22.4 - '@rollup/rollup-linux-powerpc64le-gnu': 4.22.4 - '@rollup/rollup-linux-riscv64-gnu': 4.22.4 - '@rollup/rollup-linux-s390x-gnu': 4.22.4 - '@rollup/rollup-linux-x64-gnu': 4.22.4 - '@rollup/rollup-linux-x64-musl': 4.22.4 - '@rollup/rollup-win32-arm64-msvc': 4.22.4 - '@rollup/rollup-win32-ia32-msvc': 4.22.4 - '@rollup/rollup-win32-x64-msvc': 4.22.4 + '@rollup/rollup-android-arm-eabi': 4.24.0 + '@rollup/rollup-android-arm64': 4.24.0 + '@rollup/rollup-darwin-arm64': 4.24.0 + '@rollup/rollup-darwin-x64': 4.24.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 + '@rollup/rollup-linux-arm-musleabihf': 4.24.0 + '@rollup/rollup-linux-arm64-gnu': 4.24.0 + '@rollup/rollup-linux-arm64-musl': 4.24.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 + '@rollup/rollup-linux-riscv64-gnu': 4.24.0 + '@rollup/rollup-linux-s390x-gnu': 4.24.0 + '@rollup/rollup-linux-x64-gnu': 4.24.0 + '@rollup/rollup-linux-x64-musl': 4.24.0 + '@rollup/rollup-win32-arm64-msvc': 4.24.0 + '@rollup/rollup-win32-ia32-msvc': 4.24.0 + '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 rrweb-cssom@0.7.1: {} @@ -9601,7 +9639,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.79.3: + sass@1.79.4: dependencies: chokidar: 4.0.1 immutable: 4.3.7 @@ -9677,7 +9715,7 @@ snapshots: sirv@2.0.4: dependencies: - '@polka/url': 1.0.0-next.27 + '@polka/url': 1.0.0-next.28 mrmime: 2.0.0 totalist: 3.0.1 @@ -9814,12 +9852,12 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.3.1): + styled-jsx@5.1.1(@babel/core@7.25.8)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 optionalDependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 stylis@4.2.0: {} @@ -9865,10 +9903,10 @@ snapshots: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.33.0 + terser: 5.34.1 webpack: 5.94.0 - terser@5.33.0: + terser@5.34.1: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.12.1 @@ -9904,9 +9942,9 @@ snapshots: tinyexec@0.3.0: {} - tinyglobby@0.2.6: + tinyglobby@0.2.9: dependencies: - fdir: 6.3.0(picomatch@4.0.2) + fdir: 6.4.0(picomatch@4.0.2) picomatch: 4.0.2 tinypool@1.0.1: {} @@ -9958,13 +9996,13 @@ snapshots: trough@2.2.0: {} - ts-essentials@10.0.2(typescript@5.6.2): + ts-essentials@10.0.2(typescript@5.6.3): optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 - tsconfck@3.1.3(typescript@5.6.2): + tsconfck@3.1.3(typescript@5.6.3): optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 tslib@2.7.0: {} @@ -9974,7 +10012,7 @@ snapshots: type-fest@4.26.1: {} - typescript@5.6.2: {} + typescript@5.6.3: {} undefsafe@2.0.5: {} @@ -10024,9 +10062,9 @@ snapshots: webpack-sources: 3.2.3 webpack-virtual-modules: 0.5.0 - update-browserslist-db@1.1.0(browserslist@4.23.3): + update-browserslist-db@1.1.1(browserslist@4.24.0): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.0 escalade: 3.2.0 picocolors: 1.1.0 @@ -10039,32 +10077,32 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - use-callback-ref@1.3.2(@types/react@18.3.8)(react@18.3.1): + use-callback-ref@1.3.2(@types/react@18.3.11)(react@18.3.1): dependencies: react: 18.3.1 tslib: 2.7.0 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - use-intl@3.19.4(react@18.3.1): + use-intl@3.21.1(react@18.3.1): dependencies: '@formatjs/fast-memoize': 2.2.0 - intl-messageformat: 10.5.14 + intl-messageformat: 10.6.0 react: 18.3.1 - use-isomorphic-layout-effect@1.1.2(@types/react@18.3.8)(react@18.3.1): + use-isomorphic-layout-effect@1.1.2(@types/react@18.3.11)(react@18.3.1): dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 - use-sidecar@1.1.2(@types/react@18.3.8)(react@18.3.1): + use-sidecar@1.1.2(@types/react@18.3.11)(react@18.3.1): dependencies: detect-node-es: 1.1.0 react: 18.3.1 tslib: 2.7.0 optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 use-sync-external-store@1.2.2(react@18.3.1): dependencies: @@ -10095,12 +10133,12 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@2.1.1(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0): + vite-node@2.1.2(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 - vite: 5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) transitivePeerDependencies: - '@types/node' - less @@ -10112,43 +10150,43 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@4.3.2(typescript@5.6.2)(vite@5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0)): + vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)): dependencies: debug: 4.3.7(supports-color@5.5.0) globrex: 0.1.2 - tsconfck: 3.1.3(typescript@5.6.2) + tsconfck: 3.1.3(typescript@5.6.3) optionalDependencies: - vite: 5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) transitivePeerDependencies: - supports-color - typescript - vite@5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0): + vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 - rollup: 4.22.4 + rollup: 4.24.0 optionalDependencies: - '@types/node': 22.6.0 + '@types/node': 22.7.5 fsevents: 2.3.3 - sass: 1.79.3 - terser: 5.33.0 + sass: 1.79.4 + terser: 5.34.1 - vitest-mock-extended@2.0.2(typescript@5.6.2)(vitest@2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0)): + vitest-mock-extended@2.0.2(typescript@5.6.3)(vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1)): dependencies: - ts-essentials: 10.0.2(typescript@5.6.2) - typescript: 5.6.2 - vitest: 2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0) + ts-essentials: 10.0.2(typescript@5.6.3) + typescript: 5.6.3 + vitest: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) - vitest@2.1.1(@types/node@22.6.0)(@vitest/ui@2.1.1)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.2))(sass@1.79.3)(terser@5.33.0): + vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1): dependencies: - '@vitest/expect': 2.1.1 - '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(msw@2.4.9(typescript@5.6.2))(vite@5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0)) - '@vitest/pretty-format': 2.1.1 - '@vitest/runner': 2.1.1 - '@vitest/snapshot': 2.1.1 - '@vitest/spy': 2.1.1 - '@vitest/utils': 2.1.1 + '@vitest/expect': 2.1.2 + '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(msw@2.4.9(typescript@5.6.3))(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)) + '@vitest/pretty-format': 2.1.2 + '@vitest/runner': 2.1.2 + '@vitest/snapshot': 2.1.2 + '@vitest/spy': 2.1.2 + '@vitest/utils': 2.1.2 chai: 5.1.1 debug: 4.3.7(supports-color@5.5.0) magic-string: 0.30.11 @@ -10158,12 +10196,12 @@ snapshots: tinyexec: 0.3.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.6(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0) - vite-node: 2.1.1(@types/node@22.6.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) + vite-node: 2.1.2(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.6.0 - '@vitest/ui': 2.1.1(vitest@2.1.1) + '@types/node': 22.7.5 + '@vitest/ui': 2.1.2(vitest@2.1.2) jsdom: 25.0.1 transitivePeerDependencies: - less @@ -10220,7 +10258,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.12.1 acorn: 8.12.1 acorn-import-attributes: 1.9.5(acorn@8.12.1) - browserslist: 4.23.3 + browserslist: 4.24.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.17.1 es-module-lexer: 1.5.4 @@ -10267,13 +10305,13 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - winston-transport@4.7.1: + winston-transport@4.8.0: dependencies: logform: 2.6.1 - readable-stream: 3.6.2 + readable-stream: 4.5.2 triple-beam: 1.4.1 - winston@3.14.2: + winston@3.15.0: dependencies: '@colors/colors': 1.6.0 '@dabh/diagnostics': 2.0.3 @@ -10285,7 +10323,7 @@ snapshots: safe-stable-stringify: 2.5.0 stack-trace: 0.0.10 triple-beam: 1.4.1 - winston-transport: 4.7.1 + winston-transport: 4.8.0 wrap-ansi@6.2.0: dependencies: @@ -10349,11 +10387,11 @@ snapshots: zod@3.23.8: {} - zustand@4.5.5(@types/react@18.3.8)(react@18.3.1): + zustand@4.5.5(@types/react@18.3.11)(react@18.3.1): dependencies: use-sync-external-store: 1.2.2(react@18.3.1) optionalDependencies: - '@types/react': 18.3.8 + '@types/react': 18.3.11 react: 18.3.1 zwitch@2.0.4: {} From eff54f4cbad667767729bcdba38a75598e79ca4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:24:47 +0000 Subject: [PATCH 003/241] chore(deps): bump codex-/return-dispatch from 1 to 2 Bumps [codex-/return-dispatch](https://github.com/codex-/return-dispatch) from 1 to 2. - [Release notes](https://github.com/codex-/return-dispatch/releases) - [Changelog](https://github.com/Codex-/return-dispatch/blob/main/.release-it.json) - [Commits](https://github.com/codex-/return-dispatch/compare/v1...v2) --- updated-dependencies: - dependency-name: codex-/return-dispatch dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/alpha-release.yml | 2 +- .github/workflows/beta-release.yml | 4 ++-- .github/workflows/nightly-release.yml | 2 +- .github/workflows/release.yml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml index d7bf26e5bc..a3d8be6cc2 100644 --- a/.github/workflows/alpha-release.yml +++ b/.github/workflows/alpha-release.yml @@ -72,7 +72,7 @@ jobs: needs: create-tag steps: - name: Dispatch an action and get the run ID - uses: codex-/return-dispatch@v1 + uses: codex-/return-dispatch@v2 id: return_dispatch with: token: ${{ secrets.PAT_CLI }} diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 643886a648..7b05d91622 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -72,7 +72,7 @@ jobs: needs: create-tag steps: - name: Dispatch an action and get the run ID - uses: codex-/return-dispatch@v1 + uses: codex-/return-dispatch@v2 id: return_dispatch with: token: ${{ secrets.PAT_CLI }} @@ -124,7 +124,7 @@ jobs: needs: create-tag steps: - name: Dispatch the release action - uses: codex-/return-dispatch@v1 + uses: codex-/return-dispatch@v2 with: token: ${{ secrets.PAT_CLI }} ref: main diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index f829ee8362..154f9e85c8 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Dispatch an action and get the run ID - uses: codex-/return-dispatch@v1 + uses: codex-/return-dispatch@v2 id: return_dispatch with: token: ${{ secrets.PAT_CLI }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f27bb6b301..d8ee7e32c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,7 +68,7 @@ jobs: needs: create-tag steps: - name: Dispatch an action and get the run ID - uses: codex-/return-dispatch@v1 + uses: codex-/return-dispatch@v2 id: return_dispatch with: token: ${{ secrets.PAT_CLI }} @@ -120,7 +120,7 @@ jobs: needs: create-tag steps: - name: Dispatch the release action - uses: codex-/return-dispatch@v1 + uses: codex-/return-dispatch@v2 with: token: ${{ secrets.PAT_CLI }} ref: main From 7a236929c7596f3c2539c0adfa8885aaa24dfe12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:49:39 +0000 Subject: [PATCH 004/241] chore(deps): bump hono from 4.6.3 to 4.6.5 Bumps [hono](https://github.com/honojs/hono) from 4.6.3 to 4.6.5. - [Release notes](https://github.com/honojs/hono/releases) - [Commits](https://github.com/honojs/hono/compare/v4.6.3...v4.6.5) --- updated-dependencies: - dependency-name: hono dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- packages/worker/package.json | 2 +- pnpm-lock.yaml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/worker/package.json b/packages/worker/package.json index 15f48da529..908dfe1bf5 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -38,7 +38,7 @@ "bullmq": "^5.18.0", "dotenv": "^16.4.5", "drizzle-orm": "^0.34.1", - "hono": "^4.6.3", + "hono": "^4.6.5", "inversify": "^6.0.2", "reflect-metadata": "^0.2.2", "socket.io": "^4.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b3fe2447e..e580181bb7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -372,7 +372,7 @@ importers: dependencies: '@hono/node-server': specifier: ^1.13.1 - version: 1.13.1(hono@4.6.3) + version: 1.13.1(hono@4.6.5) '@runtipi/cache': specifier: workspace:^ version: link:../cache @@ -401,8 +401,8 @@ importers: specifier: ^0.34.1 version: 0.34.1(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) hono: - specifier: ^4.6.3 - version: 4.6.3 + specifier: ^4.6.5 + version: 4.6.5 inversify: specifier: ^6.0.2 version: 6.0.2 @@ -3369,8 +3369,8 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - hono@4.6.3: - resolution: {integrity: sha512-0LeEuBNFeSHGqZ9sNVVgZjB1V5fmhkBSB0hZrpqStSMLOWgfLy0dHOvrjbJh0H2khsjet6rbHfWTHY0kpYThKQ==} + hono@4.6.5: + resolution: {integrity: sha512-qsmN3V5fgtwdKARGLgwwHvcdLKursMd+YOt69eGpl1dUCJb8mCd7hZfyZnBYjxCegBG7qkJRQRUy2oO25yHcyQ==} engines: {node: '>=16.9.0'} html-encoding-sniffer@4.0.0: @@ -5753,9 +5753,9 @@ snapshots: dependencies: tslib: 2.7.0 - '@hono/node-server@1.13.1(hono@4.6.3)': + '@hono/node-server@1.13.1(hono@4.6.5)': dependencies: - hono: 4.6.3 + hono: 4.6.5 '@hookform/resolvers@3.9.0(react-hook-form@7.53.0(react@18.3.1))': dependencies: @@ -8210,7 +8210,7 @@ snapshots: dependencies: react-is: 16.13.1 - hono@4.6.3: {} + hono@4.6.5: {} html-encoding-sniffer@4.0.0: dependencies: From a50fc84776c85b9df8ee00fbb2f4554ba88d6402 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:53:16 +0200 Subject: [PATCH 005/241] New translations en.json (Turkish) --- src/client/messages/tr-TR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/messages/tr-TR.json b/src/client/messages/tr-TR.json index a1f740cc79..4823c1debe 100644 --- a/src/client/messages/tr-TR.json +++ b/src/client/messages/tr-TR.json @@ -202,7 +202,7 @@ "DASHBOARD_CPU_TITLE": "CPU yükü", "DASHBOARD_DISK_SPACE_SUBTITLE": "{total} GB'den kullanılan", "DASHBOARD_DISK_SPACE_TITLE": "Disk alanı", - "DASHBOARD_MEMORY_TITLE": "Kullanılan Bellek", + "DASHBOARD_MEMORY_TITLE": "Kullanılan bellek", "DASHBOARD_TITLE": "Gösterge Paneli", "DASHBOARD_IP_WARNING_TITLE": "Güvensiz yapılandırma", "DASHBOARD_IP_WARNING": "Uyarı, risk altında olabilirsiniz! Görünüşe göre instance'ınıza genel bir IP adresi üzerinden erişiyorsunuz. Bu, gösterge panelinizi ve kurduğunuz tüm uygulamaları saldırganlara karşı savunmasız hale getirir.", From 89b0a81789371e9d8f134611b3d64c54b60be83d Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Sat, 28 Sep 2024 17:14:35 +0200 Subject: [PATCH 006/241] New translations en.json (Chinese Traditional) --- src/client/messages/zh-TW.json | 176 ++++++++++++++++----------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/src/client/messages/zh-TW.json b/src/client/messages/zh-TW.json index eb706fdf7f..91172db6c2 100644 --- a/src/client/messages/zh-TW.json +++ b/src/client/messages/zh-TW.json @@ -12,7 +12,7 @@ "APP_CATEGORY_AI": "AI", "APP_CATEGORY_AUTOMATION": "自動化", "APP_CATEGORY_BOOKS": "書籍", - "APP_CATEGORY_DATA": "數據", + "APP_CATEGORY_DATA": "資料", "APP_CATEGORY_DEVELOPMENT": "開發", "APP_CATEGORY_FEATURED": "精選", "APP_CATEGORY_FINANCE": "金融", @@ -25,10 +25,10 @@ "APP_CATEGORY_SOCIAL": "社交", "APP_CATEGORY_UTILITIES": "實用工具", "APP_DETAILS_AUTHOR": "作者", - "APP_DETAILS_BASE_INFO": "基本信息", + "APP_DETAILS_BASE_INFO": "基本資訊", "APP_DETAILS_CATEGORIES_TITLE": "類別", "APP_DETAILS_CHOOSE_OPEN_METHOD": "選擇開啟方法", - "APP_DETAILS_DEPRECATED_ALERT_SUBTITLE": "A breaking change in this app prevents it from being updated automatically. You can still use this version and update it manually, but it is recommended to switch to a newer version and migrate your data. You can find an updated version in the app store under the same name.", + "APP_DETAILS_DEPRECATED_ALERT_SUBTITLE": "此應用程式的重大變更使其無法自動更新。你仍然可以使用這個版本並手動更新它,但建議你切換到較新的版本並遷移你的資料。你可以在同名應用程式商店中找到最新版本。", "APP_DETAILS_DEPRECATED_ALERT_TITLE": "這個應用程式已被棄用", "APP_DETAILS_DESCRIPTION": "描述", "APP_DETAILS_LINK": "連結", @@ -45,34 +45,34 @@ "APP_ERROR_APP_FAILED_TO_RESTART": "重新啟用應用程式 {id} 失敗,請參閱日誌以瞭解更多詳細資訊", "APP_ERROR_APP_FAILED_TO_UNINSTALL": "解除安裝應用程式 {id} 失敗,請參閱日誌以瞭解更多詳細資訊", "APP_ERROR_APP_FAILED_TO_UPDATE": "更新應用程式 {id} 失敗,請參閱日誌以瞭解更多詳細資訊", - "APP_ERROR_APP_FORCE_EXPOSED": "應用程序 {id} 僅適用於公開域名", - "APP_ERROR_APP_NOT_EXPOSABLE": "應用 {id} 不可公開", - "APP_ERROR_APP_NOT_FOUND": "未找到應用 {id}", - "APP_ERROR_ARCHITECTURE_NOT_SUPPORTED": "你的架構 {arch} 不被應用程式 {id} 支援", - "APP_ERROR_DOMAIN_ALREADY_IN_USE": "域名 {domain} 已被應用程序 {id} 使用", - "APP_ERROR_DOMAIN_NOT_VALID": "{domain} 不是有效域名", - "APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP": "如果應用程序公開,則需要域名", - "APP_ERROR_INVALID_CONFIG": "應用 {id} 的 config.json 文件無效", + "APP_ERROR_APP_FORCE_EXPOSED": "應用程式 {id} 僅適用於公開網域", + "APP_ERROR_APP_NOT_EXPOSABLE": "應用程式 {id} 不可公開", + "APP_ERROR_APP_NOT_FOUND": "未找到應用程式 {id}", + "APP_ERROR_ARCHITECTURE_NOT_SUPPORTED": "應用程式 {id} 不支援你的 {arch} 架構", + "APP_ERROR_DOMAIN_ALREADY_IN_USE": "網域 {domain} 已被應用程式 {id} 使用", + "APP_ERROR_DOMAIN_NOT_VALID": "{domain} 不是有效網域", + "APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP": "公開應用程式需要網域", + "APP_ERROR_INVALID_CONFIG": "應用程式 {id} 的 config.json 文件無效", "APP_INSTALL_FORM_CHOOSE_OPTION": "選擇一個選項...", "APP_INSTALL_FORM_DISPLAY_ON_GUEST_DASHBOARD": "顯示在訪客儀錶板上", - "APP_INSTALL_FORM_DOMAIN_NAME": "域名", - "APP_INSTALL_FORM_DOMAIN_NAME_HINT": "確保這個確切的域包含指向您的 IP 的 A 記錄。", + "APP_INSTALL_FORM_DOMAIN_NAME": "網域", + "APP_INSTALL_FORM_DOMAIN_NAME_HINT": "請確保此網域含有指向你的 IP 的 A 記錄。", "APP_INSTALL_FORM_ERROR_BETWEEN_LENGTH": "{label} 必須介於 {min} 和 {max} 個字符之間", - "APP_INSTALL_FORM_ERROR_FQDN": "{label} 必須是有效域名", - "APP_INSTALL_FORM_ERROR_FQDNIP": "{label} 必須是有效的域或 IP 地址", + "APP_INSTALL_FORM_ERROR_FQDN": "{label} 必須是有效網域", + "APP_INSTALL_FORM_ERROR_FQDNIP": "{label} 必須是有效的網域或 IP 地址", "APP_INSTALL_FORM_ERROR_INVALID_EMAIL": "{label} 必須是有效的電子郵件地址", "APP_INSTALL_FORM_ERROR_IP": "{label} 必須是有效的 IP 地址", "APP_INSTALL_FORM_ERROR_MAX_LENGTH": "{label} 必須少於 {max} 個字符", "APP_INSTALL_FORM_ERROR_MIN_LENGTH": "{label} 必須至少為 {min} 個字符", "APP_INSTALL_FORM_ERROR_NUMBER": "{label} 必須是數字", - "APP_INSTALL_FORM_ERROR_REGEX": "{label} 必須匹配模式 {pattern}", - "APP_INSTALL_FORM_ERROR_REQUIRED": "{label} 是必需的", + "APP_INSTALL_FORM_ERROR_REGEX": "{label} 必須符合模式 {pattern}", + "APP_INSTALL_FORM_ERROR_REQUIRED": "{label} 是必填欄位", "APP_INSTALL_FORM_ERROR_URL": "{label} 必須是有效的網址", - "APP_INSTALL_FORM_EXPOSE_APP": "在網路上曝露應用程式", - "APP_INSTALL_FORM_OPEN_PORT": "Open port", - "APP_INSTALL_FORM_OPEN_PORT_HINT": "Open a port on the host? This app will be accessible at {internalIp}:{port}. (Easiest but less secure)", - "APP_INSTALL_FORM_EXPOSE_LOCAL": "在本地網路上曝露應用程式", - "APP_INSTALL_FORM_EXPOSE_LOCAL_HINT": "Expose the app on the local network? This app will be accessible at {appId}.{domain}. (Visit settings page to setup your local domain)", + "APP_INSTALL_FORM_EXPOSE_APP": "在網路上公開應用程式", + "APP_INSTALL_FORM_OPEN_PORT": "開放端口", + "APP_INSTALL_FORM_OPEN_PORT_HINT": "在伺服器上開放端口? 此軟體將能從 {internalIp}:{port} 進入(簡單但較不安全)", + "APP_INSTALL_FORM_EXPOSE_LOCAL": "在區域網路中開放應用程式", + "APP_INSTALL_FORM_EXPOSE_LOCAL_HINT": "將軟體開放在區域網路? 此軟體將能從 {appId}.{domain} 進入(在設定頁面裡設定內部網域)", "APP_INSTALL_FORM_RESET": "重置應用程式", "APP_INSTALL_FORM_SUBMIT_INSTALL": "安裝", "APP_INSTALL_FORM_SUBMIT_UPDATE": "更新", @@ -80,29 +80,29 @@ "APP_INSTALL_FORM_GENERAL": "一般", "APP_INSTALL_FORM_REVERSE_PROXY": "反向代理", "APP_INSTALL_SUCCESS": "應用程式 {id} 已成功安裝", - "APP_LOGS_TAB_FOLLOW": "Follow logs", + "APP_LOGS_TAB_FOLLOW": "追蹤日誌", "APP_LOGS_TAB_MAX_LINES": "最大行數:", "APP_LOGS_TAB_TITLE": "日誌", "APP_LOGS_TAB_WRAP_LINES": "換行", "APP_NEW": "新", "APP_RESET_FORM_SUBMIT": "重置", - "APP_RESET_FORM_SUBTITLE": "此應用程序的所有數據都將丟失。", + "APP_RESET_FORM_SUBTITLE": "此應用程式的所有資料都將遺失。", "APP_RESET_FORM_TITLE": "重置 {name}?", - "APP_RESET_FORM_WARNING": "您確定嗎?這個動作無法被復原!", + "APP_RESET_FORM_WARNING": "你確定嗎?這個動作無法被復原!", "APP_RESET_SUCCESS": "應用程式 {id} 已成功重置", "APP_START_SUCCESS": "應用程式 {id} 已成功啟動", - "APP_BACKUP_TITLE": "Backup {name}", - "APP_BACKUP_SUBTITLE": "A tar archive will be created in the backups folder to store your app's data.", - "APP_BACKUP_SUBMIT": "Backup", - "APP_RESTORE_TITLE": "Restore {name} backup", - "APP_RESTORE_WARNING": "Do you really want to restore backup {id} made on {date}?", - "APP_RESTORE_SUBTITLE": "All the current data of the app will be erased and replaced with the data from the backup. It is recommended to backup your app before restoring.", - "APP_RESTORE_SUBMIT": "Restore", - "APP_BACKUPS_TAB_TITLE": "Backups", - "APP_SETTINGS_GENERAL_TITLE": "General", - "APP_SETTINGS_BACKUPS_TITLE": "Backups", + "APP_BACKUP_TITLE": "備份{name}", + "APP_BACKUP_SUBTITLE": "你的應用程式資料將會儲存在備份資料夾中的一個 .tar 檔案中", + "APP_BACKUP_SUBMIT": "備份", + "APP_RESTORE_TITLE": "復原 {name} 備份", + "APP_RESTORE_WARNING": "你真的要復原於 {date} 製作的 {id} 備份嗎?", + "APP_RESTORE_SUBTITLE": "目前應用程式所有資料將會被刪除,並以備份資料取代。 建議在復原前先備份您的應用程式。", + "APP_RESTORE_SUBMIT": "復原", + "APP_BACKUPS_TAB_TITLE": "備份", + "APP_SETTINGS_GENERAL_TITLE": "一般設定", + "APP_SETTINGS_BACKUPS_TITLE": "備份", "APP_STATUS_INSTALLING": "安裝中", - "APP_STATUS_MISSING": "失蹤", + "APP_STATUS_MISSING": "遺失", "APP_STATUS_RESETTING": "正在重置", "APP_STATUS_RUNNING": "執行中", "APP_STATUS_STARTING": "啟動中", @@ -111,41 +111,41 @@ "APP_STATUS_RESTARTING": "正在重新啟動", "APP_STATUS_UNINSTALLING": "解除安裝中", "APP_STATUS_UPDATING": "正在更新", - "APP_STATUS_BACKING_UP": "Backing up", - "APP_STATUS_RESTORING": "Restoring", + "APP_STATUS_BACKING_UP": "備份中", + "APP_STATUS_RESTORING": "復原中", "APP_STOP_FORM_SUBMIT": "停止", - "APP_STOP_FORM_SUBTITLE": "所有數據將被保留", + "APP_STOP_FORM_SUBTITLE": "所有資料將被保留", "APP_STOP_FORM_TITLE": "停止{name}?", "APP_STOP_SUCCESS": "應用程式 {id} 已成功停止", "APP_RESTART_FORM_SUBMIT": "重新啟動", - "APP_RESTART_FORM_SUBTITLE": "All data will be retained", + "APP_RESTART_FORM_SUBTITLE": "所有資料將會保留", "APP_RESTART_FORM_TITLE": "重新啟動 {name}?", "APP_RESTART_SUCCESS": "應用程式 {id} 已成功重新啟動", "APP_STORE_CATEGORY_PLACEHOLDER": "選擇一個類別", - "APP_STORE_NO_RESULTS": "未找到应用", - "APP_STORE_NO_RESULTS_SUBTITLE": "嘗試優化您的搜索", + "APP_STORE_NO_RESULTS": "未找到任何應用程式", + "APP_STORE_NO_RESULTS_SUBTITLE": "請嘗試更精確地搜尋", "APP_STORE_SEARCH_PLACEHOLDER": "搜尋應用程式", "APP_STORE_TITLE": "應用程式商店", "APP_UNINSTALL_FORM_SUBMIT": "卸載", - "APP_UNINSTALL_FORM_SUBTITLE": "此應用程序的所有數據都將丟失。", + "APP_UNINSTALL_FORM_SUBTITLE": "此應用程式的所有資料將會遺失。", "APP_UNINSTALL_FORM_TITLE": "卸載{name}?", "APP_UNINSTALL_FORM_WARNING": "您確定嗎?這個動作無法被復原!", "APP_UNINSTALL_SUCCESS": "應用程式 {id} 已成功解除安裝", - "APP_UPDATE_CONFIG_SUCCESS": "應用配置更新成功。 重新啟動應用程序以應用更改", - "APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.", + "APP_UPDATE_CONFIG_SUCCESS": "應用程式設定已成功更新。請重新啟動應用程式以應用變更。", + "APP_UPDATE_ERROR_MIN_TIPI_VERSION": "應用程式 {id} 更新需要 Tipi 版本 {minVersion} 或更高的版本,請更新你的系統。", "APP_UPDATE_FORM_SUBMIT": "更新", "APP_UPDATE_FORM_SUBTITLE_1": "更新到最新版本:", "APP_UPDATE_FORM_SUBTITLE_2": "這將重置您的自定義配置 (例如 docker-compose. yml 中的更改)", "APP_UPDATE_FORM_TITLE": "更新{name}?", - "APP_UPDATE_SETTINGS_FORM_TITLE": "更新 {name} 配置", + "APP_UPDATE_SETTINGS_FORM_TITLE": "更新 {name} 設定", "APP_UPDATE_SUCCESS": "應用程式 {id} 已成功更新", - "APP_UPDATE_FORM_BACKUP": "Backup app before updating", - "APP_BACKUP_SUCCESS": "App {id} backed up successfully", - "APP_BACKUP_ERROR": "Failed to backup {id}, see logs for more details", - "APP_RESTORE_SUCCESS": "App {id} restored successfully", - "APP_RESTORE_ERROR": "Failed to restore {id}, see logs for more details", - "AUTH_ERROR_ADMIN_ALREADY_EXISTS": "已經有一個管理員用戶。 請登錄以從管理面板創建新用戶。", - "AUTH_ERROR_ERROR_CREATING_USER": "建立用戶時發生錯誤", + "APP_UPDATE_FORM_BACKUP": "更新前 備份應用程式", + "APP_BACKUP_SUCCESS": "應用程式 {id} 備份成功", + "APP_BACKUP_ERROR": "備份 {id} 失敗 請見日誌以獲取更多詳情", + "APP_RESTORE_SUCCESS": "應用程式 {id} 復原成功", + "APP_RESTORE_ERROR": "恢復 {id} 失敗 請見日誌以獲取更多詳情", + "AUTH_ERROR_ADMIN_ALREADY_EXISTS": "已經有一個管理使用者。請登入管理面板以建立新的使用者。", + "AUTH_ERROR_ERROR_CREATING_USER": "創建使用者時發生錯誤", "AUTH_ERROR_INVALID_CREDENTIALS": "無效的登入憑證", "AUTH_ERROR_INVALID_PASSWORD": "無效的密碼", "AUTH_ERROR_INVALID_PASSWORD_LENGTH": "密碼長度必須至少為8個字元", @@ -182,7 +182,7 @@ "AUTH_REGISTER_TITLE": "註冊您的帳戶", "AUTH_RESET_PASSWORD_BACK_TO_LOGIN": "返回登入", "AUTH_RESET_PASSWORD_CANCEL": "取消密碼更改請求", - "AUTH_RESET_PASSWORD_INSTRUCTIONS": "To get started, run this command on your server and then refresh this page. If you have previously done so, the password reset request may have expired. In that case please retry", + "AUTH_RESET_PASSWORD_INSTRUCTIONS": "首先,請在你的伺服器上執行此命令,然後刷新此頁面。如果你之前已完成操作,則密碼重設請求可能已過期,請重新嘗試。", "AUTH_RESET_PASSWORD_SUBMIT": "密碼重置", "AUTH_RESET_PASSWORD_SUCCESS": "您的密碼已重置。 您現在可以使用新密碼登錄。 還有你的電子郵件 {email}", "AUTH_RESET_PASSWORD_SUCCESS_TITLE": "重置密碼", @@ -190,13 +190,13 @@ "AUTH_TOTP_INSTRUCTIONS": "輸入驗證應用程式中的驗證代碼", "AUTH_TOTP_SUBMIT": "確認", "AUTH_TOTP_TITLE": "兩步驟驗證", - "BACKUPS_LIST": "Backups list", - "BACKUPS_LIST_BACKUP_NOW": "Backup now", + "BACKUPS_LIST": "備份清單", + "BACKUPS_LIST_BACKUP_NOW": "立即備份", "BACKUPS_LIST_ROW_TITLE_ID": "ID", - "BACKUPS_LIST_ROW_TITLE_DATE": "Date", - "BACKUPS_LIST_ROW_TITLE_ACTIONS": "Actions", - "BACKUPS_LIST_ROW_TITLE_SIZE": "Size", - "BACKUPS_LIST_DELETE_SUCCESS": "Backup deleted successfully", + "BACKUPS_LIST_ROW_TITLE_DATE": "日期", + "BACKUPS_LIST_ROW_TITLE_ACTIONS": "執行", + "BACKUPS_LIST_ROW_TITLE_SIZE": "大小", + "BACKUPS_LIST_DELETE_SUCCESS": "成功刪除備份", "COMMON_CLOSE": "關閉", "DASHBOARD_CPU_SUBTITLE": "卸載應用程序以減少負載", "DASHBOARD_CPU_TITLE": "CPU 負載", @@ -205,14 +205,14 @@ "DASHBOARD_MEMORY_TITLE": "已使用記憶體", "DASHBOARD_TITLE": "儀表板", "DASHBOARD_IP_WARNING_TITLE": "不安全的配置", - "DASHBOARD_IP_WARNING": "Warning, you might be at risk! it looks like you are accessing your instance through a public IP address. This makes your dashboard and all apps that you install vulnerable to attackers", - "DELETE_BACKUP_MODAL_TITLE": "Delete backup", - "DELETE_BACKUP_MODAL_WARNING": "Are you sure you want to delete backup {id} made on {date}?", - "DELETE_BACKUP_MODAL_SUBTITLE": "This action cannot be undone", - "DELETE_BACKUP_MODAL_SUBMIT": "Delete", + "DASHBOARD_IP_WARNING": "注意,你的安全性可能會受到威脅!看起來你正透過公共 IP 位址存取系統。這使得你的儀表板和你安裝的所有應用程式容易受到攻擊。", + "DELETE_BACKUP_MODAL_TITLE": "刪除備份", + "DELETE_BACKUP_MODAL_WARNING": "你確定要刪除建立於 {date} 的 {id} 備份嗎?", + "DELETE_BACKUP_MODAL_SUBTITLE": "此動作無法撤銷", + "DELETE_BACKUP_MODAL_SUBMIT": "刪除", "GUEST_DASHBOARD": "訪客儀錶板", "GUEST_DASHBOARD_NO_APPS": "沒有應用程式可以顯示", - "GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Ask your administrator to add apps to the guest dashboard or login to see your apps.", + "GUEST_DASHBOARD_NO_APPS_SUBTITLE": "請詢問你的管理員將應用程式加入來賓儀表板,或登入查看你的應用程式。", "HEADER_APPS": "我的應用程式", "HEADER_APP_STORE": "應用程式商店", "HEADER_DARK_MODE": "深色模式", @@ -226,17 +226,17 @@ "HEADER_UPDATE_AVAILABLE": "有可用更新", "INTERNAL_SERVER_ERROR": "內部伺服器錯誤", "LINKS_ADD_SUBMIT": "提交", - "LINKS_ADD_SUBTITLE": "Add external link to the dashboard", - "LINKS_ADD_SUCCESS": "Link added successfully", + "LINKS_ADD_SUBTITLE": "增加外部連結到儀表板", + "LINKS_ADD_SUCCESS": "成功增加連結", "LINKS_ADD_TITLE": "新增外部連結", "LINKS_DELETE_CONTEXT_MENU": "刪除", "LINKS_DELETE_SUBMIT": "刪除", - "LINKS_DELETE_SUBTITLE": "Are you sure you want to delete this external link?", - "LINKS_DELETE_SUCCESS": "Link deleted successfully", + "LINKS_DELETE_SUBTITLE": "你確定要刪除此外部連結?", + "LINKS_DELETE_SUCCESS": "成功刪除連結", "LINKS_DELETE_TITLE": "刪除外部連結", "LINKS_EDIT_CONTEXT_MENU": "編輯", "LINKS_EDIT_SUBMIT": "儲存", - "LINKS_EDIT_SUCCESS": "Link edited successfully", + "LINKS_EDIT_SUCCESS": "成功編輯連結", "LINKS_EDIT_TITLE": "編輯連結", "LINKS_FORM_ICON_PLACEHOLDER": "連結標誌網址", "LINKS_FORM_ICON_URL": "圖示網址", @@ -249,8 +249,8 @@ "MY_APPS_EMPTY_TITLE": "未安裝任何應用程式", "MY_APPS_TITLE": "我的應用程式", "MY_APPS_UPDATE_ALL_FORM_SUBMIT": "更新全部", - "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_1": "Do you want to update all your apps to the latest version?", - "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_2": "This will update all your apps to the latest version. Make sure you've read the release notes of the apps and you've backed up your app data.", + "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_1": "你要將所有應用程式更新為最新版本嗎?", + "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_2": "這將更新所有應用程式為最新版本。請確保你已閱讀了應用程式的更新通知,並且已備份你的應用程式資料。", "MY_APPS_UPDATE_ALL_FORM_TITLE": "更新全部應用程式", "MY_APPS_UPDATE_ALL_IN_PROGRESS": "正在更新全部應用程式", "MY_APPS_UPDATE_AVAILABLE": "有可用更新", @@ -267,15 +267,15 @@ "SETTINGS_ACTIONS_STAY_UP_TO_DATE": "及時了解最新版本的 Tipi", "SETTINGS_ACTIONS_TAB_TITLE": "操作", "SETTINGS_ACTIONS_TITLE": "操作", - "SETTINGS_ACTIONS_UPDATE_REPO_TITLE": "Update Repository", - "SETTINGS_ACTIONS_UPDATE_REPO_SUBTITLE": "Use this button to update your appstore", - "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_SUBTITLE": "This will reset your repository and pull the latest changes from GitHub", - "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_BUTTON": "Update", - "SETTINGS_ACTIONS_UPDATE_REPO_SUCCESS": "Appstore repository updated successfully", + "SETTINGS_ACTIONS_UPDATE_REPO_TITLE": "更新repository", + "SETTINGS_ACTIONS_UPDATE_REPO_SUBTITLE": "使用此按鈕更新你的應用商店", + "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_SUBTITLE": "這將會重設你的repository並從GitHub下載最新變動", + "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_BUTTON": "更新", + "SETTINGS_ACTIONS_UPDATE_REPO_SUCCESS": "應用商店repository更新成功", "SETTINGS_GENERAL_ALLOW_AUTO_THEMES": "允許自動主題", - "SETTINGS_GENERAL_ALLOW_AUTO_THEMES_HINT": "Be surprised by themes that change automatically based on the time of the year.", - "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING": "Allow anonymous error monitoring", - "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING_HINT": "Error monitoring is used to track errors and improve Tipi. Keep this option enabled to help us improve Tipi.", + "SETTINGS_GENERAL_ALLOW_AUTO_THEMES_HINT": "主題將會隨著年度的時間變化", + "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING": "允許匿名錯誤監控", + "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING_HINT": "錯誤監控用於追蹤錯誤並改進Tipi。保持此選項啟用以幫助我們改進Tipi。", "SETTINGS_GENERAL_APPS_REPO": "應用存儲庫 URL", "SETTINGS_GENERAL_APPS_REPO_HINT": "應用存儲庫的 URL", "SETTINGS_GENERAL_DNS_IP": "DNS IP", @@ -283,7 +283,7 @@ "SETTINGS_GENERAL_DOMAIN_NAME_HINT": "確保這個確切的域包含指向您的 IP 的 A 記錄。", "SETTINGS_GENERAL_DOWNLOAD_CERTIFICATE": "下載證書", "SETTINGS_GENERAL_GUEST_DASHBOARD": "啟用訪客儀錶板", - "SETTINGS_GENERAL_GUEST_DASHBOARD_HINT": "This will allow non-authenticated users to see a limited dashboard and easily access the running apps on your instance.", + "SETTINGS_GENERAL_GUEST_DASHBOARD_HINT": "這將允許未驗證使用者查看一個有限的儀表板,並簡單的訪問系統上正在運行的應用程式。", "SETTINGS_GENERAL_INTERNAL_IP": "內部IP", "SETTINGS_GENERAL_INTERNAL_IP_HINT": "您的服務器正在偵聽的 IP 地址", "SETTINGS_GENERAL_INVALID_DOMAIN": "無效的域名", @@ -295,7 +295,7 @@ "SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "訪問本地網絡應用所使用的域名。您的應用將可以在app-name.local-domain訪問。", "SETTINGS_GENERAL_SETTINGS_UPDATED": "設置更新。 重新啟動您的實例以應用新設置。", "SETTINGS_GENERAL_STORAGE_PATH": "儲存路徑", - "SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.", + "SETTINGS_GENERAL_STORAGE_PATH_HINT": "儲存目錄的路徑。請確保它是絕對路徑並且存在。", "SETTINGS_GENERAL_SUBMIT": "更新設定", "SETTINGS_GENERAL_SUBTITLE": "這將更新您的 settings.json 文件。 在更新這些值之前,請確保您知道自己在做什麼。", "SETTINGS_GENERAL_TAB_TITLE": "設定", @@ -311,9 +311,9 @@ "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_INVALID_USERNAME": "必須是有效的電子郵件地址", "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_NEW_USERNAME": "新使用者名稱", "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_PASSWORD": "密碼", - "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_PASSWORD_NEEDED_HINT": "Your password is required to change your username.", + "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_PASSWORD_NEEDED_HINT": "需要你的密碼來更改你的使用者名稱。", "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_SUBMIT": "變更使用者名稱", - "SETTINGS_SECURITY_CHANGE_USERNAME_SUBTITLE": "Changing your username will log you out of all devices.", + "SETTINGS_SECURITY_CHANGE_USERNAME_SUBTITLE": "更改你的使用者名稱將會登出所有設備。", "SETTINGS_SECURITY_CHANGE_USERNAME_SUCCESS": "使用者名稱已成功變更", "SETTINGS_SECURITY_CHANGE_USERNAME_TITLE": "變更使用者名稱", "SETTINGS_SECURITY_DISABLE_2FA": "關閉雙因素驗證", @@ -338,7 +338,7 @@ "SYSTEM_ERROR_CURRENT_VERSION_IS_LATEST": "當前版本已經是最新的", "SYSTEM_ERROR_DEMO_MODE_LIMIT": "在演示模式下只能安裝6個應用。請卸載其他應用來安裝新應用。", "SYSTEM_ERROR_MAJOR_VERSION_UPDATE": "主要版本已更改。 請手動更新(参照更新說明)", - "SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN": "You must be logged in to perform this action", - "TIMEZONE_SELECTOR_LABEL": "Timezone", - "TIMEZONE_SELECTOR_PLACEHOLDER": "Select a timezone" + "SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN": "你必須登入,才能執行此項操作。", + "TIMEZONE_SELECTOR_LABEL": "時區", + "TIMEZONE_SELECTOR_PLACEHOLDER": "選擇一個時區" } From a39993127e8720cd4558a68ef991d266edcc693e Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Sat, 28 Sep 2024 18:33:56 +0200 Subject: [PATCH 007/241] New translations en.json (Chinese Traditional) --- src/client/messages/zh-TW.json | 120 ++++++++++++++++----------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/src/client/messages/zh-TW.json b/src/client/messages/zh-TW.json index 91172db6c2..e41b0cf1ee 100644 --- a/src/client/messages/zh-TW.json +++ b/src/client/messages/zh-TW.json @@ -150,46 +150,46 @@ "AUTH_ERROR_INVALID_PASSWORD": "無效的密碼", "AUTH_ERROR_INVALID_PASSWORD_LENGTH": "密碼長度必須至少為8個字元", "AUTH_ERROR_INVALID_USERNAME": "無效的使用者名稱", - "AUTH_ERROR_MISSING_EMAIL_OR_PASSWORD": "缺少電子郵箱或密碼。", - "AUTH_ERROR_NO_CHANGE_PASSWORD_REQUEST": "未找到更改密碼請求", - "AUTH_ERROR_OPERATOR_NOT_FOUND": "未找到操作員用戶", - "AUTH_ERROR_TOTP_ALREADY_ENABLED": "該用戶已啟用2FA", + "AUTH_ERROR_MISSING_EMAIL_OR_PASSWORD": "缺少電子郵箱或密碼", + "AUTH_ERROR_NO_CHANGE_PASSWORD_REQUEST": "未找到任何變更密碼請求", + "AUTH_ERROR_OPERATOR_NOT_FOUND": "未找到操作員使用者", + "AUTH_ERROR_TOTP_ALREADY_ENABLED": "該使用者已啟用2FA", "AUTH_ERROR_TOTP_INVALID_CODE": "無效的2FA驗證碼", - "AUTH_ERROR_TOTP_NOT_ENABLED": "此用戶未啟用2FA。", - "AUTH_ERROR_TOTP_SESSION_NOT_FOUND": "找不到 2FA 會話", + "AUTH_ERROR_TOTP_NOT_ENABLED": "此使用者未啟用2FA", + "AUTH_ERROR_TOTP_SESSION_NOT_FOUND": "找不到 2FA session", "AUTH_ERROR_USER_ALREADY_EXISTS": "使用者已經存在", - "AUTH_ERROR_USER_NOT_FOUND": "未找到用戶", + "AUTH_ERROR_USER_NOT_FOUND": "找不到使用者", "AUTH_FORM_EMAIL": "電子郵件地址", "AUTH_FORM_EMAIL_PLACEHOLDER": "you@example.com", "AUTH_FORM_ERROR_EMAIL_EMAIL": "電子郵件地址無效", "AUTH_FORM_ERROR_EMAIL_INVALID": "電子郵件地址無效", - "AUTH_FORM_ERROR_EMAIL_REQUIRED": "電子郵件位址為必填", + "AUTH_FORM_ERROR_EMAIL_REQUIRED": "電子郵件為必填欄位", "AUTH_FORM_ERROR_PASSWORD_CONFIRMATION_LENGTH": "密碼必須至少8個字元", - "AUTH_FORM_ERROR_PASSWORD_CONFIRMATION_MATCH": "密碼不匹配", + "AUTH_FORM_ERROR_PASSWORD_CONFIRMATION_MATCH": "密碼不符", "AUTH_FORM_ERROR_PASSWORD_CONFIRMATION_REQUIRED": "需要確認密碼", "AUTH_FORM_ERROR_PASSWORD_LENGTH": "密碼必須至少8個字元", - "AUTH_FORM_ERROR_PASSWORD_REQUIRED": "密碼為必填", + "AUTH_FORM_ERROR_PASSWORD_REQUIRED": "密碼為必填欄位", "AUTH_FORM_FORGOT": "忘記密碼?", - "AUTH_FORM_NEW_PASSWORD_CONFIRMATION_PLACEHOLDER": "確認您的新密碼", - "AUTH_FORM_NEW_PASSWORD_PLACEHOLDER": "您的新密碼", + "AUTH_FORM_NEW_PASSWORD_CONFIRMATION_PLACEHOLDER": "確認你的新密碼", + "AUTH_FORM_NEW_PASSWORD_PLACEHOLDER": "你的新密碼", "AUTH_FORM_PASSWORD": "密碼", "AUTH_FORM_PASSWORD_CONFIRMATION": "確認密碼", - "AUTH_FORM_PASSWORD_CONFIRMATION_PLACEHOLDER": "确认您的密码", + "AUTH_FORM_PASSWORD_CONFIRMATION_PLACEHOLDER": "確認你的密碼", "AUTH_FORM_PASSWORD_PLACEHOLDER": "輸入你的密碼", - "AUTH_LOGIN_SUBMIT": "登录", - "AUTH_LOGIN_TITLE": "登入您的帳號", + "AUTH_LOGIN_SUBMIT": "登入", + "AUTH_LOGIN_TITLE": "登入你的帳號", "AUTH_REGISTER_SUBMIT": "註冊帳號", - "AUTH_REGISTER_TITLE": "註冊您的帳戶", + "AUTH_REGISTER_TITLE": "註冊你的帳號", "AUTH_RESET_PASSWORD_BACK_TO_LOGIN": "返回登入", "AUTH_RESET_PASSWORD_CANCEL": "取消密碼更改請求", "AUTH_RESET_PASSWORD_INSTRUCTIONS": "首先,請在你的伺服器上執行此命令,然後刷新此頁面。如果你之前已完成操作,則密碼重設請求可能已過期,請重新嘗試。", "AUTH_RESET_PASSWORD_SUBMIT": "密碼重置", - "AUTH_RESET_PASSWORD_SUCCESS": "您的密碼已重置。 您現在可以使用新密碼登錄。 還有你的電子郵件 {email}", + "AUTH_RESET_PASSWORD_SUCCESS": "你的密碼已重置。 你現在可以使用新密碼登錄。 還有你的電子郵件 {email}", "AUTH_RESET_PASSWORD_SUCCESS_TITLE": "重置密碼", - "AUTH_RESET_PASSWORD_TITLE": "重置您的密碼", + "AUTH_RESET_PASSWORD_TITLE": "重置你的密碼", "AUTH_TOTP_INSTRUCTIONS": "輸入驗證應用程式中的驗證代碼", "AUTH_TOTP_SUBMIT": "確認", - "AUTH_TOTP_TITLE": "兩步驟驗證", + "AUTH_TOTP_TITLE": "2FA 雙重驗證", "BACKUPS_LIST": "備份清單", "BACKUPS_LIST_BACKUP_NOW": "立即備份", "BACKUPS_LIST_ROW_TITLE_ID": "ID", @@ -198,17 +198,17 @@ "BACKUPS_LIST_ROW_TITLE_SIZE": "大小", "BACKUPS_LIST_DELETE_SUCCESS": "成功刪除備份", "COMMON_CLOSE": "關閉", - "DASHBOARD_CPU_SUBTITLE": "卸載應用程序以減少負載", - "DASHBOARD_CPU_TITLE": "CPU 負載", - "DASHBOARD_DISK_SPACE_SUBTITLE": "总计{total} GB中已使用", + "DASHBOARD_CPU_SUBTITLE": "卸載應用程式以減少資源承載", + "DASHBOARD_CPU_TITLE": "CPU 承載", + "DASHBOARD_DISK_SPACE_SUBTITLE": "{total} GB 中已使用", "DASHBOARD_DISK_SPACE_TITLE": "硬碟空間", "DASHBOARD_MEMORY_TITLE": "已使用記憶體", "DASHBOARD_TITLE": "儀表板", - "DASHBOARD_IP_WARNING_TITLE": "不安全的配置", + "DASHBOARD_IP_WARNING_TITLE": "不安全的設定", "DASHBOARD_IP_WARNING": "注意,你的安全性可能會受到威脅!看起來你正透過公共 IP 位址存取系統。這使得你的儀表板和你安裝的所有應用程式容易受到攻擊。", "DELETE_BACKUP_MODAL_TITLE": "刪除備份", "DELETE_BACKUP_MODAL_WARNING": "你確定要刪除建立於 {date} 的 {id} 備份嗎?", - "DELETE_BACKUP_MODAL_SUBTITLE": "此動作無法撤銷", + "DELETE_BACKUP_MODAL_SUBTITLE": "此動作無法復原", "DELETE_BACKUP_MODAL_SUBMIT": "刪除", "GUEST_DASHBOARD": "訪客儀錶板", "GUEST_DASHBOARD_NO_APPS": "沒有應用程式可以顯示", @@ -218,11 +218,11 @@ "HEADER_DARK_MODE": "深色模式", "HEADER_DASHBOARD": "儀表板", "HEADER_LIGHT_MODE": "淺色模式", - "HEADER_LOGIN": "登录", + "HEADER_LOGIN": "登入", "HEADER_LOGOUT": "登出", "HEADER_SETTINGS": "設定", - "HEADER_SOURCE_CODE": "源代码", - "HEADER_SPONSOR": "贊助者", + "HEADER_SOURCE_CODE": "原始碼", + "HEADER_SPONSOR": "贊助我吧", "HEADER_UPDATE_AVAILABLE": "有可用更新", "INTERNAL_SERVER_ERROR": "內部伺服器錯誤", "LINKS_ADD_SUBMIT": "提交", @@ -238,14 +238,14 @@ "LINKS_EDIT_SUBMIT": "儲存", "LINKS_EDIT_SUCCESS": "成功編輯連結", "LINKS_EDIT_TITLE": "編輯連結", - "LINKS_FORM_ICON_PLACEHOLDER": "連結標誌網址", + "LINKS_FORM_ICON_PLACEHOLDER": "連結Logo網址", "LINKS_FORM_ICON_URL": "圖示網址", "LINKS_FORM_LINK_TITLE": "連結標題", "LINKS_FORM_LINK_URL": "連結網址", "LINKS_FROM_LINK_DESCRIPTION": "連結描述", "MY_APPS_DEPRECATED": "這個應用程式已被棄用", - "MY_APPS_EMPTY_ACTION": "前往App Store", - "MY_APPS_EMPTY_SUBTITLE": "應用商店安裝應用以開始使用", + "MY_APPS_EMPTY_ACTION": "前往應用程式商店", + "MY_APPS_EMPTY_SUBTITLE": "開始安裝你的第一個應用程式吧", "MY_APPS_EMPTY_TITLE": "未安裝任何應用程式", "MY_APPS_TITLE": "我的應用程式", "MY_APPS_UPDATE_ALL_FORM_SUBMIT": "更新全部", @@ -256,57 +256,57 @@ "MY_APPS_UPDATE_AVAILABLE": "有可用更新", "RUNTIPI": "Runtipi", "SERVER_ERROR_INVALID_LOCALE": "語言環境無效", - "SERVER_ERROR_NOT_ALLOWED_IN_DEMO": "演示模式不允許", - "SERVER_ERROR_NOT_ALLOWED_IN_DEV": "開發模式不允許", + "SERVER_ERROR_NOT_ALLOWED_IN_DEMO": "Demo模式下禁止此動作", + "SERVER_ERROR_NOT_ALLOWED_IN_DEV": "開發模式下禁止此動作", "SETTINGS_ACTIONS_ALREADY_LATEST": "已是最新版本", "SETTINGS_ACTIONS_CURRENT_VERSION": "目前版本: {version}", - "SETTINGS_ACTIONS_MAINTENANCE_SUBTITLE": "在您的實例上執行的常見操作", + "SETTINGS_ACTIONS_MAINTENANCE_SUBTITLE": "你可以在系統上執行的常用操作", "SETTINGS_ACTIONS_MAINTENANCE_TITLE": "維護", "SETTINGS_ACTIONS_NEW_VERSION": "Tipi 的新版本 ({version}) 可用", "SETTINGS_ACTIONS_RESTART": "重新啟動", - "SETTINGS_ACTIONS_STAY_UP_TO_DATE": "及時了解最新版本的 Tipi", + "SETTINGS_ACTIONS_STAY_UP_TO_DATE": "即時了解最新版本的 Tipi", "SETTINGS_ACTIONS_TAB_TITLE": "操作", "SETTINGS_ACTIONS_TITLE": "操作", - "SETTINGS_ACTIONS_UPDATE_REPO_TITLE": "更新repository", + "SETTINGS_ACTIONS_UPDATE_REPO_TITLE": "更新 repository", "SETTINGS_ACTIONS_UPDATE_REPO_SUBTITLE": "使用此按鈕更新你的應用商店", - "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_SUBTITLE": "這將會重設你的repository並從GitHub下載最新變動", + "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_SUBTITLE": "這將會重設你的 repository 並從 GitHub下載最新變動", "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_BUTTON": "更新", - "SETTINGS_ACTIONS_UPDATE_REPO_SUCCESS": "應用商店repository更新成功", + "SETTINGS_ACTIONS_UPDATE_REPO_SUCCESS": "應用商店 repository 更新成功", "SETTINGS_GENERAL_ALLOW_AUTO_THEMES": "允許自動主題", "SETTINGS_GENERAL_ALLOW_AUTO_THEMES_HINT": "主題將會隨著年度的時間變化", "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING": "允許匿名錯誤監控", "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING_HINT": "錯誤監控用於追蹤錯誤並改進Tipi。保持此選項啟用以幫助我們改進Tipi。", - "SETTINGS_GENERAL_APPS_REPO": "應用存儲庫 URL", - "SETTINGS_GENERAL_APPS_REPO_HINT": "應用存儲庫的 URL", + "SETTINGS_GENERAL_APPS_REPO": "應用程式 repo URL", + "SETTINGS_GENERAL_APPS_REPO_HINT": "應用程式 repo URL", "SETTINGS_GENERAL_DNS_IP": "DNS IP", - "SETTINGS_GENERAL_DOMAIN_NAME": "域名", - "SETTINGS_GENERAL_DOMAIN_NAME_HINT": "確保這個確切的域包含指向您的 IP 的 A 記錄。", + "SETTINGS_GENERAL_DOMAIN_NAME": "網域", + "SETTINGS_GENERAL_DOMAIN_NAME_HINT": "請確認此網域包含指向你的 IP 的 A 記錄", "SETTINGS_GENERAL_DOWNLOAD_CERTIFICATE": "下載證書", "SETTINGS_GENERAL_GUEST_DASHBOARD": "啟用訪客儀錶板", "SETTINGS_GENERAL_GUEST_DASHBOARD_HINT": "這將允許未驗證使用者查看一個有限的儀表板,並簡單的訪問系統上正在運行的應用程式。", "SETTINGS_GENERAL_INTERNAL_IP": "內部IP", - "SETTINGS_GENERAL_INTERNAL_IP_HINT": "您的服務器正在偵聽的 IP 地址", - "SETTINGS_GENERAL_INVALID_DOMAIN": "無效的域名", + "SETTINGS_GENERAL_INTERNAL_IP_HINT": "你的伺服器正在監聽的 IP 地址", + "SETTINGS_GENERAL_INVALID_DOMAIN": "無效的網域", "SETTINGS_GENERAL_INVALID_IP": "無效的 IP 位址", "SETTINGS_GENERAL_INVALID_URL": "URL不正確", "SETTINGS_GENERAL_LANGUAGE": "語言設定", "SETTINGS_GENERAL_LANGUAGE_HELP_TRANSLATE": "協助翻譯", - "SETTINGS_GENERAL_LOCAL_DOMAIN": "本地域名", - "SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "訪問本地網絡應用所使用的域名。您的應用將可以在app-name.local-domain訪問。", - "SETTINGS_GENERAL_SETTINGS_UPDATED": "設置更新。 重新啟動您的實例以應用新設置。", + "SETTINGS_GENERAL_LOCAL_DOMAIN": "區域網域", + "SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "用於在本地網路存取應用程式的網域。你的應用程式將可以在 app-name.local-domain 存取。", + "SETTINGS_GENERAL_SETTINGS_UPDATED": "設定已更新。請重新啟動你的系統以應用新設定。", "SETTINGS_GENERAL_STORAGE_PATH": "儲存路徑", "SETTINGS_GENERAL_STORAGE_PATH_HINT": "儲存目錄的路徑。請確保它是絕對路徑並且存在。", "SETTINGS_GENERAL_SUBMIT": "更新設定", - "SETTINGS_GENERAL_SUBTITLE": "這將更新您的 settings.json 文件。 在更新這些值之前,請確保您知道自己在做什麼。", + "SETTINGS_GENERAL_SUBTITLE": "這將更新你的 settings.json 文件。 在更新這些資料之前,請務必確認你知道自己在做什麼。", "SETTINGS_GENERAL_TAB_TITLE": "設定", "SETTINGS_GENERAL_TITLE": "一般設定", "SETTINGS_GENERAL_USER_SETTINGS": "使用者設定", - "SETTINGS_SECURITY_2FA_DISABLE_SUCCESS": "兩步驟驗證已停用", - "SETTINGS_SECURITY_2FA_ENABLE_SUCCESS": "已啟用兩步驟驗證", - "SETTINGS_SECURITY_2FA_SUBTITLE": "兩步驟驗證為您的帳戶多增加了一道防線", - "SETTINGS_SECURITY_2FA_SUBTITLE_2": "啟用後,系統會提示您在登錄時輸入驗證器應用程序中的代碼", - "SETTINGS_SECURITY_2FA_TITLE": "兩步驟驗證", - "SETTINGS_SECURITY_CHANGE_PASSWORD_SUBTITLE": "更改密碼將使您退出所有設備", + "SETTINGS_SECURITY_2FA_DISABLE_SUCCESS": "2FA 雙重驗證已停用", + "SETTINGS_SECURITY_2FA_ENABLE_SUCCESS": "已啟用 2FA 雙重驗證", + "SETTINGS_SECURITY_2FA_SUBTITLE": "雙重驗證 (2FA) 為你的帳號多增加了一道防線", + "SETTINGS_SECURITY_2FA_SUBTITLE_2": "啟用後,你將在登入時被提示輸入驗證程式碼。", + "SETTINGS_SECURITY_2FA_TITLE": "雙重驗證", + "SETTINGS_SECURITY_CHANGE_PASSWORD_SUBTITLE": "更改你的密碼將會登出所有設備。", "SETTINGS_SECURITY_CHANGE_PASSWORD_TITLE": "更改密碼", "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_INVALID_USERNAME": "必須是有效的電子郵件地址", "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_NEW_USERNAME": "新使用者名稱", @@ -316,27 +316,27 @@ "SETTINGS_SECURITY_CHANGE_USERNAME_SUBTITLE": "更改你的使用者名稱將會登出所有設備。", "SETTINGS_SECURITY_CHANGE_USERNAME_SUCCESS": "使用者名稱已成功變更", "SETTINGS_SECURITY_CHANGE_USERNAME_TITLE": "變更使用者名稱", - "SETTINGS_SECURITY_DISABLE_2FA": "關閉雙因素驗證", - "SETTINGS_SECURITY_ENABLE_2FA": "啟用兩步驟驗證", + "SETTINGS_SECURITY_DISABLE_2FA": "關閉雙重驗證", + "SETTINGS_SECURITY_ENABLE_2FA": "啟用雙重驗證", "SETTINGS_SECURITY_ENTER_2FA_CODE": "輸入驗證器所產生的6位數驗證碼", "SETTINGS_SECURITY_ENTER_KEY_MANUALLY": "或手動輸入此代碼", "SETTINGS_SECURITY_FORM_CHANGE_PASSWORD_SUBMIT": "更改密碼", "SETTINGS_SECURITY_FORM_CONFIRM_PASSWORD": "確認新密碼", - "SETTINGS_SECURITY_FORM_CURRENT_PASSWORD": "当前的密碼", + "SETTINGS_SECURITY_FORM_CURRENT_PASSWORD": "目前的密碼", "SETTINGS_SECURITY_FORM_NEW_PASSWORD": "新密碼", "SETTINGS_SECURITY_FORM_PASSWORD": "密碼", "SETTINGS_SECURITY_FORM_PASSWORD_LENGTH": "密碼必須至少8個字元", - "SETTINGS_SECURITY_FORM_PASSWORD_MATCH": "密碼不匹配", + "SETTINGS_SECURITY_FORM_PASSWORD_MATCH": "密碼不符", "SETTINGS_SECURITY_PASSWORD_CHANGE_SUCCESS": "密碼變更成功", "SETTINGS_SECURITY_PASSWORD_NEEDED": "需要輸入密碼", - "SETTINGS_SECURITY_PASSWORD_NEEDED_HINT": "需要您的密碼才能更改雙因素身份驗證設置", - "SETTINGS_SECURITY_SCAN_QR_CODE": "使用您的驗證器程式掃描此 QR Code", + "SETTINGS_SECURITY_PASSWORD_NEEDED_HINT": "需要你的密碼才能更改雙重驗證設定", + "SETTINGS_SECURITY_SCAN_QR_CODE": "使用你的驗證器程式掃描此 QR Code", "SETTINGS_SECURITY_TAB_TITLE": "安全性", "SETTINGS_LOGS_TAB_TITLE": "日誌", "SETTINGS_TITLE": "設定", "SYSTEM_ERROR_COULD_NOT_GET_LATEST_VERSION": "無法檢查最新版本", "SYSTEM_ERROR_CURRENT_VERSION_IS_LATEST": "當前版本已經是最新的", - "SYSTEM_ERROR_DEMO_MODE_LIMIT": "在演示模式下只能安裝6個應用。請卸載其他應用來安裝新應用。", + "SYSTEM_ERROR_DEMO_MODE_LIMIT": "在Demo模式下最多只能安裝六個應用程式。請卸載其他應用程式才能安裝新的應用程式。", "SYSTEM_ERROR_MAJOR_VERSION_UPDATE": "主要版本已更改。 請手動更新(参照更新說明)", "SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN": "你必須登入,才能執行此項操作。", "TIMEZONE_SELECTOR_LABEL": "時區", From b4d8688ecd0abd31162ad3d5ae1f9c1cccc43889 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Sat, 5 Oct 2024 18:29:08 +0200 Subject: [PATCH 008/241] New translations en.json (Chinese Traditional) --- src/client/messages/zh-TW.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/messages/zh-TW.json b/src/client/messages/zh-TW.json index e41b0cf1ee..f3dab608b1 100644 --- a/src/client/messages/zh-TW.json +++ b/src/client/messages/zh-TW.json @@ -91,7 +91,7 @@ "APP_RESET_FORM_WARNING": "你確定嗎?這個動作無法被復原!", "APP_RESET_SUCCESS": "應用程式 {id} 已成功重置", "APP_START_SUCCESS": "應用程式 {id} 已成功啟動", - "APP_BACKUP_TITLE": "備份{name}", + "APP_BACKUP_TITLE": "備份 {name}", "APP_BACKUP_SUBTITLE": "你的應用程式資料將會儲存在備份資料夾中的一個 .tar 檔案中", "APP_BACKUP_SUBMIT": "備份", "APP_RESTORE_TITLE": "復原 {name} 備份", @@ -198,9 +198,9 @@ "BACKUPS_LIST_ROW_TITLE_SIZE": "大小", "BACKUPS_LIST_DELETE_SUCCESS": "成功刪除備份", "COMMON_CLOSE": "關閉", - "DASHBOARD_CPU_SUBTITLE": "卸載應用程式以減少資源承載", + "DASHBOARD_CPU_SUBTITLE": "解除安裝應用程式以減少資源負載", "DASHBOARD_CPU_TITLE": "CPU 承載", - "DASHBOARD_DISK_SPACE_SUBTITLE": "{total} GB 中已使用", + "DASHBOARD_DISK_SPACE_SUBTITLE": "在 {total} GB 中已使用", "DASHBOARD_DISK_SPACE_TITLE": "硬碟空間", "DASHBOARD_MEMORY_TITLE": "已使用記憶體", "DASHBOARD_TITLE": "儀表板", From 54d77614a103b7d249fb337ca44936fbd253a122 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Sat, 5 Oct 2024 19:30:47 +0200 Subject: [PATCH 009/241] New translations en.json (Chinese Traditional) --- src/client/messages/zh-TW.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/messages/zh-TW.json b/src/client/messages/zh-TW.json index f3dab608b1..b53d8c8db1 100644 --- a/src/client/messages/zh-TW.json +++ b/src/client/messages/zh-TW.json @@ -153,9 +153,9 @@ "AUTH_ERROR_MISSING_EMAIL_OR_PASSWORD": "缺少電子郵箱或密碼", "AUTH_ERROR_NO_CHANGE_PASSWORD_REQUEST": "未找到任何變更密碼請求", "AUTH_ERROR_OPERATOR_NOT_FOUND": "未找到操作員使用者", - "AUTH_ERROR_TOTP_ALREADY_ENABLED": "該使用者已啟用2FA", + "AUTH_ERROR_TOTP_ALREADY_ENABLED": "該使用者已啟用 2FA", "AUTH_ERROR_TOTP_INVALID_CODE": "無效的2FA驗證碼", - "AUTH_ERROR_TOTP_NOT_ENABLED": "此使用者未啟用2FA", + "AUTH_ERROR_TOTP_NOT_ENABLED": "此使用者未啟用 2FA", "AUTH_ERROR_TOTP_SESSION_NOT_FOUND": "找不到 2FA session", "AUTH_ERROR_USER_ALREADY_EXISTS": "使用者已經存在", "AUTH_ERROR_USER_NOT_FOUND": "找不到使用者", @@ -337,7 +337,7 @@ "SYSTEM_ERROR_COULD_NOT_GET_LATEST_VERSION": "無法檢查最新版本", "SYSTEM_ERROR_CURRENT_VERSION_IS_LATEST": "當前版本已經是最新的", "SYSTEM_ERROR_DEMO_MODE_LIMIT": "在Demo模式下最多只能安裝六個應用程式。請卸載其他應用程式才能安裝新的應用程式。", - "SYSTEM_ERROR_MAJOR_VERSION_UPDATE": "主要版本已更改。 請手動更新(参照更新說明)", + "SYSTEM_ERROR_MAJOR_VERSION_UPDATE": "主要版本已更改。 請手動更新(參考更新日誌中的說明)", "SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN": "你必須登入,才能執行此項操作。", "TIMEZONE_SELECTOR_LABEL": "時區", "TIMEZONE_SELECTOR_PLACEHOLDER": "選擇一個時區" From 0a1ae5c84fd3491d5293e317ecae238050e70ae1 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:10:03 +0200 Subject: [PATCH 010/241] New translations en.json (Spanish) --- src/client/messages/es-ES.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/messages/es-ES.json b/src/client/messages/es-ES.json index d522e217f4..e2feeaae85 100644 --- a/src/client/messages/es-ES.json +++ b/src/client/messages/es-ES.json @@ -5,7 +5,7 @@ "APP_ACTION_OPEN": "Abrir", "APP_ACTION_REMOVE": "Borrar", "APP_ACTION_SETTINGS": "Configuración", - "APP_ACTION_START": "Comenzar", + "APP_ACTION_START": "Iniciar", "APP_ACTION_STOP": "Detener", "APP_ACTION_RESTART": "Reiniciar", "APP_ACTION_UPDATE": "Actualizar", @@ -39,7 +39,7 @@ "APP_DETAILS_VERSION": "Versión", "APP_DETAILS_WEBSITE": "Página web", "APP_ERROR_APP_FAILED_TO_INSTALL": "Error al instalar la aplicación {id}, consulta los registros para obtener más detalles", - "APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details", + "APP_ERROR_APP_FAILED_TO_RESET": "Error restableciendo {id}, mira las trazas para más detalles", "APP_ERROR_APP_FAILED_TO_START": "No se pudo iniciar la aplicación {id}, consulta los registros para obtener más detalles", "APP_ERROR_APP_FAILED_TO_STOP": "No se pudo detener la aplicación {id}, consulta los registros para obtener más detalles", "APP_ERROR_APP_FAILED_TO_RESTART": "No se pudo iniciar la aplicación {id}, consulta los registros para obtener más detalles", @@ -54,7 +54,7 @@ "APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP": "Necesitas un dominio si la aplicación está expuesta", "APP_ERROR_INVALID_CONFIG": "La aplicación {id} tiene un archivo config.json inválido", "APP_INSTALL_FORM_CHOOSE_OPTION": "Elige una opción...", - "APP_INSTALL_FORM_DISPLAY_ON_GUEST_DASHBOARD": "Display on guest dashboard", + "APP_INSTALL_FORM_DISPLAY_ON_GUEST_DASHBOARD": "Mostrar en el panel de invitados", "APP_INSTALL_FORM_DOMAIN_NAME": "Nombre de dominio", "APP_INSTALL_FORM_DOMAIN_NAME_HINT": "Asegúrate de que este dominio exacto contiene un registro A apuntando a tu IP.", "APP_INSTALL_FORM_ERROR_BETWEEN_LENGTH": "{label} debe tener entre {min} y {max} caracteres", @@ -73,7 +73,7 @@ "APP_INSTALL_FORM_OPEN_PORT_HINT": "Open a port on the host? This app will be accessible at {internalIp}:{port}. (Easiest but less secure)", "APP_INSTALL_FORM_EXPOSE_LOCAL": "Exponer la aplicación en la red local", "APP_INSTALL_FORM_EXPOSE_LOCAL_HINT": "¿Exponer la aplicación en la red local? Esta aplicación será accesible en {appId}.{domain}. (Visita la página de configuración para configurar tu dominio local)", - "APP_INSTALL_FORM_RESET": "Reset app", + "APP_INSTALL_FORM_RESET": "Restablecer aplicación", "APP_INSTALL_FORM_SUBMIT_INSTALL": "Instalar", "APP_INSTALL_FORM_SUBMIT_UPDATE": "Actualizar", "APP_INSTALL_FORM_TITLE": "Instalar {name}", @@ -87,9 +87,9 @@ "APP_NEW": "NUEVA", "APP_RESET_FORM_SUBMIT": "Reset", "APP_RESET_FORM_SUBTITLE": "Todos los datos de esta aplicación se perderán.", - "APP_RESET_FORM_TITLE": "Reset {name} ?", + "APP_RESET_FORM_TITLE": "Restablecer {name} ?", "APP_RESET_FORM_WARNING": "Estás seguro? Esta acción no se puede deshacer.", - "APP_RESET_SUCCESS": "App {id} reset successfully", + "APP_RESET_SUCCESS": "App {id} reseteada correctamente", "APP_START_SUCCESS": "App {id} started successfully", "APP_BACKUP_TITLE": "Backup {name}", "APP_BACKUP_SUBTITLE": "A tar archive will be created in the backups folder to store your app's data.", From 69692e325306758ccad8e3842bcc8fce8eb7c11c Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:40:03 +0200 Subject: [PATCH 011/241] New translations en.json (Spanish) --- src/client/messages/es-ES.json | 100 ++++++++++++++++----------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/client/messages/es-ES.json b/src/client/messages/es-ES.json index e2feeaae85..41e7cd6664 100644 --- a/src/client/messages/es-ES.json +++ b/src/client/messages/es-ES.json @@ -85,11 +85,11 @@ "APP_LOGS_TAB_TITLE": "Trazas", "APP_LOGS_TAB_WRAP_LINES": "Ajustar líneas", "APP_NEW": "NUEVA", - "APP_RESET_FORM_SUBMIT": "Reset", + "APP_RESET_FORM_SUBMIT": "Restablecer", "APP_RESET_FORM_SUBTITLE": "Todos los datos de esta aplicación se perderán.", "APP_RESET_FORM_TITLE": "Restablecer {name} ?", "APP_RESET_FORM_WARNING": "Estás seguro? Esta acción no se puede deshacer.", - "APP_RESET_SUCCESS": "App {id} reseteada correctamente", + "APP_RESET_SUCCESS": "App {id} restablecida correctamente", "APP_START_SUCCESS": "App {id} started successfully", "APP_BACKUP_TITLE": "Backup {name}", "APP_BACKUP_SUBTITLE": "A tar archive will be created in the backups folder to store your app's data.", @@ -98,9 +98,9 @@ "APP_RESTORE_WARNING": "Do you really want to restore backup {id} made on {date}?", "APP_RESTORE_SUBTITLE": "All the current data of the app will be erased and replaced with the data from the backup. It is recommended to backup your app before restoring.", "APP_RESTORE_SUBMIT": "Restore", - "APP_BACKUPS_TAB_TITLE": "Backups", + "APP_BACKUPS_TAB_TITLE": "Copias de seguridad", "APP_SETTINGS_GENERAL_TITLE": "General", - "APP_SETTINGS_BACKUPS_TITLE": "Backups", + "APP_SETTINGS_BACKUPS_TITLE": "Copias de seguridad", "APP_STATUS_INSTALLING": "Instalando", "APP_STATUS_MISSING": "Falta", "APP_STATUS_RESETTING": "Resetting", @@ -116,7 +116,7 @@ "APP_STOP_FORM_SUBMIT": "Detener", "APP_STOP_FORM_SUBTITLE": "Todos los datos serán conservados", "APP_STOP_FORM_TITLE": "Detener {name}?", - "APP_STOP_SUCCESS": "App {id} stopped successfully", + "APP_STOP_SUCCESS": "App {id} detenida correctamente", "APP_RESTART_FORM_SUBMIT": "Reiniciar", "APP_RESTART_FORM_SUBTITLE": "Todos los datos serán conservados", "APP_RESTART_FORM_TITLE": "Reiniciar {name} ?", @@ -183,39 +183,39 @@ "AUTH_RESET_PASSWORD_BACK_TO_LOGIN": "Volver al inicio de sesión", "AUTH_RESET_PASSWORD_CANCEL": "Cancelar cambio de contraseña", "AUTH_RESET_PASSWORD_INSTRUCTIONS": "Ejecuta este comando en el servidor y recarga la página. Si ya has hecho esto previamente, es posible que la solicitud haya caducado. En ese caso, inténtelo de nuevo por favor", - "AUTH_RESET_PASSWORD_SUBMIT": "Reset password", - "AUTH_RESET_PASSWORD_SUCCESS": "Your password has been reset. You can now login with your new password. And your email {email}", - "AUTH_RESET_PASSWORD_SUCCESS_TITLE": "Password reset", - "AUTH_RESET_PASSWORD_TITLE": "Reset your password", + "AUTH_RESET_PASSWORD_SUBMIT": "Restablecer contraseña", + "AUTH_RESET_PASSWORD_SUCCESS": "Su contraseña ha sido restablecida. Ya puedes iniciar sesión con tu nueva contraseña. Y tu correo electrónico {email}", + "AUTH_RESET_PASSWORD_SUCCESS_TITLE": "Restablecer contraseña", + "AUTH_RESET_PASSWORD_TITLE": "Restablece tu contraseña", "AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app", - "AUTH_TOTP_SUBMIT": "Confirm", + "AUTH_TOTP_SUBMIT": "Confirmar", "AUTH_TOTP_TITLE": "Two-factor authentication", "BACKUPS_LIST": "Backups list", "BACKUPS_LIST_BACKUP_NOW": "Backup now", "BACKUPS_LIST_ROW_TITLE_ID": "ID", - "BACKUPS_LIST_ROW_TITLE_DATE": "Date", + "BACKUPS_LIST_ROW_TITLE_DATE": "Fecha", "BACKUPS_LIST_ROW_TITLE_ACTIONS": "Actions", - "BACKUPS_LIST_ROW_TITLE_SIZE": "Size", + "BACKUPS_LIST_ROW_TITLE_SIZE": "Tamaño", "BACKUPS_LIST_DELETE_SUCCESS": "Backup deleted successfully", "COMMON_CLOSE": "Cerrar", "DASHBOARD_CPU_SUBTITLE": "Uninstall apps to reduce load", "DASHBOARD_CPU_TITLE": "CPU load", "DASHBOARD_DISK_SPACE_SUBTITLE": "Used out of {total} GB", - "DASHBOARD_DISK_SPACE_TITLE": "Disk space", - "DASHBOARD_MEMORY_TITLE": "Memory used", + "DASHBOARD_DISK_SPACE_TITLE": "Espacio en disco", + "DASHBOARD_MEMORY_TITLE": "Memoria en uso", "DASHBOARD_TITLE": "Dashboard", "DASHBOARD_IP_WARNING_TITLE": "Configuración insegura", "DASHBOARD_IP_WARNING": "¡Advertencia, podrías estar en riesgo! Parece que estás accediendo a tu instancia a través de una dirección IP pública. Esto hace que tu panel de control y todas las aplicaciones que instales sean vulnerables a los atacantes", - "DELETE_BACKUP_MODAL_TITLE": "Delete backup", - "DELETE_BACKUP_MODAL_WARNING": "Are you sure you want to delete backup {id} made on {date}?", - "DELETE_BACKUP_MODAL_SUBTITLE": "This action cannot be undone", - "DELETE_BACKUP_MODAL_SUBMIT": "Delete", - "GUEST_DASHBOARD": "Guest dashboard", + "DELETE_BACKUP_MODAL_TITLE": "Eliminar la copia de seguridad", + "DELETE_BACKUP_MODAL_WARNING": "¿Estás seguro de que quieres eliminar la copia de seguridad {id} realizada en {date}?", + "DELETE_BACKUP_MODAL_SUBTITLE": "Esta acción no se puede deshacer", + "DELETE_BACKUP_MODAL_SUBMIT": "Eliminar", + "GUEST_DASHBOARD": "Panel de invitado", "GUEST_DASHBOARD_NO_APPS": "No apps to display", "GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Ask your administrator to add apps to the guest dashboard or login to see your apps.", - "HEADER_APPS": "My Apps", - "HEADER_APP_STORE": "App Store", - "HEADER_DARK_MODE": "Dark Mode", + "HEADER_APPS": "Mis Apps", + "HEADER_APP_STORE": "Tienda de aplicaciones", + "HEADER_DARK_MODE": "Modo oscuro", "HEADER_DASHBOARD": "Dashboard", "HEADER_LIGHT_MODE": "Light Mode", "HEADER_LOGIN": "Login", @@ -233,27 +233,27 @@ "LINKS_DELETE_SUBMIT": "Delete", "LINKS_DELETE_SUBTITLE": "Are you sure you want to delete this external link?", "LINKS_DELETE_SUCCESS": "Link deleted successfully", - "LINKS_DELETE_TITLE": "Delete external link", - "LINKS_EDIT_CONTEXT_MENU": "Edit", - "LINKS_EDIT_SUBMIT": "Save", - "LINKS_EDIT_SUCCESS": "Link edited successfully", - "LINKS_EDIT_TITLE": "Edit link", - "LINKS_FORM_ICON_PLACEHOLDER": "Link logo URL", - "LINKS_FORM_ICON_URL": "Icon URL", - "LINKS_FORM_LINK_TITLE": "Link title", - "LINKS_FORM_LINK_URL": "Link URL", - "LINKS_FROM_LINK_DESCRIPTION": "Link Description", - "MY_APPS_DEPRECATED": "This app is deprecated", - "MY_APPS_EMPTY_ACTION": "Go to app store", - "MY_APPS_EMPTY_SUBTITLE": "Install an app from the app store to get started", - "MY_APPS_EMPTY_TITLE": "No app installed", - "MY_APPS_TITLE": "My Apps", - "MY_APPS_UPDATE_ALL_FORM_SUBMIT": "Update all", - "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_1": "Do you want to update all your apps to the latest version?", - "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_2": "This will update all your apps to the latest version. Make sure you've read the release notes of the apps and you've backed up your app data.", - "MY_APPS_UPDATE_ALL_FORM_TITLE": "Update all apps", - "MY_APPS_UPDATE_ALL_IN_PROGRESS": "Updating all apps", - "MY_APPS_UPDATE_AVAILABLE": "Update available", + "LINKS_DELETE_TITLE": "Eliminar enlace externo", + "LINKS_EDIT_CONTEXT_MENU": "Editar", + "LINKS_EDIT_SUBMIT": "Guardar", + "LINKS_EDIT_SUCCESS": "Enlace editado correctamente", + "LINKS_EDIT_TITLE": "Editar enlace", + "LINKS_FORM_ICON_PLACEHOLDER": "URL del logo del enlace", + "LINKS_FORM_ICON_URL": "URL del icono", + "LINKS_FORM_LINK_TITLE": "Título del enlace", + "LINKS_FORM_LINK_URL": "URL del enlace", + "LINKS_FROM_LINK_DESCRIPTION": "Descripción del enlace", + "MY_APPS_DEPRECATED": "Esta aplicación está obsoleta", + "MY_APPS_EMPTY_ACTION": "Ir a la tienda de aplicaciones", + "MY_APPS_EMPTY_SUBTITLE": "Instala una aplicación desde la tienda de aplicaciones para empezar", + "MY_APPS_EMPTY_TITLE": "Ninguna aplicación instalada", + "MY_APPS_TITLE": "Mis aplicaciones", + "MY_APPS_UPDATE_ALL_FORM_SUBMIT": "Actualizar todas", + "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_1": "¿Quieres actualizar todas las aplicaciones a su última versión?", + "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_2": "Esto actualizará todas sus aplicaciones a la última versión. Asegúrate de haber leído las notas de la versión de las aplicaciones y de haber realizado una copia de seguridad de los datos de tu aplicación.", + "MY_APPS_UPDATE_ALL_FORM_TITLE": "Actualizar todas las aplicaciones", + "MY_APPS_UPDATE_ALL_IN_PROGRESS": "Actualizando todas las aplicaciones", + "MY_APPS_UPDATE_AVAILABLE": "Actualización disponible", "RUNTIPI": "Runtipi", "SERVER_ERROR_INVALID_LOCALE": "Local inválido", "SERVER_ERROR_NOT_ALLOWED_IN_DEMO": "No se permite en modo demo", @@ -270,7 +270,7 @@ "SETTINGS_ACTIONS_UPDATE_REPO_TITLE": "Update Repository", "SETTINGS_ACTIONS_UPDATE_REPO_SUBTITLE": "Use this button to update your appstore", "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_SUBTITLE": "This will reset your repository and pull the latest changes from GitHub", - "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_BUTTON": "Update", + "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_BUTTON": "Actualizar", "SETTINGS_ACTIONS_UPDATE_REPO_SUCCESS": "Appstore repository updated successfully", "SETTINGS_GENERAL_ALLOW_AUTO_THEMES": "Allow auto themes", "SETTINGS_GENERAL_ALLOW_AUTO_THEMES_HINT": "Be surprised by themes that change automatically based on the time of the year.", @@ -282,7 +282,7 @@ "SETTINGS_GENERAL_DOMAIN_NAME": "Nombre de dominio", "SETTINGS_GENERAL_DOMAIN_NAME_HINT": "Asegúrate de que este dominio exacto contiene un registro A apuntando a tu IP.", "SETTINGS_GENERAL_DOWNLOAD_CERTIFICATE": "Descargar certificado", - "SETTINGS_GENERAL_GUEST_DASHBOARD": "Enable guest dashboard", + "SETTINGS_GENERAL_GUEST_DASHBOARD": "Habilitar panel de invitado", "SETTINGS_GENERAL_GUEST_DASHBOARD_HINT": "This will allow non-authenticated users to see a limited dashboard and easily access the running apps on your instance.", "SETTINGS_GENERAL_INTERNAL_IP": "IP interna", "SETTINGS_GENERAL_INTERNAL_IP_HINT": "Dirección IP en la que escucha tu servidor.", @@ -309,13 +309,13 @@ "SETTINGS_SECURITY_CHANGE_PASSWORD_SUBTITLE": "Changing your password will log you out of all devices.", "SETTINGS_SECURITY_CHANGE_PASSWORD_TITLE": "Cambiar contraseña", "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_INVALID_USERNAME": "Debe ser una dirección de correo electrónico válida", - "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_NEW_USERNAME": "New username", + "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_NEW_USERNAME": "Nuevo nombre de usuario", "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_PASSWORD": "Contraseña", "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_PASSWORD_NEEDED_HINT": "Your password is required to change your username.", - "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_SUBMIT": "Change username", + "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_SUBMIT": "Cambiar de nombre de usuario", "SETTINGS_SECURITY_CHANGE_USERNAME_SUBTITLE": "Changing your username will log you out of all devices.", - "SETTINGS_SECURITY_CHANGE_USERNAME_SUCCESS": "Username changed successfully", - "SETTINGS_SECURITY_CHANGE_USERNAME_TITLE": "Change username", + "SETTINGS_SECURITY_CHANGE_USERNAME_SUCCESS": "Nombre de usuario cambiado con éxito", + "SETTINGS_SECURITY_CHANGE_USERNAME_TITLE": "Cambiar de nombre de usuario", "SETTINGS_SECURITY_DISABLE_2FA": "Desactivar la autenticación de doble factor", "SETTINGS_SECURITY_ENABLE_2FA": "Habilitar la autenticación de dos factores", "SETTINGS_SECURITY_ENTER_2FA_CODE": "Introduce el código de seis dígitos de tu aplicación de autenticación", @@ -339,6 +339,6 @@ "SYSTEM_ERROR_DEMO_MODE_LIMIT": "En la versión demo, únicamente pueden instalarse 6 aplicaciones. Por favor, desinstale otra aplicación para instalar una nueva.", "SYSTEM_ERROR_MAJOR_VERSION_UPDATE": "La versión principal ha cambiado. Por favor, actualice manualmente (instrucciones en las notas de la versión)", "SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN": "You must be logged in to perform this action", - "TIMEZONE_SELECTOR_LABEL": "Timezone", + "TIMEZONE_SELECTOR_LABEL": "Zona horaria", "TIMEZONE_SELECTOR_PLACEHOLDER": "Select a timezone" } From 6f6229f98af3b9a0cb4a831f4cd568e8d48e41e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 07:32:28 +0000 Subject: [PATCH 012/241] chore(deps-dev): bump @faker-js/faker from 8.4.1 to 9.0.3 Bumps [@faker-js/faker](https://github.com/faker-js/faker) from 8.4.1 to 9.0.3. - [Release notes](https://github.com/faker-js/faker/releases) - [Changelog](https://github.com/faker-js/faker/blob/next/CHANGELOG.md) - [Commits](https://github.com/faker-js/faker/compare/v8.4.1...v9.0.3) --- updated-dependencies: - dependency-name: "@faker-js/faker" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- packages/worker/package.json | 2 +- pnpm-lock.yaml | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index fd0246bf9d..ad42356590 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "devDependencies": { "@babel/core": "^7.25.8", "@biomejs/biome": "1.9.3", - "@faker-js/faker": "^8.4.1", + "@faker-js/faker": "^9.0.3", "@playwright/test": "^1.48.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.5.0", diff --git a/packages/worker/package.json b/packages/worker/package.json index 908dfe1bf5..294ba469a5 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -15,7 +15,7 @@ "author": "", "license": "ISC", "devDependencies": { - "@faker-js/faker": "^8.4.1", + "@faker-js/faker": "^9.0.3", "@sentry/esbuild-plugin": "^2.22.5", "@types/web-push": "^3.6.3", "dotenv-cli": "^7.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e580181bb7..a1d942f60c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -202,8 +202,8 @@ importers: specifier: 1.9.3 version: 1.9.3 '@faker-js/faker': - specifier: ^8.4.1 - version: 8.4.1 + specifier: ^9.0.3 + version: 9.0.3 '@playwright/test': specifier: ^1.48.0 version: 1.48.0 @@ -429,8 +429,8 @@ importers: version: 3.23.8 devDependencies: '@faker-js/faker': - specifier: ^8.4.1 - version: 8.4.1 + specifier: ^9.0.3 + version: 9.0.3 '@sentry/esbuild-plugin': specifier: ^2.22.5 version: 2.22.5 @@ -976,9 +976,9 @@ packages: cpu: [x64] os: [win32] - '@faker-js/faker@8.4.1': - resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + '@faker-js/faker@9.0.3': + resolution: {integrity: sha512-lWrrK4QNlFSU+13PL9jMbMKLJYXDFu3tQfayBsMXX7KL/GiQeqfB1CzHkqD5UHBUtPAuPo6XwGbMFNdVMZObRA==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} '@floating-ui/core@1.6.8': resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} @@ -5709,7 +5709,7 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true - '@faker-js/faker@8.4.1': {} + '@faker-js/faker@9.0.3': {} '@floating-ui/core@1.6.8': dependencies: From 607ec4d4af4150b9f832dfffe57983d7533e18fc Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Fri, 18 Oct 2024 08:46:49 +0200 Subject: [PATCH 013/241] chore: fix faker new signature for internet.password --- public/mockServiceWorker.js | 2 +- src/server/services/auth/auth.service.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index b65d2b97b5..a8262f093f 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -8,7 +8,7 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.4.8' +const PACKAGE_VERSION = '2.4.9' const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/src/server/services/auth/auth.service.test.ts b/src/server/services/auth/auth.service.test.ts index 5234248c57..ce803a8426 100644 --- a/src/server/services/auth/auth.service.test.ts +++ b/src/server/services/auth/auth.service.test.ts @@ -739,7 +739,7 @@ describe('AuthService', () => { // arrange const email = faker.internet.email(); const user = await createUser({ email }); - const newPassword = faker.internet.password(7); + const newPassword = faker.internet.password({ length: 7 }); mockQueries.getUserById.calledWith(user.id).mockResolvedValue(user); // act & assert From dda193eee989c2fc32f92c14a0e8922fa9da4de7 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 19 Oct 2024 19:10:04 +0200 Subject: [PATCH 014/241] refactor(archive-manager): switch to plain tar usage --- .../src/node/modules/archive/archive-manager.ts | 12 +++--------- .../shared/src/node/modules/backup/backup-manager.ts | 10 ++++++++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/shared/src/node/modules/archive/archive-manager.ts b/packages/shared/src/node/modules/archive/archive-manager.ts index 48cf57e69b..07e5fcbfa4 100644 --- a/packages/shared/src/node/modules/archive/archive-manager.ts +++ b/packages/shared/src/node/modules/archive/archive-manager.ts @@ -1,17 +1,11 @@ -import fs from 'node:fs'; -import gunzip from 'gunzip-maybe'; -import { extract, pack } from 'tar-fs'; +import { execAsync } from 'src/node/helpers/exec-async'; export class ArchiveManager { createTarGz = async (sourceDir: string, destinationFile: string) => { - return new Promise((resolve, reject) => { - pack(sourceDir).pipe(gunzip()).pipe(fs.createWriteStream(destinationFile)).on('finish', resolve).on('error', reject); - }); + return execAsync(`tar -czf ${destinationFile} -C ${sourceDir} .`); }; extractTarGz = async (sourceFile: string, destinationDir: string) => { - return new Promise((resolve, reject) => { - fs.createReadStream(sourceFile).pipe(gunzip()).pipe(extract(destinationDir)).on('finish', resolve).on('error', reject); - }); + return execAsync(`tar -xzf ${sourceFile} -C ${destinationDir}`); }; } diff --git a/packages/shared/src/node/modules/backup/backup-manager.ts b/packages/shared/src/node/modules/backup/backup-manager.ts index 467026fbc7..76690eb866 100644 --- a/packages/shared/src/node/modules/backup/backup-manager.ts +++ b/packages/shared/src/node/modules/backup/backup-manager.ts @@ -44,7 +44,10 @@ export class BackupManager { this.logger.info('Creating archive...'); // Create the archive - await this.archiveManager.createTarGz(tempDir, `${path.join(tempDir, backupName)}.tar.gz`); + const { stdout, stderr } = await this.archiveManager.createTarGz(tempDir, `${path.join(tempDir, backupName)}.tar.gz`); + this.logger.debug('--- archiveManager.createTarGz ---'); + this.logger.debug('stderr:', stderr); + this.logger.debug('stdout:', stdout); this.logger.info('Moving archive to backup directory...'); @@ -73,7 +76,10 @@ export class BackupManager { await fs.promises.mkdir(restoreDir, { recursive: true }); this.logger.info('Extracting archive...'); - await this.archiveManager.extractTarGz(archive, restoreDir); + const { stderr, stdout } = await this.archiveManager.extractTarGz(archive, restoreDir); + this.logger.debug('--- archiveManager.extractTarGz ---'); + this.logger.debug('stderr:', stderr); + this.logger.debug('stdout:', stdout); const appDataDirPath = path.join(this.appDataDir, appId); const appDirPath = path.join(this.dataDir, 'apps', appId); From 8071f2ac2aaca23986c066b3e56ba4a4411a3b6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:31:36 +0000 Subject: [PATCH 015/241] chore(deps): bump the minor-patch group across 1 directory with 18 updates Bumps the minor-patch group with 18 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.59.9` | `5.59.15` | | [bullmq](https://github.com/taskforcesh/bullmq) | `5.18.0` | `5.21.1` | | [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) | `0.34.1` | `0.35.2` | | [next-safe-action](https://github.com/TheEdoRan/next-safe-action) | `7.9.3` | `7.9.4` | | [sass](https://github.com/sass/dart-sass) | `1.79.4` | `1.80.2` | | [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `1.9.3` | `1.9.4` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.48.0` | `1.48.1` | | [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) | `6.5.0` | `6.6.2` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.7.5` | `22.7.6` | | [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `18.3.0` | `18.3.1` | | [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `2.1.2` | `2.1.3` | | [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `2.1.2` | `2.1.3` | | [memfs](https://github.com/streamich/memfs) | `4.13.0` | `4.14.0` | | [msw](https://github.com/mswjs/msw) | `2.4.9` | `2.4.11` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `2.1.2` | `2.1.3` | | [@hono/node-server](https://github.com/honojs/node-server) | `1.13.1` | `1.13.2` | | [yaml](https://github.com/eemeli/yaml) | `2.5.1` | `2.6.0` | | [@sentry/esbuild-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) | `2.22.5` | `2.22.6` | Updates `@tanstack/react-query` from 5.59.9 to 5.59.15 - [Release notes](https://github.com/TanStack/query/releases) - [Commits](https://github.com/TanStack/query/commits/v5.59.15/packages/react-query) Updates `bullmq` from 5.18.0 to 5.21.1 - [Release notes](https://github.com/taskforcesh/bullmq/releases) - [Commits](https://github.com/taskforcesh/bullmq/compare/v5.18.0...v5.21.1) Updates `drizzle-orm` from 0.34.1 to 0.35.2 - [Release notes](https://github.com/drizzle-team/drizzle-orm/releases) - [Commits](https://github.com/drizzle-team/drizzle-orm/compare/0.34.1...0.35.2) Updates `next-safe-action` from 7.9.3 to 7.9.4 - [Release notes](https://github.com/TheEdoRan/next-safe-action/releases) - [Commits](https://github.com/TheEdoRan/next-safe-action/compare/v7.9.3...v7.9.4) Updates `sass` from 1.79.4 to 1.80.2 - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.79.4...1.80.2) Updates `@biomejs/biome` from 1.9.3 to 1.9.4 - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/cli/v1.9.4/packages/@biomejs/biome) Updates `@playwright/test` from 1.48.0 to 1.48.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.48.0...v1.48.1) Updates `@testing-library/jest-dom` from 6.5.0 to 6.6.2 - [Release notes](https://github.com/testing-library/jest-dom/releases) - [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md) - [Commits](https://github.com/testing-library/jest-dom/compare/v6.5.0...v6.6.2) Updates `@types/node` from 22.7.5 to 22.7.6 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@types/react-dom` from 18.3.0 to 18.3.1 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) Updates `@vitest/coverage-v8` from 2.1.2 to 2.1.3 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.3/packages/coverage-v8) Updates `@vitest/ui` from 2.1.2 to 2.1.3 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.3/packages/ui) Updates `memfs` from 4.13.0 to 4.14.0 - [Release notes](https://github.com/streamich/memfs/releases) - [Changelog](https://github.com/streamich/memfs/blob/master/CHANGELOG.md) - [Commits](https://github.com/streamich/memfs/compare/v4.13.0...v4.14.0) Updates `msw` from 2.4.9 to 2.4.11 - [Release notes](https://github.com/mswjs/msw/releases) - [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md) - [Commits](https://github.com/mswjs/msw/compare/v2.4.9...v2.4.11) Updates `vitest` from 2.1.2 to 2.1.3 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.3/packages/vitest) Updates `@hono/node-server` from 1.13.1 to 1.13.2 - [Release notes](https://github.com/honojs/node-server/releases) - [Commits](https://github.com/honojs/node-server/compare/v1.13.1...v1.13.2) Updates `yaml` from 2.5.1 to 2.6.0 - [Release notes](https://github.com/eemeli/yaml/releases) - [Commits](https://github.com/eemeli/yaml/compare/v2.5.1...v2.6.0) Updates `@sentry/esbuild-plugin` from 2.22.5 to 2.22.6 - [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases) - [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/2.22.5...2.22.6) --- updated-dependencies: - dependency-name: "@tanstack/react-query" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: bullmq dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: drizzle-orm dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: next-safe-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: sass dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@biomejs/biome" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@playwright/test" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@testing-library/jest-dom" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@types/react-dom" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@vitest/coverage-v8" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@vitest/ui" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: memfs dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: msw dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: vitest dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@hono/node-server" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: yaml dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@sentry/esbuild-plugin" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch ... Signed-off-by: dependabot[bot] --- package.json | 30 +- packages/cache/package.json | 2 +- packages/db/package.json | 2 +- packages/worker/package.json | 14 +- pnpm-lock.yaml | 1007 ++++++++++++++++++++-------------- 5 files changed, 608 insertions(+), 447 deletions(-) diff --git a/package.json b/package.json index ad42356590..265d5e7d3d 100644 --- a/package.json +++ b/package.json @@ -42,14 +42,14 @@ "@sentry/nextjs": "^8.34.0", "@tabler/core": "1.0.0-beta21", "@tabler/icons-react": "^3.19.0", - "@tanstack/react-query": "^5.59.9", + "@tanstack/react-query": "^5.59.15", "@uidotdev/usehooks": "^2.4.1", "argon2": "^0.41.1", - "bullmq": "^5.18.0", + "bullmq": "^5.21.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "dompurify": "^3.1.7", - "drizzle-orm": "^0.34.1", + "drizzle-orm": "^0.35.2", "fs-extra": "^11.2.0", "geist": "^1.3.1", "inversify": "^6.0.2", @@ -61,7 +61,7 @@ "next": "14.2.15", "next-client-cookies": "^1.1.1", "next-intl": "^3.21.1", - "next-safe-action": "7.9.3", + "next-safe-action": "7.9.4", "pg": "^8.13.0", "qrcode.react": "^4.0.1", "react": "18.3.1", @@ -76,7 +76,7 @@ "rehype-raw": "^7.0.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.0", - "sass": "^1.79.4", + "sass": "^1.80.2", "semver": "^7.6.3", "sharp": "0.33.5", "socket.io-client": "^4.8.0", @@ -88,11 +88,11 @@ }, "devDependencies": { "@babel/core": "^7.25.8", - "@biomejs/biome": "1.9.3", + "@biomejs/biome": "1.9.4", "@faker-js/faker": "^9.0.3", - "@playwright/test": "^1.48.0", + "@playwright/test": "^1.48.1", "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.5.0", + "@testing-library/jest-dom": "^6.6.2", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", "@total-typescript/shoehorn": "^0.1.2", @@ -101,25 +101,25 @@ "@types/fs-extra": "^11.0.4", "@types/jsonwebtoken": "^9.0.7", "@types/lodash.merge": "^4.6.9", - "@types/node": "22.7.5", + "@types/node": "22.7.6", "@types/pg": "^8.11.10", "@types/react": "18.3.11", - "@types/react-dom": "18.3.0", + "@types/react-dom": "18.3.1", "@types/semver": "^7.5.8", "@types/uuid": "^10.0.0", "@types/validator": "^13.12.2", "@vitejs/plugin-react": "^4.3.2", - "@vitest/coverage-v8": "^2.1.2", - "@vitest/ui": "^2.1.2", + "@vitest/coverage-v8": "^2.1.3", + "@vitest/ui": "^2.1.3", "dotenv-cli": "^7.4.1", "jsdom": "^25.0.1", "knip": "^5.33.3", - "memfs": "^4.13.0", - "msw": "^2.4.9", + "memfs": "^4.14.0", + "msw": "^2.4.11", "next-router-mock": "^0.9.13", "typescript": "5.6.3", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^2.1.2", + "vitest": "^2.1.3", "vitest-mock-extended": "^2.0.2", "wait-for-expect": "^3.0.2" }, diff --git a/packages/cache/package.json b/packages/cache/package.json index 7dd226bfec..20ac6bb503 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -15,6 +15,6 @@ "ioredis": "^5.4.1" }, "devDependencies": { - "vitest": "^2.1.2" + "vitest": "^2.1.3" } } diff --git a/packages/db/package.json b/packages/db/package.json index 7ed64326ce..15c2982354 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -13,7 +13,7 @@ "dependencies": { "@runtipi/postgres-migrations": "^5.3.0", "@runtipi/shared": "workspace:^", - "drizzle-orm": "^0.34.1", + "drizzle-orm": "^0.35.2", "pg": "^8.13.0" } } diff --git a/packages/worker/package.json b/packages/worker/package.json index 294ba469a5..3940649274 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -16,28 +16,28 @@ "license": "ISC", "devDependencies": { "@faker-js/faker": "^9.0.3", - "@sentry/esbuild-plugin": "^2.22.5", + "@sentry/esbuild-plugin": "^2.22.6", "@types/web-push": "^3.6.3", "dotenv-cli": "^7.4.2", "esbuild": "^0.23.1", "knip": "^5.33.3", - "memfs": "^4.13.0", + "memfs": "^4.14.0", "nodemon": "^3.1.7", "typescript": "^5.6.3", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^2.1.2" + "vitest": "^2.1.3" }, "dependencies": { - "@hono/node-server": "^1.13.1", + "@hono/node-server": "^1.13.2", "@runtipi/cache": "workspace:^", "@runtipi/db": "workspace:^", "@runtipi/shared": "workspace:^", "@sentry/integrations": "^7.114.0", "@sentry/node": "^8.34.0", "ansi-to-html": "^0.7.2", - "bullmq": "^5.18.0", + "bullmq": "^5.21.1", "dotenv": "^16.4.5", - "drizzle-orm": "^0.34.1", + "drizzle-orm": "^0.35.2", "hono": "^4.6.5", "inversify": "^6.0.2", "reflect-metadata": "^0.2.2", @@ -45,7 +45,7 @@ "source-map-support": "^0.5.21", "systeminformation": "^5.23.5", "web-push": "^3.6.7", - "yaml": "^2.5.1", + "yaml": "^2.6.0", "zod": "^3.23.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1d942f60c..5f21a89a18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,28 +22,28 @@ importers: version: 12.0.1 '@radix-ui/react-context-menu': specifier: ^2.2.2 - version: 2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.2.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': specifier: ^1.1.2 - version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dropdown-menu': specifier: ^2.1.2 - version: 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-scroll-area': specifier: ^1.2.0 - version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-select': specifier: ^2.1.2 - version: 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.1.0 version: 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-switch': specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-tabs': specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@runtipi/cache': specifier: workspace:^ version: link:packages/cache @@ -58,7 +58,7 @@ importers: version: link:packages/shared '@sentry/nextjs': specifier: ^8.34.0 - version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1)(webpack@5.94.0) + version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react@18.3.1)(webpack@5.94.0) '@tabler/core': specifier: 1.0.0-beta21 version: 1.0.0-beta21 @@ -66,8 +66,8 @@ importers: specifier: ^3.19.0 version: 3.19.0(react@18.3.1) '@tanstack/react-query': - specifier: ^5.59.9 - version: 5.59.9(react@18.3.1) + specifier: ^5.59.15 + version: 5.59.15(react@18.3.1) '@uidotdev/usehooks': specifier: ^2.4.1 version: 2.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -75,8 +75,8 @@ importers: specifier: ^0.41.1 version: 0.41.1 bullmq: - specifier: ^5.18.0 - version: 5.18.0 + specifier: ^5.21.1 + version: 5.21.1 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -87,14 +87,14 @@ importers: specifier: ^3.1.7 version: 3.1.7 drizzle-orm: - specifier: ^0.34.1 - version: 0.34.1(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) + specifier: ^0.35.2 + version: 0.35.2(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) fs-extra: specifier: ^11.2.0 version: 11.2.0 geist: specifier: ^1.3.1 - version: 1.3.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4)) + version: 1.3.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2)) inversify: specifier: ^6.0.2 version: 6.0.2 @@ -115,16 +115,16 @@ importers: version: 7.1.0 next: specifier: 14.2.15 - version: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + version: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2) next-client-cookies: specifier: ^1.1.1 - version: 1.1.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1) + version: 1.1.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react@18.3.1) next-intl: specifier: ^3.21.1 - version: 3.21.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1) + version: 3.21.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react@18.3.1) next-safe-action: - specifier: 7.9.3 - version: 7.9.3(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8) + specifier: 7.9.4 + version: 7.9.4(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8) pg: specifier: ^8.13.0 version: 8.13.0 @@ -168,8 +168,8 @@ importers: specifier: ^4.0.0 version: 4.0.0 sass: - specifier: ^1.79.4 - version: 1.79.4 + specifier: ^1.80.2 + version: 1.80.2 semver: specifier: ^7.6.3 version: 7.6.3 @@ -199,23 +199,23 @@ importers: specifier: ^7.25.8 version: 7.25.8 '@biomejs/biome': - specifier: 1.9.3 - version: 1.9.3 + specifier: 1.9.4 + version: 1.9.4 '@faker-js/faker': specifier: ^9.0.3 version: 9.0.3 '@playwright/test': - specifier: ^1.48.0 - version: 1.48.0 + specifier: ^1.48.1 + version: 1.48.1 '@testing-library/dom': specifier: ^10.4.0 version: 10.4.0 '@testing-library/jest-dom': - specifier: ^6.5.0 - version: 6.5.0 + specifier: ^6.6.2 + version: 6.6.2 '@testing-library/react': specifier: ^16.0.1 - version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@testing-library/user-event': specifier: ^14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) @@ -238,8 +238,8 @@ importers: specifier: ^4.6.9 version: 4.6.9 '@types/node': - specifier: 22.7.5 - version: 22.7.5 + specifier: 22.7.6 + version: 22.7.6 '@types/pg': specifier: ^8.11.10 version: 8.11.10 @@ -247,8 +247,8 @@ importers: specifier: 18.3.11 version: 18.3.11 '@types/react-dom': - specifier: 18.3.0 - version: 18.3.0 + specifier: 18.3.1 + version: 18.3.1 '@types/semver': specifier: ^7.5.8 version: 7.5.8 @@ -260,13 +260,13 @@ importers: version: 13.12.2 '@vitejs/plugin-react': specifier: ^4.3.2 - version: 4.3.2(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)) + version: 4.3.2(vite@5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0)) '@vitest/coverage-v8': - specifier: ^2.1.2 - version: 2.1.2(vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1)) + specifier: ^2.1.3 + version: 2.1.3(vitest@2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0)) '@vitest/ui': - specifier: ^2.1.2 - version: 2.1.2(vitest@2.1.2) + specifier: ^2.1.3 + version: 2.1.3(vitest@2.1.3) dotenv-cli: specifier: ^7.4.1 version: 7.4.2 @@ -275,28 +275,28 @@ importers: version: 25.0.1 knip: specifier: ^5.33.3 - version: 5.33.3(@types/node@22.7.5)(typescript@5.6.3) + version: 5.33.3(@types/node@22.7.6)(typescript@5.6.3) memfs: - specifier: ^4.13.0 - version: 4.13.0 + specifier: ^4.14.0 + version: 4.14.0 msw: - specifier: ^2.4.9 - version: 2.4.9(typescript@5.6.3) + specifier: ^2.4.11 + version: 2.4.11(typescript@5.6.3) next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1) + version: 0.9.13(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react@18.3.1) typescript: specifier: 5.6.3 version: 5.6.3 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)) + version: 4.3.2(typescript@5.6.3)(vite@5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0)) vitest: - specifier: ^2.1.2 - version: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) + specifier: ^2.1.3 + version: 2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0) vitest-mock-extended: specifier: ^2.0.2 - version: 2.0.2(typescript@5.6.3)(vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1)) + version: 2.0.2(typescript@5.6.3)(vitest@2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0)) wait-for-expect: specifier: ^3.0.2 version: 3.0.2 @@ -311,8 +311,8 @@ importers: version: 5.4.1 devDependencies: vitest: - specifier: ^2.1.2 - version: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) + specifier: ^2.1.3 + version: 2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0) packages/db: dependencies: @@ -323,8 +323,8 @@ importers: specifier: workspace:^ version: link:../shared drizzle-orm: - specifier: ^0.34.1 - version: 0.34.1(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) + specifier: ^0.35.2 + version: 0.35.2(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) pg: specifier: ^8.13.0 version: 8.13.0 @@ -371,8 +371,8 @@ importers: packages/worker: dependencies: '@hono/node-server': - specifier: ^1.13.1 - version: 1.13.1(hono@4.6.5) + specifier: ^1.13.2 + version: 1.13.2(hono@4.6.5) '@runtipi/cache': specifier: workspace:^ version: link:../cache @@ -392,14 +392,14 @@ importers: specifier: ^0.7.2 version: 0.7.2 bullmq: - specifier: ^5.18.0 - version: 5.18.0 + specifier: ^5.21.1 + version: 5.21.1 dotenv: specifier: ^16.4.5 version: 16.4.5 drizzle-orm: - specifier: ^0.34.1 - version: 0.34.1(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) + specifier: ^0.35.2 + version: 0.35.2(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1) hono: specifier: ^4.6.5 version: 4.6.5 @@ -422,8 +422,8 @@ importers: specifier: ^3.6.7 version: 3.6.7 yaml: - specifier: ^2.5.1 - version: 2.5.1 + specifier: ^2.6.0 + version: 2.6.0 zod: specifier: ^3.23.8 version: 3.23.8 @@ -432,8 +432,8 @@ importers: specifier: ^9.0.3 version: 9.0.3 '@sentry/esbuild-plugin': - specifier: ^2.22.5 - version: 2.22.5 + specifier: ^2.22.6 + version: 2.22.6 '@types/web-push': specifier: ^3.6.3 version: 3.6.3 @@ -445,10 +445,10 @@ importers: version: 0.23.1 knip: specifier: ^5.33.3 - version: 5.33.3(@types/node@22.7.5)(typescript@5.6.3) + version: 5.33.3(@types/node@22.7.6)(typescript@5.6.3) memfs: - specifier: ^4.13.0 - version: 4.13.0 + specifier: ^4.14.0 + version: 4.14.0 nodemon: specifier: ^3.1.7 version: 3.1.7 @@ -457,10 +457,10 @@ importers: version: 5.6.3 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)) + version: 4.3.2(typescript@5.6.3)(vite@5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0)) vitest: - specifier: ^2.1.2 - version: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) + specifier: ^2.1.3 + version: 2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0) packages: @@ -581,55 +581,55 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@biomejs/biome@1.9.3': - resolution: {integrity: sha512-POjAPz0APAmX33WOQFGQrwLvlu7WLV4CFJMlB12b6ZSg+2q6fYu9kZwLCOA+x83zXfcPd1RpuWOKJW0GbBwLIQ==} + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@1.9.3': - resolution: {integrity: sha512-QZzD2XrjJDUyIZK+aR2i5DDxCJfdwiYbUKu9GzkCUJpL78uSelAHAPy7m0GuPMVtF/Uo+OKv97W3P9nuWZangQ==} + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@1.9.3': - resolution: {integrity: sha512-vSCoIBJE0BN3SWDFuAY/tRavpUtNoqiceJ5PrU3xDfsLcm/U6N93JSM0M9OAiC/X7mPPfejtr6Yc9vSgWlEgVw==} + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@1.9.3': - resolution: {integrity: sha512-VBzyhaqqqwP3bAkkBrhVq50i3Uj9+RWuj+pYmXrMDgjS5+SKYGE56BwNw4l8hR3SmYbLSbEo15GcV043CDSk+Q==} + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@1.9.3': - resolution: {integrity: sha512-vJkAimD2+sVviNTbaWOGqEBy31cW0ZB52KtpVIbkuma7PlfII3tsLhFa+cwbRAcRBkobBBhqZ06hXoZAN8NODQ==} + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@1.9.3': - resolution: {integrity: sha512-TJmnOG2+NOGM72mlczEsNki9UT+XAsMFAOo8J0me/N47EJ/vkLXxf481evfHLlxMejTY6IN8SdRSiPVLv6AHlA==} + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@1.9.3': - resolution: {integrity: sha512-x220V4c+romd26Mu1ptU+EudMXVS4xmzKxPVb9mgnfYlN4Yx9vD5NZraSx/onJnd3Gh/y8iPUdU5CDZJKg9COA==} + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@1.9.3': - resolution: {integrity: sha512-lg/yZis2HdQGsycUvHWSzo9kOvnGgvtrYRgoCEwPBwwAL8/6crOp3+f47tPwI/LI1dZrhSji7PNsGKGHbwyAhw==} + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@1.9.3': - resolution: {integrity: sha512-cQMy2zanBkVLpmmxXdK6YePzmZx0s5Z7KEnwmrW54rcXK3myCNbQa09SwGZ8i/8sLw0H9F3X7K4rxVNGU8/D4Q==} + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -1010,8 +1010,8 @@ packages: '@formatjs/intl-localematcher@0.5.4': resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} - '@hono/node-server@1.13.1': - resolution: {integrity: sha512-TSxE6cT5RHnawbjnveexVN7H2Dpn1YaLxQrCOLCUwD+hFbqbFsnJBgdWcYtASqtWVjA+Qgi8uqFug39GsHjo5A==} + '@hono/node-server@1.13.2': + resolution: {integrity: sha512-0w8nEmAyx0Ul0CQp8BL2VtAG4YVdpzXd/mvvM+l0G5Oq22pUyHS+KeFFPSY+czLOF5NAiV3MUNPD1n14Ol5svg==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 @@ -1134,8 +1134,8 @@ packages: resolution: {integrity: sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==} engines: {node: '>=18'} - '@inquirer/figures@1.0.6': - resolution: {integrity: sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==} + '@inquirer/figures@1.0.7': + resolution: {integrity: sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==} engines: {node: '>=18'} '@inquirer/type@1.5.5': @@ -1226,8 +1226,8 @@ packages: cpu: [x64] os: [win32] - '@mswjs/interceptors@0.35.8': - resolution: {integrity: sha512-PFfqpHplKa7KMdoQdj5td03uG05VK2Ng1dG0sP4pT9h0dGSX2v9txYt/AnrzPb/vAmfyBBC0NQV7VaBEX+efgQ==} + '@mswjs/interceptors@0.35.9': + resolution: {integrity: sha512-SSnyl/4ni/2ViHKkiZb8eajA/eN1DNFaHjhGiLUdZvDz6PKF4COSf/17xqSz64nOo2Ia29SA6B2KNCsyCbVmaQ==} engines: {node: '>=18'} '@next/env@14.2.15': @@ -1517,6 +1517,82 @@ packages: '@otplib/plugin-thirty-two@12.0.1': resolution: {integrity: sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==} + '@parcel/watcher-android-arm64@2.4.1': + resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.4.1': + resolution: {integrity: sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.4.1': + resolution: {integrity: sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.4.1': + resolution: {integrity: sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.4.1': + resolution: {integrity: sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.4.1': + resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.4.1': + resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.4.1': + resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.4.1': + resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.4.1': + resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.4.1': + resolution: {integrity: sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.4.1': + resolution: {integrity: sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.4.1': + resolution: {integrity: sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==} + engines: {node: '>= 10.0.0'} + '@phc/format@1.0.0': resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} engines: {node: '>=10'} @@ -1525,8 +1601,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.48.0': - resolution: {integrity: sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==} + '@playwright/test@1.48.1': + resolution: {integrity: sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==} engines: {node: '>=18'} hasBin: true @@ -2031,8 +2107,8 @@ packages: resolution: {integrity: sha512-OlHA+i+vnQHRIdry4glpiS/xTOtgjmpXOt6IBOUqynx5Jd/iK1+fj+t8CckqOx9wRacO/hru2wfW/jFq0iViLg==} engines: {node: '>= 14'} - '@sentry/babel-plugin-component-annotate@2.22.5': - resolution: {integrity: sha512-+93qwB9vTX1nj4hD8AMWowXZsZVkvmP9OwTqSh5d4kOeiJ+dZftUk4+FKeKkAX9lvY2reyHV8Gms5mo67c27RQ==} + '@sentry/babel-plugin-component-annotate@2.22.6': + resolution: {integrity: sha512-V2g1Y1I5eSe7dtUVMBvAJr8BaLRr4CLrgNgtPaZyMT4Rnps82SrZ5zqmEkLXPumlXhLUWR6qzoMNN2u+RXVXfQ==} engines: {node: '>= 14'} '@sentry/browser@8.34.0': @@ -2043,8 +2119,8 @@ packages: resolution: {integrity: sha512-DeoUl0WffcqZZRl5Wy9aHvX4WfZbbWt0QbJ7NJrcEViq+dRAI2FQTYECFLwdZi5Gtb3oyqZICO+P7k8wDnzsjQ==} engines: {node: '>= 14'} - '@sentry/bundler-plugin-core@2.22.5': - resolution: {integrity: sha512-nfvTthV0aNM9/MwgnCi1WjAlCtau1I4kw6+oZIDOwJRDqGNziz517mYRXSsvCUebtGxDZtPcF7hSEBMSHjpncA==} + '@sentry/bundler-plugin-core@2.22.6': + resolution: {integrity: sha512-1esQdgSUCww9XAntO4pr7uAM5cfGhLsgTK9MEwAKNfvpMYJi9NUTYa3A7AZmdA8V6107Lo4OD7peIPrDRbaDCg==} engines: {node: '>= 14'} '@sentry/cli-darwin@2.37.0': @@ -2101,8 +2177,8 @@ packages: resolution: {integrity: sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA==} engines: {node: '>=14.18'} - '@sentry/esbuild-plugin@2.22.5': - resolution: {integrity: sha512-I9fgTbydbZA5cwFflTLRRwEtkee0tvajjW3gh3Lb2ziMA4tnX1VU0iwoWMdiLHVDn3mLUB++L0puYa+QHLY6+Q==} + '@sentry/esbuild-plugin@2.22.6': + resolution: {integrity: sha512-yyp4lbO6189PhhH7VYITCQY9cmd5Mr5akIzENXUYGsea6uPRPxpgqpydsbrpn6us8qGA8MVE6q/ZmlkgQwcj4g==} engines: {node: '>= 14'} '@sentry/integrations@7.114.0': @@ -2247,11 +2323,11 @@ packages: '@tabler/icons@3.19.0': resolution: {integrity: sha512-A4WEWqpdbTfnpFEtwXqwAe9qf9sp1yRPvzppqAuwcoF0q5YInqB+JkJtSFToCyBpPVeLxJUxxkapLvt2qQgnag==} - '@tanstack/query-core@5.59.9': - resolution: {integrity: sha512-vFGnblfJOKlOPyTR5M0ohWKb/03eGubh5KuGyzsDfc7VQ6F0nsB75kQIoLpwp3Wfj6fKv0wGoTUX8BsIfhxDfw==} + '@tanstack/query-core@5.59.13': + resolution: {integrity: sha512-Oou0bBu/P8+oYjXsJQ11j+gcpLAMpqW42UlokQYEz4dE7+hOtVO9rVuolJKgEccqzvyFzqX4/zZWY+R/v1wVsQ==} - '@tanstack/react-query@5.59.9': - resolution: {integrity: sha512-g2cbiw/ZIIrnUaQqhGtarTAsuLdKDNLtY5HNfRHVWY9kHDj96M4qs4ygJxHc119tPQpzZe4i9W7d2Gc2Gvng2A==} + '@tanstack/react-query@5.59.15': + resolution: {integrity: sha512-QbVlAkTI78wB4Mqgf2RDmgC0AOiJqer2c5k9STOOSXGv1S6ZkY37r/6UpE8DbQ2Du0ohsdoXgFNEyv+4eDoPEw==} peerDependencies: react: ^18 || ^19 @@ -2259,8 +2335,8 @@ packages: resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.5.0': - resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} + '@testing-library/jest-dom@6.6.2': + resolution: {integrity: sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} '@testing-library/react@16.0.1': @@ -2368,8 +2444,8 @@ packages: '@types/mysql@2.15.26': resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/node@22.7.5': - resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + '@types/node@22.7.6': + resolution: {integrity: sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -2386,8 +2462,8 @@ packages: '@types/prop-types@15.7.13': resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} - '@types/react-dom@18.3.0': - resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} '@types/react-transition-group@4.4.11': resolution: {integrity: sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==} @@ -2453,22 +2529,22 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitest/coverage-v8@2.1.2': - resolution: {integrity: sha512-b7kHrFrs2urS0cOk5N10lttI8UdJ/yP3nB4JYTREvR5o18cR99yPpK4gK8oQgI42BVv0ILWYUSYB7AXkAUDc0g==} + '@vitest/coverage-v8@2.1.3': + resolution: {integrity: sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==} peerDependencies: - '@vitest/browser': 2.1.2 - vitest: 2.1.2 + '@vitest/browser': 2.1.3 + vitest: 2.1.3 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@2.1.2': - resolution: {integrity: sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==} + '@vitest/expect@2.1.3': + resolution: {integrity: sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==} - '@vitest/mocker@2.1.2': - resolution: {integrity: sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==} + '@vitest/mocker@2.1.3': + resolution: {integrity: sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==} peerDependencies: - '@vitest/spy': 2.1.2 + '@vitest/spy': 2.1.3 msw: ^2.3.5 vite: ^5.0.0 peerDependenciesMeta: @@ -2477,25 +2553,25 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.2': - resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==} + '@vitest/pretty-format@2.1.3': + resolution: {integrity: sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==} - '@vitest/runner@2.1.2': - resolution: {integrity: sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==} + '@vitest/runner@2.1.3': + resolution: {integrity: sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==} - '@vitest/snapshot@2.1.2': - resolution: {integrity: sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==} + '@vitest/snapshot@2.1.3': + resolution: {integrity: sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==} - '@vitest/spy@2.1.2': - resolution: {integrity: sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==} + '@vitest/spy@2.1.3': + resolution: {integrity: sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==} - '@vitest/ui@2.1.2': - resolution: {integrity: sha512-92gcNzkDnmxOxyHzQrQYRsoV9Q0Aay0r4QMLnV+B+lbqlUWa8nDg9ivyLV5mMVTtGirHsYUGGh/zbIA55gBZqA==} + '@vitest/ui@2.1.3': + resolution: {integrity: sha512-2XwTrHVJw3t9NYES26LQUYy51ZB8W4bRPgqUH2Eyda3kIuOlYw1ZdPNU22qcVlUVx4WKgECFQOSXuopsczuVjQ==} peerDependencies: - vitest: 2.1.2 + vitest: 2.1.3 - '@vitest/utils@2.1.2': - resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==} + '@vitest/utils@2.1.3': + resolution: {integrity: sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==} '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -2566,6 +2642,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.13.0: + resolution: {integrity: sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -2637,8 +2718,8 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - aria-query@5.3.1: - resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} asn1.js@5.4.1: @@ -2728,8 +2809,8 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bullmq@5.18.0: - resolution: {integrity: sha512-j7gMwvbe/un51W/JeS2V26s1q3whFrEaH98EMjlJWaJDXGgrBTcTx/iVK6Rte63dv3ZpPBhsxol/h6oPujMe6g==} + bullmq@5.21.1: + resolution: {integrity: sha512-+yvsd5LkbWkTW2K5C/1s8h1+gGK4F9wVfKM6AJUBSWGsbfWHXnni0Se7xHj1dieVkx6XEsfCzFtO6kZnD+mtHQ==} busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -2960,6 +3041,11 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -2994,8 +3080,8 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} - drizzle-orm@0.34.1: - resolution: {integrity: sha512-t+zCwyWWt8xTqtYV4doE/xYmT7hpv1L8pQ66zddEz+3VWyedBBtctjMAp22mAJPfyWurRQXUJ1nrTtqLq+DqNA==} + drizzle-orm@0.35.2: + resolution: {integrity: sha512-bLQtRchl8QvRo2MyG6kcZC90UDzR7Ubir4YwOHV3cZPdJbF+4jU/Yt0QOczsoXe25wLRt6CtCWLXtSDQKft3yg==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' @@ -3216,8 +3302,8 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - fdir@6.4.0: - resolution: {integrity: sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==} + fdir@6.4.2: + resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -3709,8 +3795,8 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - magic-string@0.30.11: - resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} magic-string@0.30.8: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} @@ -3774,8 +3860,8 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - memfs@4.13.0: - resolution: {integrity: sha512-dIs5KGy24fbdDhIAg0RxXpFqQp3RwL6wgSMRF9OSuphL/Uc9a4u2/SDJKPLj/zUgtOGKuHrRMrj563+IErj4Cg==} + memfs@4.14.0: + resolution: {integrity: sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==} engines: {node: '>= 4.0.0'} memoize-one@6.0.0: @@ -3933,8 +4019,8 @@ packages: msgpackr@1.11.0: resolution: {integrity: sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==} - msw@2.4.9: - resolution: {integrity: sha512-1m8xccT6ipN4PTqLinPwmzhxQREuxaEJYdx4nIbggxP8aM7r1e71vE7RtOUSQoAm1LydjGfZKy7370XD/tsuYg==} + msw@2.4.11: + resolution: {integrity: sha512-TVEw9NOPTc6ufOQLJ53234S9NBRxQbu7xFMxs+OCP43JQcNEIOKiZHxEm2nDzYIrwccoIhUxUf8wr99SukD76A==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -3977,8 +4063,8 @@ packages: next: '>=10.0.0' react: '>=17.0.0' - next-safe-action@7.9.3: - resolution: {integrity: sha512-2GH7/iRiM5R/y6sIQZsNHGeRr/iKQJsg8ejP63WhTS7fXS9KzxVbEKrWwLNNhL33V9cn0448cPSI/aiSK/PUbA==} + next-safe-action@7.9.4: + resolution: {integrity: sha512-ZQtkLzaSaf+3vMAHS5G7U1nG/Nw1o++25br52J1dHiAGU0RbyE8erhoie7A5HsypmjbdTNMZotTvWLYt1PGeDA==} engines: {node: '>=18.17'} peerDependencies: '@sinclair/typebox': '>= 0.33.3' @@ -4019,6 +4105,9 @@ packages: node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-addon-api@8.1.0: resolution: {integrity: sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==} engines: {node: ^18 || ^20 || >= 21} @@ -4184,6 +4273,9 @@ packages: picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -4192,13 +4284,13 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - playwright-core@1.48.0: - resolution: {integrity: sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==} + playwright-core@1.48.1: + resolution: {integrity: sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==} engines: {node: '>=18'} hasBin: true - playwright@1.48.0: - resolution: {integrity: sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==} + playwright@1.48.1: + resolution: {integrity: sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==} engines: {node: '>=18'} hasBin: true @@ -4512,8 +4604,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.79.4: - resolution: {integrity: sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==} + sass@1.80.2: + resolution: {integrity: sha512-9wXY8cGBlUmoUoT+vwOZOFCiS+naiWVjqlreN9ar9PudXbGwlMTFwCR5K9kB4dFumJ6ib98wZyAObJKsWf1nAA==} engines: {node: '>=14.0.0'} hasBin: true @@ -4760,8 +4852,8 @@ packages: uglify-js: optional: true - terser@5.34.1: - resolution: {integrity: sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==} + terser@5.36.0: + resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} engines: {node: '>=10'} hasBin: true @@ -4794,8 +4886,8 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@0.3.0: - resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} tinyglobby@0.2.9: resolution: {integrity: sha512-8or1+BGEdk1Zkkw2ii16qSS7uVrQJPre5A9o/XkWPATkk23FZh/15BKFxPnlTy6vkljZxLqYCzzBMj30ZrSvjw==} @@ -4885,8 +4977,8 @@ packages: typescript: optional: true - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tslib@2.8.0: + resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} @@ -5019,8 +5111,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@2.1.2: - resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==} + vite-node@2.1.3: + resolution: {integrity: sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -5032,8 +5124,8 @@ packages: vite: optional: true - vite@5.4.8: - resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} + vite@5.4.9: + resolution: {integrity: sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5069,15 +5161,15 @@ packages: typescript: 3.x || 4.x || 5.x vitest: '>=2.0.0' - vitest@2.1.2: - resolution: {integrity: sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==} + vitest@2.1.3: + resolution: {integrity: sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.2 - '@vitest/ui': 2.1.2 + '@vitest/browser': 2.1.3 + '@vitest/ui': 2.1.3 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -5238,8 +5330,8 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.5.1: - resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} + yaml@2.6.0: + resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} engines: {node: '>= 14'} hasBin: true @@ -5443,39 +5535,39 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@biomejs/biome@1.9.3': + '@biomejs/biome@1.9.4': optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.3 - '@biomejs/cli-darwin-x64': 1.9.3 - '@biomejs/cli-linux-arm64': 1.9.3 - '@biomejs/cli-linux-arm64-musl': 1.9.3 - '@biomejs/cli-linux-x64': 1.9.3 - '@biomejs/cli-linux-x64-musl': 1.9.3 - '@biomejs/cli-win32-arm64': 1.9.3 - '@biomejs/cli-win32-x64': 1.9.3 - - '@biomejs/cli-darwin-arm64@1.9.3': + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + + '@biomejs/cli-darwin-arm64@1.9.4': optional: true - '@biomejs/cli-darwin-x64@1.9.3': + '@biomejs/cli-darwin-x64@1.9.4': optional: true - '@biomejs/cli-linux-arm64-musl@1.9.3': + '@biomejs/cli-linux-arm64-musl@1.9.4': optional: true - '@biomejs/cli-linux-arm64@1.9.3': + '@biomejs/cli-linux-arm64@1.9.4': optional: true - '@biomejs/cli-linux-x64-musl@1.9.3': + '@biomejs/cli-linux-x64-musl@1.9.4': optional: true - '@biomejs/cli-linux-x64@1.9.3': + '@biomejs/cli-linux-x64@1.9.4': optional: true - '@biomejs/cli-win32-arm64@1.9.3': + '@biomejs/cli-win32-arm64@1.9.4': optional: true - '@biomejs/cli-win32-x64@1.9.3': + '@biomejs/cli-win32-x64@1.9.4': optional: true '@bundled-es-modules/cookie@2.0.0': @@ -5501,7 +5593,7 @@ snapshots: '@emnapi/runtime@1.2.0': dependencies: - tslib: 2.7.0 + tslib: 2.8.0 optional: true '@emotion/babel-plugin@11.12.0': @@ -5732,28 +5824,28 @@ snapshots: dependencies: '@formatjs/fast-memoize': 2.2.0 '@formatjs/intl-localematcher': 0.5.4 - tslib: 2.7.0 + tslib: 2.8.0 '@formatjs/fast-memoize@2.2.0': dependencies: - tslib: 2.7.0 + tslib: 2.8.0 '@formatjs/icu-messageformat-parser@2.7.9': dependencies: '@formatjs/ecma402-abstract': 2.1.0 '@formatjs/icu-skeleton-parser': 1.8.3 - tslib: 2.7.0 + tslib: 2.8.0 '@formatjs/icu-skeleton-parser@1.8.3': dependencies: '@formatjs/ecma402-abstract': 2.1.0 - tslib: 2.7.0 + tslib: 2.8.0 '@formatjs/intl-localematcher@0.5.4': dependencies: - tslib: 2.7.0 + tslib: 2.8.0 - '@hono/node-server@1.13.1(hono@4.6.5)': + '@hono/node-server@1.13.2(hono@4.6.5)': dependencies: hono: 4.6.5 @@ -5843,10 +5935,10 @@ snapshots: '@inquirer/core@9.2.1': dependencies: - '@inquirer/figures': 1.0.6 + '@inquirer/figures': 1.0.7 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -5856,7 +5948,7 @@ snapshots: wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.2 - '@inquirer/figures@1.0.6': {} + '@inquirer/figures@1.0.7': {} '@inquirer/type@1.5.5': dependencies: @@ -5901,21 +5993,21 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@jsonjoy.com/base64@1.1.2(tslib@2.7.0)': + '@jsonjoy.com/base64@1.1.2(tslib@2.8.0)': dependencies: - tslib: 2.7.0 + tslib: 2.8.0 - '@jsonjoy.com/json-pack@1.1.0(tslib@2.7.0)': + '@jsonjoy.com/json-pack@1.1.0(tslib@2.8.0)': dependencies: - '@jsonjoy.com/base64': 1.1.2(tslib@2.7.0) - '@jsonjoy.com/util': 1.5.0(tslib@2.7.0) + '@jsonjoy.com/base64': 1.1.2(tslib@2.8.0) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.0) hyperdyperid: 1.2.0 - thingies: 1.21.0(tslib@2.7.0) - tslib: 2.7.0 + thingies: 1.21.0(tslib@2.8.0) + tslib: 2.8.0 - '@jsonjoy.com/util@1.5.0(tslib@2.7.0)': + '@jsonjoy.com/util@1.5.0(tslib@2.8.0)': dependencies: - tslib: 2.7.0 + tslib: 2.8.0 '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true @@ -5935,7 +6027,7 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true - '@mswjs/interceptors@0.35.8': + '@mswjs/interceptors@0.35.9': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -6266,14 +6358,70 @@ snapshots: '@otplib/core': 12.0.1 thirty-two: 1.0.2 + '@parcel/watcher-android-arm64@2.4.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.4.1': + optional: true + + '@parcel/watcher-darwin-x64@2.4.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.4.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.4.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.4.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.4.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.4.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.4.1': + optional: true + + '@parcel/watcher-win32-arm64@2.4.1': + optional: true + + '@parcel/watcher-win32-ia32@2.4.1': + optional: true + + '@parcel/watcher-win32-x64@2.4.1': + optional: true + + '@parcel/watcher@2.4.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.4.1 + '@parcel/watcher-darwin-arm64': 2.4.1 + '@parcel/watcher-darwin-x64': 2.4.1 + '@parcel/watcher-freebsd-x64': 2.4.1 + '@parcel/watcher-linux-arm-glibc': 2.4.1 + '@parcel/watcher-linux-arm64-glibc': 2.4.1 + '@parcel/watcher-linux-arm64-musl': 2.4.1 + '@parcel/watcher-linux-x64-glibc': 2.4.1 + '@parcel/watcher-linux-x64-musl': 2.4.1 + '@parcel/watcher-win32-arm64': 2.4.1 + '@parcel/watcher-win32-ia32': 2.4.1 + '@parcel/watcher-win32-x64': 2.4.1 + '@phc/format@1.0.0': {} '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.48.0': + '@playwright/test@1.48.1': dependencies: - playwright: 1.48.0 + playwright: 1.48.1 '@polka/url@1.0.0-next.28': {} @@ -6291,26 +6439,26 @@ snapshots: '@radix-ui/primitive@1.1.0': {} - '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(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.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: @@ -6318,19 +6466,19 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 - '@radix-ui/react-context-menu@2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-context-menu@2.2.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 '@radix-ui/react-context@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: @@ -6344,18 +6492,18 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 - '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) aria-hidden: 1.2.4 @@ -6364,7 +6512,7 @@ snapshots: react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 '@radix-ui/react-direction@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: @@ -6372,33 +6520,33 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 - '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-dropdown-menu@2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dropdown-menu@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.11)(react@18.3.1)': dependencies: @@ -6406,16 +6554,16 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 - '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 '@radix-ui/react-id@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: @@ -6424,22 +6572,22 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 - '@radix-ui/react-menu@2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-menu@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) aria-hidden: 1.2.4 @@ -6448,15 +6596,15 @@ snapshots: react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.11)(react@18.3.1) @@ -6466,19 +6614,19 @@ snapshots: react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) @@ -6486,79 +6634,79 @@ snapshots: react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-scroll-area@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-scroll-area@1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.0 '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-select@2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-select@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.0 '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 '@radix-ui/react-slot@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: @@ -6567,12 +6715,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 - '@radix-ui/react-switch@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-switch@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.11)(react@18.3.1) @@ -6580,23 +6728,23 @@ snapshots: react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 - '@radix-ui/react-tabs@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-tabs@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.11)(react@18.3.1)': dependencies: @@ -6644,14 +6792,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 - '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(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.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 '@radix-ui/rect@1.1.0': {} @@ -6662,7 +6810,7 @@ snapshots: estree-walker: 2.0.2 glob: 10.4.5 is-reference: 1.2.1 - magic-string: 0.30.11 + magic-string: 0.30.12 optionalDependencies: rollup: 3.29.5 @@ -6757,7 +6905,7 @@ snapshots: '@sentry/babel-plugin-component-annotate@2.22.3': {} - '@sentry/babel-plugin-component-annotate@2.22.5': {} + '@sentry/babel-plugin-component-annotate@2.22.6': {} '@sentry/browser@8.34.0': dependencies: @@ -6783,10 +6931,10 @@ snapshots: - encoding - supports-color - '@sentry/bundler-plugin-core@2.22.5': + '@sentry/bundler-plugin-core@2.22.6': dependencies: '@babel/core': 7.25.8 - '@sentry/babel-plugin-component-annotate': 2.22.5 + '@sentry/babel-plugin-component-annotate': 2.22.6 '@sentry/cli': 2.37.0 dotenv: 16.4.5 find-up: 5.0.0 @@ -6847,9 +6995,9 @@ snapshots: '@sentry/types': 8.34.0 '@sentry/utils': 8.34.0 - '@sentry/esbuild-plugin@2.22.5': + '@sentry/esbuild-plugin@2.22.6': dependencies: - '@sentry/bundler-plugin-core': 2.22.5 + '@sentry/bundler-plugin-core': 2.22.6 unplugin: 1.0.1 uuid: 9.0.1 transitivePeerDependencies: @@ -6863,7 +7011,7 @@ snapshots: '@sentry/utils': 7.114.0 localforage: 1.10.0 - '@sentry/nextjs@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1)(webpack@5.94.0)': + '@sentry/nextjs@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react@18.3.1)(webpack@5.94.0)': dependencies: '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 @@ -6878,7 +7026,7 @@ snapshots: '@sentry/vercel-edge': 8.34.0 '@sentry/webpack-plugin': 2.22.3(webpack@5.94.0) chalk: 3.0.0 - next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 @@ -6994,7 +7142,7 @@ snapshots: '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 - tslib: 2.7.0 + tslib: 2.8.0 '@tabler/core@1.0.0-beta21': dependencies: @@ -7011,11 +7159,11 @@ snapshots: '@tabler/icons@3.19.0': {} - '@tanstack/query-core@5.59.9': {} + '@tanstack/query-core@5.59.13': {} - '@tanstack/react-query@5.59.9(react@18.3.1)': + '@tanstack/react-query@5.59.15(react@18.3.1)': dependencies: - '@tanstack/query-core': 5.59.9 + '@tanstack/query-core': 5.59.13 react: 18.3.1 '@testing-library/dom@10.4.0': @@ -7029,17 +7177,17 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.5.0': + '@testing-library/jest-dom@6.6.2': dependencies: '@adobe/css-tools': 4.4.0 - aria-query: 5.3.1 + aria-query: 5.3.2 chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.6 '@testing-library/dom': 10.4.0 @@ -7047,7 +7195,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.3.1 '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': dependencies: @@ -7082,7 +7230,7 @@ snapshots: '@types/connect@3.4.36': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/cookie@0.4.1': {} @@ -7090,7 +7238,7 @@ snapshots: '@types/cors@2.8.17': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/debug@4.1.12': dependencies: @@ -7109,11 +7257,11 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/gunzip-maybe@1.4.2': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/hast@3.0.4': dependencies: @@ -7123,11 +7271,11 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/jsonwebtoken@9.0.7': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/lodash.clonedeep@4.5.9': dependencies: @@ -7147,13 +7295,13 @@ snapshots: '@types/mute-stream@0.0.4': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/mysql@2.15.26': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 - '@types/node@22.7.5': + '@types/node@22.7.6': dependencies: undici-types: 6.19.8 @@ -7165,19 +7313,19 @@ snapshots: '@types/pg@8.11.10': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 pg-protocol: 1.7.0 pg-types: 4.0.2 '@types/pg@8.6.1': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 pg-protocol: 1.7.0 pg-types: 2.2.0 '@types/prop-types@15.7.13': {} - '@types/react-dom@18.3.0': + '@types/react-dom@18.3.1': dependencies: '@types/react': 18.3.11 @@ -7198,12 +7346,12 @@ snapshots: '@types/tar-fs@2.0.4': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/tar-stream': 3.1.3 '@types/tar-stream@3.1.3': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/tough-cookie@4.0.5': {} @@ -7221,7 +7369,7 @@ snapshots: '@types/web-push@3.6.3': dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 '@types/wrap-ansi@3.0.0': {} @@ -7232,18 +7380,18 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react@4.3.2(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1))': + '@vitejs/plugin-react@4.3.2(vite@5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0))': dependencies: '@babel/core': 7.25.8 '@babel/plugin-transform-react-jsx-self': 7.25.7(@babel/core@7.25.8) '@babel/plugin-transform-react-jsx-source': 7.25.7(@babel/core@7.25.8) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) + vite: 5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.1.2(vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1))': + '@vitest/coverage-v8@2.1.3(vitest@2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -7252,64 +7400,64 @@ snapshots: istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.1.7 - magic-string: 0.30.11 + magic-string: 0.30.12 magicast: 0.3.5 std-env: 3.7.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) + vitest: 2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.2': + '@vitest/expect@2.1.3': dependencies: - '@vitest/spy': 2.1.2 - '@vitest/utils': 2.1.2 + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(msw@2.4.9(typescript@5.6.3))(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1))': + '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(msw@2.4.11(typescript@5.6.3))(vite@5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0))': dependencies: - '@vitest/spy': 2.1.2 + '@vitest/spy': 2.1.3 estree-walker: 3.0.3 - magic-string: 0.30.11 + magic-string: 0.30.12 optionalDependencies: - msw: 2.4.9(typescript@5.6.3) - vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) + msw: 2.4.11(typescript@5.6.3) + vite: 5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0) - '@vitest/pretty-format@2.1.2': + '@vitest/pretty-format@2.1.3': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.2': + '@vitest/runner@2.1.3': dependencies: - '@vitest/utils': 2.1.2 + '@vitest/utils': 2.1.3 pathe: 1.1.2 - '@vitest/snapshot@2.1.2': + '@vitest/snapshot@2.1.3': dependencies: - '@vitest/pretty-format': 2.1.2 - magic-string: 0.30.11 + '@vitest/pretty-format': 2.1.3 + magic-string: 0.30.12 pathe: 1.1.2 - '@vitest/spy@2.1.2': + '@vitest/spy@2.1.3': dependencies: tinyspy: 3.0.2 - '@vitest/ui@2.1.2(vitest@2.1.2)': + '@vitest/ui@2.1.3(vitest@2.1.3)': dependencies: - '@vitest/utils': 2.1.2 + '@vitest/utils': 2.1.3 fflate: 0.8.2 flatted: 3.3.1 pathe: 1.1.2 sirv: 2.0.4 tinyglobby: 0.2.9 tinyrainbow: 1.2.0 - vitest: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) + vitest: 2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0) - '@vitest/utils@2.1.2': + '@vitest/utils@2.1.3': dependencies: - '@vitest/pretty-format': 2.1.2 + '@vitest/pretty-format': 2.1.3 loupe: 3.1.2 tinyrainbow: 1.2.0 @@ -7406,8 +7554,14 @@ snapshots: dependencies: acorn: 8.12.1 + acorn-import-attributes@1.9.5(acorn@8.13.0): + dependencies: + acorn: 8.13.0 + acorn@8.12.1: {} + acorn@8.13.0: {} + agent-base@6.0.2: dependencies: debug: 4.3.7(supports-color@5.5.0) @@ -7475,13 +7629,13 @@ snapshots: aria-hidden@1.2.4: dependencies: - tslib: 2.7.0 + tslib: 2.8.0 aria-query@5.3.0: dependencies: dequal: 2.0.3 - aria-query@5.3.1: {} + aria-query@5.3.2: {} asn1.js@5.4.1: dependencies: @@ -7577,14 +7731,14 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bullmq@5.18.0: + bullmq@5.21.1: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 msgpackr: 1.11.0 node-abort-controller: 3.1.1 semver: 7.6.3 - tslib: 2.7.0 + tslib: 2.8.0 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -7799,6 +7953,8 @@ snapshots: dequal@2.0.3: {} + detect-libc@1.0.3: {} + detect-libc@2.0.3: {} detect-node-es@1.1.0: {} @@ -7829,7 +7985,7 @@ snapshots: dotenv@16.4.5: {} - drizzle-orm@0.34.1(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1): + drizzle-orm@0.35.2(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(react@18.3.1): optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/pg': 8.11.10 @@ -7886,7 +8042,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 22.7.5 + '@types/node': 22.7.6 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -8020,7 +8176,7 @@ snapshots: dependencies: reusify: 1.0.4 - fdir@6.4.0(picomatch@4.0.2): + fdir@6.4.2(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -8070,9 +8226,9 @@ snapshots: function-bind@1.1.2: {} - geist@1.3.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4)): + geist@1.3.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2)): dependencies: - next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2) gensync@1.0.0-beta.2: {} @@ -8284,7 +8440,7 @@ snapshots: '@formatjs/ecma402-abstract': 2.1.0 '@formatjs/fast-memoize': 2.2.0 '@formatjs/icu-messageformat-parser': 2.7.9 - tslib: 2.7.0 + tslib: 2.8.0 invariant@2.2.4: dependencies: @@ -8390,7 +8546,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -8481,11 +8637,11 @@ snapshots: jwa: 2.0.0 safe-buffer: 5.2.1 - knip@5.33.3(@types/node@22.7.5)(typescript@5.6.3): + knip@5.33.3(@types/node@22.7.6)(typescript@5.6.3): dependencies: '@nodelib/fs.walk': 1.2.8 '@snyk/github-codeowners': 1.1.0 - '@types/node': 22.7.5 + '@types/node': 22.7.6 easy-table: 1.2.0 enhanced-resolve: 5.17.1 fast-glob: 3.3.2 @@ -8573,7 +8729,7 @@ snapshots: lz-string@1.5.0: {} - magic-string@0.30.11: + magic-string@0.30.12: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -8750,12 +8906,12 @@ snapshots: dependencies: '@types/mdast': 4.0.4 - memfs@4.13.0: + memfs@4.14.0: dependencies: - '@jsonjoy.com/json-pack': 1.1.0(tslib@2.7.0) - '@jsonjoy.com/util': 1.5.0(tslib@2.7.0) - tree-dump: 1.0.2(tslib@2.7.0) - tslib: 2.7.0 + '@jsonjoy.com/json-pack': 1.1.0(tslib@2.8.0) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.0) + tree-dump: 1.0.2(tslib@2.8.0) + tslib: 2.8.0 memoize-one@6.0.0: {} @@ -9011,13 +9167,13 @@ snapshots: optionalDependencies: msgpackr-extract: 3.0.3 - msw@2.4.9(typescript@5.6.3): + msw@2.4.11(typescript@5.6.3): dependencies: '@bundled-es-modules/cookie': 2.0.0 '@bundled-es-modules/statuses': 1.0.1 '@bundled-es-modules/tough-cookie': 0.1.6 '@inquirer/confirm': 3.2.0 - '@mswjs/interceptors': 0.35.8 + '@mswjs/interceptors': 0.35.9 '@open-draft/until': 2.1.0 '@types/cookie': 0.6.0 '@types/statuses': 2.0.5 @@ -9041,34 +9197,34 @@ snapshots: neo-async@2.6.2: {} - next-client-cookies@1.1.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1): + next-client-cookies@1.1.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react@18.3.1): dependencies: js-cookie: 3.0.5 - next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2) react: 18.3.1 - next-intl@3.21.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1): + next-intl@3.21.1(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2) react: 18.3.1 use-intl: 3.21.1(react@18.3.1) - next-router-mock@0.9.13(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1): + next-router-mock@0.9.13(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react@18.3.1): dependencies: - next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2) react: 18.3.1 - next-safe-action@7.9.3(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8): + next-safe-action@7.9.4(next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8): dependencies: - next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + next: 14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: zod: 3.23.8 - next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4): + next@14.2.15(@babel/core@7.25.8)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.2): dependencies: '@next/env': 14.2.15 '@swc/helpers': 0.5.5 @@ -9090,14 +9246,16 @@ snapshots: '@next/swc-win32-ia32-msvc': 14.2.15 '@next/swc-win32-x64-msvc': 14.2.15 '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.48.0 - sass: 1.79.4 + '@playwright/test': 1.48.1 + sass: 1.80.2 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros node-abort-controller@3.1.1: {} + node-addon-api@7.1.1: {} + node-addon-api@8.1.0: {} node-fetch@2.7.0: @@ -9262,15 +9420,17 @@ snapshots: picocolors@1.1.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} picomatch@4.0.2: {} - playwright-core@1.48.0: {} + playwright-core@1.48.1: {} - playwright@1.48.0: + playwright@1.48.1: dependencies: - playwright-core: 1.48.0 + playwright-core: 1.48.1 optionalDependencies: fsevents: 2.3.2 @@ -9283,7 +9443,7 @@ snapshots: postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.1.0 + picocolors: 1.1.1 source-map-js: 1.2.1 postgres-array@2.0.0: {} @@ -9415,7 +9575,7 @@ snapshots: dependencies: react: 18.3.1 react-style-singleton: 2.2.1(@types/react@18.3.11)(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.0 optionalDependencies: '@types/react': 18.3.11 @@ -9424,7 +9584,7 @@ snapshots: react: 18.3.1 react-remove-scroll-bar: 2.3.6(@types/react@18.3.11)(react@18.3.1) react-style-singleton: 2.2.1(@types/react@18.3.11)(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.0 use-callback-ref: 1.3.2(@types/react@18.3.11)(react@18.3.1) use-sidecar: 1.1.2(@types/react@18.3.11)(react@18.3.1) optionalDependencies: @@ -9452,7 +9612,7 @@ snapshots: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 - tslib: 2.7.0 + tslib: 2.8.0 optionalDependencies: '@types/react': 18.3.11 @@ -9639,8 +9799,9 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.79.4: + sass@1.80.2: dependencies: + '@parcel/watcher': 2.4.1 chokidar: 4.0.1 immutable: 4.3.7 source-map-js: 1.2.1 @@ -9903,13 +10064,13 @@ snapshots: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.34.1 + terser: 5.36.0 webpack: 5.94.0 - terser@5.34.1: + terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 + acorn: 8.13.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -9925,9 +10086,9 @@ snapshots: text-hex@1.0.0: {} - thingies@1.21.0(tslib@2.7.0): + thingies@1.21.0(tslib@2.8.0): dependencies: - tslib: 2.7.0 + tslib: 2.8.0 thirty-two@1.0.2: {} @@ -9940,11 +10101,11 @@ snapshots: tinybench@2.9.0: {} - tinyexec@0.3.0: {} + tinyexec@0.3.1: {} tinyglobby@0.2.9: dependencies: - fdir: 6.4.0(picomatch@4.0.2) + fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 tinypool@1.0.1: {} @@ -9986,9 +10147,9 @@ snapshots: dependencies: punycode: 2.3.1 - tree-dump@1.0.2(tslib@2.7.0): + tree-dump@1.0.2(tslib@2.8.0): dependencies: - tslib: 2.7.0 + tslib: 2.8.0 trim-lines@3.0.1: {} @@ -10004,7 +10165,7 @@ snapshots: optionalDependencies: typescript: 5.6.3 - tslib@2.7.0: {} + tslib@2.8.0: {} type-fest@0.21.3: {} @@ -10057,7 +10218,7 @@ snapshots: unplugin@1.0.1: dependencies: - acorn: 8.12.1 + acorn: 8.13.0 chokidar: 3.6.0 webpack-sources: 3.2.3 webpack-virtual-modules: 0.5.0 @@ -10066,7 +10227,7 @@ snapshots: dependencies: browserslist: 4.24.0 escalade: 3.2.0 - picocolors: 1.1.0 + picocolors: 1.1.1 uri-js@4.4.1: dependencies: @@ -10080,7 +10241,7 @@ snapshots: use-callback-ref@1.3.2(@types/react@18.3.11)(react@18.3.1): dependencies: react: 18.3.1 - tslib: 2.7.0 + tslib: 2.8.0 optionalDependencies: '@types/react': 18.3.11 @@ -10100,7 +10261,7 @@ snapshots: dependencies: detect-node-es: 1.1.0 react: 18.3.1 - tslib: 2.7.0 + tslib: 2.8.0 optionalDependencies: '@types/react': 18.3.11 @@ -10133,12 +10294,12 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@2.1.2(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1): + vite-node@2.1.3(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 - vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) + vite: 5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0) transitivePeerDependencies: - '@types/node' - less @@ -10150,58 +10311,58 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)): + vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0)): dependencies: debug: 4.3.7(supports-color@5.5.0) globrex: 0.1.2 tsconfck: 3.1.3(typescript@5.6.3) optionalDependencies: - vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) + vite: 5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0) transitivePeerDependencies: - supports-color - typescript - vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1): + vite@5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.24.0 optionalDependencies: - '@types/node': 22.7.5 + '@types/node': 22.7.6 fsevents: 2.3.3 - sass: 1.79.4 - terser: 5.34.1 + sass: 1.80.2 + terser: 5.36.0 - vitest-mock-extended@2.0.2(typescript@5.6.3)(vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1)): + vitest-mock-extended@2.0.2(typescript@5.6.3)(vitest@2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0)): dependencies: ts-essentials: 10.0.2(typescript@5.6.3) typescript: 5.6.3 - vitest: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1) + vitest: 2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0) - vitest@2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(jsdom@25.0.1)(msw@2.4.9(typescript@5.6.3))(sass@1.79.4)(terser@5.34.1): + vitest@2.1.3(@types/node@22.7.6)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.4.11(typescript@5.6.3))(sass@1.80.2)(terser@5.36.0): dependencies: - '@vitest/expect': 2.1.2 - '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(msw@2.4.9(typescript@5.6.3))(vite@5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1)) - '@vitest/pretty-format': 2.1.2 - '@vitest/runner': 2.1.2 - '@vitest/snapshot': 2.1.2 - '@vitest/spy': 2.1.2 - '@vitest/utils': 2.1.2 + '@vitest/expect': 2.1.3 + '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(msw@2.4.11(typescript@5.6.3))(vite@5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0)) + '@vitest/pretty-format': 2.1.3 + '@vitest/runner': 2.1.3 + '@vitest/snapshot': 2.1.3 + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 chai: 5.1.1 debug: 4.3.7(supports-color@5.5.0) - magic-string: 0.30.11 + magic-string: 0.30.12 pathe: 1.1.2 std-env: 3.7.0 tinybench: 2.9.0 - tinyexec: 0.3.0 + tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.8(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) - vite-node: 2.1.2(@types/node@22.7.5)(sass@1.79.4)(terser@5.34.1) + vite: 5.4.9(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0) + vite-node: 2.1.3(@types/node@22.7.6)(sass@1.80.2)(terser@5.36.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.7.5 - '@vitest/ui': 2.1.2(vitest@2.1.2) + '@types/node': 22.7.6 + '@vitest/ui': 2.1.3(vitest@2.1.3) jsdom: 25.0.1 transitivePeerDependencies: - less @@ -10256,8 +10417,8 @@ snapshots: '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/wasm-edit': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.12.1 - acorn-import-attributes: 1.9.5(acorn@8.12.1) + acorn: 8.13.0 + acorn-import-attributes: 1.9.5(acorn@8.13.0) browserslist: 4.24.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.17.1 @@ -10363,7 +10524,7 @@ snapshots: yaml@1.10.2: {} - yaml@2.5.1: {} + yaml@2.6.0: {} yargs-parser@21.1.1: {} From 1309c3dd1f1dbb2610f73b27438c742ce29b11d7 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 19 Oct 2024 19:11:48 +0200 Subject: [PATCH 016/241] chore: fix import type --- packages/shared/src/node/modules/archive/archive-manager.ts | 2 +- .../(dashboard)/app-store/[id]/components/InstallForm/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/node/modules/archive/archive-manager.ts b/packages/shared/src/node/modules/archive/archive-manager.ts index 07e5fcbfa4..f66de103f8 100644 --- a/packages/shared/src/node/modules/archive/archive-manager.ts +++ b/packages/shared/src/node/modules/archive/archive-manager.ts @@ -1,4 +1,4 @@ -import { execAsync } from 'src/node/helpers/exec-async'; +import { execAsync } from '../../helpers/exec-async'; export class ArchiveManager { createTarGz = async (sourceDir: string, destinationFile: string) => { diff --git a/src/app/(dashboard)/app-store/[id]/components/InstallForm/index.ts b/src/app/(dashboard)/app-store/[id]/components/InstallForm/index.ts index 8c0f391e4c..4b2cf2e45c 100644 --- a/src/app/(dashboard)/app-store/[id]/components/InstallForm/index.ts +++ b/src/app/(dashboard)/app-store/[id]/components/InstallForm/index.ts @@ -1,2 +1,2 @@ export { InstallForm } from './InstallForm'; -export { type FormValues } from './InstallForm.types'; +export type { FormValues } from './InstallForm.types'; From 340243e3a8319c7b793b7feef2a3dea4843b8754 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 19 Oct 2024 19:20:20 +0200 Subject: [PATCH 017/241] chore: bump version to 3.6.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 265d5e7d3d..d78e0db9e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "runtipi", - "version": "3.6.2", + "version": "3.6.3", "description": "A homeserver for everyone", "packageManager": "pnpm@9.4.0", "scripts": { From e5e4323dac4c55fdd1d28dc315090b4b57e7d4f6 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 2 Nov 2024 13:19:22 +0100 Subject: [PATCH 018/241] chore: remove auto issue stale workflow --- .github/workflows/issue-auto-close.yml | 23 ----------------------- public/mockServiceWorker.js | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 26 deletions(-) delete mode 100644 .github/workflows/issue-auto-close.yml diff --git a/.github/workflows/issue-auto-close.yml b/.github/workflows/issue-auto-close.yml deleted file mode 100644 index 58a6e1867d..0000000000 --- a/.github/workflows/issue-auto-close.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Close inactive issues -on: - schedule: - - cron: "30 1 * * *" - -jobs: - close-issues: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - uses: actions/stale@v9 - with: - days-before-issue-stale: 30 - days-before-issue-close: 14 - stale-issue-label: "stale" - stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." - close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." - close-issue-reason: "completed" - any-of-issue-labels: "bug" - days-before-pr-stale: -1 - days-before-pr-close: -1 - repo-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index a8262f093f..f62264a6c9 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -8,8 +8,8 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.4.9' -const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423' +const PACKAGE_VERSION = '2.5.1' +const INTEGRITY_CHECKSUM = '07a8241b182f8a246a7cd39894799a9e' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() @@ -62,7 +62,12 @@ self.addEventListener('message', async function (event) { sendToClient(client, { type: 'MOCKING_ENABLED', - payload: true, + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, }) break } @@ -155,6 +160,10 @@ async function handleRequest(event, requestId) { async function resolveMainClient(event) { const client = await self.clients.get(event.clientId) + if (activeClientIds.has(event.clientId)) { + return client + } + if (client?.frameType === 'top-level') { return client } From 71dd20dcde277385897745733e32b78af57c8034 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 20:46:07 +0000 Subject: [PATCH 019/241] chore(deps): bump the minor-patch group across 1 directory with 21 updates Bumps the minor-patch group with 21 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `3.9.0` | `3.9.1` | | [@sentry/nextjs](https://github.com/getsentry/sentry-javascript) | `8.35.0` | `8.36.0` | | [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) | `3.20.0` | `3.21.0` | | [bullmq](https://github.com/taskforcesh/bullmq) | `5.21.2` | `5.22.0` | | [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) | `0.35.3` | `0.36.0` | | [next-intl](https://github.com/amannn/next-intl) | `3.23.5` | `3.24.0` | | [next-safe-action](https://github.com/TheEdoRan/next-safe-action) | `7.9.7` | `7.9.9` | | [sass](https://github.com/sass/dart-sass) | `1.80.4` | `1.80.5` | | [@faker-js/faker](https://github.com/faker-js/faker) | `9.0.3` | `9.1.0` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.48.1` | `1.48.2` | | [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) | `6.6.2` | `6.6.3` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.8.0` | `22.8.6` | | [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `2.1.3` | `2.1.4` | | [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `2.1.3` | `2.1.4` | | [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `5.34.0` | `5.36.1` | | [msw](https://github.com/mswjs/msw) | `2.5.1` | `2.6.0` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `2.1.3` | `2.1.4` | | [@sentry/types](https://github.com/getsentry/sentry-javascript) | `8.35.0` | `8.36.0` | | [@hono/node-server](https://github.com/honojs/node-server) | `1.13.2` | `1.13.4` | | [@sentry/node](https://github.com/getsentry/sentry-javascript) | `8.35.0` | `8.36.0` | | [hono](https://github.com/honojs/hono) | `4.6.6` | `4.6.8` | Updates `@hookform/resolvers` from 3.9.0 to 3.9.1 - [Release notes](https://github.com/react-hook-form/resolvers/releases) - [Commits](https://github.com/react-hook-form/resolvers/compare/v3.9.0...v3.9.1) Updates `@sentry/nextjs` from 8.35.0 to 8.36.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.35.0...8.36.0) Updates `@tabler/icons-react` from 3.20.0 to 3.21.0 - [Release notes](https://github.com/tabler/tabler-icons/releases) - [Commits](https://github.com/tabler/tabler-icons/commits/v3.21.0/packages/icons-react) Updates `bullmq` from 5.21.2 to 5.22.0 - [Release notes](https://github.com/taskforcesh/bullmq/releases) - [Commits](https://github.com/taskforcesh/bullmq/compare/v5.21.2...v5.22.0) Updates `drizzle-orm` from 0.35.3 to 0.36.0 - [Release notes](https://github.com/drizzle-team/drizzle-orm/releases) - [Commits](https://github.com/drizzle-team/drizzle-orm/compare/0.35.3...0.36.0) Updates `next-intl` from 3.23.5 to 3.24.0 - [Release notes](https://github.com/amannn/next-intl/releases) - [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md) - [Commits](https://github.com/amannn/next-intl/compare/v3.23.5...v3.24.0) Updates `next-safe-action` from 7.9.7 to 7.9.9 - [Release notes](https://github.com/TheEdoRan/next-safe-action/releases) - [Commits](https://github.com/TheEdoRan/next-safe-action/compare/v7.9.7...v7.9.9) Updates `sass` from 1.80.4 to 1.80.5 - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.80.4...1.80.5) Updates `@faker-js/faker` from 9.0.3 to 9.1.0 - [Release notes](https://github.com/faker-js/faker/releases) - [Changelog](https://github.com/faker-js/faker/blob/next/CHANGELOG.md) - [Commits](https://github.com/faker-js/faker/compare/v9.0.3...v9.1.0) Updates `@playwright/test` from 1.48.1 to 1.48.2 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.48.1...v1.48.2) Updates `@testing-library/jest-dom` from 6.6.2 to 6.6.3 - [Release notes](https://github.com/testing-library/jest-dom/releases) - [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md) - [Commits](https://github.com/testing-library/jest-dom/compare/v6.6.2...v6.6.3) Updates `@types/node` from 22.8.0 to 22.8.6 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@vitest/coverage-v8` from 2.1.3 to 2.1.4 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.4/packages/coverage-v8) Updates `@vitest/ui` from 2.1.3 to 2.1.4 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.4/packages/ui) Updates `knip` from 5.34.0 to 5.36.1 - [Release notes](https://github.com/webpro-nl/knip/releases) - [Changelog](https://github.com/webpro-nl/knip/blob/main/packages/knip/.release-it.json) - [Commits](https://github.com/webpro-nl/knip/commits/5.36.1/packages/knip) Updates `msw` from 2.5.1 to 2.6.0 - [Release notes](https://github.com/mswjs/msw/releases) - [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md) - [Commits](https://github.com/mswjs/msw/compare/v2.5.1...v2.6.0) Updates `vitest` from 2.1.3 to 2.1.4 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.4/packages/vitest) Updates `@sentry/types` from 8.35.0 to 8.36.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.35.0...8.36.0) Updates `@hono/node-server` from 1.13.2 to 1.13.4 - [Release notes](https://github.com/honojs/node-server/releases) - [Commits](https://github.com/honojs/node-server/compare/v1.13.2...v1.13.4) Updates `@sentry/node` from 8.35.0 to 8.36.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.35.0...8.36.0) Updates `hono` from 4.6.6 to 4.6.8 - [Release notes](https://github.com/honojs/hono/releases) - [Commits](https://github.com/honojs/hono/compare/v4.6.6...v4.6.8) --- updated-dependencies: - dependency-name: "@hookform/resolvers" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@sentry/nextjs" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@tabler/icons-react" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: bullmq dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: drizzle-orm dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: next-intl dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: next-safe-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: sass dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@faker-js/faker" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@playwright/test" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@testing-library/jest-dom" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@vitest/coverage-v8" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@vitest/ui" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: knip dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: msw dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: vitest dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@sentry/types" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@hono/node-server" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@sentry/node" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: hono dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch ... Signed-off-by: dependabot[bot] --- package.json | 34 +- packages/cache/package.json | 2 +- packages/db/package.json | 2 +- packages/shared/package.json | 2 +- packages/worker/package.json | 16 +- pnpm-lock.yaml | 1171 +++++++++++++++++----------------- 6 files changed, 609 insertions(+), 618 deletions(-) diff --git a/package.json b/package.json index 852623a191..3ca4dd0b37 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "postinstall": "./scripts/postinstall.sh" }, "dependencies": { - "@hookform/resolvers": "^3.9.0", + "@hookform/resolvers": "^3.9.1", "@otplib/core": "^12.0.1", "@otplib/plugin-crypto": "^12.0.1", "@otplib/plugin-thirty-two": "^12.0.1", @@ -39,17 +39,17 @@ "@runtipi/db": "workspace:^", "@runtipi/postgres-migrations": "^5.3.0", "@runtipi/shared": "workspace:^", - "@sentry/nextjs": "^8.35.0", + "@sentry/nextjs": "^8.36.0", "@tabler/core": "1.0.0-beta21", - "@tabler/icons-react": "^3.20.0", + "@tabler/icons-react": "^3.21.0", "@tanstack/react-query": "^5.59.16", "@uidotdev/usehooks": "^2.4.1", "argon2": "^0.41.1", - "bullmq": "^5.21.2", + "bullmq": "^5.22.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "dompurify": "^3.1.7", - "drizzle-orm": "^0.35.3", + "drizzle-orm": "^0.36.0", "fs-extra": "^11.2.0", "geist": "^1.3.1", "inversify": "^6.0.3", @@ -60,8 +60,8 @@ "minisearch": "^7.1.0", "next": "14.2.15", "next-client-cookies": "^1.1.1", - "next-intl": "^3.23.5", - "next-safe-action": "7.9.7", + "next-intl": "^3.24.0", + "next-safe-action": "7.9.9", "pg": "^8.13.1", "qrcode.react": "^4.1.0", "react": "18.3.1", @@ -76,7 +76,7 @@ "rehype-raw": "^7.0.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.0", - "sass": "^1.80.4", + "sass": "^1.80.5", "semver": "^7.6.3", "sharp": "0.33.5", "socket.io-client": "^4.8.1", @@ -89,10 +89,10 @@ "devDependencies": { "@babel/core": "^7.26.0", "@biomejs/biome": "1.9.4", - "@faker-js/faker": "^9.0.3", - "@playwright/test": "^1.48.1", + "@faker-js/faker": "^9.1.0", + "@playwright/test": "^1.48.2", "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.6.2", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", "@total-typescript/shoehorn": "^0.1.2", @@ -101,7 +101,7 @@ "@types/fs-extra": "^11.0.4", "@types/jsonwebtoken": "^9.0.7", "@types/lodash.merge": "^4.6.9", - "@types/node": "22.8.0", + "@types/node": "22.8.6", "@types/pg": "^8.11.10", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", @@ -109,17 +109,17 @@ "@types/uuid": "^10.0.0", "@types/validator": "^13.12.2", "@vitejs/plugin-react": "^4.3.3", - "@vitest/coverage-v8": "^2.1.3", - "@vitest/ui": "^2.1.3", + "@vitest/coverage-v8": "^2.1.4", + "@vitest/ui": "^2.1.4", "dotenv-cli": "^7.4.1", "jsdom": "^25.0.1", - "knip": "^5.34.0", + "knip": "^5.36.1", "memfs": "^4.14.0", - "msw": "^2.5.1", + "msw": "^2.6.0", "next-router-mock": "^0.9.13", "typescript": "5.6.3", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^2.1.3", + "vitest": "^2.1.4", "vitest-mock-extended": "^2.0.2", "wait-for-expect": "^3.0.2" }, diff --git a/packages/cache/package.json b/packages/cache/package.json index 20ac6bb503..84215dfb46 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -15,6 +15,6 @@ "ioredis": "^5.4.1" }, "devDependencies": { - "vitest": "^2.1.3" + "vitest": "^2.1.4" } } diff --git a/packages/db/package.json b/packages/db/package.json index 81e7b4b71a..4c529274e4 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -13,7 +13,7 @@ "dependencies": { "@runtipi/postgres-migrations": "^5.3.0", "@runtipi/shared": "workspace:^", - "drizzle-orm": "^0.35.3", + "drizzle-orm": "^0.36.0", "pg": "^8.13.1" } } diff --git a/packages/shared/package.json b/packages/shared/package.json index 0408296193..655e968814 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -33,7 +33,7 @@ "zod": "^3.23.8" }, "devDependencies": { - "@sentry/types": "^8.35.0", + "@sentry/types": "^8.36.0", "@types/gunzip-maybe": "^1.4.2", "@types/lodash.clonedeep": "^4.5.9", "@types/tar-fs": "^2.0.4" diff --git a/packages/worker/package.json b/packages/worker/package.json index b1f8947259..24dae6ba8a 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -15,30 +15,30 @@ "author": "", "license": "ISC", "devDependencies": { - "@faker-js/faker": "^9.0.3", + "@faker-js/faker": "^9.1.0", "@sentry/esbuild-plugin": "^2.22.6", "@types/web-push": "^3.6.4", "dotenv-cli": "^7.4.2", "esbuild": "^0.23.1", - "knip": "^5.34.0", + "knip": "^5.36.1", "memfs": "^4.14.0", "nodemon": "^3.1.7", "typescript": "^5.6.3", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^2.1.3" + "vitest": "^2.1.4" }, "dependencies": { - "@hono/node-server": "^1.13.2", + "@hono/node-server": "^1.13.4", "@runtipi/cache": "workspace:^", "@runtipi/db": "workspace:^", "@runtipi/shared": "workspace:^", "@sentry/integrations": "^7.114.0", - "@sentry/node": "^8.35.0", + "@sentry/node": "^8.36.0", "ansi-to-html": "^0.7.2", - "bullmq": "^5.21.2", + "bullmq": "^5.22.0", "dotenv": "^16.4.5", - "drizzle-orm": "^0.35.3", - "hono": "^4.6.6", + "drizzle-orm": "^0.36.0", + "hono": "^4.6.8", "inversify": "^6.0.3", "reflect-metadata": "^0.2.2", "socket.io": "^4.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e70f88d66..35e89bf7de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@hookform/resolvers': - specifier: ^3.9.0 - version: 3.9.0(react-hook-form@7.53.1(react@18.3.1)) + specifier: ^3.9.1 + version: 3.9.1(react-hook-form@7.53.1(react@18.3.1)) '@otplib/core': specifier: ^12.0.1 version: 12.0.1 @@ -57,14 +57,14 @@ importers: specifier: workspace:^ version: link:packages/shared '@sentry/nextjs': - specifier: ^8.35.0 - version: 8.35.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react@18.3.1)(webpack@5.94.0) + specifier: ^8.36.0 + version: 8.36.0(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react@18.3.1)(webpack@5.94.0) '@tabler/core': specifier: 1.0.0-beta21 version: 1.0.0-beta21 '@tabler/icons-react': - specifier: ^3.20.0 - version: 3.20.0(react@18.3.1) + specifier: ^3.21.0 + version: 3.21.0(react@18.3.1) '@tanstack/react-query': specifier: ^5.59.16 version: 5.59.16(react@18.3.1) @@ -75,8 +75,8 @@ importers: specifier: ^0.41.1 version: 0.41.1 bullmq: - specifier: ^5.21.2 - version: 5.21.2 + specifier: ^5.22.0 + version: 5.22.0 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -87,14 +87,14 @@ importers: specifier: ^3.1.7 version: 3.1.7 drizzle-orm: - specifier: ^0.35.3 - version: 0.35.3(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.12)(pg@8.13.1)(react@18.3.1) + specifier: ^0.36.0 + version: 0.36.0(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.12)(pg@8.13.1)(react@18.3.1) fs-extra: specifier: ^11.2.0 version: 11.2.0 geist: specifier: ^1.3.1 - version: 1.3.1(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4)) + version: 1.3.1(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5)) inversify: specifier: ^6.0.3 version: 6.0.3 @@ -115,16 +115,16 @@ importers: version: 7.1.0 next: specifier: 14.2.15 - version: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4) + version: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5) next-client-cookies: specifier: ^1.1.1 - version: 1.1.1(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react@18.3.1) + version: 1.1.1(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react@18.3.1) next-intl: - specifier: ^3.23.5 - version: 3.23.5(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react@18.3.1) + specifier: ^3.24.0 + version: 3.24.0(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react@18.3.1) next-safe-action: - specifier: 7.9.7 - version: 7.9.7(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8) + specifier: 7.9.9 + version: 7.9.9(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8) pg: specifier: ^8.13.1 version: 8.13.1 @@ -168,8 +168,8 @@ importers: specifier: ^4.0.0 version: 4.0.0 sass: - specifier: ^1.80.4 - version: 1.80.4 + specifier: ^1.80.5 + version: 1.80.5 semver: specifier: ^7.6.3 version: 7.6.3 @@ -202,17 +202,17 @@ importers: specifier: 1.9.4 version: 1.9.4 '@faker-js/faker': - specifier: ^9.0.3 - version: 9.0.3 + specifier: ^9.1.0 + version: 9.1.0 '@playwright/test': - specifier: ^1.48.1 - version: 1.48.1 + specifier: ^1.48.2 + version: 1.48.2 '@testing-library/dom': specifier: ^10.4.0 version: 10.4.0 '@testing-library/jest-dom': - specifier: ^6.6.2 - version: 6.6.2 + specifier: ^6.6.3 + version: 6.6.3 '@testing-library/react': specifier: ^16.0.1 version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -238,8 +238,8 @@ importers: specifier: ^4.6.9 version: 4.6.9 '@types/node': - specifier: 22.8.0 - version: 22.8.0 + specifier: 22.8.6 + version: 22.8.6 '@types/pg': specifier: ^8.11.10 version: 8.11.10 @@ -260,13 +260,13 @@ importers: version: 13.12.2 '@vitejs/plugin-react': specifier: ^4.3.3 - version: 4.3.3(vite@5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0)) + version: 4.3.3(vite@5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0)) '@vitest/coverage-v8': - specifier: ^2.1.3 - version: 2.1.3(vitest@2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0)) + specifier: ^2.1.4 + version: 2.1.4(vitest@2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0)) '@vitest/ui': - specifier: ^2.1.3 - version: 2.1.3(vitest@2.1.3) + specifier: ^2.1.4 + version: 2.1.4(vitest@2.1.4) dotenv-cli: specifier: ^7.4.1 version: 7.4.2 @@ -274,29 +274,29 @@ importers: specifier: ^25.0.1 version: 25.0.1 knip: - specifier: ^5.34.0 - version: 5.34.0(@types/node@22.8.0)(typescript@5.6.3) + specifier: ^5.36.1 + version: 5.36.1(@types/node@22.8.6)(typescript@5.6.3) memfs: specifier: ^4.14.0 version: 4.14.0 msw: - specifier: ^2.5.1 - version: 2.5.1(@types/node@22.8.0)(typescript@5.6.3) + specifier: ^2.6.0 + version: 2.6.0(@types/node@22.8.6)(typescript@5.6.3) next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react@18.3.1) + version: 0.9.13(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react@18.3.1) typescript: specifier: 5.6.3 version: 5.6.3 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.3)(vite@5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0)) vitest: - specifier: ^2.1.3 - version: 2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0) + specifier: ^2.1.4 + version: 2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0) vitest-mock-extended: specifier: ^2.0.2 - version: 2.0.2(typescript@5.6.3)(vitest@2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0)) + version: 2.0.2(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0)) wait-for-expect: specifier: ^3.0.2 version: 3.0.2 @@ -311,8 +311,8 @@ importers: version: 5.4.1 devDependencies: vitest: - specifier: ^2.1.3 - version: 2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0) + specifier: ^2.1.4 + version: 2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0) packages/db: dependencies: @@ -323,8 +323,8 @@ importers: specifier: workspace:^ version: link:../shared drizzle-orm: - specifier: ^0.35.3 - version: 0.35.3(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.12)(pg@8.13.1)(react@18.3.1) + specifier: ^0.36.0 + version: 0.36.0(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.12)(pg@8.13.1)(react@18.3.1) pg: specifier: ^8.13.1 version: 8.13.1 @@ -354,8 +354,8 @@ importers: version: 3.23.8 devDependencies: '@sentry/types': - specifier: ^8.35.0 - version: 8.35.0 + specifier: ^8.36.0 + version: 8.36.0 '@types/gunzip-maybe': specifier: ^1.4.2 version: 1.4.2 @@ -371,8 +371,8 @@ importers: packages/worker: dependencies: '@hono/node-server': - specifier: ^1.13.2 - version: 1.13.2(hono@4.6.6) + specifier: ^1.13.4 + version: 1.13.4(hono@4.6.8) '@runtipi/cache': specifier: workspace:^ version: link:../cache @@ -386,23 +386,23 @@ importers: specifier: ^7.114.0 version: 7.114.0 '@sentry/node': - specifier: ^8.35.0 - version: 8.35.0 + specifier: ^8.36.0 + version: 8.36.0 ansi-to-html: specifier: ^0.7.2 version: 0.7.2 bullmq: - specifier: ^5.21.2 - version: 5.21.2 + specifier: ^5.22.0 + version: 5.22.0 dotenv: specifier: ^16.4.5 version: 16.4.5 drizzle-orm: - specifier: ^0.35.3 - version: 0.35.3(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.12)(pg@8.13.1)(react@18.3.1) + specifier: ^0.36.0 + version: 0.36.0(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.12)(pg@8.13.1)(react@18.3.1) hono: - specifier: ^4.6.6 - version: 4.6.6 + specifier: ^4.6.8 + version: 4.6.8 inversify: specifier: ^6.0.3 version: 6.0.3 @@ -429,8 +429,8 @@ importers: version: 3.23.8 devDependencies: '@faker-js/faker': - specifier: ^9.0.3 - version: 9.0.3 + specifier: ^9.1.0 + version: 9.1.0 '@sentry/esbuild-plugin': specifier: ^2.22.6 version: 2.22.6 @@ -444,8 +444,8 @@ importers: specifier: ^0.23.1 version: 0.23.1 knip: - specifier: ^5.34.0 - version: 5.34.0(@types/node@22.8.0)(typescript@5.6.3) + specifier: ^5.36.1 + version: 5.36.1(@types/node@22.8.6)(typescript@5.6.3) memfs: specifier: ^4.14.0 version: 4.14.0 @@ -457,10 +457,10 @@ importers: version: 5.6.3 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.3)(vite@5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0)) vitest: - specifier: ^2.1.3 - version: 2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0) + specifier: ^2.1.4 + version: 2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0) packages: @@ -479,6 +479,10 @@ packages: resolution: {integrity: sha512-INCKxTtbXtcNbUZ3YXutwMpEleqttcswhAdee7dhuoVrD2cnuc3PqtERBtxkX5nziX9vnBL8WXmSGwv8CuPV6g==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.26.0': resolution: {integrity: sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA==} engines: {node: '>=6.9.0'} @@ -534,6 +538,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-transform-react-jsx-self@7.25.9': resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} engines: {node: '>=6.9.0'} @@ -964,8 +973,8 @@ packages: cpu: [x64] os: [win32] - '@faker-js/faker@9.0.3': - resolution: {integrity: sha512-lWrrK4QNlFSU+13PL9jMbMKLJYXDFu3tQfayBsMXX7KL/GiQeqfB1CzHkqD5UHBUtPAuPo6XwGbMFNdVMZObRA==} + '@faker-js/faker@9.1.0': + resolution: {integrity: sha512-GJvX9iM9PBtKScJVlXQ0tWpihK3i0pha/XAhzQa1hPK/ILLa1Wq3I63Ij7lRtqTwmdTxRCyrUhLC5Sly9SLbug==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} '@floating-ui/core@1.6.8': @@ -974,6 +983,9 @@ packages: '@floating-ui/dom@1.6.11': resolution: {integrity: sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==} + '@floating-ui/dom@1.6.12': + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} + '@floating-ui/react-dom@2.1.2': resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} peerDependencies: @@ -998,14 +1010,14 @@ packages: '@formatjs/intl-localematcher@0.5.6': resolution: {integrity: sha512-roz1+Ba5e23AHX6KUAWmLEyTRZegM5YDuxuvkHCyK3RJddf/UXB2f+s7pOMm9ktfPGla0g+mQXOn5vsuYirnaA==} - '@hono/node-server@1.13.2': - resolution: {integrity: sha512-0w8nEmAyx0Ul0CQp8BL2VtAG4YVdpzXd/mvvM+l0G5Oq22pUyHS+KeFFPSY+czLOF5NAiV3MUNPD1n14Ol5svg==} + '@hono/node-server@1.13.4': + resolution: {integrity: sha512-3tW9lC0+2mRmoGUEnq0d8Pi3SToWjg8flM9Z+h5Aamy23jjpiFr7n3/HLqXve6EudOsgN2tG2hZusqzNYVRmRw==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 - '@hookform/resolvers@3.9.0': - resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==} + '@hookform/resolvers@3.9.1': + resolution: {integrity: sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==} peerDependencies: react-hook-form: ^7.0.0 @@ -1222,8 +1234,8 @@ packages: cpu: [x64] os: [win32] - '@mswjs/interceptors@0.36.6': - resolution: {integrity: sha512-issnYydStyH0wPEeU7CMwfO7kI668ffVtzKRMRS7H7BliOYuPuwEZxh9dwiXV+oeHBxT5SXT0wPwV8T7V2PJUA==} + '@mswjs/interceptors@0.36.7': + resolution: {integrity: sha512-sdx02Wlus5hv6Bx7uUDb25gb0WGjCuSgnJB2LVERemoSGuqkZMe3QI6nEXhieFGtYwPrZbYrT2vPbsFN2XfbUw==} engines: {node: '>=18'} '@next/env@14.2.15': @@ -1312,6 +1324,10 @@ packages: resolution: {integrity: sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==} engines: {node: '>=14'} + '@opentelemetry/api-logs@0.54.0': + resolution: {integrity: sha512-9HhEh5GqFrassUndqJsyW7a0PzfyWr2eV2xwzHLIS+wX3125+9HE9FMRAKmJRwxZhgZGwH3HNQQjoMGZqmOeVA==} + engines: {node: '>=14'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -1340,8 +1356,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-connect@0.39.0': - resolution: {integrity: sha512-pGBiKevLq7NNglMgqzmeKczF4XQMTOUOTkK8afRHMZMnrK3fcETyTH7lVaSozwiOM3Ws+SuEmXZT7DYrrhxGlg==} + '@opentelemetry/instrumentation-connect@0.40.0': + resolution: {integrity: sha512-3aR/3YBQ160siitwwRLjwqrv2KBT16897+bo6yz8wIfel6nWOxTZBJudcbsK3p42pTC7qrbotJ9t/1wRLpv79Q==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1352,8 +1368,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-express@0.43.0': - resolution: {integrity: sha512-bxTIlzn9qPXJgrhz8/Do5Q3jIlqfpoJrSUtVGqH+90eM1v2PkPHc+SdE+zSqe4q9Y1UQJosmZ4N4bm7Zj/++MA==} + '@opentelemetry/instrumentation-express@0.44.0': + resolution: {integrity: sha512-GWgibp6Q0wxyFaaU8ERIgMMYgzcHmGrw3ILUtGchLtLncHNOKk0SNoWGqiylXWWT4HTn5XdV8MGawUgpZh80cA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1364,8 +1380,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-fs@0.15.0': - resolution: {integrity: sha512-JWVKdNLpu1skqZQA//jKOcKdJC66TWKqa2FUFq70rKohvaSq47pmXlnabNO+B/BvLfmidfiaN35XakT5RyMl2Q==} + '@opentelemetry/instrumentation-fs@0.16.0': + resolution: {integrity: sha512-hMDRUxV38ln1R3lNz6osj3YjlO32ykbHqVrzG7gEhGXFQfu7LJUx8t9tEwE4r2h3CD4D0Rw4YGDU4yF4mP3ilg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1400,8 +1416,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-kafkajs@0.3.0': - resolution: {integrity: sha512-UnkZueYK1ise8FXQeKlpBd7YYUtC7mM8J0wzUSccEfc/G8UqHQqAzIyYCUOUPUKp8GsjLnWOOK/3hJc4owb7Jg==} + '@opentelemetry/instrumentation-kafkajs@0.4.0': + resolution: {integrity: sha512-I9VwDG314g7SDL4t8kD/7+1ytaDBRbZQjhVaQaVIDR8K+mlsoBhLsWH79yHxhHQKvwCSZwqXF+TiTOhoQVUt7A==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1478,6 +1494,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.54.0': + resolution: {integrity: sha512-B0Ydo9g9ehgNHwtpc97XivEzjz0XBKR6iQ83NTENIxEEf5NHE0otZQuZLgDdey1XNk+bP1cfRpIkSFWM5YlSyg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/redis-common@0.36.2': resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==} engines: {node: '>=14'} @@ -1609,8 +1631,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.48.1': - resolution: {integrity: sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==} + '@playwright/test@1.48.2': + resolution: {integrity: sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==} engines: {node: '>=18'} hasBin: true @@ -2010,83 +2032,93 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} + '@rollup/rollup-android-arm-eabi@4.24.3': + resolution: {integrity: sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.24.0': - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} + '@rollup/rollup-android-arm64@4.24.3': + resolution: {integrity: sha512-iAHpft/eQk9vkWIV5t22V77d90CRofgR2006UiCjHcHJFVI1E0oBkQIAbz+pLtthFw3hWEmVB4ilxGyBf48i2Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.24.0': - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} + '@rollup/rollup-darwin-arm64@4.24.3': + resolution: {integrity: sha512-QPW2YmkWLlvqmOa2OwrfqLJqkHm7kJCIMq9kOz40Zo9Ipi40kf9ONG5Sz76zszrmIZZ4hgRIkez69YnTHgEz1w==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.0': - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} + '@rollup/rollup-darwin-x64@4.24.3': + resolution: {integrity: sha512-KO0pN5x3+uZm1ZXeIfDqwcvnQ9UEGN8JX5ufhmgH5Lz4ujjZMAnxQygZAVGemFWn+ZZC0FQopruV4lqmGMshow==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} + '@rollup/rollup-freebsd-arm64@4.24.3': + resolution: {integrity: sha512-CsC+ZdIiZCZbBI+aRlWpYJMSWvVssPuWqrDy/zi9YfnatKKSLFCe6fjna1grHuo/nVaHG+kiglpRhyBQYRTK4A==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.24.3': + resolution: {integrity: sha512-F0nqiLThcfKvRQhZEzMIXOQG4EeX61im61VYL1jo4eBxv4aZRmpin6crnBJQ/nWnCsjH5F6J3W6Stdm0mBNqBg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.24.3': + resolution: {integrity: sha512-KRSFHyE/RdxQ1CSeOIBVIAxStFC/hnBgVcaiCkQaVC+EYDtTe4X7z5tBkFyRoBgUGtB6Xg6t9t2kulnX6wJc6A==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + '@rollup/rollup-linux-arm-musleabihf@4.24.3': + resolution: {integrity: sha512-h6Q8MT+e05zP5BxEKz0vi0DhthLdrNEnspdLzkoFqGwnmOzakEHSlXfVyA4HJ322QtFy7biUAVFPvIDEDQa6rw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.24.0': - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} + '@rollup/rollup-linux-arm64-gnu@4.24.3': + resolution: {integrity: sha512-fKElSyXhXIJ9pqiYRqisfirIo2Z5pTTve5K438URf08fsypXrEkVmShkSfM8GJ1aUyvjakT+fn2W7Czlpd/0FQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.24.0': - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} + '@rollup/rollup-linux-arm64-musl@4.24.3': + resolution: {integrity: sha512-YlddZSUk8G0px9/+V9PVilVDC6ydMz7WquxozToozSnfFK6wa6ne1ATUjUvjin09jp34p84milxlY5ikueoenw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.24.3': + resolution: {integrity: sha512-yNaWw+GAO8JjVx3s3cMeG5Esz1cKVzz8PkTJSfYzE5u7A+NvGmbVFEHP+BikTIyYWuz0+DX9kaA3pH9Sqxp69g==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + '@rollup/rollup-linux-riscv64-gnu@4.24.3': + resolution: {integrity: sha512-lWKNQfsbpv14ZCtM/HkjCTm4oWTKTfxPmr7iPfp3AHSqyoTz5AgLemYkWLwOBWc+XxBbrU9SCokZP0WlBZM9lA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + '@rollup/rollup-linux-s390x-gnu@4.24.3': + resolution: {integrity: sha512-HoojGXTC2CgCcq0Woc/dn12wQUlkNyfH0I1ABK4Ni9YXyFQa86Fkt2Q0nqgLfbhkyfQ6003i3qQk9pLh/SpAYw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.24.0': - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} + '@rollup/rollup-linux-x64-gnu@4.24.3': + resolution: {integrity: sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + '@rollup/rollup-linux-x64-musl@4.24.3': + resolution: {integrity: sha512-rMTzawBPimBQkG9NKpNHvquIUTQPzrnPxPbCY1Xt+mFkW7pshvyIS5kYgcf74goxXOQk0CP3EoOC1zcEezKXhw==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.24.0': - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} + '@rollup/rollup-win32-arm64-msvc@4.24.3': + resolution: {integrity: sha512-2lg1CE305xNvnH3SyiKwPVsTVLCg4TmNCF1z7PSHX2uZY2VbUpdkgAllVoISD7JO7zu+YynpWNSKAtOrX3AiuA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.24.0': - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + '@rollup/rollup-win32-ia32-msvc@4.24.3': + resolution: {integrity: sha512-9SjYp1sPyxJsPWuhOCX6F4jUMXGbVVd5obVpoVEi8ClZqo52ViZewA6eFz85y8ezuOA+uJMP5A5zo6Oz4S5rVQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.24.0': - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} + '@rollup/rollup-win32-x64-msvc@4.24.3': + resolution: {integrity: sha512-HGZgRFFYrMrP3TJlq58nR1xy8zHKId25vhmm5S9jETEfDf6xybPxsavFTJaufe2zgOGYJBskGlj49CwtEuFhWQ==} cpu: [x64] os: [win32] @@ -2095,38 +2127,30 @@ packages: engines: {node: '>10.17.0'} hasBin: true - '@sentry-internal/browser-utils@8.35.0': - resolution: {integrity: sha512-uj9nwERm7HIS13f/Q52hF/NUS5Al8Ma6jkgpfYGeppYvU0uSjPkwMogtqoJQNbOoZg973tV8qUScbcWY616wNA==} + '@sentry-internal/browser-utils@8.36.0': + resolution: {integrity: sha512-AVJ9GmQW7jYxaal6hjQnnktsDNype01ajVC4q1RyOn1SfzSnXg6mXwj4xm4ovuJV+aBI7fAZJ55vEX5ASuP0ZA==} engines: {node: '>=14.18'} - '@sentry-internal/feedback@8.35.0': - resolution: {integrity: sha512-7bjSaUhL0bDArozre6EiIhhdWdT/1AWNWBC1Wc5w1IxEi5xF7nvF/FfvjQYrONQzZAI3HRxc45J2qhLUzHBmoQ==} + '@sentry-internal/feedback@8.36.0': + resolution: {integrity: sha512-aAMTm3uDBj8Ta7FwoohpLmJOpWzpWXvvtTbtmSgkeCtPJLUS8DZDCTZ9uCILUkpuYrv2savRUHsdPkxNjgL8FA==} engines: {node: '>=14.18'} - '@sentry-internal/replay-canvas@8.35.0': - resolution: {integrity: sha512-TUrH6Piv19kvHIiRyIuapLdnuwxk/Un/l1WDCQfq7mK9p1Pac0FkQ7Uufjp6zY3lyhDDZQ8qvCS4ioCMibCwQg==} + '@sentry-internal/replay-canvas@8.36.0': + resolution: {integrity: sha512-KJPLf+qYdrQdmouoAqIPZ2KeapIBlHWbzNdQqNxJFWLHFFjpLUtt0b+87ruvbA/q3NYy2fDwD7EB0tGS1RHBaA==} engines: {node: '>=14.18'} - '@sentry-internal/replay@8.35.0': - resolution: {integrity: sha512-3wkW03vXYMyWtTLxl9yrtkV+qxbnKFgfASdoGWhXzfLjycgT6o4/04eb3Gn71q9aXqRwH17ISVQbVswnRqMcmA==} + '@sentry-internal/replay@8.36.0': + resolution: {integrity: sha512-lbic98GsSkDeinQDix54tBFEgHUlmBtO+HjXECk9jIE0vOzR4As20/s5ta46t1rKMLlnxOtJuT5jKXeUYogBUw==} engines: {node: '>=14.18'} - '@sentry/babel-plugin-component-annotate@2.22.3': - resolution: {integrity: sha512-OlHA+i+vnQHRIdry4glpiS/xTOtgjmpXOt6IBOUqynx5Jd/iK1+fj+t8CckqOx9wRacO/hru2wfW/jFq0iViLg==} - engines: {node: '>= 14'} - '@sentry/babel-plugin-component-annotate@2.22.6': resolution: {integrity: sha512-V2g1Y1I5eSe7dtUVMBvAJr8BaLRr4CLrgNgtPaZyMT4Rnps82SrZ5zqmEkLXPumlXhLUWR6qzoMNN2u+RXVXfQ==} engines: {node: '>= 14'} - '@sentry/browser@8.35.0': - resolution: {integrity: sha512-WHfI+NoZzpCsmIvtr6ChOe7yWPLQyMchPnVhY3Z4UeC70bkYNdKcoj/4XZbX3m0D8+71JAsm0mJ9s9OC3Ue6MQ==} + '@sentry/browser@8.36.0': + resolution: {integrity: sha512-bLrQNe+wD4DkCfB8OD5TF3Rr8KA2+aTo5wF3t3Bf6KVn8//iX1ia1hhtptYiRnbRkG/0AEPxlqL6XfPZYVPQ5A==} engines: {node: '>=14.18'} - '@sentry/bundler-plugin-core@2.22.3': - resolution: {integrity: sha512-DeoUl0WffcqZZRl5Wy9aHvX4WfZbbWt0QbJ7NJrcEViq+dRAI2FQTYECFLwdZi5Gtb3oyqZICO+P7k8wDnzsjQ==} - engines: {node: '>= 14'} - '@sentry/bundler-plugin-core@2.22.6': resolution: {integrity: sha512-1esQdgSUCww9XAntO4pr7uAM5cfGhLsgTK9MEwAKNfvpMYJi9NUTYa3A7AZmdA8V6107Lo4OD7peIPrDRbaDCg==} engines: {node: '>= 14'} @@ -2136,99 +2160,53 @@ packages: engines: {node: '>=10'} os: [darwin] - '@sentry/cli-darwin@2.38.0': - resolution: {integrity: sha512-OvOaV9Vg4+b9ObK2z1oFj3zbRoqOSpD/wSz9t/mtSWwMQi7wlUXj88XGGsL5ZwF7VGBYL+kX59X3Ygl+dHFPlg==} - engines: {node: '>=10'} - os: [darwin] - '@sentry/cli-linux-arm64@2.37.0': resolution: {integrity: sha512-2vzUWHLZ3Ct5gpcIlfd/2Qsha+y9M8LXvbZE26VxzYrIkRoLAWcnClBv8m4XsHLMURYvz3J9QSZHMZHSO7kAzw==} engines: {node: '>=10'} cpu: [arm64] os: [linux, freebsd] - '@sentry/cli-linux-arm64@2.38.0': - resolution: {integrity: sha512-oUiRTyek0Ixe30zoqNlEFsLY07B9hK3FRXKv5lw341rim9PiTteh5tk5ewpuD63K+QjbEAJqp4f3zM19DEASlg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux, freebsd] - '@sentry/cli-linux-arm@2.37.0': resolution: {integrity: sha512-Dz0qH4Yt+gGUgoVsqVt72oDj4VQynRF1QB1/Sr8g76Vbi+WxWZmUh0iFwivYVwWxdQGu/OQrE0tx946HToCRyA==} engines: {node: '>=10'} cpu: [arm] os: [linux, freebsd] - '@sentry/cli-linux-arm@2.38.0': - resolution: {integrity: sha512-lXMSEX1Sv9F2wXnnAlsS+kwy09iLQTfK10n08xzeJLIvUCLua/OFInwH6WUxNT3tIBPfBVQZPR7iQMRycH4Ilw==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux, freebsd] - '@sentry/cli-linux-i686@2.37.0': resolution: {integrity: sha512-MHRLGs4t/CQE1pG+mZBQixyWL6xDZfNalCjO8GMcTTbZFm44S3XRHfYJZNVCgdtnUP7b6OHGcu1v3SWE10LcwQ==} engines: {node: '>=10'} cpu: [x86, ia32] os: [linux, freebsd] - '@sentry/cli-linux-i686@2.38.0': - resolution: {integrity: sha512-+luFmbQymDON16O7R/A7bmnkUjtnq1nRSehnnRJjuFCtDABCKatZzBjWvan0KNgzHhCquMSvEqHKzfVSptHeHw==} - engines: {node: '>=10'} - cpu: [x86, ia32] - os: [linux, freebsd] - '@sentry/cli-linux-x64@2.37.0': resolution: {integrity: sha512-k76ClefKZaDNJZU/H3mGeR8uAzAGPzDRG/A7grzKfBeyhP3JW09L7Nz9IQcSjCK+xr399qLhM2HFCaPWQ6dlMw==} engines: {node: '>=10'} cpu: [x64] os: [linux, freebsd] - '@sentry/cli-linux-x64@2.38.0': - resolution: {integrity: sha512-yY593xXbf2W+afyHKDvO4QJwoWQX97/K0NYUAqnpg3TVmIfLV9DNVid+M1w6vKIif6n8UQgAFWtR1Ys4P75mBg==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux, freebsd] - '@sentry/cli-win32-i686@2.37.0': resolution: {integrity: sha512-FFyi5RNYQQkEg4GkP2f3BJcgQn0F4fjFDMiWkjCkftNPXQG+HFUEtrGsWr6mnHPdFouwbYg3tEPUWNxAoypvTw==} engines: {node: '>=10'} cpu: [x86, ia32] os: [win32] - '@sentry/cli-win32-i686@2.38.0': - resolution: {integrity: sha512-ipDnBvXaMqi0ZbkT/pqB11F4AaicVz5YRoidn5oxi1IJPDUd8qF0mnqabALLH3mAd5TOtKBliY5pllCFG/TvzA==} - engines: {node: '>=10'} - cpu: [x86, ia32] - os: [win32] - '@sentry/cli-win32-x64@2.37.0': resolution: {integrity: sha512-nSMj4OcfQmyL+Tu/jWCJwhKCXFsCZW1MUk6wjjQlRt9SDLfgeapaMlK1ZvT1eZv5ZH6bj3qJfefwj4U8160uOA==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@sentry/cli-win32-x64@2.38.0': - resolution: {integrity: sha512-NqlKOqNF8i239mygARkNZK9BPzwWK91j+HPEfCKoHsZKHeBT1JauoipgPykO21qn04erq5pJkA0MsiuNRNQnMA==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - '@sentry/cli@2.37.0': resolution: {integrity: sha512-fM3V4gZRJR/s8lafc3O07hhOYRnvkySdPkvL/0e0XW0r+xRwqIAgQ5ECbsZO16A5weUiXVSf03ztDL1FcmbJCQ==} engines: {node: '>= 10'} hasBin: true - '@sentry/cli@2.38.0': - resolution: {integrity: sha512-ld9+1GdPkDaFr6T4SGocxoMcrBB/K6Z37TvBx8IMrDQC+eJDkBFiyqmHnzrj/8xoj5O220pqjPZCfvqzH268sQ==} - engines: {node: '>= 10'} - hasBin: true - '@sentry/core@7.114.0': resolution: {integrity: sha512-YnanVlmulkjgZiVZ9BfY9k6I082n+C+LbZo52MTvx3FY6RE5iyiPMpaOh67oXEZRWcYQEGm+bKruRxLVP6RlbA==} engines: {node: '>=8'} - '@sentry/core@8.35.0': - resolution: {integrity: sha512-Ci0Nmtw5ETWLqQJGY4dyF+iWh7PWKy6k303fCEoEmqj2czDrKJCp7yHBNV0XYbo00prj2ZTbCr6I7albYiyONA==} + '@sentry/core@8.36.0': + resolution: {integrity: sha512-cbq1WQyRqc/+YpPhjwQxfniUM3ZxmO3Pm1oisTB8dw6mlbgQfGD6aznEIjXWWJY6k6acewJlMUx09N7DnprtBw==} engines: {node: '>=14.18'} '@sentry/esbuild-plugin@2.22.6': @@ -2239,22 +2217,18 @@ packages: resolution: {integrity: sha512-BJIBWXGKeIH0ifd7goxOS29fBA8BkEgVVCahs6xIOXBjX1IRS6PmX0zYx/GP23nQTfhJiubv2XPzoYOlZZmDxg==} engines: {node: '>=8'} - '@sentry/nextjs@8.35.0': - resolution: {integrity: sha512-7V6Yd0llWvarebVhtK2UyIqkfw/BzKn/hQxJAob/FQ6V9wKFjF5W0EFtE2n/T0RCetL2JPF8iHu3/b4/TVREmg==} + '@sentry/nextjs@8.36.0': + resolution: {integrity: sha512-JLPK5ZSZdGyIPVx40qnOomHV04TB3p+HymYvfQwX7C7/Ocm8U0Q7v+0164IXHu0FVR/BfqhbE84s+mpAEnHvig==} engines: {node: '>=14.18'} peerDependencies: next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0 - webpack: '>=5.0.0' - peerDependenciesMeta: - webpack: - optional: true - '@sentry/node@8.35.0': - resolution: {integrity: sha512-B0FLOcZEfYe3CJ2t0l1N0HJcHXcIrLlGENQ2kf5HqR2zcOcOzRxyITJTSV5brCnmzVNgkz9PG8VWo3w0HXZQpA==} + '@sentry/node@8.36.0': + resolution: {integrity: sha512-2RRbSck90TGpVz8F3OaNbq5Q9RXgeRlq5leGWHU7NfQOl3LmkG+vkzTbOqPDPZLtiYcw5KQ3G5G+vybrDS6AGg==} engines: {node: '>=14.18'} - '@sentry/opentelemetry@8.35.0': - resolution: {integrity: sha512-2mWMpEiIFop/omia9BqTJa+0Khe+tSsiZSUrxbnSpxM0zgw8DFIzJMHbiqw/I7Qaluz9pnO2HZXqgUTwNPsU8A==} + '@sentry/opentelemetry@8.36.0': + resolution: {integrity: sha512-pMKMphH0j1Mh8zknLWEEUaaaxeYn76rniGOxKLoQVk1pCUhhzkFEJdxKC41aR8yin/uN8X3CGWQb9vp/przwSg==} engines: {node: '>=14.18'} peerDependencies: '@opentelemetry/api': ^1.9.0 @@ -2263,8 +2237,8 @@ packages: '@opentelemetry/sdk-trace-base': ^1.26.0 '@opentelemetry/semantic-conventions': ^1.27.0 - '@sentry/react@8.35.0': - resolution: {integrity: sha512-8Y+s4pE9hvT2TwSo5JS/Enw2cNFlwiLcJDNGCj/Hho+FePFYA59hbN06ouTHWARnO+swANHKZQj24Wp57p1/tg==} + '@sentry/react@8.36.0': + resolution: {integrity: sha512-YIJZUx7Q5aulK034cRri0p/7MeP3tdLfdP6vMJMwrVlqoWQI9gKZXikmLIqHUQegZdMRYX5tr03gTWJu3dhYwg==} engines: {node: '>=14.18'} peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x @@ -2273,24 +2247,24 @@ packages: resolution: {integrity: sha512-tsqkkyL3eJtptmPtT0m9W/bPLkU7ILY7nvwpi1hahA5jrM7ppoU0IMaQWAgTD+U3rzFH40IdXNBFb8Gnqcva4w==} engines: {node: '>=8'} - '@sentry/types@8.35.0': - resolution: {integrity: sha512-AVEZjb16MlYPifiDDvJ19dPQyDn0jlrtC1PHs6ZKO+Rzyz+2EX2BRdszvanqArldexPoU1p5Bn2w81XZNXThBA==} + '@sentry/types@8.36.0': + resolution: {integrity: sha512-K1pVFfdGHw115RzGHpwSOqoEPeayn4N1F9IfM0kxrYpQSbFT1X29eak88GBfC8gPiLEF0iFGlSaQ4ERmF7oRcA==} engines: {node: '>=14.18'} '@sentry/utils@7.114.0': resolution: {integrity: sha512-319N90McVpupQ6vws4+tfCy/03AdtsU0MurIE4+W5cubHME08HtiEWlfacvAxX+yuKFhvdsO4K4BB/dj54ideg==} engines: {node: '>=8'} - '@sentry/utils@8.35.0': - resolution: {integrity: sha512-MdMb6+uXjqND7qIPWhulubpSeHzia6HtxeJa8jYI09OCvIcmNGPydv/Gx/LZBwosfMHrLdTWcFH7Y7aCxrq7cg==} + '@sentry/utils@8.36.0': + resolution: {integrity: sha512-oJ3EDPj0I00z+AwC3EWBpSidXYUoKW0Id8MfMQP5Hflniz3gif7UEReblT+FJgPEVo6+6uNzAncY0MuNMxmDKQ==} engines: {node: '>=14.18'} - '@sentry/vercel-edge@8.35.0': - resolution: {integrity: sha512-Wp5HCkBb6hA1oE4gETzi4laMsPsc7UBqKCMY4H/UOkuD6HzgpyWuHZeS6nrs2A3MJWcoNoFZ2sJD1hdo4apzGQ==} + '@sentry/vercel-edge@8.36.0': + resolution: {integrity: sha512-5zSw+5JniztTkiOXU4pUXypD9opPVf91Gd71/khrFfDbDBT8gF/LlClp21ACt/c3jc1Bbk79j+lyYEXVe8/jIg==} engines: {node: '>=14.18'} - '@sentry/webpack-plugin@2.22.3': - resolution: {integrity: sha512-Sq1S6bL3nuoTP5typkj+HPjQ13dqftIE8kACAq4tKkXOpWO9bf6HtqcruEQCxMekbWDTdljsrknQ17ZBx2q66Q==} + '@sentry/webpack-plugin@2.22.6': + resolution: {integrity: sha512-BiLhAzQYAz/9kCXKj2LeUKWf/9GBVn2dD0DeYK89s+sjDEaxjbcLBBiLlLrzT7eC9QVj2tUZRKOi6puCfc8ysw==} engines: {node: '>= 14'} peerDependencies: webpack: '>=4.40.0' @@ -2366,16 +2340,16 @@ packages: tom-select: optional: true - '@tabler/icons-react@3.20.0': - resolution: {integrity: sha512-a47oaL48bb5Cx/WUVfg/NZrsWwFExrcDQO8thUZ7S6h/OQYFu7sm4E5pZsmUtGCjikB3lRzjtmMD+C4s7mr9yw==} + '@tabler/icons-react@3.21.0': + resolution: {integrity: sha512-Qq0GnZzzccbv/zuMyXAUUPlogNAqx9KsF8cr/ev3bxs+GMObqNEjXv1eZl9GFzxyQTS435siJNU8A1BaIYhX8g==} peerDependencies: react: '>= 16' '@tabler/icons@3.17.0': resolution: {integrity: sha512-sCSfAQ0w93KSnSL7tS08n73CdIKpuHP8foeLMWgDKiZaCs8ZE//N3ytazCk651ZtruTtByI3b+ZDj7nRf+hHvA==} - '@tabler/icons@3.20.0': - resolution: {integrity: sha512-nXSeUzsCOxX/Of+kdUVQfxL9bG+ck8XCWNf9dGSpE+nhVexRwk/4HiDQDxFDysfT7vfgSut6GXnrZsU5M5dSlA==} + '@tabler/icons@3.21.0': + resolution: {integrity: sha512-5+GkkmWCr1wgMor5cOF1/YYflTQdc15y10FUikJ3HW8hDiFjfbuoAHJi17FT1vwsr1sA78rkJMn+fDoOOjnnPA==} '@tanstack/query-core@5.59.16': resolution: {integrity: sha512-crHn+G3ltqb5JG0oUv6q+PMz1m1YkjpASrXTU+sYWW9pLk0t2GybUHNRqYPZWhxgjPaVGC4yp92gSFEJgYEsPw==} @@ -2389,8 +2363,8 @@ packages: resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.6.2': - resolution: {integrity: sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==} + '@testing-library/jest-dom@6.6.3': + resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} '@testing-library/react@16.0.1': @@ -2495,8 +2469,8 @@ packages: '@types/mysql@2.15.26': resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/node@22.8.0': - resolution: {integrity: sha512-84rafSBHC/z1i1E3p0cJwKA+CfYDNSXX9WSZBRopjIzLET8oNt6ht2tei4C7izwDeEiLLfdeSVBv1egOH916hg==} + '@types/node@22.8.6': + resolution: {integrity: sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -2577,23 +2551,22 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitest/coverage-v8@2.1.3': - resolution: {integrity: sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==} + '@vitest/coverage-v8@2.1.4': + resolution: {integrity: sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==} peerDependencies: - '@vitest/browser': 2.1.3 - vitest: 2.1.3 + '@vitest/browser': 2.1.4 + vitest: 2.1.4 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@2.1.3': - resolution: {integrity: sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==} + '@vitest/expect@2.1.4': + resolution: {integrity: sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==} - '@vitest/mocker@2.1.3': - resolution: {integrity: sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==} + '@vitest/mocker@2.1.4': + resolution: {integrity: sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==} peerDependencies: - '@vitest/spy': 2.1.3 - msw: ^2.3.5 + msw: ^2.4.9 vite: ^5.0.0 peerDependenciesMeta: msw: @@ -2601,25 +2574,25 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.3': - resolution: {integrity: sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==} + '@vitest/pretty-format@2.1.4': + resolution: {integrity: sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==} - '@vitest/runner@2.1.3': - resolution: {integrity: sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==} + '@vitest/runner@2.1.4': + resolution: {integrity: sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==} - '@vitest/snapshot@2.1.3': - resolution: {integrity: sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==} + '@vitest/snapshot@2.1.4': + resolution: {integrity: sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==} - '@vitest/spy@2.1.3': - resolution: {integrity: sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==} + '@vitest/spy@2.1.4': + resolution: {integrity: sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==} - '@vitest/ui@2.1.3': - resolution: {integrity: sha512-2XwTrHVJw3t9NYES26LQUYy51ZB8W4bRPgqUH2Eyda3kIuOlYw1ZdPNU22qcVlUVx4WKgECFQOSXuopsczuVjQ==} + '@vitest/ui@2.1.4': + resolution: {integrity: sha512-Zd9e5oU063c+j9N9XzGJagCLNvG71x/2tOme3Js4JEZKX55zsgxhJwUgLI8hkN6NjMLpdJO8d7nVUUuPGAA58Q==} peerDependencies: - vitest: 2.1.3 + vitest: 2.1.4 - '@vitest/utils@2.1.3': - resolution: {integrity: sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==} + '@vitest/utils@2.1.4': + resolution: {integrity: sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==} '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -2690,6 +2663,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -2852,8 +2830,8 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bullmq@5.21.2: - resolution: {integrity: sha512-LPuNoGaDc5CON2X6h4cJ2iVfd+B+02xubFU+IB/fyJHd+/HqUZRqnlYryUCAuhVHBhUKtA6oyVdJxqSa62i+og==} + bullmq@5.22.0: + resolution: {integrity: sha512-nwjJSQt/kpO4bIfAznyKKz3+m5OZ6YSaz2Vg7oNoZWTD5wCnJJJy6b9iWM5QIF0bADhDWyorLCO0hU3de+iKMA==} busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -2876,8 +2854,8 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chai@5.1.1: - resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} engines: {node: '>=12'} chalk@2.4.2: @@ -3126,12 +3104,12 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} - drizzle-orm@0.35.3: - resolution: {integrity: sha512-Uv6N+b36x4BaZlxc96e+ag7RnMapBLGhc4SSi2F7RDwqYJipWjaU/P68RUp1FbW9r+mxoDp8nMz2Eece8PJxfA==} + drizzle-orm@0.36.0: + resolution: {integrity: sha512-6BETYPdKSR7cDHC0ZfqZk2VrKJ8n/Rfd3ajFPsAbc69gi87nwZ6oBA2wUGMELHA0tQE4kUKN0Ds00LUZQ6Z69A==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' - '@electric-sql/pglite': '>=0.1.1' + '@electric-sql/pglite': '>=0.2.0' '@libsql/client': '>=0.10.0' '@libsql/client-wasm': '>=0.10.0' '@neondatabase/serverless': '>=0.1' @@ -3167,6 +3145,8 @@ packages: optional: true '@libsql/client': optional: true + '@libsql/client-wasm': + optional: true '@neondatabase/serverless': optional: true '@op-engineering/op-sqlite': @@ -3330,6 +3310,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -3502,8 +3486,8 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - hono@4.6.6: - resolution: {integrity: sha512-euUj5qwvtkG+p38GFs0LYacwaoS2hYRAGn9ysAggiwT2QBcPnT1XYUCW3hatW4C1KzAXTYuQ08BlVDJtAGuhlg==} + hono@4.6.8: + resolution: {integrity: sha512-f+2Ec9JAzabT61pglDiLJcF/DjiSefZkjCn9bzm1cYLGkD5ExJ3Jnv93ax9h0bn7UPLHF81KktoyjdQfWI2n1Q==} engines: {node: '>=16.9.0'} html-encoding-sniffer@4.0.0: @@ -3688,8 +3672,8 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - jiti@2.3.3: - resolution: {integrity: sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==} + jiti@2.4.0: + resolution: {integrity: sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==} hasBin: true js-base64@3.7.7: @@ -3750,8 +3734,8 @@ packages: jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} - knip@5.34.0: - resolution: {integrity: sha512-TuPl8Tfrqoc1LWlR5S1RZwwN2Vk+CUCAGUbji6VAAUFv/nS5S6pzNwBYwDP0tCCN/iAPf9/NhbAni7s135vrQQ==} + knip@5.36.1: + resolution: {integrity: sha512-xtzA6ArOA+6TOJucXuLQdDAg78VabaxAgrI9Zj1KwxpRmUrJqJoeS6B2SyQStOOw5CNZKXMfbNVCyNvO4UEFSg==} engines: {node: '>=18.6.0'} hasBin: true peerDependencies: @@ -4066,11 +4050,11 @@ packages: resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} hasBin: true - msgpackr@1.11.0: - resolution: {integrity: sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==} + msgpackr@1.11.2: + resolution: {integrity: sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==} - msw@2.5.1: - resolution: {integrity: sha512-V0BmHvFkbWGXqbyrc+XiuQ8DU3qzcb6lb8gB9Vzltp3cgHLHLCDF/KmmFo0xw58StNaRMTebw3/xpWVvU9xq9g==} + msw@2.6.0: + resolution: {integrity: sha512-n3tx2w0MZ3H4pxY0ozrQ4sNPzK/dGtlr2cIIyuEsgq2Bhy4wvcW6ZH2w/gXM9+MEUY6HC1fWhqtcXDxVZr5Jxw==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -4105,11 +4089,11 @@ packages: next: '>= 13.0.0' react: '>= 16.8.0' - next-intl@3.23.5: - resolution: {integrity: sha512-mUuz3JFRdtWpyYEPN8xUXpkn4qEnAADxA5GQjUGobbLY32kWrYfZYs4MrYHAVl2zAAbPcD9oisXF0q8B1ifszA==} + next-intl@3.24.0: + resolution: {integrity: sha512-48X68QsI92grir2dH1W15yhyVnEjW4c9qmwNt+du+k6mI1QtlE6GyANWHoL4/leTixHv8knZ1y9B/Ys06gmKLg==} peerDependencies: next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 next-router-mock@0.9.13: resolution: {integrity: sha512-906n2RRaE6Y28PfYJbaz5XZeJ6Tw8Xz1S6E31GGwZ0sXB6/XjldD1/2azn1ZmBmRk5PQRkzjg+n+RHZe5xQzWA==} @@ -4117,8 +4101,8 @@ packages: next: '>=10.0.0' react: '>=17.0.0' - next-safe-action@7.9.7: - resolution: {integrity: sha512-xulVnnGtmL8nf2p93qG9Q2npFWWfqqnWrUgpnTKATLIFHqTIE6fBdYr5KavhODK+GfDg5hHD+f07JQzHsehLsg==} + next-safe-action@7.9.9: + resolution: {integrity: sha512-wFKKCgfHNsObfbDrbOQV8WAE6RnVx7dwmuUazqdNaTL3ZdDzUlRTnIIVI36qSjmgA3zwwxj3nvfxgK9d0fWr5w==} engines: {node: '>=18.17'} peerDependencies: '@sinclair/typebox': '>= 0.33.3' @@ -4335,13 +4319,13 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - playwright-core@1.48.1: - resolution: {integrity: sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==} + playwright-core@1.48.2: + resolution: {integrity: sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==} engines: {node: '>=18'} hasBin: true - playwright@1.48.1: - resolution: {integrity: sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==} + playwright@1.48.2: + resolution: {integrity: sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==} engines: {node: '>=18'} hasBin: true @@ -4631,8 +4615,8 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true - rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} + rollup@4.24.3: + resolution: {integrity: sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4655,8 +4639,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.80.4: - resolution: {integrity: sha512-rhMQ2tSF5CsuuspvC94nPM9rToiAFw2h3JTrLlgmNw1MH79v8Cr3DH6KF6o6r+8oofY3iYVPUf66KzC8yuVN1w==} + sass@1.80.5: + resolution: {integrity: sha512-TQd2aoQl/+zsxRMEDSxVdpPIqeq9UFc6pr7PzkugiTx3VYCFPUaa3P4RrBQsqok4PO200Vkz0vXQBNlg7W907g==} engines: {node: '>=14.0.0'} hasBin: true @@ -4712,9 +4696,9 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} - sirv@2.0.4: - resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} - engines: {node: '>= 10'} + sirv@3.0.0: + resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} + engines: {node: '>=18'} smol-toml@1.3.0: resolution: {integrity: sha512-tWpi2TsODPScmi48b/OQZGi2lgUmBCHy6SZrhi/FdnnHiU1GwebbCfuQuxsC3nHaLwtYeJGPrDZDIeodDOc4pA==} @@ -4940,8 +4924,8 @@ packages: tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} - tinyglobby@0.2.9: - resolution: {integrity: sha512-8or1+BGEdk1Zkkw2ii16qSS7uVrQJPre5A9o/XkWPATkk23FZh/15BKFxPnlTy6vkljZxLqYCzzBMj30ZrSvjw==} + tinyglobby@0.2.10: + resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} tinypool@1.0.1: @@ -5027,6 +5011,9 @@ packages: tslib@2.8.0: resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -5101,10 +5088,10 @@ packages: '@types/react': optional: true - use-intl@3.23.5: - resolution: {integrity: sha512-t+iwRqyAzKUY3E0DbA70eTsNxLKr6RndqdIOedqIQPfcNNkFxHs3g6+6i0PWXAGYRXaqOzzE3ZWYFOMCmnUkWg==} + use-intl@3.24.0: + resolution: {integrity: sha512-lmrARod7yjMYehbyY9xBLjjgnlNcJsl1UAltAPlgspRG7RH6H0JYaGo4C3PZW/BTy0Dgmcvcl8rH/VemzGIhgQ==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 use-isomorphic-layout-effect@1.1.2: resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} @@ -5158,8 +5145,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@2.1.3: - resolution: {integrity: sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==} + vite-node@2.1.4: + resolution: {integrity: sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -5171,8 +5158,8 @@ packages: vite: optional: true - vite@5.4.9: - resolution: {integrity: sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==} + vite@5.4.10: + resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5208,15 +5195,15 @@ packages: typescript: 3.x || 4.x || 5.x vitest: '>=2.0.0' - vitest@2.1.3: - resolution: {integrity: sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==} + vitest@2.1.4: + resolution: {integrity: sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.3 - '@vitest/ui': 2.1.3 + '@vitest/browser': 2.1.4 + '@vitest/ui': 2.1.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -5445,6 +5432,12 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.26.0': {} '@babel/core@7.26.0': @@ -5523,6 +5516,10 @@ snapshots: dependencies: '@babel/types': 7.26.0 + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -5624,7 +5621,7 @@ snapshots: '@emnapi/runtime@1.2.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@emotion/babel-plugin@11.12.0': @@ -5832,7 +5829,7 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true - '@faker-js/faker@9.0.3': {} + '@faker-js/faker@9.1.0': {} '@floating-ui/core@1.6.8': dependencies: @@ -5843,6 +5840,11 @@ snapshots: '@floating-ui/core': 1.6.8 '@floating-ui/utils': 0.2.8 + '@floating-ui/dom@1.6.12': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/dom': 1.6.11 @@ -5855,32 +5857,32 @@ snapshots: dependencies: '@formatjs/fast-memoize': 2.2.2 '@formatjs/intl-localematcher': 0.5.6 - tslib: 2.8.0 + tslib: 2.8.1 '@formatjs/fast-memoize@2.2.2': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 '@formatjs/icu-messageformat-parser@2.9.1': dependencies: '@formatjs/ecma402-abstract': 2.2.1 '@formatjs/icu-skeleton-parser': 1.8.5 - tslib: 2.8.0 + tslib: 2.8.1 '@formatjs/icu-skeleton-parser@1.8.5': dependencies: '@formatjs/ecma402-abstract': 2.2.1 - tslib: 2.8.0 + tslib: 2.8.1 '@formatjs/intl-localematcher@0.5.6': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 - '@hono/node-server@1.13.2(hono@4.6.6)': + '@hono/node-server@1.13.4(hono@4.6.8)': dependencies: - hono: 4.6.6 + hono: 4.6.8 - '@hookform/resolvers@3.9.0(react-hook-form@7.53.1(react@18.3.1))': + '@hookform/resolvers@3.9.1(react-hook-form@7.53.1(react@18.3.1))': dependencies: react-hook-form: 7.53.1(react@18.3.1) @@ -5959,16 +5961,16 @@ snapshots: '@img/sharp-win32-x64@0.33.5': optional: true - '@inquirer/confirm@5.0.1(@types/node@22.8.0)': + '@inquirer/confirm@5.0.1(@types/node@22.8.6)': dependencies: - '@inquirer/core': 10.0.1(@types/node@22.8.0) - '@inquirer/type': 3.0.0(@types/node@22.8.0) - '@types/node': 22.8.0 + '@inquirer/core': 10.0.1(@types/node@22.8.6) + '@inquirer/type': 3.0.0(@types/node@22.8.6) + '@types/node': 22.8.6 - '@inquirer/core@10.0.1(@types/node@22.8.0)': + '@inquirer/core@10.0.1(@types/node@22.8.6)': dependencies: '@inquirer/figures': 1.0.7 - '@inquirer/type': 3.0.0(@types/node@22.8.0) + '@inquirer/type': 3.0.0(@types/node@22.8.6) ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -5981,9 +5983,9 @@ snapshots: '@inquirer/figures@1.0.7': {} - '@inquirer/type@3.0.0(@types/node@22.8.0)': + '@inquirer/type@3.0.0(@types/node@22.8.6)': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@ioredis/commands@1.2.0': {} @@ -6040,10 +6042,12 @@ snapshots: dependencies: '@libsql/core': 0.14.0 js-base64: 3.7.7 + optional: true '@libsql/core@0.14.0': dependencies: js-base64: 3.7.7 + optional: true '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true @@ -6063,7 +6067,7 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true - '@mswjs/interceptors@0.36.6': + '@mswjs/interceptors@0.36.7': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -6130,6 +6134,10 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs@0.54.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api@1.9.0': {} '@opentelemetry/context-async-hooks@1.27.0(@opentelemetry/api@1.9.0)': @@ -6155,11 +6163,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-connect@0.39.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-connect@0.40.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.54.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 '@types/connect': 3.4.36 transitivePeerDependencies: @@ -6172,11 +6180,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-express@0.43.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-express@0.44.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.54.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 transitivePeerDependencies: - supports-color @@ -6190,11 +6198,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fs@0.15.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-fs@0.16.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.54.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -6240,10 +6248,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-kafkajs@0.3.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-kafkajs@0.4.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.54.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 transitivePeerDependencies: - supports-color @@ -6360,6 +6368,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation@0.54.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.0 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.11.2 + require-in-the-middle: 7.4.0 + semver: 7.6.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + '@opentelemetry/redis-common@0.36.2': {} '@opentelemetry/resources@1.26.0(@opentelemetry/api@1.9.0)': @@ -6466,9 +6486,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.48.1': + '@playwright/test@1.48.2': dependencies: - playwright: 1.48.1 + playwright: 1.48.2 '@polka/url@1.0.0-next.28': {} @@ -6869,52 +6889,58 @@ snapshots: optionalDependencies: rollup: 3.29.5 - '@rollup/rollup-android-arm-eabi@4.24.0': + '@rollup/rollup-android-arm-eabi@4.24.3': + optional: true + + '@rollup/rollup-android-arm64@4.24.3': optional: true - '@rollup/rollup-android-arm64@4.24.0': + '@rollup/rollup-darwin-arm64@4.24.3': optional: true - '@rollup/rollup-darwin-arm64@4.24.0': + '@rollup/rollup-darwin-x64@4.24.3': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rollup/rollup-freebsd-arm64@4.24.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rollup/rollup-freebsd-x64@4.24.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rollup/rollup-linux-arm-gnueabihf@4.24.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rollup/rollup-linux-arm-musleabihf@4.24.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rollup/rollup-linux-arm64-gnu@4.24.3': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rollup/rollup-linux-arm64-musl@4.24.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.24.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.0': + '@rollup/rollup-linux-riscv64-gnu@4.24.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.24.0': + '@rollup/rollup-linux-s390x-gnu@4.24.3': optional: true - '@rollup/rollup-linux-x64-musl@4.24.0': + '@rollup/rollup-linux-x64-gnu@4.24.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.24.0': + '@rollup/rollup-linux-x64-musl@4.24.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.24.0': + '@rollup/rollup-win32-arm64-msvc@4.24.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.24.0': + '@rollup/rollup-win32-ia32-msvc@4.24.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.24.3': optional: true '@runtipi/postgres-migrations@5.3.0': @@ -6924,59 +6950,43 @@ snapshots: transitivePeerDependencies: - pg-native - '@sentry-internal/browser-utils@8.35.0': + '@sentry-internal/browser-utils@8.36.0': dependencies: - '@sentry/core': 8.35.0 - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 + '@sentry/core': 8.36.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 - '@sentry-internal/feedback@8.35.0': + '@sentry-internal/feedback@8.36.0': dependencies: - '@sentry/core': 8.35.0 - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 + '@sentry/core': 8.36.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 - '@sentry-internal/replay-canvas@8.35.0': + '@sentry-internal/replay-canvas@8.36.0': dependencies: - '@sentry-internal/replay': 8.35.0 - '@sentry/core': 8.35.0 - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 + '@sentry-internal/replay': 8.36.0 + '@sentry/core': 8.36.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 - '@sentry-internal/replay@8.35.0': + '@sentry-internal/replay@8.36.0': dependencies: - '@sentry-internal/browser-utils': 8.35.0 - '@sentry/core': 8.35.0 - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 - - '@sentry/babel-plugin-component-annotate@2.22.3': {} + '@sentry-internal/browser-utils': 8.36.0 + '@sentry/core': 8.36.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 '@sentry/babel-plugin-component-annotate@2.22.6': {} - '@sentry/browser@8.35.0': + '@sentry/browser@8.36.0': dependencies: - '@sentry-internal/browser-utils': 8.35.0 - '@sentry-internal/feedback': 8.35.0 - '@sentry-internal/replay': 8.35.0 - '@sentry-internal/replay-canvas': 8.35.0 - '@sentry/core': 8.35.0 - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 - - '@sentry/bundler-plugin-core@2.22.3': - dependencies: - '@babel/core': 7.26.0 - '@sentry/babel-plugin-component-annotate': 2.22.3 - '@sentry/cli': 2.38.0 - dotenv: 16.4.5 - find-up: 5.0.0 - glob: 9.3.5 - magic-string: 0.30.8 - unplugin: 1.0.1 - transitivePeerDependencies: - - encoding - - supports-color + '@sentry-internal/browser-utils': 8.36.0 + '@sentry-internal/feedback': 8.36.0 + '@sentry-internal/replay': 8.36.0 + '@sentry-internal/replay-canvas': 8.36.0 + '@sentry/core': 8.36.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 '@sentry/bundler-plugin-core@2.22.6': dependencies: @@ -6995,45 +7005,24 @@ snapshots: '@sentry/cli-darwin@2.37.0': optional: true - '@sentry/cli-darwin@2.38.0': - optional: true - '@sentry/cli-linux-arm64@2.37.0': optional: true - '@sentry/cli-linux-arm64@2.38.0': - optional: true - '@sentry/cli-linux-arm@2.37.0': optional: true - '@sentry/cli-linux-arm@2.38.0': - optional: true - '@sentry/cli-linux-i686@2.37.0': optional: true - '@sentry/cli-linux-i686@2.38.0': - optional: true - '@sentry/cli-linux-x64@2.37.0': optional: true - '@sentry/cli-linux-x64@2.38.0': - optional: true - '@sentry/cli-win32-i686@2.37.0': optional: true - '@sentry/cli-win32-i686@2.38.0': - optional: true - '@sentry/cli-win32-x64@2.37.0': optional: true - '@sentry/cli-win32-x64@2.38.0': - optional: true - '@sentry/cli@2.37.0': dependencies: https-proxy-agent: 5.0.1 @@ -7053,34 +7042,15 @@ snapshots: - encoding - supports-color - '@sentry/cli@2.38.0': - dependencies: - https-proxy-agent: 5.0.1 - node-fetch: 2.7.0 - progress: 2.0.3 - proxy-from-env: 1.1.0 - which: 2.0.2 - optionalDependencies: - '@sentry/cli-darwin': 2.38.0 - '@sentry/cli-linux-arm': 2.38.0 - '@sentry/cli-linux-arm64': 2.38.0 - '@sentry/cli-linux-i686': 2.38.0 - '@sentry/cli-linux-x64': 2.38.0 - '@sentry/cli-win32-i686': 2.38.0 - '@sentry/cli-win32-x64': 2.38.0 - transitivePeerDependencies: - - encoding - - supports-color - '@sentry/core@7.114.0': dependencies: '@sentry/types': 7.114.0 '@sentry/utils': 7.114.0 - '@sentry/core@8.35.0': + '@sentry/core@8.36.0': dependencies: - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 '@sentry/esbuild-plugin@2.22.6': dependencies: @@ -7098,54 +7068,53 @@ snapshots: '@sentry/utils': 7.114.0 localforage: 1.10.0 - '@sentry/nextjs@8.35.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react@18.3.1)(webpack@5.94.0)': + '@sentry/nextjs@8.36.0(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react@18.3.1)(webpack@5.94.0)': dependencies: + '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 '@rollup/plugin-commonjs': 26.0.1(rollup@3.29.5) - '@sentry-internal/browser-utils': 8.35.0 - '@sentry/core': 8.35.0 - '@sentry/node': 8.35.0 - '@sentry/opentelemetry': 8.35.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0) - '@sentry/react': 8.35.0(react@18.3.1) - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 - '@sentry/vercel-edge': 8.35.0 - '@sentry/webpack-plugin': 2.22.3(webpack@5.94.0) + '@sentry-internal/browser-utils': 8.36.0 + '@sentry/core': 8.36.0 + '@sentry/node': 8.36.0 + '@sentry/opentelemetry': 8.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0) + '@sentry/react': 8.36.0(react@18.3.1) + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 + '@sentry/vercel-edge': 8.36.0 + '@sentry/webpack-plugin': 2.22.6(webpack@5.94.0) chalk: 3.0.0 - next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 - optionalDependencies: - webpack: 5.94.0 transitivePeerDependencies: - - '@opentelemetry/api' - '@opentelemetry/core' - '@opentelemetry/instrumentation' - '@opentelemetry/sdk-trace-base' - encoding - react - supports-color + - webpack - '@sentry/node@8.35.0': + '@sentry/node@8.36.0': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-amqplib': 0.42.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-connect': 0.39.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.40.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-dataloader': 0.12.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-express': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.44.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-fastify': 0.40.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-fs': 0.15.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.16.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-generic-pool': 0.39.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-graphql': 0.43.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-hapi': 0.41.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-ioredis': 0.43.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-kafkajs': 0.3.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.4.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-koa': 0.43.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-lru-memoizer': 0.40.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-mongodb': 0.47.0(@opentelemetry/api@1.9.0) @@ -7160,55 +7129,67 @@ snapshots: '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 '@prisma/instrumentation': 5.19.1 - '@sentry/core': 8.35.0 - '@sentry/opentelemetry': 8.35.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0) - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 + '@sentry/core': 8.36.0 + '@sentry/opentelemetry': 8.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0) + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 import-in-the-middle: 1.11.2 transitivePeerDependencies: - supports-color - '@sentry/opentelemetry@8.35.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0)': + '@sentry/opentelemetry@8.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 - '@sentry/core': 8.35.0 - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 + '@sentry/core': 8.36.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 - '@sentry/react@8.35.0(react@18.3.1)': + '@sentry/opentelemetry@8.36.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0)': dependencies: - '@sentry/browser': 8.35.0 - '@sentry/core': 8.35.0 - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + '@sentry/core': 8.36.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 + + '@sentry/react@8.36.0(react@18.3.1)': + dependencies: + '@sentry/browser': 8.36.0 + '@sentry/core': 8.36.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 hoist-non-react-statics: 3.3.2 react: 18.3.1 '@sentry/types@7.114.0': {} - '@sentry/types@8.35.0': {} + '@sentry/types@8.36.0': {} '@sentry/utils@7.114.0': dependencies: '@sentry/types': 7.114.0 - '@sentry/utils@8.35.0': + '@sentry/utils@8.36.0': dependencies: - '@sentry/types': 8.35.0 + '@sentry/types': 8.36.0 - '@sentry/vercel-edge@8.35.0': + '@sentry/vercel-edge@8.36.0': dependencies: - '@sentry/core': 8.35.0 - '@sentry/types': 8.35.0 - '@sentry/utils': 8.35.0 + '@opentelemetry/api': 1.9.0 + '@sentry/core': 8.36.0 + '@sentry/types': 8.36.0 + '@sentry/utils': 8.36.0 - '@sentry/webpack-plugin@2.22.3(webpack@5.94.0)': + '@sentry/webpack-plugin@2.22.6(webpack@5.94.0)': dependencies: - '@sentry/bundler-plugin-core': 2.22.3 + '@sentry/bundler-plugin-core': 2.22.6 unplugin: 1.0.1 uuid: 9.0.1 webpack: 5.94.0 @@ -7229,7 +7210,7 @@ snapshots: '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 - tslib: 2.8.0 + tslib: 2.8.1 '@tabler/core@1.0.0-beta21': dependencies: @@ -7237,14 +7218,14 @@ snapshots: '@tabler/icons': 3.17.0 bootstrap: 5.3.3(@popperjs/core@2.11.8) - '@tabler/icons-react@3.20.0(react@18.3.1)': + '@tabler/icons-react@3.21.0(react@18.3.1)': dependencies: - '@tabler/icons': 3.20.0 + '@tabler/icons': 3.21.0 react: 18.3.1 '@tabler/icons@3.17.0': {} - '@tabler/icons@3.20.0': {} + '@tabler/icons@3.21.0': {} '@tanstack/query-core@5.59.16': {} @@ -7264,7 +7245,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.6.2': + '@testing-library/jest-dom@6.6.3': dependencies: '@adobe/css-tools': 4.4.0 aria-query: 5.3.2 @@ -7317,7 +7298,7 @@ snapshots: '@types/connect@3.4.36': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@types/cookie@0.4.1': {} @@ -7325,7 +7306,7 @@ snapshots: '@types/cors@2.8.17': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@types/debug@4.1.12': dependencies: @@ -7344,11 +7325,11 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@types/gunzip-maybe@1.4.2': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@types/hast@3.0.4': dependencies: @@ -7358,11 +7339,11 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@types/jsonwebtoken@9.0.7': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@types/lodash.clonedeep@4.5.9': dependencies: @@ -7382,9 +7363,9 @@ snapshots: '@types/mysql@2.15.26': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 - '@types/node@22.8.0': + '@types/node@22.8.6': dependencies: undici-types: 6.19.8 @@ -7396,13 +7377,13 @@ snapshots: '@types/pg@8.11.10': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 pg-protocol: 1.7.0 pg-types: 4.0.2 '@types/pg@8.6.1': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 pg-protocol: 1.7.0 pg-types: 2.2.0 @@ -7429,12 +7410,12 @@ snapshots: '@types/tar-fs@2.0.4': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@types/tar-stream': 3.1.3 '@types/tar-stream@3.1.3': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@types/tough-cookie@4.0.5': {} @@ -7452,7 +7433,7 @@ snapshots: '@types/web-push@3.6.4': dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 '@uidotdev/usehooks@2.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -7461,18 +7442,18 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react@4.3.3(vite@5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0))': + '@vitejs/plugin-react@4.3.3(vite@5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0) + vite: 5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.1.3(vitest@2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0))': + '@vitest/coverage-v8@2.1.4(vitest@2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -7486,59 +7467,59 @@ snapshots: std-env: 3.7.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0) + vitest: 2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.3': + '@vitest/expect@2.1.4': dependencies: - '@vitest/spy': 2.1.3 - '@vitest/utils': 2.1.3 - chai: 5.1.1 + '@vitest/spy': 2.1.4 + '@vitest/utils': 2.1.4 + chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(vite@5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0))': + '@vitest/mocker@2.1.4(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(vite@5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0))': dependencies: - '@vitest/spy': 2.1.3 + '@vitest/spy': 2.1.4 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: - msw: 2.5.1(@types/node@22.8.0)(typescript@5.6.3) - vite: 5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0) + msw: 2.6.0(@types/node@22.8.6)(typescript@5.6.3) + vite: 5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0) - '@vitest/pretty-format@2.1.3': + '@vitest/pretty-format@2.1.4': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.3': + '@vitest/runner@2.1.4': dependencies: - '@vitest/utils': 2.1.3 + '@vitest/utils': 2.1.4 pathe: 1.1.2 - '@vitest/snapshot@2.1.3': + '@vitest/snapshot@2.1.4': dependencies: - '@vitest/pretty-format': 2.1.3 + '@vitest/pretty-format': 2.1.4 magic-string: 0.30.12 pathe: 1.1.2 - '@vitest/spy@2.1.3': + '@vitest/spy@2.1.4': dependencies: tinyspy: 3.0.2 - '@vitest/ui@2.1.3(vitest@2.1.3)': + '@vitest/ui@2.1.4(vitest@2.1.4)': dependencies: - '@vitest/utils': 2.1.3 + '@vitest/utils': 2.1.4 fflate: 0.8.2 flatted: 3.3.1 pathe: 1.1.2 - sirv: 2.0.4 - tinyglobby: 0.2.9 + sirv: 3.0.0 + tinyglobby: 0.2.10 tinyrainbow: 1.2.0 - vitest: 2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0) + vitest: 2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0) - '@vitest/utils@2.1.3': + '@vitest/utils@2.1.4': dependencies: - '@vitest/pretty-format': 2.1.3 + '@vitest/pretty-format': 2.1.4 loupe: 3.1.2 tinyrainbow: 1.2.0 @@ -7631,12 +7612,14 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-import-attributes@1.9.5(acorn@8.13.0): + acorn-import-attributes@1.9.5(acorn@8.14.0): dependencies: - acorn: 8.13.0 + acorn: 8.14.0 acorn@8.13.0: {} + acorn@8.14.0: {} + agent-base@6.0.2: dependencies: debug: 4.3.7(supports-color@5.5.0) @@ -7704,7 +7687,7 @@ snapshots: aria-hidden@1.2.4: dependencies: - tslib: 2.8.0 + tslib: 2.8.1 aria-query@5.3.0: dependencies: @@ -7806,14 +7789,14 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bullmq@5.21.2: + bullmq@5.22.0: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 - msgpackr: 1.11.0 + msgpackr: 1.11.2 node-abort-controller: 3.1.1 semver: 7.6.3 - tslib: 2.8.0 + tslib: 2.8.1 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -7832,7 +7815,7 @@ snapshots: ccount@2.0.1: {} - chai@5.1.1: + chai@5.1.2: dependencies: assertion-error: 2.0.1 check-error: 2.1.1 @@ -8062,10 +8045,9 @@ snapshots: dotenv@16.4.5: {} - drizzle-orm@0.35.3(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.12)(pg@8.13.1)(react@18.3.1): - dependencies: - '@libsql/client-wasm': 0.14.0 + drizzle-orm@0.36.0(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.12)(pg@8.13.1)(react@18.3.1): optionalDependencies: + '@libsql/client-wasm': 0.14.0 '@opentelemetry/api': 1.9.0 '@types/pg': 8.11.10 '@types/react': 18.3.12 @@ -8121,7 +8103,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 22.8.0 + '@types/node': 22.8.6 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -8235,6 +8217,8 @@ snapshots: events@3.3.0: {} + expect-type@1.1.0: {} + extend@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -8305,9 +8289,9 @@ snapshots: function-bind@1.1.2: {} - geist@1.3.1(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4)): + geist@1.3.1(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5)): dependencies: - next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5) gensync@1.0.0-beta.2: {} @@ -8445,7 +8429,7 @@ snapshots: dependencies: react-is: 16.13.1 - hono@4.6.6: {} + hono@4.6.8: {} html-encoding-sniffer@4.0.0: dependencies: @@ -8503,8 +8487,8 @@ snapshots: import-in-the-middle@1.11.2: dependencies: - acorn: 8.13.0 - acorn-import-attributes: 1.9.5(acorn@8.13.0) + acorn: 8.14.0 + acorn-import-attributes: 1.9.5(acorn@8.14.0) cjs-module-lexer: 1.4.1 module-details-from-path: 1.0.3 @@ -8519,7 +8503,7 @@ snapshots: '@formatjs/ecma402-abstract': 2.2.1 '@formatjs/fast-memoize': 2.2.2 '@formatjs/icu-messageformat-parser': 2.9.1 - tslib: 2.8.0 + tslib: 2.8.1 invariant@2.2.4: dependencies: @@ -8625,13 +8609,14 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 merge-stream: 2.0.0 supports-color: 8.1.1 - jiti@2.3.3: {} + jiti@2.4.0: {} - js-base64@3.7.7: {} + js-base64@3.7.7: + optional: true js-cookie@3.0.5: {} @@ -8718,15 +8703,15 @@ snapshots: jwa: 2.0.0 safe-buffer: 5.2.1 - knip@5.34.0(@types/node@22.8.0)(typescript@5.6.3): + knip@5.36.1(@types/node@22.8.6)(typescript@5.6.3): dependencies: '@nodelib/fs.walk': 1.2.8 '@snyk/github-codeowners': 1.1.0 - '@types/node': 22.8.0 + '@types/node': 22.8.6 easy-table: 1.2.0 enhanced-resolve: 5.17.1 fast-glob: 3.3.2 - jiti: 2.3.3 + jiti: 2.4.0 js-yaml: 4.1.0 minimist: 1.2.8 picocolors: 1.1.1 @@ -8820,7 +8805,7 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.26.0 + '@babel/parser': 7.26.2 '@babel/types': 7.26.0 source-map-js: 1.2.1 @@ -9244,17 +9229,18 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 optional: true - msgpackr@1.11.0: + msgpackr@1.11.2: optionalDependencies: msgpackr-extract: 3.0.3 - msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3): + msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3): dependencies: '@bundled-es-modules/cookie': 2.0.0 '@bundled-es-modules/statuses': 1.0.1 '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.0.1(@types/node@22.8.0) - '@mswjs/interceptors': 0.36.6 + '@inquirer/confirm': 5.0.1(@types/node@22.8.6) + '@mswjs/interceptors': 0.36.7 + '@open-draft/deferred-promise': 2.2.0 '@open-draft/until': 2.1.0 '@types/cookie': 0.6.0 '@types/statuses': 2.0.5 @@ -9282,34 +9268,34 @@ snapshots: neo-async@2.6.2: {} - next-client-cookies@1.1.1(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react@18.3.1): + next-client-cookies@1.1.1(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react@18.3.1): dependencies: js-cookie: 3.0.5 - next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5) react: 18.3.1 - next-intl@3.23.5(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react@18.3.1): + next-intl@3.24.0(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.6 negotiator: 1.0.0 - next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5) react: 18.3.1 - use-intl: 3.23.5(react@18.3.1) + use-intl: 3.24.0(react@18.3.1) - next-router-mock@0.9.13(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react@18.3.1): + next-router-mock@0.9.13(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react@18.3.1): dependencies: - next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5) react: 18.3.1 - next-safe-action@7.9.7(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8): + next-safe-action@7.9.9(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8): dependencies: - next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: zod: 3.23.8 - next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4): + next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.5): dependencies: '@next/env': 14.2.15 '@swc/helpers': 0.5.5 @@ -9331,8 +9317,8 @@ snapshots: '@next/swc-win32-ia32-msvc': 14.2.15 '@next/swc-win32-x64-msvc': 14.2.15 '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.48.1 - sass: 1.80.4 + '@playwright/test': 1.48.2 + sass: 1.80.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -9420,7 +9406,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.26.0 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -9509,11 +9495,11 @@ snapshots: picomatch@4.0.2: {} - playwright-core@1.48.1: {} + playwright-core@1.48.2: {} - playwright@1.48.1: + playwright@1.48.2: dependencies: - playwright-core: 1.48.1 + playwright-core: 1.48.2 optionalDependencies: fsevents: 2.3.2 @@ -9658,7 +9644,7 @@ snapshots: dependencies: react: 18.3.1 react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) - tslib: 2.8.0 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.12 @@ -9667,7 +9653,7 @@ snapshots: react: 18.3.1 react-remove-scroll-bar: 2.3.6(@types/react@18.3.12)(react@18.3.1) react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) - tslib: 2.8.0 + tslib: 2.8.1 use-callback-ref: 1.3.2(@types/react@18.3.12)(react@18.3.1) use-sidecar: 1.1.2(@types/react@18.3.12)(react@18.3.1) optionalDependencies: @@ -9678,7 +9664,7 @@ snapshots: '@babel/runtime': 7.26.0 '@emotion/cache': 11.13.1 '@emotion/react': 11.13.3(@types/react@18.3.12)(react@18.3.1) - '@floating-ui/dom': 1.6.11 + '@floating-ui/dom': 1.6.12 '@types/react-transition-group': 4.4.11 memoize-one: 6.0.0 prop-types: 15.8.1 @@ -9695,7 +9681,7 @@ snapshots: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 - tslib: 2.8.0 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.12 @@ -9846,26 +9832,28 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - rollup@4.24.0: + rollup@4.24.3: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 + '@rollup/rollup-android-arm-eabi': 4.24.3 + '@rollup/rollup-android-arm64': 4.24.3 + '@rollup/rollup-darwin-arm64': 4.24.3 + '@rollup/rollup-darwin-x64': 4.24.3 + '@rollup/rollup-freebsd-arm64': 4.24.3 + '@rollup/rollup-freebsd-x64': 4.24.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.3 + '@rollup/rollup-linux-arm-musleabihf': 4.24.3 + '@rollup/rollup-linux-arm64-gnu': 4.24.3 + '@rollup/rollup-linux-arm64-musl': 4.24.3 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.3 + '@rollup/rollup-linux-riscv64-gnu': 4.24.3 + '@rollup/rollup-linux-s390x-gnu': 4.24.3 + '@rollup/rollup-linux-x64-gnu': 4.24.3 + '@rollup/rollup-linux-x64-musl': 4.24.3 + '@rollup/rollup-win32-arm64-msvc': 4.24.3 + '@rollup/rollup-win32-ia32-msvc': 4.24.3 + '@rollup/rollup-win32-x64-msvc': 4.24.3 fsevents: 2.3.3 rrweb-cssom@0.7.1: {} @@ -9882,7 +9870,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.80.4: + sass@1.80.5: dependencies: '@parcel/watcher': 2.4.1 chokidar: 4.0.1 @@ -9957,7 +9945,7 @@ snapshots: dependencies: semver: 7.6.3 - sirv@2.0.4: + sirv@3.0.0: dependencies: '@polka/url': 1.0.0-next.28 mrmime: 2.0.0 @@ -10153,7 +10141,7 @@ snapshots: terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.13.0 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -10186,7 +10174,7 @@ snapshots: tinyexec@0.3.1: {} - tinyglobby@0.2.9: + tinyglobby@0.2.10: dependencies: fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 @@ -10248,6 +10236,8 @@ snapshots: tslib@2.8.0: {} + tslib@2.8.1: {} + type-fest@0.21.3: {} type-fest@0.7.1: {} @@ -10322,11 +10312,11 @@ snapshots: use-callback-ref@1.3.2(@types/react@18.3.12)(react@18.3.1): dependencies: react: 18.3.1 - tslib: 2.8.0 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.12 - use-intl@3.23.5(react@18.3.1): + use-intl@3.24.0(react@18.3.1): dependencies: '@formatjs/fast-memoize': 2.2.2 intl-messageformat: 10.7.3 @@ -10342,7 +10332,7 @@ snapshots: dependencies: detect-node-es: 1.1.0 react: 18.3.1 - tslib: 2.8.0 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.12 @@ -10375,12 +10365,12 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@2.1.3(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0): + vite-node@2.1.4(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@5.5.0) pathe: 1.1.2 - vite: 5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0) + vite: 5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0) transitivePeerDependencies: - '@types/node' - less @@ -10392,45 +10382,46 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0)): + vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0)): dependencies: debug: 4.3.7(supports-color@5.5.0) globrex: 0.1.2 tsconfck: 3.1.3(typescript@5.6.3) optionalDependencies: - vite: 5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0) + vite: 5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0) transitivePeerDependencies: - supports-color - typescript - vite@5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0): + vite@5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0): dependencies: esbuild: 0.21.5 postcss: 8.4.47 - rollup: 4.24.0 + rollup: 4.24.3 optionalDependencies: - '@types/node': 22.8.0 + '@types/node': 22.8.6 fsevents: 2.3.3 - sass: 1.80.4 + sass: 1.80.5 terser: 5.36.0 - vitest-mock-extended@2.0.2(typescript@5.6.3)(vitest@2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0)): + vitest-mock-extended@2.0.2(typescript@5.6.3)(vitest@2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0)): dependencies: ts-essentials: 10.0.2(typescript@5.6.3) typescript: 5.6.3 - vitest: 2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0) - - vitest@2.1.3(@types/node@22.8.0)(@vitest/ui@2.1.3)(jsdom@25.0.1)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(sass@1.80.4)(terser@5.36.0): - dependencies: - '@vitest/expect': 2.1.3 - '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(msw@2.5.1(@types/node@22.8.0)(typescript@5.6.3))(vite@5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0)) - '@vitest/pretty-format': 2.1.3 - '@vitest/runner': 2.1.3 - '@vitest/snapshot': 2.1.3 - '@vitest/spy': 2.1.3 - '@vitest/utils': 2.1.3 - chai: 5.1.1 + vitest: 2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0) + + vitest@2.1.4(@types/node@22.8.6)(@vitest/ui@2.1.4)(jsdom@25.0.1)(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(sass@1.80.5)(terser@5.36.0): + dependencies: + '@vitest/expect': 2.1.4 + '@vitest/mocker': 2.1.4(msw@2.6.0(@types/node@22.8.6)(typescript@5.6.3))(vite@5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0)) + '@vitest/pretty-format': 2.1.4 + '@vitest/runner': 2.1.4 + '@vitest/snapshot': 2.1.4 + '@vitest/spy': 2.1.4 + '@vitest/utils': 2.1.4 + chai: 5.1.2 debug: 4.3.7(supports-color@5.5.0) + expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 std-env: 3.7.0 @@ -10438,12 +10429,12 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.9(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0) - vite-node: 2.1.3(@types/node@22.8.0)(sass@1.80.4)(terser@5.36.0) + vite: 5.4.10(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0) + vite-node: 2.1.4(@types/node@22.8.6)(sass@1.80.5)(terser@5.36.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.8.0 - '@vitest/ui': 2.1.3(vitest@2.1.3) + '@types/node': 22.8.6 + '@vitest/ui': 2.1.4(vitest@2.1.4) jsdom: 25.0.1 transitivePeerDependencies: - less @@ -10498,8 +10489,8 @@ snapshots: '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/wasm-edit': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.13.0 - acorn-import-attributes: 1.9.5(acorn@8.13.0) + acorn: 8.14.0 + acorn-import-attributes: 1.9.5(acorn@8.14.0) browserslist: 4.24.2 chrome-trace-event: 1.0.4 enhanced-resolve: 5.17.1 From 265f51aed3aac7d1d83599f2b37a2d54e94c2e5c Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 2 Nov 2024 14:13:16 +0100 Subject: [PATCH 020/241] fix(app-config): use open port default TRUE --- packages/shared/src/index.ts | 2 ++ packages/shared/src/schemas/queue-schemas.ts | 2 +- .../worker/src/services/app/app.executors.ts | 4 +++- .../commands/install-app-command.ts | 18 +++++++++++----- .../commands/reset-app-command.ts | 14 ++++++++----- .../commands/restart-app-command.ts | 14 ++++++++----- .../commands/start-app-command.ts | 14 ++++++++----- .../commands/stop-app-command.ts | 14 ++++++++----- .../commands/uninstall-app-command.ts | 14 ++++++++----- .../commands/update-app-command.ts | 9 ++++---- .../commands/update-app-config-command.ts | 21 ++++++++++++------- 11 files changed, 82 insertions(+), 44 deletions(-) diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index c5354b1274..589fdff64f 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -9,6 +9,7 @@ import { type SystemEvent, type AppEventForm, type AppEventFormInput, + formSchema, } from './schemas/queue-schemas'; import { linkSchema, type LinkInfo, type LinkInfoInput } from './schemas/link-schemas'; import { socketEventSchema, type SocketEvent } from './schemas/socket-schemas'; @@ -26,6 +27,7 @@ export { ARCHITECTURES, EVENT_TYPES, // Schemas + formSchema, appInfoSchema, formFieldSchema, envSchema, diff --git a/packages/shared/src/schemas/queue-schemas.ts b/packages/shared/src/schemas/queue-schemas.ts index 1b4aeb8821..19e967debe 100644 --- a/packages/shared/src/schemas/queue-schemas.ts +++ b/packages/shared/src/schemas/queue-schemas.ts @@ -8,7 +8,7 @@ export const EVENT_TYPES = { export type EventType = (typeof EVENT_TYPES)[keyof typeof EVENT_TYPES]; -const formSchema = z +export const formSchema = z .object({ exposed: z.boolean().optional(), exposedLocal: z.boolean().optional(), diff --git a/packages/worker/src/services/app/app.executors.ts b/packages/worker/src/services/app/app.executors.ts index 71a0792186..7717118e45 100644 --- a/packages/worker/src/services/app/app.executors.ts +++ b/packages/worker/src/services/app/app.executors.ts @@ -66,8 +66,10 @@ export class AppExecutors implements IAppExecutors { await this.appFileAccessor.copyAppFromRepoToInstalled(appId); } + const appInfo = await this.appFileAccessor.getInstalledAppInfo(appId); + const rawComposeConfig = await this.appFileAccessor.getInstalledAppDockerComposeJson(appId); - if (rawComposeConfig) { + if (rawComposeConfig && appInfo?.dynamic_config) { try { const composeFile = getDockerCompose(rawComposeConfig.services, form); diff --git a/src/server/services/app-lifecycle/commands/install-app-command.ts b/src/server/services/app-lifecycle/commands/install-app-command.ts index d7fa871aae..be5099249e 100644 --- a/src/server/services/app-lifecycle/commands/install-app-command.ts +++ b/src/server/services/app-lifecycle/commands/install-app-command.ts @@ -1,6 +1,6 @@ import { TipiConfig } from '@/server/core/TipiConfig'; import { TranslatedError } from '@/server/utils/errors'; -import type { AppEventFormInput } from '@runtipi/shared'; +import { formSchema } from '@runtipi/shared'; import { lt, valid } from 'semver'; import { isFQDN } from 'validator'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; @@ -9,9 +9,14 @@ import { getClass } from 'src/inversify.config'; export class InstallAppCommand implements IAppLifecycleCommand { constructor(private params: AppLifecycleCommandParams) {} - private async sendEvent(appId: string, form: AppEventFormInput): Promise { + private async sendEvent(appId: string, form: unknown): Promise { const logger = getClass('ILogger'); - const { success, stdout } = await this.params.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'install', appid: appId, form }); + const { success, stdout } = await this.params.eventDispatcher.dispatchEventAsync({ + type: 'app', + command: 'install', + appid: appId, + form: formSchema.parse(form), + }); if (success) { await this.params.queries.updateApp(appId, { status: 'running' }); @@ -25,16 +30,19 @@ export class InstallAppCommand implements IAppLifecycleCommand { await this.params.executeOtherCommand('startApp', { appId }); } - async execute(params: { appId: string; form: AppEventFormInput }): Promise { + async execute(params: { appId: string; form: unknown }): Promise { const { appId, form } = params; + const parsedForm = formSchema.parse(form); + const app = await this.params.queries.getApp(appId); if (app) { return this.startApp(appId); } - const { exposed, exposedLocal, openPort, domain, isVisibleOnGuestDashboard } = form; + const { exposed, exposedLocal, openPort, domain, isVisibleOnGuestDashboard } = parsedForm; + const apps = await this.params.queries.getApps(); if (apps.length >= 6 && TipiConfig.getConfig().demoMode) { diff --git a/src/server/services/app-lifecycle/commands/reset-app-command.ts b/src/server/services/app-lifecycle/commands/reset-app-command.ts index ab4a462fd6..72d112439b 100644 --- a/src/server/services/app-lifecycle/commands/reset-app-command.ts +++ b/src/server/services/app-lifecycle/commands/reset-app-command.ts @@ -1,7 +1,6 @@ -import { castAppConfig } from '@/lib/helpers/castAppConfig'; import type { IAppQueries } from '@/server/queries/apps/apps.queries'; import { TranslatedError } from '@/server/utils/errors'; -import type { AppEventFormInput } from '@runtipi/shared'; +import { formSchema } from '@runtipi/shared'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; import type { IEventDispatcher } from '@/server/core/EventDispatcher/EventDispatcher'; import { getClass } from 'src/inversify.config'; @@ -15,9 +14,14 @@ export class ResetAppCommand implements IAppLifecycleCommand { this.eventDispatcher = params.eventDispatcher; } - private async sendEvent(appId: string, form: AppEventFormInput): Promise { + private async sendEvent(appId: string, form: unknown): Promise { const logger = getClass('ILogger'); - const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'reset', appid: appId, form }); + const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ + type: 'app', + command: 'reset', + appid: appId, + form: formSchema.parse(form), + }); if (success) { await this.queries.updateApp(appId, { status: 'running' }); @@ -37,6 +41,6 @@ export class ResetAppCommand implements IAppLifecycleCommand { await this.queries.updateApp(appId, { status: 'resetting' }); - void this.sendEvent(appId, castAppConfig(app.config)); + void this.sendEvent(appId, app.config); } } diff --git a/src/server/services/app-lifecycle/commands/restart-app-command.ts b/src/server/services/app-lifecycle/commands/restart-app-command.ts index 75a20b6a76..0da016652d 100644 --- a/src/server/services/app-lifecycle/commands/restart-app-command.ts +++ b/src/server/services/app-lifecycle/commands/restart-app-command.ts @@ -1,10 +1,9 @@ -import { castAppConfig } from '@/lib/helpers/castAppConfig'; import type { IAppQueries } from '@/server/queries/apps/apps.queries'; import { TranslatedError } from '@/server/utils/errors'; -import type { AppEventFormInput } from '@runtipi/shared'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; import type { IEventDispatcher } from '@/server/core/EventDispatcher/EventDispatcher'; import { getClass } from 'src/inversify.config'; +import { formSchema } from 'packages/shared/src'; export class RestartAppCommand implements IAppLifecycleCommand { private queries: IAppQueries; @@ -15,9 +14,14 @@ export class RestartAppCommand implements IAppLifecycleCommand { this.eventDispatcher = params.eventDispatcher; } - private async sendEvent(appId: string, form: AppEventFormInput): Promise { + private async sendEvent(appId: string, form: unknown): Promise { const logger = getClass('ILogger'); - const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'restart', appid: appId, form }); + const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ + type: 'app', + command: 'restart', + appid: appId, + form: formSchema.parse(form), + }); if (success) { await this.queries.updateApp(appId, { status: 'running' }); @@ -38,6 +42,6 @@ export class RestartAppCommand implements IAppLifecycleCommand { // Run script await this.queries.updateApp(appId, { status: 'restarting' }); - void this.sendEvent(appId, castAppConfig(app.config)); + void this.sendEvent(appId, app.config); } } diff --git a/src/server/services/app-lifecycle/commands/start-app-command.ts b/src/server/services/app-lifecycle/commands/start-app-command.ts index daadb87275..dd257ad4e1 100644 --- a/src/server/services/app-lifecycle/commands/start-app-command.ts +++ b/src/server/services/app-lifecycle/commands/start-app-command.ts @@ -1,7 +1,6 @@ -import { castAppConfig } from '@/lib/helpers/castAppConfig'; import type { IAppQueries } from '@/server/queries/apps/apps.queries'; import { TranslatedError } from '@/server/utils/errors'; -import type { AppEventFormInput } from '@runtipi/shared'; +import { formSchema } from '@runtipi/shared'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; import type { IEventDispatcher } from '@/server/core/EventDispatcher/EventDispatcher'; import { getClass } from 'src/inversify.config'; @@ -15,9 +14,14 @@ export class StartAppCommand implements IAppLifecycleCommand { this.eventDispatcher = params.eventDispatcher; } - private async sendEvent(appId: string, form: AppEventFormInput): Promise { + private async sendEvent(appId: string, form: unknown): Promise { const logger = getClass('ILogger'); - const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'start', appid: appId, form }); + const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ + type: 'app', + command: 'start', + appid: appId, + form: formSchema.parse(form), + }); if (success) { await this.queries.updateApp(appId, { status: 'running' }); @@ -37,6 +41,6 @@ export class StartAppCommand implements IAppLifecycleCommand { await this.queries.updateApp(appId, { status: 'starting' }); - void this.sendEvent(appId, castAppConfig(app.config)); + void this.sendEvent(appId, app.config); } } diff --git a/src/server/services/app-lifecycle/commands/stop-app-command.ts b/src/server/services/app-lifecycle/commands/stop-app-command.ts index 9e00490982..7b50ad80e2 100644 --- a/src/server/services/app-lifecycle/commands/stop-app-command.ts +++ b/src/server/services/app-lifecycle/commands/stop-app-command.ts @@ -1,7 +1,6 @@ -import { castAppConfig } from '@/lib/helpers/castAppConfig'; import type { IAppQueries } from '@/server/queries/apps/apps.queries'; import { TranslatedError } from '@/server/utils/errors'; -import type { AppEventFormInput } from '@runtipi/shared'; +import { formSchema } from '@runtipi/shared'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; import type { IEventDispatcher } from '@/server/core/EventDispatcher/EventDispatcher'; import { getClass } from 'src/inversify.config'; @@ -15,9 +14,14 @@ export class StopAppCommand implements IAppLifecycleCommand { this.eventDispatcher = params.eventDispatcher; } - private async sendEvent(appId: string, form: AppEventFormInput): Promise { + private async sendEvent(appId: string, form: unknown): Promise { const logger = getClass('ILogger'); - const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'stop', appid: appId, form }); + const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ + type: 'app', + command: 'stop', + appid: appId, + form: formSchema.parse(form), + }); if (success) { await this.queries.updateApp(appId, { status: 'stopped' }); @@ -37,6 +41,6 @@ export class StopAppCommand implements IAppLifecycleCommand { await this.queries.updateApp(appId, { status: 'stopping' }); - void this.sendEvent(appId, castAppConfig(app.config)); + void this.sendEvent(appId, app.config); } } diff --git a/src/server/services/app-lifecycle/commands/uninstall-app-command.ts b/src/server/services/app-lifecycle/commands/uninstall-app-command.ts index e7806d730b..7c5a68fb91 100644 --- a/src/server/services/app-lifecycle/commands/uninstall-app-command.ts +++ b/src/server/services/app-lifecycle/commands/uninstall-app-command.ts @@ -1,7 +1,6 @@ -import { castAppConfig } from '@/lib/helpers/castAppConfig'; import type { IAppQueries } from '@/server/queries/apps/apps.queries'; import { TranslatedError } from '@/server/utils/errors'; -import type { AppEventFormInput } from '@runtipi/shared'; +import { formSchema } from '@runtipi/shared'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; import type { IEventDispatcher } from '@/server/core/EventDispatcher/EventDispatcher'; import { getClass } from 'src/inversify.config'; @@ -17,9 +16,14 @@ export class UninstallAppCommand implements IAppLifecycleCommand { this.executeOtherCommand = params.executeOtherCommand; } - private async sendEvent(appId: string, form: AppEventFormInput) { + private async sendEvent(appId: string, form: unknown) { const logger = getClass('ILogger'); - const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'uninstall', appid: appId, form }); + const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ + type: 'app', + command: 'uninstall', + appid: appId, + form: formSchema.parse(form), + }); if (success) { await this.queries.deleteApp(appId); @@ -48,6 +52,6 @@ export class UninstallAppCommand implements IAppLifecycleCommand { await this.queries.updateApp(appId, { status: 'uninstalling' }); - void this.sendEvent(appId, castAppConfig(app.config)); + void this.sendEvent(appId, app.config); } } diff --git a/src/server/services/app-lifecycle/commands/update-app-command.ts b/src/server/services/app-lifecycle/commands/update-app-command.ts index bd8a73eb81..ab3862eddf 100644 --- a/src/server/services/app-lifecycle/commands/update-app-command.ts +++ b/src/server/services/app-lifecycle/commands/update-app-command.ts @@ -1,11 +1,10 @@ -import { castAppConfig } from '@/lib/helpers/castAppConfig'; import { TipiConfig } from '@/server/core/TipiConfig'; import { TranslatedError } from '@/server/utils/errors'; import type { AppStatus } from '@runtipi/db'; -import type { AppEventFormInput } from '@runtipi/shared'; import semver from 'semver'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; import { getClass } from 'src/inversify.config'; +import { formSchema } from 'packages/shared/src'; const FIFTEEN_MINUTES = 15 * 60 * 1000; @@ -14,7 +13,7 @@ export class UpdateAppCommand implements IAppLifecycleCommand { private async sendEvent(params: { appId: string; - form: AppEventFormInput; + form: unknown; appStatusBeforeUpdate: AppStatus; performBackup: boolean; }): Promise { @@ -22,7 +21,7 @@ export class UpdateAppCommand implements IAppLifecycleCommand { const { appId, form, appStatusBeforeUpdate, performBackup } = params; const { success, stdout } = await this.params.eventDispatcher.dispatchEventAsync( - { type: 'app', command: 'update', appid: appId, form, performBackup }, + { type: 'app', command: 'update', appid: appId, form: formSchema.parse(form), performBackup }, performBackup ? FIFTEEN_MINUTES : undefined, ); @@ -59,6 +58,6 @@ export class UpdateAppCommand implements IAppLifecycleCommand { await this.params.queries.updateApp(appId, { status: 'updating' }); - void this.sendEvent({ appId, form: castAppConfig(app.config), appStatusBeforeUpdate: app.status || 'missing', performBackup }); + void this.sendEvent({ appId, form: app.config, appStatusBeforeUpdate: app.status || 'missing', performBackup }); } } diff --git a/src/server/services/app-lifecycle/commands/update-app-config-command.ts b/src/server/services/app-lifecycle/commands/update-app-config-command.ts index d85474022f..ac1fdcf44e 100644 --- a/src/server/services/app-lifecycle/commands/update-app-config-command.ts +++ b/src/server/services/app-lifecycle/commands/update-app-config-command.ts @@ -1,15 +1,17 @@ import { TranslatedError } from '@/server/utils/errors'; -import type { AppEventFormInput } from '@runtipi/shared'; import validator from 'validator'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; +import { formSchema } from '@runtipi/shared'; export class UpdateAppConfigCommand implements IAppLifecycleCommand { constructor(private params: AppLifecycleCommandParams) {} - async execute(params: { appId: string; form: AppEventFormInput }): Promise { + async execute(params: { appId: string; form: unknown }): Promise { const { appId, form } = params; - const { exposed, domain } = form; + const parsedForm = formSchema.parse(form); + + const { exposed, domain } = parsedForm; if (exposed && !domain) { throw new TranslatedError('APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP'); @@ -47,7 +49,12 @@ export class UpdateAppConfigCommand implements IAppLifecycleCommand { } } - const { success } = await this.params.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'generate_env', appid: appId, form }); + const { success } = await this.params.eventDispatcher.dispatchEventAsync({ + type: 'app', + command: 'generate_env', + appid: appId, + form: parsedForm, + }); if (!success) { throw new TranslatedError('APP_ERROR_APP_FAILED_TO_UPDATE', { id: appId }); @@ -55,11 +62,11 @@ export class UpdateAppConfigCommand implements IAppLifecycleCommand { await this.params.queries.updateApp(appId, { exposed: exposed || false, - exposedLocal: form.exposedLocal || false, - openPort: form.openPort || false, + exposedLocal: parsedForm.exposedLocal || false, + openPort: parsedForm.openPort || false, domain: domain || null, config: form, - isVisibleOnGuestDashboard: form.isVisibleOnGuestDashboard, + isVisibleOnGuestDashboard: parsedForm.isVisibleOnGuestDashboard, }); } } From bf15b780e971340563c471f4792b50c0c15a9f3d Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 2 Nov 2024 14:18:42 +0100 Subject: [PATCH 021/241] fix: add parsed form in db config when updating --- .../app-lifecycle/commands/update-app-config-command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/services/app-lifecycle/commands/update-app-config-command.ts b/src/server/services/app-lifecycle/commands/update-app-config-command.ts index ac1fdcf44e..22a3ac34a0 100644 --- a/src/server/services/app-lifecycle/commands/update-app-config-command.ts +++ b/src/server/services/app-lifecycle/commands/update-app-config-command.ts @@ -65,7 +65,7 @@ export class UpdateAppConfigCommand implements IAppLifecycleCommand { exposedLocal: parsedForm.exposedLocal || false, openPort: parsedForm.openPort || false, domain: domain || null, - config: form, + config: parsedForm, isVisibleOnGuestDashboard: parsedForm.isVisibleOnGuestDashboard, }); } From cb001bcc7947bb033d16229fee23f0480d9e1324 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 2 Nov 2024 14:19:50 +0100 Subject: [PATCH 022/241] chore: update @shared package imports --- .../services/app-lifecycle/commands/restart-app-command.ts | 2 +- .../services/app-lifecycle/commands/update-app-command.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/services/app-lifecycle/commands/restart-app-command.ts b/src/server/services/app-lifecycle/commands/restart-app-command.ts index 0da016652d..704b6ba6b3 100644 --- a/src/server/services/app-lifecycle/commands/restart-app-command.ts +++ b/src/server/services/app-lifecycle/commands/restart-app-command.ts @@ -3,7 +3,7 @@ import { TranslatedError } from '@/server/utils/errors'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; import type { IEventDispatcher } from '@/server/core/EventDispatcher/EventDispatcher'; import { getClass } from 'src/inversify.config'; -import { formSchema } from 'packages/shared/src'; +import { formSchema } from '@runtipi/shared'; export class RestartAppCommand implements IAppLifecycleCommand { private queries: IAppQueries; diff --git a/src/server/services/app-lifecycle/commands/update-app-command.ts b/src/server/services/app-lifecycle/commands/update-app-command.ts index ab3862eddf..a0795ad986 100644 --- a/src/server/services/app-lifecycle/commands/update-app-command.ts +++ b/src/server/services/app-lifecycle/commands/update-app-command.ts @@ -4,7 +4,7 @@ import type { AppStatus } from '@runtipi/db'; import semver from 'semver'; import type { AppLifecycleCommandParams, IAppLifecycleCommand } from './types'; import { getClass } from 'src/inversify.config'; -import { formSchema } from 'packages/shared/src'; +import { formSchema } from '@runtipi/shared'; const FIFTEEN_MINUTES = 15 * 60 * 1000; From 59782dce704a5a74af104dc2f9d2b3f8ceaf310c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Oct 2024 13:03:50 +0000 Subject: [PATCH 023/241] chore(deps): bump zustand from 4.5.5 to 5.0.0 Bumps [zustand](https://github.com/pmndrs/zustand) from 4.5.5 to 5.0.0. - [Release notes](https://github.com/pmndrs/zustand/releases) - [Commits](https://github.com/pmndrs/zustand/compare/v4.5.5...v5.0.0) --- updated-dependencies: - dependency-name: zustand dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 3ca4dd0b37..6d2d4eff8b 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "validator": "^13.12.0", "winston": "^3.15.0", "zod": "^3.23.8", - "zustand": "^4.5.5" + "zustand": "^5.0.0" }, "devDependencies": { "@babel/core": "^7.26.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35e89bf7de..26ae1837dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,8 +192,8 @@ importers: specifier: ^3.23.8 version: 3.23.8 zustand: - specifier: ^4.5.5 - version: 4.5.5(@types/react@18.3.12)(react@18.3.1) + specifier: ^5.0.0 + version: 5.0.0(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1)) devDependencies: '@babel/core': specifier: ^7.26.0 @@ -5394,13 +5394,14 @@ packages: zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - zustand@4.5.5: - resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} - engines: {node: '>=12.7.0'} + zustand@5.0.0: + resolution: {integrity: sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==} + engines: {node: '>=12.20.0'} peerDependencies: - '@types/react': '>=16.8' + '@types/react': '>=18.0.0' immer: '>=9.0.6' - react: '>=16.8' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' peerDependenciesMeta: '@types/react': optional: true @@ -5408,6 +5409,8 @@ packages: optional: true react: optional: true + use-sync-external-store: + optional: true zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -10339,6 +10342,7 @@ snapshots: use-sync-external-store@1.2.2(react@18.3.1): dependencies: react: 18.3.1 + optional: true util-deprecate@1.0.2: {} @@ -10620,11 +10624,10 @@ snapshots: zod@3.23.8: {} - zustand@4.5.5(@types/react@18.3.12)(react@18.3.1): - dependencies: - use-sync-external-store: 1.2.2(react@18.3.1) + zustand@5.0.0(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1)): optionalDependencies: '@types/react': 18.3.12 react: 18.3.1 + use-sync-external-store: 1.2.2(react@18.3.1) zwitch@2.0.4: {} From 5dfa83207e27a341768a32457d0bc40a43979574 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:15:35 +0100 Subject: [PATCH 024/241] New translations en.json (Portuguese) --- src/client/messages/pt-PT.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/client/messages/pt-PT.json b/src/client/messages/pt-PT.json index 715f1b3a76..2d8feb4671 100644 --- a/src/client/messages/pt-PT.json +++ b/src/client/messages/pt-PT.json @@ -39,10 +39,10 @@ "APP_DETAILS_VERSION": "Versão", "APP_DETAILS_WEBSITE": "Website", "APP_ERROR_APP_FAILED_TO_INSTALL": "Falha ao instalar o aplicativo {id}, consulte os logs para obter mais detalhes", - "APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details", + "APP_ERROR_APP_FAILED_TO_RESET": "", "APP_ERROR_APP_FAILED_TO_START": "Falha ao iniciar a aplicação {id}, consulte os logs para obter mais detalhes", "APP_ERROR_APP_FAILED_TO_STOP": "Falha ao interromper o aplicativo {id}, consulte os logs para obter mais detalhes", - "APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details", + "APP_ERROR_APP_FAILED_TO_RESTART": "", "APP_ERROR_APP_FAILED_TO_UNINSTALL": "Falha ao desinstalar o aplicativo {id}, consulte os logs para obter mais detalhes", "APP_ERROR_APP_FAILED_TO_UPDATE": "Falha ao atualizar o aplicativo {id}, consulte os logs para obter mais detalhes", "APP_ERROR_APP_FORCE_EXPOSED": "O App {id} só funciona com domínio exposto", @@ -54,7 +54,7 @@ "APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP": "O domínio é necessário se o aplicativo estiver exposto", "APP_ERROR_INVALID_CONFIG": "O aplicativo {id} tem um arquivo config.json inválido", "APP_INSTALL_FORM_CHOOSE_OPTION": "Escolher uma opção...", - "APP_INSTALL_FORM_DISPLAY_ON_GUEST_DASHBOARD": "Display on guest dashboard", + "APP_INSTALL_FORM_DISPLAY_ON_GUEST_DASHBOARD": "Mostrar no painel do convidado", "APP_INSTALL_FORM_DOMAIN_NAME": "Nome de domínio", "APP_INSTALL_FORM_DOMAIN_NAME_HINT": "Certifique-se de que este domínio exato contém um registro que aponta para seu IP.", "APP_INSTALL_FORM_ERROR_BETWEEN_LENGTH": "{label} deve estar entre {min} e {max} caracteres", @@ -140,7 +140,7 @@ "APP_UPDATE_SETTINGS_FORM_TITLE": "Atualizar configuração do {name}", "APP_UPDATE_SUCCESS": "App {id} updated successfully", "APP_UPDATE_FORM_BACKUP": "Backup app before updating", - "APP_BACKUP_SUCCESS": "App {id} backed up successfully", + "APP_BACKUP_SUCCESS": "App {id} iniciado com sucesso", "APP_BACKUP_ERROR": "Failed to backup {id}, see logs for more details", "APP_RESTORE_SUCCESS": "App {id} restored successfully", "APP_RESTORE_ERROR": "Failed to restore {id}, see logs for more details", @@ -271,11 +271,11 @@ "SETTINGS_ACTIONS_UPDATE_REPO_SUBTITLE": "Use this button to update your appstore", "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_SUBTITLE": "This will reset your repository and pull the latest changes from GitHub", "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_BUTTON": "Update", - "SETTINGS_ACTIONS_UPDATE_REPO_SUCCESS": "Appstore repository updated successfully", + "SETTINGS_ACTIONS_UPDATE_REPO_SUCCESS": "Repositório da Appstore atualizado com sucesso", "SETTINGS_GENERAL_ALLOW_AUTO_THEMES": "Allow auto themes", - "SETTINGS_GENERAL_ALLOW_AUTO_THEMES_HINT": "Be surprised by themes that change automatically based on the time of the year.", + "SETTINGS_GENERAL_ALLOW_AUTO_THEMES_HINT": "Seja surpreendido com temas que são alterados automaticamente com base na hora do ano.", "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING": "Allow anonymous error monitoring", - "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING_HINT": "Error monitoring is used to track errors and improve Tipi. Keep this option enabled to help us improve Tipi.", + "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING_HINT": "O monitoramento de erros é utilizado para rastrear erros e melhorar o Tipi. Mantenha esta opção ativada para nos ajudar a melhorar o Tipi.", "SETTINGS_GENERAL_APPS_REPO": "URL do repositório dos aplicativos", "SETTINGS_GENERAL_APPS_REPO_HINT": "URL para o repositório de aplicativos.", "SETTINGS_GENERAL_DNS_IP": "IP DNS", From 8e95540d07270e43a9258b8efcdbe50fe4552165 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Sat, 2 Nov 2024 14:24:51 +0100 Subject: [PATCH 025/241] Update src/client/messages/pt-PT.json Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/client/messages/pt-PT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/messages/pt-PT.json b/src/client/messages/pt-PT.json index 2d8feb4671..53c2dcc2ce 100644 --- a/src/client/messages/pt-PT.json +++ b/src/client/messages/pt-PT.json @@ -140,7 +140,7 @@ "APP_UPDATE_SETTINGS_FORM_TITLE": "Atualizar configuração do {name}", "APP_UPDATE_SUCCESS": "App {id} updated successfully", "APP_UPDATE_FORM_BACKUP": "Backup app before updating", - "APP_BACKUP_SUCCESS": "App {id} iniciado com sucesso", + "APP_BACKUP_SUCCESS": "Backup do App {id} realizado com sucesso", "APP_BACKUP_ERROR": "Failed to backup {id}, see logs for more details", "APP_RESTORE_SUCCESS": "App {id} restored successfully", "APP_RESTORE_ERROR": "Failed to restore {id}, see logs for more details", From 8d01d7aac7a7a7fd2ae553578c21d4a090f493c3 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger <47644445+meienberger@users.noreply.github.com> Date: Sat, 2 Nov 2024 14:28:53 +0100 Subject: [PATCH 026/241] New translations en.json (Portuguese) --- src/client/messages/pt-PT.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/messages/pt-PT.json b/src/client/messages/pt-PT.json index 53c2dcc2ce..c0f6898ff4 100644 --- a/src/client/messages/pt-PT.json +++ b/src/client/messages/pt-PT.json @@ -39,10 +39,10 @@ "APP_DETAILS_VERSION": "Versão", "APP_DETAILS_WEBSITE": "Website", "APP_ERROR_APP_FAILED_TO_INSTALL": "Falha ao instalar o aplicativo {id}, consulte os logs para obter mais detalhes", - "APP_ERROR_APP_FAILED_TO_RESET": "", + "APP_ERROR_APP_FAILED_TO_RESET": "Falha ao redefinir o aplicativo {id}, consulte os logs para obter mais detalhes", "APP_ERROR_APP_FAILED_TO_START": "Falha ao iniciar a aplicação {id}, consulte os logs para obter mais detalhes", "APP_ERROR_APP_FAILED_TO_STOP": "Falha ao interromper o aplicativo {id}, consulte os logs para obter mais detalhes", - "APP_ERROR_APP_FAILED_TO_RESTART": "", + "APP_ERROR_APP_FAILED_TO_RESTART": "Falha ao reiniciar o aplicativo {id}, consulte os logs para obter mais detalhes", "APP_ERROR_APP_FAILED_TO_UNINSTALL": "Falha ao desinstalar o aplicativo {id}, consulte os logs para obter mais detalhes", "APP_ERROR_APP_FAILED_TO_UPDATE": "Falha ao atualizar o aplicativo {id}, consulte os logs para obter mais detalhes", "APP_ERROR_APP_FORCE_EXPOSED": "O App {id} só funciona com domínio exposto", From 33a341f55a25417dcacef004a19c9668c28d7159 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:16:50 +0000 Subject: [PATCH 027/241] chore(deps): bump uuid from 10.0.0 to 11.0.2 Bumps [uuid](https://github.com/uuidjs/uuid) from 10.0.0 to 11.0.2. - [Release notes](https://github.com/uuidjs/uuid/releases) - [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md) - [Commits](https://github.com/uuidjs/uuid/compare/v10.0.0...v11.0.2) --- updated-dependencies: - dependency-name: uuid dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 6d2d4eff8b..743b70350c 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "semver": "^7.6.3", "sharp": "0.33.5", "socket.io-client": "^4.8.1", - "uuid": "^10.0.0", + "uuid": "^11.0.2", "validator": "^13.12.0", "winston": "^3.15.0", "zod": "^3.23.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26ae1837dc..c758a5b62e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -180,8 +180,8 @@ importers: specifier: ^4.8.1 version: 4.8.1 uuid: - specifier: ^10.0.0 - version: 10.0.0 + specifier: ^11.0.2 + version: 11.0.2 validator: specifier: ^13.12.0 version: 13.12.0 @@ -5120,8 +5120,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + uuid@11.0.2: + resolution: {integrity: sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==} hasBin: true uuid@9.0.1: @@ -10346,7 +10346,7 @@ snapshots: util-deprecate@1.0.2: {} - uuid@10.0.0: {} + uuid@11.0.2: {} uuid@9.0.1: {} From ed67bc1df4c0226e97e9ccdfc29e46ee34156eb7 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 2 Nov 2024 14:37:22 +0100 Subject: [PATCH 028/241] chore: bump version to 3.6.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea6a94f9d1..e6bf34e8ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "runtipi", - "version": "3.6.3", + "version": "3.6.4", "description": "A homeserver for everyone", "packageManager": "pnpm@9.4.0", "scripts": { From f62e72eed37f349697f91aa344328720a9e0b971 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 2 Nov 2024 14:48:37 +0100 Subject: [PATCH 029/241] ci: increase timeout on CLI build to 10 minutes --- .github/workflows/alpha-release.yml | 2 +- .github/workflows/beta-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml index a3d8be6cc2..44fdaf8aea 100644 --- a/.github/workflows/alpha-release.yml +++ b/.github/workflows/alpha-release.yml @@ -89,7 +89,7 @@ jobs: repo: cli owner: runtipi run_id: ${{ steps.return_dispatch.outputs.run_id }} - run_timeout_seconds: 300 + run_timeout_seconds: 600 poll_interval_ms: 5000 - name: Create bin folder diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 7b05d91622..38106a112a 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -89,7 +89,7 @@ jobs: repo: cli owner: runtipi run_id: ${{ steps.return_dispatch.outputs.run_id }} - run_timeout_seconds: 300 + run_timeout_seconds: 600 poll_interval_ms: 5000 - name: Create bin folder From b20fb0619ce880d5dc1de7c876b021ebdbf7a3de Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 2 Nov 2024 14:48:47 +0100 Subject: [PATCH 030/241] chore: bump docker-compose version to v2.30.1 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 228490bfdd..9e3b04acdc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,7 +64,7 @@ WORKDIR /worker ARG TARGETARCH ENV TARGETARCH=${TARGETARCH} -ARG DOCKER_COMPOSE_VERSION="v2.29.7" +ARG DOCKER_COMPOSE_VERSION="v2.30.1" RUN echo "Building for ${TARGETARCH}" From 05db49eb50e1c5143441722c85a9b9d18c2001c4 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 2 Nov 2024 16:14:11 +0100 Subject: [PATCH 031/241] chore: increase timeout in CLI build to 20 min --- .github/workflows/alpha-release.yml | 2 +- .github/workflows/beta-release.yml | 2 +- .github/workflows/nightly-release.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml index 44fdaf8aea..ad2e9cf67f 100644 --- a/.github/workflows/alpha-release.yml +++ b/.github/workflows/alpha-release.yml @@ -89,7 +89,7 @@ jobs: repo: cli owner: runtipi run_id: ${{ steps.return_dispatch.outputs.run_id }} - run_timeout_seconds: 600 + run_timeout_seconds: 1200 poll_interval_ms: 5000 - name: Create bin folder diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 38106a112a..9e43a43b6b 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -89,7 +89,7 @@ jobs: repo: cli owner: runtipi run_id: ${{ steps.return_dispatch.outputs.run_id }} - run_timeout_seconds: 600 + run_timeout_seconds: 1200 poll_interval_ms: 5000 - name: Create bin folder diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index 154f9e85c8..28c2201f9f 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -58,7 +58,7 @@ jobs: repo: cli owner: runtipi run_id: ${{ steps.return_dispatch.outputs.run_id }} - run_timeout_seconds: 600 + run_timeout_seconds: 1200 poll_interval_ms: 5000 - name: Create bin folder diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d8ee7e32c6..2a94d8ef13 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: repo: cli owner: runtipi run_id: ${{ steps.return_dispatch.outputs.run_id }} - run_timeout_seconds: 600 + run_timeout_seconds: 1200 poll_interval_ms: 5000 - name: Create bin folder From 126a3591996a285ee1f7cfe6bd7ccda50f60c0da Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Fri, 8 Nov 2024 22:28:07 +0100 Subject: [PATCH 032/241] chore: deprectate next app --- .dockerignore => legacy/.dockerignore | 0 .env.example => legacy/.env.example | 0 .env.test => legacy/.env.test | 0 .gitignore => legacy/.gitignore | 0 Dockerfile => legacy/Dockerfile | 0 Dockerfile.dev => legacy/Dockerfile.dev | 0 .../docker-compose.dev.yml | 0 .../docker-compose.prod.yml | 0 global.d.ts => legacy/global.d.ts | 0 next-env.d.ts => legacy/next-env.d.ts | 0 next.config.mjs => legacy/next.config.mjs | 0 package.json => legacy/package.json | 0 {packages => legacy/packages}/cache/package.json | 0 {packages => legacy/packages}/cache/src/cache.ts | 0 {packages => legacy/packages}/cache/src/index.ts | 0 {packages => legacy/packages}/cache/src/mock.ts | 0 {packages => legacy/packages}/cache/tsconfig.json | 0 .../migrations/00000-create-migrations-table.sql | 0 .../db/assets/migrations/00001-initial.sql | 0 .../db/assets/migrations/00002-add-app-version.sql | 0 .../assets/migrations/00003-add-status-updating.sql | 0 .../migrations/00004-add-exposed-domain-fields.sql | 0 .../assets/migrations/00005-add-user-operator.sql | 0 .../migrations/00006-add-totp-user-fields.sql | 0 .../assets/migrations/00007-add-locale-user-col.sql | 0 .../00008-merge-config-with-domain-and-exposed.sql | 0 .../assets/migrations/00009-add-guest-dashboard.sql | 0 .../migrations/00010-add-status-resetting.sql | 0 .../assets/migrations/00011-create-link-table.sql | 0 .../migrations/00012-link-table-description.sql | 0 .../assets/migrations/00013-app-local-exposed.sql | 0 .../db/assets/migrations/00014-restarting-state.sql | 0 .../db/assets/migrations/00015-backingup-state.sql | 0 {packages => legacy/packages}/db/package.json | 0 {packages => legacy/packages}/db/src/client.ts | 0 {packages => legacy/packages}/db/src/index.ts | 0 {packages => legacy/packages}/db/src/migrator.ts | 0 {packages => legacy/packages}/db/src/schema.ts | 0 {packages => legacy/packages}/db/tsconfig.json | 0 .../packages}/shared/node/package.json | 0 {packages => legacy/packages}/shared/package.json | 0 .../packages}/shared/src/helpers/env-helpers.ts | 0 .../packages}/shared/src/helpers/error-helpers.ts | 0 .../packages}/shared/src/helpers/sanitizers.ts | 0 .../packages}/shared/src/helpers/utils.ts | 0 {packages => legacy/packages}/shared/src/index.ts | 0 .../shared/src/node/helpers/exec-async.tsx | 0 .../packages}/shared/src/node/helpers/fs-helpers.ts | 0 .../shared/src/node/helpers/typescript-helpers.ts | 0 .../packages}/shared/src/node/index.ts | 0 .../packages}/shared/src/node/logger/FileLogger.ts | 0 .../shared/src/node/logger/Logger.interface.ts | 0 .../packages}/shared/src/node/logger/Logger.ts | 0 .../packages}/shared/src/node/logger/LoggerMock.ts | 0 .../modules/app/__tests__/app-file-accessor.test.ts | 0 .../shared/src/node/modules/app/app-data-service.ts | 0 .../src/node/modules/app/app-file-accessor.ts | 0 .../src/node/modules/archive/archive-manager.ts | 0 .../src/node/modules/backup/backup-manager.ts | 0 .../packages}/shared/src/schemas/app-schemas.ts | 0 .../packages}/shared/src/schemas/env-schemas.ts | 0 .../packages}/shared/src/schemas/link-schemas.ts | 0 .../packages}/shared/src/schemas/queue-schemas.ts | 0 .../packages}/shared/src/schemas/socket-schemas.ts | 0 .../packages}/shared/src/schemas/system-schemas.ts | 0 {packages => legacy/packages}/shared/tsconfig.json | 0 {packages => legacy/packages}/worker/.env.test | 0 {packages => legacy/packages}/worker/.gitignore | 0 .../worker/assets/traefik/dynamic/dynamic.yml | 0 .../packages}/worker/assets/traefik/traefik.yml | 0 {packages => legacy/packages}/worker/build.mjs | 0 {packages => legacy/packages}/worker/nodemon.json | 0 {packages => legacy/packages}/worker/package.json | 0 {packages => legacy/packages}/worker/src/api.ts | 0 .../src/config/__tests__/docker-templates.test.ts | 0 .../packages}/worker/src/config/constants.ts | 0 .../packages}/worker/src/config/docker-templates.ts | 0 .../packages}/worker/src/config/index.ts | 0 {packages => legacy/packages}/worker/src/index.ts | 0 .../packages}/worker/src/inversify.config.ts | 0 .../lib/docker/builders/docker-compose-builder.ts | 0 .../worker/src/lib/docker/builders/schemas.ts | 0 .../src/lib/docker/builders/service-builder.ts | 0 .../lib/docker/builders/traefik-labels-builder.ts | 0 .../worker/src/lib/docker/docker-helpers.test.ts | 0 .../worker/src/lib/docker/docker-helpers.ts | 0 .../packages}/worker/src/lib/docker/index.ts | 0 .../worker/src/lib/environment/environment.ts | 0 .../packages}/worker/src/lib/environment/index.ts | 0 .../packages}/worker/src/lib/logger/index.ts | 0 .../packages}/worker/src/lib/logger/logger.ts | 0 .../worker/src/lib/socket/SocketManager.ts | 0 .../packages}/worker/src/lib/socket/index.ts | 0 .../packages}/worker/src/lib/system/index.ts | 0 .../worker/src/lib/system/system.helpers.test.ts | 0 .../worker/src/lib/system/system.helpers.ts | 0 .../services/app/__tests__/app.executors.test.ts | 0 .../src/services/app/__tests__/app.helpers.test.ts | 0 .../src/services/app/__tests__/env.helpers.test.ts | 0 .../worker/src/services/app/app.executors.ts | 0 .../worker/src/services/app/app.helpers.ts | 0 .../worker/src/services/app/env.helpers.ts | 0 .../packages}/worker/src/services/index.ts | 0 .../worker/src/services/repo/repo.executors.ts | 0 .../worker/src/services/repo/repo.helpers.ts | 0 .../worker/src/services/system/system.executors.ts | 0 .../packages}/worker/src/types/global.d.ts | 0 .../packages}/worker/src/watcher/watcher.ts | 0 .../packages}/worker/tests/apps.factory.ts | 0 .../packages}/worker/tests/mocks/fs.ts | 0 .../packages}/worker/tests/vite.setup.ts | 0 {packages => legacy/packages}/worker/tsconfig.json | 0 .../packages}/worker/vitest.config.ts | 0 {patches => legacy/patches}/.gitkeep | 0 pnpm-lock.yaml => legacy/pnpm-lock.yaml | 0 pnpm-workspace.yaml => legacy/pnpm-workspace.yaml | 0 {public => legacy/public}/app-not-found.jpg | Bin {public => legacy/public}/empty.svg | 0 {public => legacy/public}/error.png | Bin {public => legacy/public}/js/.gitkeep | 0 {public => legacy/public}/mockServiceWorker.js | 0 {public => legacy/public}/placeholder.png | Bin {public => legacy/public}/tipi-christmas.png | Bin {public => legacy/public}/tipi.png | Bin reset.d.ts => legacy/reset.d.ts | 0 .../sentry.client.config.ts | 0 .../sonar-project.properties | 0 {src => legacy/src}/app/(auth)/layout.tsx | 0 .../components/LoginContainer/LoginContainer.tsx | 0 .../(auth)/login/components/LoginContainer/index.ts | 0 .../(auth)/login/components/LoginForm/LoginForm.tsx | 0 .../app/(auth)/login/components/LoginForm/index.ts | 0 .../(auth)/login/components/TotpForm/TotpForm.tsx | 0 .../app/(auth)/login/components/TotpForm/index.ts | 0 {src => legacy/src}/app/(auth)/login/page.tsx | 0 .../RegisterContainer/RegisterContainer.tsx | 0 .../register/components/RegisterContainer/index.ts | 0 .../components/RegisterForm/RegisterForm.tsx | 0 .../register/components/RegisterForm/index.ts | 0 {src => legacy/src}/app/(auth)/register/page.tsx | 0 .../ResetPasswordContainer.tsx | 0 .../components/ResetPasswordContainer/index.ts | 0 .../ResetPasswordForm/ResetPasswordForm.tsx | 0 .../components/ResetPasswordForm/index.ts | 0 .../src}/app/(auth)/reset-password/page.tsx | 0 .../components/AppActions/AppActions.module.scss | 0 .../[id]/components/AppActions/AppActions.test.tsx | 0 .../[id]/components/AppActions/AppActions.tsx | 0 .../app-store/[id]/components/AppActions/index.ts | 0 .../AppDetailsContainer/AppDetailsContainer.tsx | 0 .../[id]/components/AppDetailsTabs/AppBackups.tsx | 0 .../AppDetailsTabs/AppDetailsTabTriggers.tsx | 0 .../components/AppDetailsTabs/AppDetailsTabs.tsx | 0 .../[id]/components/AppDetailsTabs/AppLogs.tsx | 0 .../[id]/components/AppDetailsTabs/index.ts | 0 .../[id]/components/BackupModal/BackupModal.tsx | 0 .../app-store/[id]/components/BackupModal/index.tsx | 0 .../DeleteBackupModal/DeleteBackupModal.tsx | 0 .../components/InstallForm/InstallForm.test.tsx | 0 .../[id]/components/InstallForm/InstallForm.tsx | 0 .../components/InstallForm/InstallForm.types.ts | 0 .../components/InstallForm/InstallFormField.tsx | 0 .../app-store/[id]/components/InstallForm/index.ts | 0 .../components/InstallModal/InstallModal.test.tsx | 0 .../[id]/components/InstallModal/InstallModal.tsx | 0 .../app-store/[id]/components/InstallModal/index.ts | 0 .../[id]/components/ResetAppModal/ResetAppModal.tsx | 0 .../[id]/components/ResetAppModal/index.ts | 0 .../[id]/components/RestartModal/RestartModal.tsx | 0 .../app-store/[id]/components/RestartModal/index.ts | 0 .../[id]/components/RestoreModal/RestoreModal.tsx | 0 .../app-store/[id]/components/RestoreModal/index.ts | 0 .../[id]/components/StopModal/StopModal.tsx | 0 .../app-store/[id]/components/StopModal/index.ts | 0 .../components/UninstallModal/UninstallModal.tsx | 0 .../[id]/components/UninstallModal/index.ts | 0 .../components/UpdateModal/UpdateModal.test.tsx | 0 .../[id]/components/UpdateModal/UpdateModal.tsx | 0 .../app-store/[id]/components/UpdateModal/index.ts | 0 .../UpdateSettingsModal/UpdateSettingsModal.tsx | 0 .../[id]/components/UpdateSettingsModal/index.ts | 0 .../src}/app/(dashboard)/app-store/[id]/page.tsx | 0 .../app-store/[id]/utils/validators/index.ts | 0 .../[id]/utils/validators/validators.test.tsx | 0 .../app-store/[id]/utils/validators/validators.ts | 0 .../components/AppStoreTable/AppStoreTable.tsx | 0 .../app-store/components/AppStoreTable/index.ts | 0 .../AppStoreTableActions.module.scss | 0 .../AppStoreTableActions/AppStoreTableActions.tsx | 0 .../AppStoreTile/AppStoreTile.module.scss | 0 .../components/AppStoreTile/AppStoreTile.tsx | 0 .../app-store/components/AppStoreTile/index.ts | 0 .../CategorySelector/CategorySelecte.test.tsx | 0 .../CategorySelector/CategorySelector.tsx | 0 .../app-store/components/CategorySelector/index.ts | 0 .../helpers/__tests__/table.helpers.test.ts | 0 .../(dashboard)/app-store/helpers/table.helpers.ts | 0 .../(dashboard)/app-store/helpers/table.types.ts | 0 .../src}/app/(dashboard)/app-store/page.tsx | 0 .../(dashboard)/app-store/state/appStoreState.ts | 0 .../components/UpdateAllButton/UpdateAllButton.tsx | 0 .../components/UpdateAllModal/UpdateAllModal.tsx | 0 .../apps/components/UpdateAllModal/index.ts | 0 .../src}/app/(dashboard)/apps/page.module.css | 0 {src => legacy/src}/app/(dashboard)/apps/page.tsx | 0 .../components/AddLink/AddLinkButton.tsx | 0 .../(dashboard)/components/AddLink/AddLinkModal.tsx | 0 .../components/AddLink/DeleteLinkModal.tsx | 0 .../components/AddLink/addLink.module.css | 0 .../components/AtRiskBanner/AtRiskBanner.tsx | 0 .../app/(dashboard)/components/Header/Header.tsx | 0 .../src}/app/(dashboard)/components/Header/index.ts | 0 .../components/LayoutActions/LayoutActions.tsx | 0 .../app/(dashboard)/components/NavBar/NavBar.tsx | 0 .../src}/app/(dashboard)/components/NavBar/index.ts | 0 .../(dashboard)/components/PageTitle/PageTitle.tsx | 0 .../app/(dashboard)/components/PageTitle/index.ts | 0 .../app/(dashboard)/components/Welcome/Welcome.tsx | 0 .../DashboardContainer/DashboardContainer.tsx | 0 .../components/DashboardContainer/index.ts | 0 .../dashboard/components/SystemStat/SystemStat.tsx | 0 .../dashboard/components/SystemStat/index.ts | 0 .../src}/app/(dashboard)/dashboard/page.tsx | 0 .../src}/app/(dashboard)/layout.module.scss | 0 {src => legacy/src}/app/(dashboard)/layout.tsx | 0 .../ChangePasswordForm/ChangePasswordForm.tsx | 0 .../settings/components/ChangePasswordForm/index.ts | 0 .../ChangeUsernameForm/ChangeUsernameForm.tsx | 0 .../settings/components/ChangeUsernameForm/index.ts | 0 .../components/GeneralActions/GeneralActions.tsx | 0 .../components/UpdateRepoModal/UpateRepoModal.tsx | 0 .../settings/components/GeneralActions/index.ts | 0 .../components/LogsContainer/LogsContainer.tsx | 0 .../settings/components/LogsContainer/index.ts | 0 .../settings/components/OtpForm/OtpForm.tsx | 0 .../settings/components/OtpForm/index.ts | 0 .../SecurityContainer/SecurityContainer.test.tsx | 0 .../SecurityContainer/SecurityContainer.tsx | 0 .../settings/components/SecurityContainer/index.ts | 0 .../SettingsContainer/SettingsContainer.tsx | 0 .../settings/components/SettingsContainer/index.ts | 0 .../components/SettingsForm/SettingsForm.test.tsx | 0 .../components/SettingsForm/SettingsForm.tsx | 0 .../settings/components/SettingsForm/index.ts | 0 .../SettingsTabTriggers/SettingsTabTriggers.tsx | 0 .../components/SettingsTabTriggers/index.ts | 0 .../src}/app/(dashboard)/settings/page.tsx | 0 .../src}/app/(dashboard)/test-error/page.tsx | 0 .../acknowledge-welcome/acknowledge-welcome.ts | 0 .../app/actions/app-actions/install-app-action.ts | 0 .../app/actions/app-actions/reset-app-action.ts | 0 .../app/actions/app-actions/restart-app-action.ts | 0 .../src}/app/actions/app-actions/revalidate-app.ts | 0 .../app/actions/app-actions/start-app-action.ts | 0 .../src}/app/actions/app-actions/stop-app-action.ts | 0 .../app/actions/app-actions/uninstall-app-action.ts | 0 .../actions/app-actions/update-all-apps-action.ts | 0 .../app/actions/app-actions/update-app-action.ts | 0 .../actions/app-actions/update-app-config-action.ts | 0 .../app/actions/backup/create-app-backup-action.ts | 0 .../src}/app/actions/backup/delete-app-backup.ts | 0 .../app/actions/backup/restore-app-backup-action.ts | 0 .../cancel-reset-password-action.ts | 0 .../actions/change-locale/change-locale-action.ts | 0 .../app/actions/custom-links/add-link-action.ts | 0 .../app/actions/custom-links/delete-link-action.ts | 0 .../app/actions/custom-links/edit-link-action.ts | 0 .../src}/app/actions/login/login-action.ts | 0 .../src}/app/actions/logout/logout-action.ts | 0 .../src}/app/actions/register/register-action.ts | 0 .../actions/reset-password/reset-password-action.ts | 0 .../src}/app/actions/settings/change-password.ts | 0 .../src}/app/actions/settings/change-username.ts | 0 .../src}/app/actions/settings/disable-totp.ts | 0 .../src}/app/actions/settings/get-totp-uri.ts | 0 .../src}/app/actions/settings/setup-totp-action.ts | 0 .../src}/app/actions/settings/update-repo-action.ts | 0 .../src}/app/actions/settings/update-settings.ts | 0 .../src}/app/actions/utils/ensure-user.ts | 0 .../src}/app/actions/utils/handle-api-error.ts | 0 .../app/actions/verify-totp/verify-totp-action.ts | 0 {src => legacy/src}/app/api/app-backups/route.ts | 0 {src => legacy/src}/app/api/app-image/route.ts | 0 {src => legacy/src}/app/api/app-store/route.ts | 0 {src => legacy/src}/app/api/certificate/route.ts | 0 .../app/api/system-status/fetch-system-status.ts | 0 {src => legacy/src}/app/api/system-status/route.ts | 0 {src => legacy/src}/app/api/test-error/route.ts | 0 {src => legacy/src}/app/apple-icon.png | Bin .../AppStatusProvider/app-status-provider.tsx | 0 .../AppStatusProvider/app-status-store.ts | 0 .../components/ClientProviders/ClientProviders.tsx | 0 .../ClientSettingsProvider.tsx | 0 .../client-settings-store.tsx | 0 .../SocketProvider/SocketProvider.ts | 0 .../ClientProviders/ThemeProvider/ThemeProvider.tsx | 0 .../ClientProviders/ThemeProvider/index.ts | 0 .../src}/app/components/ClientProviders/index.ts | 0 .../app/components/EmptyPage/EmptyPage.module.scss | 0 .../src}/app/components/EmptyPage/EmptyPage.tsx | 0 .../src}/app/components/EmptyPage/index.ts | 0 .../GuestDashboardApps.module.css | 0 .../GuestDashboardApps/GuestDashboardApps.tsx | 0 .../app/components/GuestDashboardApps/index.tsx | 0 .../LanguageSelector/LanguageSelector.tsx | 0 .../LanguageSelector/LanguageSelectorLabel.tsx | 0 .../src}/app/components/LanguageSelector/index.ts | 0 .../LogsTerminal/LogsTerminal.module.scss | 0 .../app/components/LogsTerminal/LogsTerminal.tsx | 0 .../src}/app/components/OffCanvas/OffCanvas.tsx | 0 .../components/TablePagination/TablePagination.tsx | 0 .../TimeZoneSelector/TimeZoneSelector.tsx | 0 {src => legacy/src}/app/favicon.ico | Bin {src => legacy/src}/app/global-error.tsx | 0 {src => legacy/src}/app/global.css | 0 {src => legacy/src}/app/hooks/useAppStatus.tsx | 0 {src => legacy/src}/app/hooks/useClientSettings.tsx | 0 {src => legacy/src}/app/icon.png | Bin {src => legacy/src}/app/layout.tsx | 0 {src => legacy/src}/app/not-found.tsx | 0 {src => legacy/src}/app/page.tsx | 0 .../client/components/AppLogo/AppLogo.module.scss | 0 .../src}/client/components/AppLogo/AppLogo.tsx | 0 .../src}/client/components/AppLogo/index.ts | 0 .../components/AppStatus/AppStatus.module.scss | 0 .../src}/client/components/AppStatus/AppStatus.tsx | 0 .../src}/client/components/AppStatus/index.tsx | 0 .../client/components/AppTile/AppTile.module.scss | 0 .../src}/client/components/AppTile/AppTile.tsx | 0 .../src}/client/components/AppTile/index.tsx | 0 .../client/components/ClientOnly/ClientOnly.tsx | 0 .../client/components/DateFormat/DateFormat.tsx | 0 .../src}/client/components/FileSize/FileSize.tsx | 0 .../src}/client/components/LinkTile/LinkTile.tsx | 0 .../src}/client/components/Markdown/Markdown.tsx | 0 .../src}/client/components/Markdown/index.ts | 0 .../client/components/StatusScreen/StatusScreen.tsx | 0 .../src}/client/components/StatusScreen/index.ts | 0 .../UnauthenticatedPage/UnauthenticatedPage.tsx | 0 .../client/components/UnauthenticatedPage/index.ts | 0 .../client/components/ui/Button/Button.module.css | 0 .../client/components/ui/Button/Button.test.tsx | 0 .../src}/client/components/ui/Button/Button.tsx | 0 .../src}/client/components/ui/Button/index.ts | 0 .../components/ui/ContextMenu/ContextMenu.tsx | 0 .../client/components/ui/DataGrid/DataGrid.test.tsx | 0 .../src}/client/components/ui/DataGrid/DataGrid.tsx | 0 .../client/components/ui/DataGrid/DataGridItem.tsx | 0 .../src}/client/components/ui/DataGrid/index.ts | 0 .../client/components/ui/Dialog/Dialog.module.scss | 0 .../src}/client/components/ui/Dialog/Dialog.tsx | 0 .../src}/client/components/ui/Dialog/index.ts | 0 .../components/ui/DropdownMenu/DropdownMenu.tsx | 0 .../src}/client/components/ui/DropdownMenu/index.ts | 0 .../components/ui/EmptyPage/EmptyPage.module.scss | 0 .../components/ui/EmptyPage/EmptyPage.test.tsx | 0 .../client/components/ui/EmptyPage/EmptyPage.tsx | 0 .../src}/client/components/ui/EmptyPage/index.ts | 0 .../components/ui/ErrorPage/ErrorPage.module.scss | 0 .../components/ui/ErrorPage/ErrorPage.test.tsx | 0 .../client/components/ui/ErrorPage/ErrorPage.tsx | 0 .../src}/client/components/ui/ErrorPage/index.ts | 0 .../src}/client/components/ui/Input/Input.test.tsx | 0 .../src}/client/components/ui/Input/Input.tsx | 0 .../src}/client/components/ui/Input/index.ts | 0 .../components/ui/OtpInput/OtpInput.module.scss | 0 .../client/components/ui/OtpInput/OtpInput.test.tsx | 0 .../src}/client/components/ui/OtpInput/OtpInput.tsx | 0 .../src}/client/components/ui/OtpInput/index.ts | 0 .../components/ui/Pagination/Pagination.module.scss | 0 .../client/components/ui/Pagination/Pagination.tsx | 0 .../components/ui/ScrollArea/ScrollArea.module.css | 0 .../client/components/ui/ScrollArea/ScrollArea.tsx | 0 .../src}/client/components/ui/ScrollArea/index.ts | 0 .../src}/client/components/ui/Select/Select.tsx | 0 .../src}/client/components/ui/Select/index.ts | 0 .../client/components/ui/Switch/Switch.module.scss | 0 .../client/components/ui/Switch/Switch.test.tsx | 0 .../src}/client/components/ui/Switch/Switch.tsx | 0 .../src}/client/components/ui/Switch/index.ts | 0 .../src}/client/components/ui/Table/Table.tsx | 0 .../src}/client/components/ui/Table/index.ts | 0 .../src}/client/components/ui/tabs/index.ts | 0 .../src}/client/components/ui/tabs/tabs.module.scss | 0 .../src}/client/components/ui/tabs/tabs.tsx | 0 .../client/hooks/__tests__/useDisclosure.test.ts | 0 {src => legacy/src}/client/hooks/useDisclosure.ts | 0 .../src}/client/hooks/useInfiniteScroll.ts | 0 {src => legacy/src}/client/messages/ach-UG.json | 0 {src => legacy/src}/client/messages/af-ZA.json | 0 {src => legacy/src}/client/messages/ar-SA.json | 0 {src => legacy/src}/client/messages/ca-ES.json | 0 {src => legacy/src}/client/messages/cs-CZ.json | 0 {src => legacy/src}/client/messages/da-DK.json | 0 {src => legacy/src}/client/messages/de-DE.json | 0 {src => legacy/src}/client/messages/el-GR.json | 0 {src => legacy/src}/client/messages/en-US.json | 0 {src => legacy/src}/client/messages/en.json | 0 {src => legacy/src}/client/messages/es-ES.json | 0 {src => legacy/src}/client/messages/fi-FI.json | 0 {src => legacy/src}/client/messages/fr-FR.json | 0 {src => legacy/src}/client/messages/he-IL.json | 0 {src => legacy/src}/client/messages/hu-HU.json | 0 {src => legacy/src}/client/messages/it-IT.json | 0 {src => legacy/src}/client/messages/ja-JP.json | 0 {src => legacy/src}/client/messages/ko-KR.json | 0 {src => legacy/src}/client/messages/nl-NL.json | 0 {src => legacy/src}/client/messages/no-NO.json | 0 {src => legacy/src}/client/messages/pl-PL.json | 0 {src => legacy/src}/client/messages/pt-BR.json | 0 {src => legacy/src}/client/messages/pt-PT.json | 0 {src => legacy/src}/client/messages/ro-RO.json | 0 {src => legacy/src}/client/messages/ru-RU.json | 0 {src => legacy/src}/client/messages/sr-SP.json | 0 {src => legacy/src}/client/messages/sv-SE.json | 0 {src => legacy/src}/client/messages/tr-TR.json | 0 {src => legacy/src}/client/messages/uk-UA.json | 0 {src => legacy/src}/client/messages/vi-VN.json | 0 {src => legacy/src}/client/messages/zh-CN.json | 0 {src => legacy/src}/client/messages/zh-TW.json | 0 {src => legacy/src}/client/mocks/browser.ts | 0 {src => legacy/src}/client/mocks/handlers.ts | 0 {src => legacy/src}/client/mocks/index.ts | 0 {src => legacy/src}/client/mocks/server.ts | 0 {src => legacy/src}/client/state/uiStore.ts | 0 .../src}/client/utils/__tests__/typescript.test.ts | 0 {src => legacy/src}/client/utils/typescript.ts | 0 {src => legacy/src}/config/constants.ts | 0 {src => legacy/src}/config/index.ts | 0 {src => legacy/src}/i18n.ts | 0 {src => legacy/src}/instrumentation.ts | 0 {src => legacy/src}/inversify.config.ts | 0 {src => legacy/src}/lib/get-translator.ts | 0 {src => legacy/src}/lib/helpers/castAppConfig.ts | 0 {src => legacy/src}/lib/helpers/text-helpers.ts | 0 {src => legacy/src}/lib/safe-action.ts | 0 {src => legacy/src}/lib/socket/useSocket.test.ts | 0 {src => legacy/src}/lib/socket/useSocket.ts | 0 {src => legacy/src}/lib/themes.ts | 0 {src => legacy/src}/server/common/fs.helpers.ts | 0 .../src}/server/common/session-manager.ts | 0 .../src}/server/common/typescript.helpers.ts | 0 .../server/core/EventDispatcher/EventDispatcher.ts | 0 .../src}/server/core/EventDispatcher/index.ts | 0 .../src}/server/core/TipiConfig/TipiConfig.test.ts | 0 .../src}/server/core/TipiConfig/TipiConfig.ts | 0 {src => legacy/src}/server/core/TipiConfig/index.ts | 0 .../src}/server/queries/apps/apps.queries.ts | 0 .../src}/server/queries/auth/auth.queries.ts | 0 .../src}/server/queries/links/links.queries.ts | 0 .../services/app-backup/app-backup.service.ts | 0 .../commands/create-app-backup-command.ts | 0 .../commands/delete-app-backup-command.ts | 0 .../app-backup/commands/get-app-backups-command.ts | 0 .../server/services/app-backup/commands/index.ts | 0 .../commands/restore-app-backup-command.ts | 0 .../server/services/app-backup/commands/types.ts | 0 .../services/app-catalog/app-catalog-cache.ts | 0 .../app-catalog/app-catalog.service.test.ts | 0 .../services/app-catalog/app-catalog.service.ts | 0 .../services/app-catalog/apps.helpers.test.ts | 0 .../app-lifecycle/app-lifecycle.service.test.ts | 0 .../services/app-lifecycle/app-lifecycle.service.ts | 0 .../commands/__tests__/install-app-command.test.ts | 0 .../commands/__tests__/reset-app-command.test.ts | 0 .../commands/__tests__/restart-app-command.test.ts | 0 .../commands/__tests__/start-app-command.test.ts | 0 .../commands/__tests__/stop-app-command.test.ts | 0 .../__tests__/uninstall-app-command.test.ts | 0 .../commands/__tests__/update-app-command.test.ts | 0 .../__tests__/update-app-config-command.test.ts | 0 .../server/services/app-lifecycle/commands/index.ts | 0 .../app-lifecycle/commands/install-app-command.ts | 0 .../app-lifecycle/commands/reset-app-command.ts | 0 .../app-lifecycle/commands/restart-app-command.ts | 0 .../app-lifecycle/commands/start-app-command.ts | 0 .../app-lifecycle/commands/stop-app-command.ts | 0 .../server/services/app-lifecycle/commands/types.ts | 0 .../app-lifecycle/commands/uninstall-app-command.ts | 0 .../app-lifecycle/commands/update-app-command.ts | 0 .../commands/update-app-config-command.ts | 0 .../src}/server/services/auth/auth.service.test.ts | 0 .../src}/server/services/auth/auth.service.ts | 0 .../services/custom-links/custom-links.service.ts | 0 {src => legacy/src}/server/services/system/index.ts | 0 .../server/services/system/system.service.test.ts | 0 .../src}/server/services/system/system.service.ts | 0 {src => legacy/src}/server/tests/apps.factory.ts | 0 {src => legacy/src}/server/tests/test-utils.ts | 0 {src => legacy/src}/server/tests/user.factory.ts | 0 .../src}/server/utils/__tests__/encryption.test.ts | 0 {src => legacy/src}/server/utils/encryption.ts | 0 {src => legacy/src}/server/utils/errors.ts | 0 {src => legacy/src}/server/utils/network.ts | 0 {src => legacy/src}/server/utils/totp.ts | 0 .../src}/shared/internationalization/locales.ts | 0 {src => legacy/src}/utils/getCurrentLocale.ts | 0 start.dev.sh => legacy/start.dev.sh | 0 start.prod.sh => legacy/start.prod.sh | 0 {tests => legacy/tests}/client/test.setup.tsx | 0 {tests => legacy/tests}/mocks/fs.ts | 0 {tests => legacy/tests}/server/test.setup.ts | 0 {tests => legacy/tests}/test-utils.tsx | 0 tsconfig.json => legacy/tsconfig.json | 0 vitest.workspace.ts => legacy/vitest.workspace.ts | 0 505 files changed, 0 insertions(+), 0 deletions(-) rename .dockerignore => legacy/.dockerignore (100%) rename .env.example => legacy/.env.example (100%) rename .env.test => legacy/.env.test (100%) rename .gitignore => legacy/.gitignore (100%) rename Dockerfile => legacy/Dockerfile (100%) rename Dockerfile.dev => legacy/Dockerfile.dev (100%) rename docker-compose.dev.yml => legacy/docker-compose.dev.yml (100%) rename docker-compose.prod.yml => legacy/docker-compose.prod.yml (100%) rename global.d.ts => legacy/global.d.ts (100%) rename next-env.d.ts => legacy/next-env.d.ts (100%) rename next.config.mjs => legacy/next.config.mjs (100%) rename package.json => legacy/package.json (100%) rename {packages => legacy/packages}/cache/package.json (100%) rename {packages => legacy/packages}/cache/src/cache.ts (100%) rename {packages => legacy/packages}/cache/src/index.ts (100%) rename {packages => legacy/packages}/cache/src/mock.ts (100%) rename {packages => legacy/packages}/cache/tsconfig.json (100%) rename {packages => legacy/packages}/db/assets/migrations/00000-create-migrations-table.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00001-initial.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00002-add-app-version.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00003-add-status-updating.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00004-add-exposed-domain-fields.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00005-add-user-operator.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00006-add-totp-user-fields.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00007-add-locale-user-col.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00008-merge-config-with-domain-and-exposed.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00009-add-guest-dashboard.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00010-add-status-resetting.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00011-create-link-table.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00012-link-table-description.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00013-app-local-exposed.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00014-restarting-state.sql (100%) rename {packages => legacy/packages}/db/assets/migrations/00015-backingup-state.sql (100%) rename {packages => legacy/packages}/db/package.json (100%) rename {packages => legacy/packages}/db/src/client.ts (100%) rename {packages => legacy/packages}/db/src/index.ts (100%) rename {packages => legacy/packages}/db/src/migrator.ts (100%) rename {packages => legacy/packages}/db/src/schema.ts (100%) rename {packages => legacy/packages}/db/tsconfig.json (100%) rename {packages => legacy/packages}/shared/node/package.json (100%) rename {packages => legacy/packages}/shared/package.json (100%) rename {packages => legacy/packages}/shared/src/helpers/env-helpers.ts (100%) rename {packages => legacy/packages}/shared/src/helpers/error-helpers.ts (100%) rename {packages => legacy/packages}/shared/src/helpers/sanitizers.ts (100%) rename {packages => legacy/packages}/shared/src/helpers/utils.ts (100%) rename {packages => legacy/packages}/shared/src/index.ts (100%) rename {packages => legacy/packages}/shared/src/node/helpers/exec-async.tsx (100%) rename {packages => legacy/packages}/shared/src/node/helpers/fs-helpers.ts (100%) rename {packages => legacy/packages}/shared/src/node/helpers/typescript-helpers.ts (100%) rename {packages => legacy/packages}/shared/src/node/index.ts (100%) rename {packages => legacy/packages}/shared/src/node/logger/FileLogger.ts (100%) rename {packages => legacy/packages}/shared/src/node/logger/Logger.interface.ts (100%) rename {packages => legacy/packages}/shared/src/node/logger/Logger.ts (100%) rename {packages => legacy/packages}/shared/src/node/logger/LoggerMock.ts (100%) rename {packages => legacy/packages}/shared/src/node/modules/app/__tests__/app-file-accessor.test.ts (100%) rename {packages => legacy/packages}/shared/src/node/modules/app/app-data-service.ts (100%) rename {packages => legacy/packages}/shared/src/node/modules/app/app-file-accessor.ts (100%) rename {packages => legacy/packages}/shared/src/node/modules/archive/archive-manager.ts (100%) rename {packages => legacy/packages}/shared/src/node/modules/backup/backup-manager.ts (100%) rename {packages => legacy/packages}/shared/src/schemas/app-schemas.ts (100%) rename {packages => legacy/packages}/shared/src/schemas/env-schemas.ts (100%) rename {packages => legacy/packages}/shared/src/schemas/link-schemas.ts (100%) rename {packages => legacy/packages}/shared/src/schemas/queue-schemas.ts (100%) rename {packages => legacy/packages}/shared/src/schemas/socket-schemas.ts (100%) rename {packages => legacy/packages}/shared/src/schemas/system-schemas.ts (100%) rename {packages => legacy/packages}/shared/tsconfig.json (100%) rename {packages => legacy/packages}/worker/.env.test (100%) rename {packages => legacy/packages}/worker/.gitignore (100%) rename {packages => legacy/packages}/worker/assets/traefik/dynamic/dynamic.yml (100%) rename {packages => legacy/packages}/worker/assets/traefik/traefik.yml (100%) rename {packages => legacy/packages}/worker/build.mjs (100%) rename {packages => legacy/packages}/worker/nodemon.json (100%) rename {packages => legacy/packages}/worker/package.json (100%) rename {packages => legacy/packages}/worker/src/api.ts (100%) rename {packages => legacy/packages}/worker/src/config/__tests__/docker-templates.test.ts (100%) rename {packages => legacy/packages}/worker/src/config/constants.ts (100%) rename {packages => legacy/packages}/worker/src/config/docker-templates.ts (100%) rename {packages => legacy/packages}/worker/src/config/index.ts (100%) rename {packages => legacy/packages}/worker/src/index.ts (100%) rename {packages => legacy/packages}/worker/src/inversify.config.ts (100%) rename {packages => legacy/packages}/worker/src/lib/docker/builders/docker-compose-builder.ts (100%) rename {packages => legacy/packages}/worker/src/lib/docker/builders/schemas.ts (100%) rename {packages => legacy/packages}/worker/src/lib/docker/builders/service-builder.ts (100%) rename {packages => legacy/packages}/worker/src/lib/docker/builders/traefik-labels-builder.ts (100%) rename {packages => legacy/packages}/worker/src/lib/docker/docker-helpers.test.ts (100%) rename {packages => legacy/packages}/worker/src/lib/docker/docker-helpers.ts (100%) rename {packages => legacy/packages}/worker/src/lib/docker/index.ts (100%) rename {packages => legacy/packages}/worker/src/lib/environment/environment.ts (100%) rename {packages => legacy/packages}/worker/src/lib/environment/index.ts (100%) rename {packages => legacy/packages}/worker/src/lib/logger/index.ts (100%) rename {packages => legacy/packages}/worker/src/lib/logger/logger.ts (100%) rename {packages => legacy/packages}/worker/src/lib/socket/SocketManager.ts (100%) rename {packages => legacy/packages}/worker/src/lib/socket/index.ts (100%) rename {packages => legacy/packages}/worker/src/lib/system/index.ts (100%) rename {packages => legacy/packages}/worker/src/lib/system/system.helpers.test.ts (100%) rename {packages => legacy/packages}/worker/src/lib/system/system.helpers.ts (100%) rename {packages => legacy/packages}/worker/src/services/app/__tests__/app.executors.test.ts (100%) rename {packages => legacy/packages}/worker/src/services/app/__tests__/app.helpers.test.ts (100%) rename {packages => legacy/packages}/worker/src/services/app/__tests__/env.helpers.test.ts (100%) rename {packages => legacy/packages}/worker/src/services/app/app.executors.ts (100%) rename {packages => legacy/packages}/worker/src/services/app/app.helpers.ts (100%) rename {packages => legacy/packages}/worker/src/services/app/env.helpers.ts (100%) rename {packages => legacy/packages}/worker/src/services/index.ts (100%) rename {packages => legacy/packages}/worker/src/services/repo/repo.executors.ts (100%) rename {packages => legacy/packages}/worker/src/services/repo/repo.helpers.ts (100%) rename {packages => legacy/packages}/worker/src/services/system/system.executors.ts (100%) rename {packages => legacy/packages}/worker/src/types/global.d.ts (100%) rename {packages => legacy/packages}/worker/src/watcher/watcher.ts (100%) rename {packages => legacy/packages}/worker/tests/apps.factory.ts (100%) rename {packages => legacy/packages}/worker/tests/mocks/fs.ts (100%) rename {packages => legacy/packages}/worker/tests/vite.setup.ts (100%) rename {packages => legacy/packages}/worker/tsconfig.json (100%) rename {packages => legacy/packages}/worker/vitest.config.ts (100%) rename {patches => legacy/patches}/.gitkeep (100%) rename pnpm-lock.yaml => legacy/pnpm-lock.yaml (100%) rename pnpm-workspace.yaml => legacy/pnpm-workspace.yaml (100%) rename {public => legacy/public}/app-not-found.jpg (100%) rename {public => legacy/public}/empty.svg (100%) rename {public => legacy/public}/error.png (100%) rename {public => legacy/public}/js/.gitkeep (100%) rename {public => legacy/public}/mockServiceWorker.js (100%) rename {public => legacy/public}/placeholder.png (100%) rename {public => legacy/public}/tipi-christmas.png (100%) rename {public => legacy/public}/tipi.png (100%) rename reset.d.ts => legacy/reset.d.ts (100%) rename sentry.client.config.ts => legacy/sentry.client.config.ts (100%) rename sonar-project.properties => legacy/sonar-project.properties (100%) rename {src => legacy/src}/app/(auth)/layout.tsx (100%) rename {src => legacy/src}/app/(auth)/login/components/LoginContainer/LoginContainer.tsx (100%) rename {src => legacy/src}/app/(auth)/login/components/LoginContainer/index.ts (100%) rename {src => legacy/src}/app/(auth)/login/components/LoginForm/LoginForm.tsx (100%) rename {src => legacy/src}/app/(auth)/login/components/LoginForm/index.ts (100%) rename {src => legacy/src}/app/(auth)/login/components/TotpForm/TotpForm.tsx (100%) rename {src => legacy/src}/app/(auth)/login/components/TotpForm/index.ts (100%) rename {src => legacy/src}/app/(auth)/login/page.tsx (100%) rename {src => legacy/src}/app/(auth)/register/components/RegisterContainer/RegisterContainer.tsx (100%) rename {src => legacy/src}/app/(auth)/register/components/RegisterContainer/index.ts (100%) rename {src => legacy/src}/app/(auth)/register/components/RegisterForm/RegisterForm.tsx (100%) rename {src => legacy/src}/app/(auth)/register/components/RegisterForm/index.ts (100%) rename {src => legacy/src}/app/(auth)/register/page.tsx (100%) rename {src => legacy/src}/app/(auth)/reset-password/components/ResetPasswordContainer/ResetPasswordContainer.tsx (100%) rename {src => legacy/src}/app/(auth)/reset-password/components/ResetPasswordContainer/index.ts (100%) rename {src => legacy/src}/app/(auth)/reset-password/components/ResetPasswordForm/ResetPasswordForm.tsx (100%) rename {src => legacy/src}/app/(auth)/reset-password/components/ResetPasswordForm/index.ts (100%) rename {src => legacy/src}/app/(auth)/reset-password/page.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.module.scss (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.test.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppActions/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppBackups.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabTriggers.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabs.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppLogs.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/BackupModal/BackupModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/BackupModal/index.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/DeleteBackupModal/DeleteBackupModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.test.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.types.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/InstallForm/InstallFormField.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/InstallForm/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.test.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/InstallModal/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/ResetAppModal/ResetAppModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/ResetAppModal/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/RestartModal/RestartModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/RestartModal/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/RestoreModal/RestoreModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/RestoreModal/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/StopModal/StopModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/StopModal/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/UninstallModal/UninstallModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/UninstallModal/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.test.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/UpdateModal/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/UpdateSettingsModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/page.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/utils/validators/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/utils/validators/validators.test.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/[id]/utils/validators/validators.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/AppStoreTable/AppStoreTable.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/AppStoreTable/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.module.scss (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.module.scss (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/AppStoreTile/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/CategorySelector/CategorySelecte.test.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/CategorySelector/CategorySelector.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/components/CategorySelector/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/helpers/__tests__/table.helpers.test.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/helpers/table.helpers.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/helpers/table.types.ts (100%) rename {src => legacy/src}/app/(dashboard)/app-store/page.tsx (100%) rename {src => legacy/src}/app/(dashboard)/app-store/state/appStoreState.ts (100%) rename {src => legacy/src}/app/(dashboard)/apps/components/UpdateAllButton/UpdateAllButton.tsx (100%) rename {src => legacy/src}/app/(dashboard)/apps/components/UpdateAllModal/UpdateAllModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/apps/components/UpdateAllModal/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/apps/page.module.css (100%) rename {src => legacy/src}/app/(dashboard)/apps/page.tsx (100%) rename {src => legacy/src}/app/(dashboard)/components/AddLink/AddLinkButton.tsx (100%) rename {src => legacy/src}/app/(dashboard)/components/AddLink/AddLinkModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/components/AddLink/DeleteLinkModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/components/AddLink/addLink.module.css (100%) rename {src => legacy/src}/app/(dashboard)/components/AtRiskBanner/AtRiskBanner.tsx (100%) rename {src => legacy/src}/app/(dashboard)/components/Header/Header.tsx (100%) rename {src => legacy/src}/app/(dashboard)/components/Header/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/components/LayoutActions/LayoutActions.tsx (100%) rename {src => legacy/src}/app/(dashboard)/components/NavBar/NavBar.tsx (100%) rename {src => legacy/src}/app/(dashboard)/components/NavBar/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/components/PageTitle/PageTitle.tsx (100%) rename {src => legacy/src}/app/(dashboard)/components/PageTitle/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/components/Welcome/Welcome.tsx (100%) rename {src => legacy/src}/app/(dashboard)/dashboard/components/DashboardContainer/DashboardContainer.tsx (100%) rename {src => legacy/src}/app/(dashboard)/dashboard/components/DashboardContainer/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/dashboard/components/SystemStat/SystemStat.tsx (100%) rename {src => legacy/src}/app/(dashboard)/dashboard/components/SystemStat/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/dashboard/page.tsx (100%) rename {src => legacy/src}/app/(dashboard)/layout.module.scss (100%) rename {src => legacy/src}/app/(dashboard)/layout.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/ChangePasswordForm/ChangePasswordForm.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/ChangePasswordForm/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/ChangeUsernameForm/ChangeUsernameForm.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/ChangeUsernameForm/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/GeneralActions/GeneralActions.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/GeneralActions/components/UpdateRepoModal/UpateRepoModal.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/GeneralActions/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/LogsContainer/LogsContainer.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/LogsContainer/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/OtpForm/OtpForm.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/OtpForm/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.test.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SecurityContainer/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SettingsContainer/SettingsContainer.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SettingsContainer/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SettingsForm/SettingsForm.test.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SettingsForm/SettingsForm.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SettingsForm/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SettingsTabTriggers/SettingsTabTriggers.tsx (100%) rename {src => legacy/src}/app/(dashboard)/settings/components/SettingsTabTriggers/index.ts (100%) rename {src => legacy/src}/app/(dashboard)/settings/page.tsx (100%) rename {src => legacy/src}/app/(dashboard)/test-error/page.tsx (100%) rename {src => legacy/src}/app/actions/acknowledge-welcome/acknowledge-welcome.ts (100%) rename {src => legacy/src}/app/actions/app-actions/install-app-action.ts (100%) rename {src => legacy/src}/app/actions/app-actions/reset-app-action.ts (100%) rename {src => legacy/src}/app/actions/app-actions/restart-app-action.ts (100%) rename {src => legacy/src}/app/actions/app-actions/revalidate-app.ts (100%) rename {src => legacy/src}/app/actions/app-actions/start-app-action.ts (100%) rename {src => legacy/src}/app/actions/app-actions/stop-app-action.ts (100%) rename {src => legacy/src}/app/actions/app-actions/uninstall-app-action.ts (100%) rename {src => legacy/src}/app/actions/app-actions/update-all-apps-action.ts (100%) rename {src => legacy/src}/app/actions/app-actions/update-app-action.ts (100%) rename {src => legacy/src}/app/actions/app-actions/update-app-config-action.ts (100%) rename {src => legacy/src}/app/actions/backup/create-app-backup-action.ts (100%) rename {src => legacy/src}/app/actions/backup/delete-app-backup.ts (100%) rename {src => legacy/src}/app/actions/backup/restore-app-backup-action.ts (100%) rename {src => legacy/src}/app/actions/cancel-reset-password/cancel-reset-password-action.ts (100%) rename {src => legacy/src}/app/actions/change-locale/change-locale-action.ts (100%) rename {src => legacy/src}/app/actions/custom-links/add-link-action.ts (100%) rename {src => legacy/src}/app/actions/custom-links/delete-link-action.ts (100%) rename {src => legacy/src}/app/actions/custom-links/edit-link-action.ts (100%) rename {src => legacy/src}/app/actions/login/login-action.ts (100%) rename {src => legacy/src}/app/actions/logout/logout-action.ts (100%) rename {src => legacy/src}/app/actions/register/register-action.ts (100%) rename {src => legacy/src}/app/actions/reset-password/reset-password-action.ts (100%) rename {src => legacy/src}/app/actions/settings/change-password.ts (100%) rename {src => legacy/src}/app/actions/settings/change-username.ts (100%) rename {src => legacy/src}/app/actions/settings/disable-totp.ts (100%) rename {src => legacy/src}/app/actions/settings/get-totp-uri.ts (100%) rename {src => legacy/src}/app/actions/settings/setup-totp-action.ts (100%) rename {src => legacy/src}/app/actions/settings/update-repo-action.ts (100%) rename {src => legacy/src}/app/actions/settings/update-settings.ts (100%) rename {src => legacy/src}/app/actions/utils/ensure-user.ts (100%) rename {src => legacy/src}/app/actions/utils/handle-api-error.ts (100%) rename {src => legacy/src}/app/actions/verify-totp/verify-totp-action.ts (100%) rename {src => legacy/src}/app/api/app-backups/route.ts (100%) rename {src => legacy/src}/app/api/app-image/route.ts (100%) rename {src => legacy/src}/app/api/app-store/route.ts (100%) rename {src => legacy/src}/app/api/certificate/route.ts (100%) rename {src => legacy/src}/app/api/system-status/fetch-system-status.ts (100%) rename {src => legacy/src}/app/api/system-status/route.ts (100%) rename {src => legacy/src}/app/api/test-error/route.ts (100%) rename {src => legacy/src}/app/apple-icon.png (100%) rename {src => legacy/src}/app/components/ClientProviders/AppStatusProvider/app-status-provider.tsx (100%) rename {src => legacy/src}/app/components/ClientProviders/AppStatusProvider/app-status-store.ts (100%) rename {src => legacy/src}/app/components/ClientProviders/ClientProviders.tsx (100%) rename {src => legacy/src}/app/components/ClientProviders/ClientSettingsProvider/ClientSettingsProvider.tsx (100%) rename {src => legacy/src}/app/components/ClientProviders/ClientSettingsProvider/client-settings-store.tsx (100%) rename {src => legacy/src}/app/components/ClientProviders/SocketProvider/SocketProvider.ts (100%) rename {src => legacy/src}/app/components/ClientProviders/ThemeProvider/ThemeProvider.tsx (100%) rename {src => legacy/src}/app/components/ClientProviders/ThemeProvider/index.ts (100%) rename {src => legacy/src}/app/components/ClientProviders/index.ts (100%) rename {src => legacy/src}/app/components/EmptyPage/EmptyPage.module.scss (100%) rename {src => legacy/src}/app/components/EmptyPage/EmptyPage.tsx (100%) rename {src => legacy/src}/app/components/EmptyPage/index.ts (100%) rename {src => legacy/src}/app/components/GuestDashboardApps/GuestDashboardApps.module.css (100%) rename {src => legacy/src}/app/components/GuestDashboardApps/GuestDashboardApps.tsx (100%) rename {src => legacy/src}/app/components/GuestDashboardApps/index.tsx (100%) rename {src => legacy/src}/app/components/LanguageSelector/LanguageSelector.tsx (100%) rename {src => legacy/src}/app/components/LanguageSelector/LanguageSelectorLabel.tsx (100%) rename {src => legacy/src}/app/components/LanguageSelector/index.ts (100%) rename {src => legacy/src}/app/components/LogsTerminal/LogsTerminal.module.scss (100%) rename {src => legacy/src}/app/components/LogsTerminal/LogsTerminal.tsx (100%) rename {src => legacy/src}/app/components/OffCanvas/OffCanvas.tsx (100%) rename {src => legacy/src}/app/components/TablePagination/TablePagination.tsx (100%) rename {src => legacy/src}/app/components/TimeZoneSelector/TimeZoneSelector.tsx (100%) rename {src => legacy/src}/app/favicon.ico (100%) rename {src => legacy/src}/app/global-error.tsx (100%) rename {src => legacy/src}/app/global.css (100%) rename {src => legacy/src}/app/hooks/useAppStatus.tsx (100%) rename {src => legacy/src}/app/hooks/useClientSettings.tsx (100%) rename {src => legacy/src}/app/icon.png (100%) rename {src => legacy/src}/app/layout.tsx (100%) rename {src => legacy/src}/app/not-found.tsx (100%) rename {src => legacy/src}/app/page.tsx (100%) rename {src => legacy/src}/client/components/AppLogo/AppLogo.module.scss (100%) rename {src => legacy/src}/client/components/AppLogo/AppLogo.tsx (100%) rename {src => legacy/src}/client/components/AppLogo/index.ts (100%) rename {src => legacy/src}/client/components/AppStatus/AppStatus.module.scss (100%) rename {src => legacy/src}/client/components/AppStatus/AppStatus.tsx (100%) rename {src => legacy/src}/client/components/AppStatus/index.tsx (100%) rename {src => legacy/src}/client/components/AppTile/AppTile.module.scss (100%) rename {src => legacy/src}/client/components/AppTile/AppTile.tsx (100%) rename {src => legacy/src}/client/components/AppTile/index.tsx (100%) rename {src => legacy/src}/client/components/ClientOnly/ClientOnly.tsx (100%) rename {src => legacy/src}/client/components/DateFormat/DateFormat.tsx (100%) rename {src => legacy/src}/client/components/FileSize/FileSize.tsx (100%) rename {src => legacy/src}/client/components/LinkTile/LinkTile.tsx (100%) rename {src => legacy/src}/client/components/Markdown/Markdown.tsx (100%) rename {src => legacy/src}/client/components/Markdown/index.ts (100%) rename {src => legacy/src}/client/components/StatusScreen/StatusScreen.tsx (100%) rename {src => legacy/src}/client/components/StatusScreen/index.ts (100%) rename {src => legacy/src}/client/components/UnauthenticatedPage/UnauthenticatedPage.tsx (100%) rename {src => legacy/src}/client/components/UnauthenticatedPage/index.ts (100%) rename {src => legacy/src}/client/components/ui/Button/Button.module.css (100%) rename {src => legacy/src}/client/components/ui/Button/Button.test.tsx (100%) rename {src => legacy/src}/client/components/ui/Button/Button.tsx (100%) rename {src => legacy/src}/client/components/ui/Button/index.ts (100%) rename {src => legacy/src}/client/components/ui/ContextMenu/ContextMenu.tsx (100%) rename {src => legacy/src}/client/components/ui/DataGrid/DataGrid.test.tsx (100%) rename {src => legacy/src}/client/components/ui/DataGrid/DataGrid.tsx (100%) rename {src => legacy/src}/client/components/ui/DataGrid/DataGridItem.tsx (100%) rename {src => legacy/src}/client/components/ui/DataGrid/index.ts (100%) rename {src => legacy/src}/client/components/ui/Dialog/Dialog.module.scss (100%) rename {src => legacy/src}/client/components/ui/Dialog/Dialog.tsx (100%) rename {src => legacy/src}/client/components/ui/Dialog/index.ts (100%) rename {src => legacy/src}/client/components/ui/DropdownMenu/DropdownMenu.tsx (100%) rename {src => legacy/src}/client/components/ui/DropdownMenu/index.ts (100%) rename {src => legacy/src}/client/components/ui/EmptyPage/EmptyPage.module.scss (100%) rename {src => legacy/src}/client/components/ui/EmptyPage/EmptyPage.test.tsx (100%) rename {src => legacy/src}/client/components/ui/EmptyPage/EmptyPage.tsx (100%) rename {src => legacy/src}/client/components/ui/EmptyPage/index.ts (100%) rename {src => legacy/src}/client/components/ui/ErrorPage/ErrorPage.module.scss (100%) rename {src => legacy/src}/client/components/ui/ErrorPage/ErrorPage.test.tsx (100%) rename {src => legacy/src}/client/components/ui/ErrorPage/ErrorPage.tsx (100%) rename {src => legacy/src}/client/components/ui/ErrorPage/index.ts (100%) rename {src => legacy/src}/client/components/ui/Input/Input.test.tsx (100%) rename {src => legacy/src}/client/components/ui/Input/Input.tsx (100%) rename {src => legacy/src}/client/components/ui/Input/index.ts (100%) rename {src => legacy/src}/client/components/ui/OtpInput/OtpInput.module.scss (100%) rename {src => legacy/src}/client/components/ui/OtpInput/OtpInput.test.tsx (100%) rename {src => legacy/src}/client/components/ui/OtpInput/OtpInput.tsx (100%) rename {src => legacy/src}/client/components/ui/OtpInput/index.ts (100%) rename {src => legacy/src}/client/components/ui/Pagination/Pagination.module.scss (100%) rename {src => legacy/src}/client/components/ui/Pagination/Pagination.tsx (100%) rename {src => legacy/src}/client/components/ui/ScrollArea/ScrollArea.module.css (100%) rename {src => legacy/src}/client/components/ui/ScrollArea/ScrollArea.tsx (100%) rename {src => legacy/src}/client/components/ui/ScrollArea/index.ts (100%) rename {src => legacy/src}/client/components/ui/Select/Select.tsx (100%) rename {src => legacy/src}/client/components/ui/Select/index.ts (100%) rename {src => legacy/src}/client/components/ui/Switch/Switch.module.scss (100%) rename {src => legacy/src}/client/components/ui/Switch/Switch.test.tsx (100%) rename {src => legacy/src}/client/components/ui/Switch/Switch.tsx (100%) rename {src => legacy/src}/client/components/ui/Switch/index.ts (100%) rename {src => legacy/src}/client/components/ui/Table/Table.tsx (100%) rename {src => legacy/src}/client/components/ui/Table/index.ts (100%) rename {src => legacy/src}/client/components/ui/tabs/index.ts (100%) rename {src => legacy/src}/client/components/ui/tabs/tabs.module.scss (100%) rename {src => legacy/src}/client/components/ui/tabs/tabs.tsx (100%) rename {src => legacy/src}/client/hooks/__tests__/useDisclosure.test.ts (100%) rename {src => legacy/src}/client/hooks/useDisclosure.ts (100%) rename {src => legacy/src}/client/hooks/useInfiniteScroll.ts (100%) rename {src => legacy/src}/client/messages/ach-UG.json (100%) rename {src => legacy/src}/client/messages/af-ZA.json (100%) rename {src => legacy/src}/client/messages/ar-SA.json (100%) rename {src => legacy/src}/client/messages/ca-ES.json (100%) rename {src => legacy/src}/client/messages/cs-CZ.json (100%) rename {src => legacy/src}/client/messages/da-DK.json (100%) rename {src => legacy/src}/client/messages/de-DE.json (100%) rename {src => legacy/src}/client/messages/el-GR.json (100%) rename {src => legacy/src}/client/messages/en-US.json (100%) rename {src => legacy/src}/client/messages/en.json (100%) rename {src => legacy/src}/client/messages/es-ES.json (100%) rename {src => legacy/src}/client/messages/fi-FI.json (100%) rename {src => legacy/src}/client/messages/fr-FR.json (100%) rename {src => legacy/src}/client/messages/he-IL.json (100%) rename {src => legacy/src}/client/messages/hu-HU.json (100%) rename {src => legacy/src}/client/messages/it-IT.json (100%) rename {src => legacy/src}/client/messages/ja-JP.json (100%) rename {src => legacy/src}/client/messages/ko-KR.json (100%) rename {src => legacy/src}/client/messages/nl-NL.json (100%) rename {src => legacy/src}/client/messages/no-NO.json (100%) rename {src => legacy/src}/client/messages/pl-PL.json (100%) rename {src => legacy/src}/client/messages/pt-BR.json (100%) rename {src => legacy/src}/client/messages/pt-PT.json (100%) rename {src => legacy/src}/client/messages/ro-RO.json (100%) rename {src => legacy/src}/client/messages/ru-RU.json (100%) rename {src => legacy/src}/client/messages/sr-SP.json (100%) rename {src => legacy/src}/client/messages/sv-SE.json (100%) rename {src => legacy/src}/client/messages/tr-TR.json (100%) rename {src => legacy/src}/client/messages/uk-UA.json (100%) rename {src => legacy/src}/client/messages/vi-VN.json (100%) rename {src => legacy/src}/client/messages/zh-CN.json (100%) rename {src => legacy/src}/client/messages/zh-TW.json (100%) rename {src => legacy/src}/client/mocks/browser.ts (100%) rename {src => legacy/src}/client/mocks/handlers.ts (100%) rename {src => legacy/src}/client/mocks/index.ts (100%) rename {src => legacy/src}/client/mocks/server.ts (100%) rename {src => legacy/src}/client/state/uiStore.ts (100%) rename {src => legacy/src}/client/utils/__tests__/typescript.test.ts (100%) rename {src => legacy/src}/client/utils/typescript.ts (100%) rename {src => legacy/src}/config/constants.ts (100%) rename {src => legacy/src}/config/index.ts (100%) rename {src => legacy/src}/i18n.ts (100%) rename {src => legacy/src}/instrumentation.ts (100%) rename {src => legacy/src}/inversify.config.ts (100%) rename {src => legacy/src}/lib/get-translator.ts (100%) rename {src => legacy/src}/lib/helpers/castAppConfig.ts (100%) rename {src => legacy/src}/lib/helpers/text-helpers.ts (100%) rename {src => legacy/src}/lib/safe-action.ts (100%) rename {src => legacy/src}/lib/socket/useSocket.test.ts (100%) rename {src => legacy/src}/lib/socket/useSocket.ts (100%) rename {src => legacy/src}/lib/themes.ts (100%) rename {src => legacy/src}/server/common/fs.helpers.ts (100%) rename {src => legacy/src}/server/common/session-manager.ts (100%) rename {src => legacy/src}/server/common/typescript.helpers.ts (100%) rename {src => legacy/src}/server/core/EventDispatcher/EventDispatcher.ts (100%) rename {src => legacy/src}/server/core/EventDispatcher/index.ts (100%) rename {src => legacy/src}/server/core/TipiConfig/TipiConfig.test.ts (100%) rename {src => legacy/src}/server/core/TipiConfig/TipiConfig.ts (100%) rename {src => legacy/src}/server/core/TipiConfig/index.ts (100%) rename {src => legacy/src}/server/queries/apps/apps.queries.ts (100%) rename {src => legacy/src}/server/queries/auth/auth.queries.ts (100%) rename {src => legacy/src}/server/queries/links/links.queries.ts (100%) rename {src => legacy/src}/server/services/app-backup/app-backup.service.ts (100%) rename {src => legacy/src}/server/services/app-backup/commands/create-app-backup-command.ts (100%) rename {src => legacy/src}/server/services/app-backup/commands/delete-app-backup-command.ts (100%) rename {src => legacy/src}/server/services/app-backup/commands/get-app-backups-command.ts (100%) rename {src => legacy/src}/server/services/app-backup/commands/index.ts (100%) rename {src => legacy/src}/server/services/app-backup/commands/restore-app-backup-command.ts (100%) rename {src => legacy/src}/server/services/app-backup/commands/types.ts (100%) rename {src => legacy/src}/server/services/app-catalog/app-catalog-cache.ts (100%) rename {src => legacy/src}/server/services/app-catalog/app-catalog.service.test.ts (100%) rename {src => legacy/src}/server/services/app-catalog/app-catalog.service.ts (100%) rename {src => legacy/src}/server/services/app-catalog/apps.helpers.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/app-lifecycle.service.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/app-lifecycle.service.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/__tests__/install-app-command.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/__tests__/reset-app-command.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/__tests__/restart-app-command.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/__tests__/start-app-command.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/__tests__/stop-app-command.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/__tests__/uninstall-app-command.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/__tests__/update-app-command.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/__tests__/update-app-config-command.test.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/index.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/install-app-command.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/reset-app-command.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/restart-app-command.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/start-app-command.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/stop-app-command.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/types.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/uninstall-app-command.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/update-app-command.ts (100%) rename {src => legacy/src}/server/services/app-lifecycle/commands/update-app-config-command.ts (100%) rename {src => legacy/src}/server/services/auth/auth.service.test.ts (100%) rename {src => legacy/src}/server/services/auth/auth.service.ts (100%) rename {src => legacy/src}/server/services/custom-links/custom-links.service.ts (100%) rename {src => legacy/src}/server/services/system/index.ts (100%) rename {src => legacy/src}/server/services/system/system.service.test.ts (100%) rename {src => legacy/src}/server/services/system/system.service.ts (100%) rename {src => legacy/src}/server/tests/apps.factory.ts (100%) rename {src => legacy/src}/server/tests/test-utils.ts (100%) rename {src => legacy/src}/server/tests/user.factory.ts (100%) rename {src => legacy/src}/server/utils/__tests__/encryption.test.ts (100%) rename {src => legacy/src}/server/utils/encryption.ts (100%) rename {src => legacy/src}/server/utils/errors.ts (100%) rename {src => legacy/src}/server/utils/network.ts (100%) rename {src => legacy/src}/server/utils/totp.ts (100%) rename {src => legacy/src}/shared/internationalization/locales.ts (100%) rename {src => legacy/src}/utils/getCurrentLocale.ts (100%) rename start.dev.sh => legacy/start.dev.sh (100%) rename start.prod.sh => legacy/start.prod.sh (100%) rename {tests => legacy/tests}/client/test.setup.tsx (100%) rename {tests => legacy/tests}/mocks/fs.ts (100%) rename {tests => legacy/tests}/server/test.setup.ts (100%) rename {tests => legacy/tests}/test-utils.tsx (100%) rename tsconfig.json => legacy/tsconfig.json (100%) rename vitest.workspace.ts => legacy/vitest.workspace.ts (100%) diff --git a/.dockerignore b/legacy/.dockerignore similarity index 100% rename from .dockerignore rename to legacy/.dockerignore diff --git a/.env.example b/legacy/.env.example similarity index 100% rename from .env.example rename to legacy/.env.example diff --git a/.env.test b/legacy/.env.test similarity index 100% rename from .env.test rename to legacy/.env.test diff --git a/.gitignore b/legacy/.gitignore similarity index 100% rename from .gitignore rename to legacy/.gitignore diff --git a/Dockerfile b/legacy/Dockerfile similarity index 100% rename from Dockerfile rename to legacy/Dockerfile diff --git a/Dockerfile.dev b/legacy/Dockerfile.dev similarity index 100% rename from Dockerfile.dev rename to legacy/Dockerfile.dev diff --git a/docker-compose.dev.yml b/legacy/docker-compose.dev.yml similarity index 100% rename from docker-compose.dev.yml rename to legacy/docker-compose.dev.yml diff --git a/docker-compose.prod.yml b/legacy/docker-compose.prod.yml similarity index 100% rename from docker-compose.prod.yml rename to legacy/docker-compose.prod.yml diff --git a/global.d.ts b/legacy/global.d.ts similarity index 100% rename from global.d.ts rename to legacy/global.d.ts diff --git a/next-env.d.ts b/legacy/next-env.d.ts similarity index 100% rename from next-env.d.ts rename to legacy/next-env.d.ts diff --git a/next.config.mjs b/legacy/next.config.mjs similarity index 100% rename from next.config.mjs rename to legacy/next.config.mjs diff --git a/package.json b/legacy/package.json similarity index 100% rename from package.json rename to legacy/package.json diff --git a/packages/cache/package.json b/legacy/packages/cache/package.json similarity index 100% rename from packages/cache/package.json rename to legacy/packages/cache/package.json diff --git a/packages/cache/src/cache.ts b/legacy/packages/cache/src/cache.ts similarity index 100% rename from packages/cache/src/cache.ts rename to legacy/packages/cache/src/cache.ts diff --git a/packages/cache/src/index.ts b/legacy/packages/cache/src/index.ts similarity index 100% rename from packages/cache/src/index.ts rename to legacy/packages/cache/src/index.ts diff --git a/packages/cache/src/mock.ts b/legacy/packages/cache/src/mock.ts similarity index 100% rename from packages/cache/src/mock.ts rename to legacy/packages/cache/src/mock.ts diff --git a/packages/cache/tsconfig.json b/legacy/packages/cache/tsconfig.json similarity index 100% rename from packages/cache/tsconfig.json rename to legacy/packages/cache/tsconfig.json diff --git a/packages/db/assets/migrations/00000-create-migrations-table.sql b/legacy/packages/db/assets/migrations/00000-create-migrations-table.sql similarity index 100% rename from packages/db/assets/migrations/00000-create-migrations-table.sql rename to legacy/packages/db/assets/migrations/00000-create-migrations-table.sql diff --git a/packages/db/assets/migrations/00001-initial.sql b/legacy/packages/db/assets/migrations/00001-initial.sql similarity index 100% rename from packages/db/assets/migrations/00001-initial.sql rename to legacy/packages/db/assets/migrations/00001-initial.sql diff --git a/packages/db/assets/migrations/00002-add-app-version.sql b/legacy/packages/db/assets/migrations/00002-add-app-version.sql similarity index 100% rename from packages/db/assets/migrations/00002-add-app-version.sql rename to legacy/packages/db/assets/migrations/00002-add-app-version.sql diff --git a/packages/db/assets/migrations/00003-add-status-updating.sql b/legacy/packages/db/assets/migrations/00003-add-status-updating.sql similarity index 100% rename from packages/db/assets/migrations/00003-add-status-updating.sql rename to legacy/packages/db/assets/migrations/00003-add-status-updating.sql diff --git a/packages/db/assets/migrations/00004-add-exposed-domain-fields.sql b/legacy/packages/db/assets/migrations/00004-add-exposed-domain-fields.sql similarity index 100% rename from packages/db/assets/migrations/00004-add-exposed-domain-fields.sql rename to legacy/packages/db/assets/migrations/00004-add-exposed-domain-fields.sql diff --git a/packages/db/assets/migrations/00005-add-user-operator.sql b/legacy/packages/db/assets/migrations/00005-add-user-operator.sql similarity index 100% rename from packages/db/assets/migrations/00005-add-user-operator.sql rename to legacy/packages/db/assets/migrations/00005-add-user-operator.sql diff --git a/packages/db/assets/migrations/00006-add-totp-user-fields.sql b/legacy/packages/db/assets/migrations/00006-add-totp-user-fields.sql similarity index 100% rename from packages/db/assets/migrations/00006-add-totp-user-fields.sql rename to legacy/packages/db/assets/migrations/00006-add-totp-user-fields.sql diff --git a/packages/db/assets/migrations/00007-add-locale-user-col.sql b/legacy/packages/db/assets/migrations/00007-add-locale-user-col.sql similarity index 100% rename from packages/db/assets/migrations/00007-add-locale-user-col.sql rename to legacy/packages/db/assets/migrations/00007-add-locale-user-col.sql diff --git a/packages/db/assets/migrations/00008-merge-config-with-domain-and-exposed.sql b/legacy/packages/db/assets/migrations/00008-merge-config-with-domain-and-exposed.sql similarity index 100% rename from packages/db/assets/migrations/00008-merge-config-with-domain-and-exposed.sql rename to legacy/packages/db/assets/migrations/00008-merge-config-with-domain-and-exposed.sql diff --git a/packages/db/assets/migrations/00009-add-guest-dashboard.sql b/legacy/packages/db/assets/migrations/00009-add-guest-dashboard.sql similarity index 100% rename from packages/db/assets/migrations/00009-add-guest-dashboard.sql rename to legacy/packages/db/assets/migrations/00009-add-guest-dashboard.sql diff --git a/packages/db/assets/migrations/00010-add-status-resetting.sql b/legacy/packages/db/assets/migrations/00010-add-status-resetting.sql similarity index 100% rename from packages/db/assets/migrations/00010-add-status-resetting.sql rename to legacy/packages/db/assets/migrations/00010-add-status-resetting.sql diff --git a/packages/db/assets/migrations/00011-create-link-table.sql b/legacy/packages/db/assets/migrations/00011-create-link-table.sql similarity index 100% rename from packages/db/assets/migrations/00011-create-link-table.sql rename to legacy/packages/db/assets/migrations/00011-create-link-table.sql diff --git a/packages/db/assets/migrations/00012-link-table-description.sql b/legacy/packages/db/assets/migrations/00012-link-table-description.sql similarity index 100% rename from packages/db/assets/migrations/00012-link-table-description.sql rename to legacy/packages/db/assets/migrations/00012-link-table-description.sql diff --git a/packages/db/assets/migrations/00013-app-local-exposed.sql b/legacy/packages/db/assets/migrations/00013-app-local-exposed.sql similarity index 100% rename from packages/db/assets/migrations/00013-app-local-exposed.sql rename to legacy/packages/db/assets/migrations/00013-app-local-exposed.sql diff --git a/packages/db/assets/migrations/00014-restarting-state.sql b/legacy/packages/db/assets/migrations/00014-restarting-state.sql similarity index 100% rename from packages/db/assets/migrations/00014-restarting-state.sql rename to legacy/packages/db/assets/migrations/00014-restarting-state.sql diff --git a/packages/db/assets/migrations/00015-backingup-state.sql b/legacy/packages/db/assets/migrations/00015-backingup-state.sql similarity index 100% rename from packages/db/assets/migrations/00015-backingup-state.sql rename to legacy/packages/db/assets/migrations/00015-backingup-state.sql diff --git a/packages/db/package.json b/legacy/packages/db/package.json similarity index 100% rename from packages/db/package.json rename to legacy/packages/db/package.json diff --git a/packages/db/src/client.ts b/legacy/packages/db/src/client.ts similarity index 100% rename from packages/db/src/client.ts rename to legacy/packages/db/src/client.ts diff --git a/packages/db/src/index.ts b/legacy/packages/db/src/index.ts similarity index 100% rename from packages/db/src/index.ts rename to legacy/packages/db/src/index.ts diff --git a/packages/db/src/migrator.ts b/legacy/packages/db/src/migrator.ts similarity index 100% rename from packages/db/src/migrator.ts rename to legacy/packages/db/src/migrator.ts diff --git a/packages/db/src/schema.ts b/legacy/packages/db/src/schema.ts similarity index 100% rename from packages/db/src/schema.ts rename to legacy/packages/db/src/schema.ts diff --git a/packages/db/tsconfig.json b/legacy/packages/db/tsconfig.json similarity index 100% rename from packages/db/tsconfig.json rename to legacy/packages/db/tsconfig.json diff --git a/packages/shared/node/package.json b/legacy/packages/shared/node/package.json similarity index 100% rename from packages/shared/node/package.json rename to legacy/packages/shared/node/package.json diff --git a/packages/shared/package.json b/legacy/packages/shared/package.json similarity index 100% rename from packages/shared/package.json rename to legacy/packages/shared/package.json diff --git a/packages/shared/src/helpers/env-helpers.ts b/legacy/packages/shared/src/helpers/env-helpers.ts similarity index 100% rename from packages/shared/src/helpers/env-helpers.ts rename to legacy/packages/shared/src/helpers/env-helpers.ts diff --git a/packages/shared/src/helpers/error-helpers.ts b/legacy/packages/shared/src/helpers/error-helpers.ts similarity index 100% rename from packages/shared/src/helpers/error-helpers.ts rename to legacy/packages/shared/src/helpers/error-helpers.ts diff --git a/packages/shared/src/helpers/sanitizers.ts b/legacy/packages/shared/src/helpers/sanitizers.ts similarity index 100% rename from packages/shared/src/helpers/sanitizers.ts rename to legacy/packages/shared/src/helpers/sanitizers.ts diff --git a/packages/shared/src/helpers/utils.ts b/legacy/packages/shared/src/helpers/utils.ts similarity index 100% rename from packages/shared/src/helpers/utils.ts rename to legacy/packages/shared/src/helpers/utils.ts diff --git a/packages/shared/src/index.ts b/legacy/packages/shared/src/index.ts similarity index 100% rename from packages/shared/src/index.ts rename to legacy/packages/shared/src/index.ts diff --git a/packages/shared/src/node/helpers/exec-async.tsx b/legacy/packages/shared/src/node/helpers/exec-async.tsx similarity index 100% rename from packages/shared/src/node/helpers/exec-async.tsx rename to legacy/packages/shared/src/node/helpers/exec-async.tsx diff --git a/packages/shared/src/node/helpers/fs-helpers.ts b/legacy/packages/shared/src/node/helpers/fs-helpers.ts similarity index 100% rename from packages/shared/src/node/helpers/fs-helpers.ts rename to legacy/packages/shared/src/node/helpers/fs-helpers.ts diff --git a/packages/shared/src/node/helpers/typescript-helpers.ts b/legacy/packages/shared/src/node/helpers/typescript-helpers.ts similarity index 100% rename from packages/shared/src/node/helpers/typescript-helpers.ts rename to legacy/packages/shared/src/node/helpers/typescript-helpers.ts diff --git a/packages/shared/src/node/index.ts b/legacy/packages/shared/src/node/index.ts similarity index 100% rename from packages/shared/src/node/index.ts rename to legacy/packages/shared/src/node/index.ts diff --git a/packages/shared/src/node/logger/FileLogger.ts b/legacy/packages/shared/src/node/logger/FileLogger.ts similarity index 100% rename from packages/shared/src/node/logger/FileLogger.ts rename to legacy/packages/shared/src/node/logger/FileLogger.ts diff --git a/packages/shared/src/node/logger/Logger.interface.ts b/legacy/packages/shared/src/node/logger/Logger.interface.ts similarity index 100% rename from packages/shared/src/node/logger/Logger.interface.ts rename to legacy/packages/shared/src/node/logger/Logger.interface.ts diff --git a/packages/shared/src/node/logger/Logger.ts b/legacy/packages/shared/src/node/logger/Logger.ts similarity index 100% rename from packages/shared/src/node/logger/Logger.ts rename to legacy/packages/shared/src/node/logger/Logger.ts diff --git a/packages/shared/src/node/logger/LoggerMock.ts b/legacy/packages/shared/src/node/logger/LoggerMock.ts similarity index 100% rename from packages/shared/src/node/logger/LoggerMock.ts rename to legacy/packages/shared/src/node/logger/LoggerMock.ts diff --git a/packages/shared/src/node/modules/app/__tests__/app-file-accessor.test.ts b/legacy/packages/shared/src/node/modules/app/__tests__/app-file-accessor.test.ts similarity index 100% rename from packages/shared/src/node/modules/app/__tests__/app-file-accessor.test.ts rename to legacy/packages/shared/src/node/modules/app/__tests__/app-file-accessor.test.ts diff --git a/packages/shared/src/node/modules/app/app-data-service.ts b/legacy/packages/shared/src/node/modules/app/app-data-service.ts similarity index 100% rename from packages/shared/src/node/modules/app/app-data-service.ts rename to legacy/packages/shared/src/node/modules/app/app-data-service.ts diff --git a/packages/shared/src/node/modules/app/app-file-accessor.ts b/legacy/packages/shared/src/node/modules/app/app-file-accessor.ts similarity index 100% rename from packages/shared/src/node/modules/app/app-file-accessor.ts rename to legacy/packages/shared/src/node/modules/app/app-file-accessor.ts diff --git a/packages/shared/src/node/modules/archive/archive-manager.ts b/legacy/packages/shared/src/node/modules/archive/archive-manager.ts similarity index 100% rename from packages/shared/src/node/modules/archive/archive-manager.ts rename to legacy/packages/shared/src/node/modules/archive/archive-manager.ts diff --git a/packages/shared/src/node/modules/backup/backup-manager.ts b/legacy/packages/shared/src/node/modules/backup/backup-manager.ts similarity index 100% rename from packages/shared/src/node/modules/backup/backup-manager.ts rename to legacy/packages/shared/src/node/modules/backup/backup-manager.ts diff --git a/packages/shared/src/schemas/app-schemas.ts b/legacy/packages/shared/src/schemas/app-schemas.ts similarity index 100% rename from packages/shared/src/schemas/app-schemas.ts rename to legacy/packages/shared/src/schemas/app-schemas.ts diff --git a/packages/shared/src/schemas/env-schemas.ts b/legacy/packages/shared/src/schemas/env-schemas.ts similarity index 100% rename from packages/shared/src/schemas/env-schemas.ts rename to legacy/packages/shared/src/schemas/env-schemas.ts diff --git a/packages/shared/src/schemas/link-schemas.ts b/legacy/packages/shared/src/schemas/link-schemas.ts similarity index 100% rename from packages/shared/src/schemas/link-schemas.ts rename to legacy/packages/shared/src/schemas/link-schemas.ts diff --git a/packages/shared/src/schemas/queue-schemas.ts b/legacy/packages/shared/src/schemas/queue-schemas.ts similarity index 100% rename from packages/shared/src/schemas/queue-schemas.ts rename to legacy/packages/shared/src/schemas/queue-schemas.ts diff --git a/packages/shared/src/schemas/socket-schemas.ts b/legacy/packages/shared/src/schemas/socket-schemas.ts similarity index 100% rename from packages/shared/src/schemas/socket-schemas.ts rename to legacy/packages/shared/src/schemas/socket-schemas.ts diff --git a/packages/shared/src/schemas/system-schemas.ts b/legacy/packages/shared/src/schemas/system-schemas.ts similarity index 100% rename from packages/shared/src/schemas/system-schemas.ts rename to legacy/packages/shared/src/schemas/system-schemas.ts diff --git a/packages/shared/tsconfig.json b/legacy/packages/shared/tsconfig.json similarity index 100% rename from packages/shared/tsconfig.json rename to legacy/packages/shared/tsconfig.json diff --git a/packages/worker/.env.test b/legacy/packages/worker/.env.test similarity index 100% rename from packages/worker/.env.test rename to legacy/packages/worker/.env.test diff --git a/packages/worker/.gitignore b/legacy/packages/worker/.gitignore similarity index 100% rename from packages/worker/.gitignore rename to legacy/packages/worker/.gitignore diff --git a/packages/worker/assets/traefik/dynamic/dynamic.yml b/legacy/packages/worker/assets/traefik/dynamic/dynamic.yml similarity index 100% rename from packages/worker/assets/traefik/dynamic/dynamic.yml rename to legacy/packages/worker/assets/traefik/dynamic/dynamic.yml diff --git a/packages/worker/assets/traefik/traefik.yml b/legacy/packages/worker/assets/traefik/traefik.yml similarity index 100% rename from packages/worker/assets/traefik/traefik.yml rename to legacy/packages/worker/assets/traefik/traefik.yml diff --git a/packages/worker/build.mjs b/legacy/packages/worker/build.mjs similarity index 100% rename from packages/worker/build.mjs rename to legacy/packages/worker/build.mjs diff --git a/packages/worker/nodemon.json b/legacy/packages/worker/nodemon.json similarity index 100% rename from packages/worker/nodemon.json rename to legacy/packages/worker/nodemon.json diff --git a/packages/worker/package.json b/legacy/packages/worker/package.json similarity index 100% rename from packages/worker/package.json rename to legacy/packages/worker/package.json diff --git a/packages/worker/src/api.ts b/legacy/packages/worker/src/api.ts similarity index 100% rename from packages/worker/src/api.ts rename to legacy/packages/worker/src/api.ts diff --git a/packages/worker/src/config/__tests__/docker-templates.test.ts b/legacy/packages/worker/src/config/__tests__/docker-templates.test.ts similarity index 100% rename from packages/worker/src/config/__tests__/docker-templates.test.ts rename to legacy/packages/worker/src/config/__tests__/docker-templates.test.ts diff --git a/packages/worker/src/config/constants.ts b/legacy/packages/worker/src/config/constants.ts similarity index 100% rename from packages/worker/src/config/constants.ts rename to legacy/packages/worker/src/config/constants.ts diff --git a/packages/worker/src/config/docker-templates.ts b/legacy/packages/worker/src/config/docker-templates.ts similarity index 100% rename from packages/worker/src/config/docker-templates.ts rename to legacy/packages/worker/src/config/docker-templates.ts diff --git a/packages/worker/src/config/index.ts b/legacy/packages/worker/src/config/index.ts similarity index 100% rename from packages/worker/src/config/index.ts rename to legacy/packages/worker/src/config/index.ts diff --git a/packages/worker/src/index.ts b/legacy/packages/worker/src/index.ts similarity index 100% rename from packages/worker/src/index.ts rename to legacy/packages/worker/src/index.ts diff --git a/packages/worker/src/inversify.config.ts b/legacy/packages/worker/src/inversify.config.ts similarity index 100% rename from packages/worker/src/inversify.config.ts rename to legacy/packages/worker/src/inversify.config.ts diff --git a/packages/worker/src/lib/docker/builders/docker-compose-builder.ts b/legacy/packages/worker/src/lib/docker/builders/docker-compose-builder.ts similarity index 100% rename from packages/worker/src/lib/docker/builders/docker-compose-builder.ts rename to legacy/packages/worker/src/lib/docker/builders/docker-compose-builder.ts diff --git a/packages/worker/src/lib/docker/builders/schemas.ts b/legacy/packages/worker/src/lib/docker/builders/schemas.ts similarity index 100% rename from packages/worker/src/lib/docker/builders/schemas.ts rename to legacy/packages/worker/src/lib/docker/builders/schemas.ts diff --git a/packages/worker/src/lib/docker/builders/service-builder.ts b/legacy/packages/worker/src/lib/docker/builders/service-builder.ts similarity index 100% rename from packages/worker/src/lib/docker/builders/service-builder.ts rename to legacy/packages/worker/src/lib/docker/builders/service-builder.ts diff --git a/packages/worker/src/lib/docker/builders/traefik-labels-builder.ts b/legacy/packages/worker/src/lib/docker/builders/traefik-labels-builder.ts similarity index 100% rename from packages/worker/src/lib/docker/builders/traefik-labels-builder.ts rename to legacy/packages/worker/src/lib/docker/builders/traefik-labels-builder.ts diff --git a/packages/worker/src/lib/docker/docker-helpers.test.ts b/legacy/packages/worker/src/lib/docker/docker-helpers.test.ts similarity index 100% rename from packages/worker/src/lib/docker/docker-helpers.test.ts rename to legacy/packages/worker/src/lib/docker/docker-helpers.test.ts diff --git a/packages/worker/src/lib/docker/docker-helpers.ts b/legacy/packages/worker/src/lib/docker/docker-helpers.ts similarity index 100% rename from packages/worker/src/lib/docker/docker-helpers.ts rename to legacy/packages/worker/src/lib/docker/docker-helpers.ts diff --git a/packages/worker/src/lib/docker/index.ts b/legacy/packages/worker/src/lib/docker/index.ts similarity index 100% rename from packages/worker/src/lib/docker/index.ts rename to legacy/packages/worker/src/lib/docker/index.ts diff --git a/packages/worker/src/lib/environment/environment.ts b/legacy/packages/worker/src/lib/environment/environment.ts similarity index 100% rename from packages/worker/src/lib/environment/environment.ts rename to legacy/packages/worker/src/lib/environment/environment.ts diff --git a/packages/worker/src/lib/environment/index.ts b/legacy/packages/worker/src/lib/environment/index.ts similarity index 100% rename from packages/worker/src/lib/environment/index.ts rename to legacy/packages/worker/src/lib/environment/index.ts diff --git a/packages/worker/src/lib/logger/index.ts b/legacy/packages/worker/src/lib/logger/index.ts similarity index 100% rename from packages/worker/src/lib/logger/index.ts rename to legacy/packages/worker/src/lib/logger/index.ts diff --git a/packages/worker/src/lib/logger/logger.ts b/legacy/packages/worker/src/lib/logger/logger.ts similarity index 100% rename from packages/worker/src/lib/logger/logger.ts rename to legacy/packages/worker/src/lib/logger/logger.ts diff --git a/packages/worker/src/lib/socket/SocketManager.ts b/legacy/packages/worker/src/lib/socket/SocketManager.ts similarity index 100% rename from packages/worker/src/lib/socket/SocketManager.ts rename to legacy/packages/worker/src/lib/socket/SocketManager.ts diff --git a/packages/worker/src/lib/socket/index.ts b/legacy/packages/worker/src/lib/socket/index.ts similarity index 100% rename from packages/worker/src/lib/socket/index.ts rename to legacy/packages/worker/src/lib/socket/index.ts diff --git a/packages/worker/src/lib/system/index.ts b/legacy/packages/worker/src/lib/system/index.ts similarity index 100% rename from packages/worker/src/lib/system/index.ts rename to legacy/packages/worker/src/lib/system/index.ts diff --git a/packages/worker/src/lib/system/system.helpers.test.ts b/legacy/packages/worker/src/lib/system/system.helpers.test.ts similarity index 100% rename from packages/worker/src/lib/system/system.helpers.test.ts rename to legacy/packages/worker/src/lib/system/system.helpers.test.ts diff --git a/packages/worker/src/lib/system/system.helpers.ts b/legacy/packages/worker/src/lib/system/system.helpers.ts similarity index 100% rename from packages/worker/src/lib/system/system.helpers.ts rename to legacy/packages/worker/src/lib/system/system.helpers.ts diff --git a/packages/worker/src/services/app/__tests__/app.executors.test.ts b/legacy/packages/worker/src/services/app/__tests__/app.executors.test.ts similarity index 100% rename from packages/worker/src/services/app/__tests__/app.executors.test.ts rename to legacy/packages/worker/src/services/app/__tests__/app.executors.test.ts diff --git a/packages/worker/src/services/app/__tests__/app.helpers.test.ts b/legacy/packages/worker/src/services/app/__tests__/app.helpers.test.ts similarity index 100% rename from packages/worker/src/services/app/__tests__/app.helpers.test.ts rename to legacy/packages/worker/src/services/app/__tests__/app.helpers.test.ts diff --git a/packages/worker/src/services/app/__tests__/env.helpers.test.ts b/legacy/packages/worker/src/services/app/__tests__/env.helpers.test.ts similarity index 100% rename from packages/worker/src/services/app/__tests__/env.helpers.test.ts rename to legacy/packages/worker/src/services/app/__tests__/env.helpers.test.ts diff --git a/packages/worker/src/services/app/app.executors.ts b/legacy/packages/worker/src/services/app/app.executors.ts similarity index 100% rename from packages/worker/src/services/app/app.executors.ts rename to legacy/packages/worker/src/services/app/app.executors.ts diff --git a/packages/worker/src/services/app/app.helpers.ts b/legacy/packages/worker/src/services/app/app.helpers.ts similarity index 100% rename from packages/worker/src/services/app/app.helpers.ts rename to legacy/packages/worker/src/services/app/app.helpers.ts diff --git a/packages/worker/src/services/app/env.helpers.ts b/legacy/packages/worker/src/services/app/env.helpers.ts similarity index 100% rename from packages/worker/src/services/app/env.helpers.ts rename to legacy/packages/worker/src/services/app/env.helpers.ts diff --git a/packages/worker/src/services/index.ts b/legacy/packages/worker/src/services/index.ts similarity index 100% rename from packages/worker/src/services/index.ts rename to legacy/packages/worker/src/services/index.ts diff --git a/packages/worker/src/services/repo/repo.executors.ts b/legacy/packages/worker/src/services/repo/repo.executors.ts similarity index 100% rename from packages/worker/src/services/repo/repo.executors.ts rename to legacy/packages/worker/src/services/repo/repo.executors.ts diff --git a/packages/worker/src/services/repo/repo.helpers.ts b/legacy/packages/worker/src/services/repo/repo.helpers.ts similarity index 100% rename from packages/worker/src/services/repo/repo.helpers.ts rename to legacy/packages/worker/src/services/repo/repo.helpers.ts diff --git a/packages/worker/src/services/system/system.executors.ts b/legacy/packages/worker/src/services/system/system.executors.ts similarity index 100% rename from packages/worker/src/services/system/system.executors.ts rename to legacy/packages/worker/src/services/system/system.executors.ts diff --git a/packages/worker/src/types/global.d.ts b/legacy/packages/worker/src/types/global.d.ts similarity index 100% rename from packages/worker/src/types/global.d.ts rename to legacy/packages/worker/src/types/global.d.ts diff --git a/packages/worker/src/watcher/watcher.ts b/legacy/packages/worker/src/watcher/watcher.ts similarity index 100% rename from packages/worker/src/watcher/watcher.ts rename to legacy/packages/worker/src/watcher/watcher.ts diff --git a/packages/worker/tests/apps.factory.ts b/legacy/packages/worker/tests/apps.factory.ts similarity index 100% rename from packages/worker/tests/apps.factory.ts rename to legacy/packages/worker/tests/apps.factory.ts diff --git a/packages/worker/tests/mocks/fs.ts b/legacy/packages/worker/tests/mocks/fs.ts similarity index 100% rename from packages/worker/tests/mocks/fs.ts rename to legacy/packages/worker/tests/mocks/fs.ts diff --git a/packages/worker/tests/vite.setup.ts b/legacy/packages/worker/tests/vite.setup.ts similarity index 100% rename from packages/worker/tests/vite.setup.ts rename to legacy/packages/worker/tests/vite.setup.ts diff --git a/packages/worker/tsconfig.json b/legacy/packages/worker/tsconfig.json similarity index 100% rename from packages/worker/tsconfig.json rename to legacy/packages/worker/tsconfig.json diff --git a/packages/worker/vitest.config.ts b/legacy/packages/worker/vitest.config.ts similarity index 100% rename from packages/worker/vitest.config.ts rename to legacy/packages/worker/vitest.config.ts diff --git a/patches/.gitkeep b/legacy/patches/.gitkeep similarity index 100% rename from patches/.gitkeep rename to legacy/patches/.gitkeep diff --git a/pnpm-lock.yaml b/legacy/pnpm-lock.yaml similarity index 100% rename from pnpm-lock.yaml rename to legacy/pnpm-lock.yaml diff --git a/pnpm-workspace.yaml b/legacy/pnpm-workspace.yaml similarity index 100% rename from pnpm-workspace.yaml rename to legacy/pnpm-workspace.yaml diff --git a/public/app-not-found.jpg b/legacy/public/app-not-found.jpg similarity index 100% rename from public/app-not-found.jpg rename to legacy/public/app-not-found.jpg diff --git a/public/empty.svg b/legacy/public/empty.svg similarity index 100% rename from public/empty.svg rename to legacy/public/empty.svg diff --git a/public/error.png b/legacy/public/error.png similarity index 100% rename from public/error.png rename to legacy/public/error.png diff --git a/public/js/.gitkeep b/legacy/public/js/.gitkeep similarity index 100% rename from public/js/.gitkeep rename to legacy/public/js/.gitkeep diff --git a/public/mockServiceWorker.js b/legacy/public/mockServiceWorker.js similarity index 100% rename from public/mockServiceWorker.js rename to legacy/public/mockServiceWorker.js diff --git a/public/placeholder.png b/legacy/public/placeholder.png similarity index 100% rename from public/placeholder.png rename to legacy/public/placeholder.png diff --git a/public/tipi-christmas.png b/legacy/public/tipi-christmas.png similarity index 100% rename from public/tipi-christmas.png rename to legacy/public/tipi-christmas.png diff --git a/public/tipi.png b/legacy/public/tipi.png similarity index 100% rename from public/tipi.png rename to legacy/public/tipi.png diff --git a/reset.d.ts b/legacy/reset.d.ts similarity index 100% rename from reset.d.ts rename to legacy/reset.d.ts diff --git a/sentry.client.config.ts b/legacy/sentry.client.config.ts similarity index 100% rename from sentry.client.config.ts rename to legacy/sentry.client.config.ts diff --git a/sonar-project.properties b/legacy/sonar-project.properties similarity index 100% rename from sonar-project.properties rename to legacy/sonar-project.properties diff --git a/src/app/(auth)/layout.tsx b/legacy/src/app/(auth)/layout.tsx similarity index 100% rename from src/app/(auth)/layout.tsx rename to legacy/src/app/(auth)/layout.tsx diff --git a/src/app/(auth)/login/components/LoginContainer/LoginContainer.tsx b/legacy/src/app/(auth)/login/components/LoginContainer/LoginContainer.tsx similarity index 100% rename from src/app/(auth)/login/components/LoginContainer/LoginContainer.tsx rename to legacy/src/app/(auth)/login/components/LoginContainer/LoginContainer.tsx diff --git a/src/app/(auth)/login/components/LoginContainer/index.ts b/legacy/src/app/(auth)/login/components/LoginContainer/index.ts similarity index 100% rename from src/app/(auth)/login/components/LoginContainer/index.ts rename to legacy/src/app/(auth)/login/components/LoginContainer/index.ts diff --git a/src/app/(auth)/login/components/LoginForm/LoginForm.tsx b/legacy/src/app/(auth)/login/components/LoginForm/LoginForm.tsx similarity index 100% rename from src/app/(auth)/login/components/LoginForm/LoginForm.tsx rename to legacy/src/app/(auth)/login/components/LoginForm/LoginForm.tsx diff --git a/src/app/(auth)/login/components/LoginForm/index.ts b/legacy/src/app/(auth)/login/components/LoginForm/index.ts similarity index 100% rename from src/app/(auth)/login/components/LoginForm/index.ts rename to legacy/src/app/(auth)/login/components/LoginForm/index.ts diff --git a/src/app/(auth)/login/components/TotpForm/TotpForm.tsx b/legacy/src/app/(auth)/login/components/TotpForm/TotpForm.tsx similarity index 100% rename from src/app/(auth)/login/components/TotpForm/TotpForm.tsx rename to legacy/src/app/(auth)/login/components/TotpForm/TotpForm.tsx diff --git a/src/app/(auth)/login/components/TotpForm/index.ts b/legacy/src/app/(auth)/login/components/TotpForm/index.ts similarity index 100% rename from src/app/(auth)/login/components/TotpForm/index.ts rename to legacy/src/app/(auth)/login/components/TotpForm/index.ts diff --git a/src/app/(auth)/login/page.tsx b/legacy/src/app/(auth)/login/page.tsx similarity index 100% rename from src/app/(auth)/login/page.tsx rename to legacy/src/app/(auth)/login/page.tsx diff --git a/src/app/(auth)/register/components/RegisterContainer/RegisterContainer.tsx b/legacy/src/app/(auth)/register/components/RegisterContainer/RegisterContainer.tsx similarity index 100% rename from src/app/(auth)/register/components/RegisterContainer/RegisterContainer.tsx rename to legacy/src/app/(auth)/register/components/RegisterContainer/RegisterContainer.tsx diff --git a/src/app/(auth)/register/components/RegisterContainer/index.ts b/legacy/src/app/(auth)/register/components/RegisterContainer/index.ts similarity index 100% rename from src/app/(auth)/register/components/RegisterContainer/index.ts rename to legacy/src/app/(auth)/register/components/RegisterContainer/index.ts diff --git a/src/app/(auth)/register/components/RegisterForm/RegisterForm.tsx b/legacy/src/app/(auth)/register/components/RegisterForm/RegisterForm.tsx similarity index 100% rename from src/app/(auth)/register/components/RegisterForm/RegisterForm.tsx rename to legacy/src/app/(auth)/register/components/RegisterForm/RegisterForm.tsx diff --git a/src/app/(auth)/register/components/RegisterForm/index.ts b/legacy/src/app/(auth)/register/components/RegisterForm/index.ts similarity index 100% rename from src/app/(auth)/register/components/RegisterForm/index.ts rename to legacy/src/app/(auth)/register/components/RegisterForm/index.ts diff --git a/src/app/(auth)/register/page.tsx b/legacy/src/app/(auth)/register/page.tsx similarity index 100% rename from src/app/(auth)/register/page.tsx rename to legacy/src/app/(auth)/register/page.tsx diff --git a/src/app/(auth)/reset-password/components/ResetPasswordContainer/ResetPasswordContainer.tsx b/legacy/src/app/(auth)/reset-password/components/ResetPasswordContainer/ResetPasswordContainer.tsx similarity index 100% rename from src/app/(auth)/reset-password/components/ResetPasswordContainer/ResetPasswordContainer.tsx rename to legacy/src/app/(auth)/reset-password/components/ResetPasswordContainer/ResetPasswordContainer.tsx diff --git a/src/app/(auth)/reset-password/components/ResetPasswordContainer/index.ts b/legacy/src/app/(auth)/reset-password/components/ResetPasswordContainer/index.ts similarity index 100% rename from src/app/(auth)/reset-password/components/ResetPasswordContainer/index.ts rename to legacy/src/app/(auth)/reset-password/components/ResetPasswordContainer/index.ts diff --git a/src/app/(auth)/reset-password/components/ResetPasswordForm/ResetPasswordForm.tsx b/legacy/src/app/(auth)/reset-password/components/ResetPasswordForm/ResetPasswordForm.tsx similarity index 100% rename from src/app/(auth)/reset-password/components/ResetPasswordForm/ResetPasswordForm.tsx rename to legacy/src/app/(auth)/reset-password/components/ResetPasswordForm/ResetPasswordForm.tsx diff --git a/src/app/(auth)/reset-password/components/ResetPasswordForm/index.ts b/legacy/src/app/(auth)/reset-password/components/ResetPasswordForm/index.ts similarity index 100% rename from src/app/(auth)/reset-password/components/ResetPasswordForm/index.ts rename to legacy/src/app/(auth)/reset-password/components/ResetPasswordForm/index.ts diff --git a/src/app/(auth)/reset-password/page.tsx b/legacy/src/app/(auth)/reset-password/page.tsx similarity index 100% rename from src/app/(auth)/reset-password/page.tsx rename to legacy/src/app/(auth)/reset-password/page.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.module.scss b/legacy/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.module.scss similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.module.scss rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.module.scss diff --git a/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.test.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.test.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.test.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.test.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/AppActions/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/AppActions/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppActions/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppActions/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppBackups.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppBackups.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppBackups.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppBackups.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabTriggers.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabTriggers.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabTriggers.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabTriggers.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabs.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabs.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabs.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppDetailsTabs.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppLogs.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppLogs.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppLogs.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/AppLogs.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/AppDetailsTabs/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/BackupModal/BackupModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/BackupModal/BackupModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/BackupModal/BackupModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/BackupModal/BackupModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/BackupModal/index.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/BackupModal/index.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/BackupModal/index.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/BackupModal/index.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/DeleteBackupModal/DeleteBackupModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/DeleteBackupModal/DeleteBackupModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/DeleteBackupModal/DeleteBackupModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/DeleteBackupModal/DeleteBackupModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.test.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.test.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.test.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.test.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.types.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.types.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.types.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallForm.types.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallFormField.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallFormField.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallFormField.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/InstallFormField.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/InstallForm/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/InstallForm/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/InstallForm/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.test.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.test.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.test.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.test.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/InstallModal/InstallModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/InstallModal/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/InstallModal/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/InstallModal/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/InstallModal/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/ResetAppModal/ResetAppModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/ResetAppModal/ResetAppModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/ResetAppModal/ResetAppModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/ResetAppModal/ResetAppModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/ResetAppModal/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/ResetAppModal/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/ResetAppModal/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/ResetAppModal/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/RestartModal/RestartModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/RestartModal/RestartModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/RestartModal/RestartModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/RestartModal/RestartModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/RestartModal/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/RestartModal/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/RestartModal/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/RestartModal/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/RestoreModal/RestoreModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/RestoreModal/RestoreModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/RestoreModal/RestoreModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/RestoreModal/RestoreModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/RestoreModal/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/RestoreModal/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/RestoreModal/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/RestoreModal/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/StopModal/StopModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/StopModal/StopModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/StopModal/StopModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/StopModal/StopModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/StopModal/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/StopModal/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/StopModal/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/StopModal/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/UninstallModal/UninstallModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/UninstallModal/UninstallModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/UninstallModal/UninstallModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/UninstallModal/UninstallModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/UninstallModal/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/UninstallModal/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/UninstallModal/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/UninstallModal/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.test.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.test.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.test.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.test.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/UpdateModal/UpdateModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/UpdateModal/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/UpdateModal/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/UpdateModal/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/UpdateModal/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/UpdateSettingsModal.tsx b/legacy/src/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/UpdateSettingsModal.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/UpdateSettingsModal.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/UpdateSettingsModal.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/components/UpdateSettingsModal/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/page.tsx b/legacy/src/app/(dashboard)/app-store/[id]/page.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/page.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/page.tsx diff --git a/src/app/(dashboard)/app-store/[id]/utils/validators/index.ts b/legacy/src/app/(dashboard)/app-store/[id]/utils/validators/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/utils/validators/index.ts rename to legacy/src/app/(dashboard)/app-store/[id]/utils/validators/index.ts diff --git a/src/app/(dashboard)/app-store/[id]/utils/validators/validators.test.tsx b/legacy/src/app/(dashboard)/app-store/[id]/utils/validators/validators.test.tsx similarity index 100% rename from src/app/(dashboard)/app-store/[id]/utils/validators/validators.test.tsx rename to legacy/src/app/(dashboard)/app-store/[id]/utils/validators/validators.test.tsx diff --git a/src/app/(dashboard)/app-store/[id]/utils/validators/validators.ts b/legacy/src/app/(dashboard)/app-store/[id]/utils/validators/validators.ts similarity index 100% rename from src/app/(dashboard)/app-store/[id]/utils/validators/validators.ts rename to legacy/src/app/(dashboard)/app-store/[id]/utils/validators/validators.ts diff --git a/src/app/(dashboard)/app-store/components/AppStoreTable/AppStoreTable.tsx b/legacy/src/app/(dashboard)/app-store/components/AppStoreTable/AppStoreTable.tsx similarity index 100% rename from src/app/(dashboard)/app-store/components/AppStoreTable/AppStoreTable.tsx rename to legacy/src/app/(dashboard)/app-store/components/AppStoreTable/AppStoreTable.tsx diff --git a/src/app/(dashboard)/app-store/components/AppStoreTable/index.ts b/legacy/src/app/(dashboard)/app-store/components/AppStoreTable/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/components/AppStoreTable/index.ts rename to legacy/src/app/(dashboard)/app-store/components/AppStoreTable/index.ts diff --git a/src/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.module.scss b/legacy/src/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.module.scss similarity index 100% rename from src/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.module.scss rename to legacy/src/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.module.scss diff --git a/src/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.tsx b/legacy/src/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.tsx similarity index 100% rename from src/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.tsx rename to legacy/src/app/(dashboard)/app-store/components/AppStoreTableActions/AppStoreTableActions.tsx diff --git a/src/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.module.scss b/legacy/src/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.module.scss similarity index 100% rename from src/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.module.scss rename to legacy/src/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.module.scss diff --git a/src/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.tsx b/legacy/src/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.tsx similarity index 100% rename from src/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.tsx rename to legacy/src/app/(dashboard)/app-store/components/AppStoreTile/AppStoreTile.tsx diff --git a/src/app/(dashboard)/app-store/components/AppStoreTile/index.ts b/legacy/src/app/(dashboard)/app-store/components/AppStoreTile/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/components/AppStoreTile/index.ts rename to legacy/src/app/(dashboard)/app-store/components/AppStoreTile/index.ts diff --git a/src/app/(dashboard)/app-store/components/CategorySelector/CategorySelecte.test.tsx b/legacy/src/app/(dashboard)/app-store/components/CategorySelector/CategorySelecte.test.tsx similarity index 100% rename from src/app/(dashboard)/app-store/components/CategorySelector/CategorySelecte.test.tsx rename to legacy/src/app/(dashboard)/app-store/components/CategorySelector/CategorySelecte.test.tsx diff --git a/src/app/(dashboard)/app-store/components/CategorySelector/CategorySelector.tsx b/legacy/src/app/(dashboard)/app-store/components/CategorySelector/CategorySelector.tsx similarity index 100% rename from src/app/(dashboard)/app-store/components/CategorySelector/CategorySelector.tsx rename to legacy/src/app/(dashboard)/app-store/components/CategorySelector/CategorySelector.tsx diff --git a/src/app/(dashboard)/app-store/components/CategorySelector/index.ts b/legacy/src/app/(dashboard)/app-store/components/CategorySelector/index.ts similarity index 100% rename from src/app/(dashboard)/app-store/components/CategorySelector/index.ts rename to legacy/src/app/(dashboard)/app-store/components/CategorySelector/index.ts diff --git a/src/app/(dashboard)/app-store/helpers/__tests__/table.helpers.test.ts b/legacy/src/app/(dashboard)/app-store/helpers/__tests__/table.helpers.test.ts similarity index 100% rename from src/app/(dashboard)/app-store/helpers/__tests__/table.helpers.test.ts rename to legacy/src/app/(dashboard)/app-store/helpers/__tests__/table.helpers.test.ts diff --git a/src/app/(dashboard)/app-store/helpers/table.helpers.ts b/legacy/src/app/(dashboard)/app-store/helpers/table.helpers.ts similarity index 100% rename from src/app/(dashboard)/app-store/helpers/table.helpers.ts rename to legacy/src/app/(dashboard)/app-store/helpers/table.helpers.ts diff --git a/src/app/(dashboard)/app-store/helpers/table.types.ts b/legacy/src/app/(dashboard)/app-store/helpers/table.types.ts similarity index 100% rename from src/app/(dashboard)/app-store/helpers/table.types.ts rename to legacy/src/app/(dashboard)/app-store/helpers/table.types.ts diff --git a/src/app/(dashboard)/app-store/page.tsx b/legacy/src/app/(dashboard)/app-store/page.tsx similarity index 100% rename from src/app/(dashboard)/app-store/page.tsx rename to legacy/src/app/(dashboard)/app-store/page.tsx diff --git a/src/app/(dashboard)/app-store/state/appStoreState.ts b/legacy/src/app/(dashboard)/app-store/state/appStoreState.ts similarity index 100% rename from src/app/(dashboard)/app-store/state/appStoreState.ts rename to legacy/src/app/(dashboard)/app-store/state/appStoreState.ts diff --git a/src/app/(dashboard)/apps/components/UpdateAllButton/UpdateAllButton.tsx b/legacy/src/app/(dashboard)/apps/components/UpdateAllButton/UpdateAllButton.tsx similarity index 100% rename from src/app/(dashboard)/apps/components/UpdateAllButton/UpdateAllButton.tsx rename to legacy/src/app/(dashboard)/apps/components/UpdateAllButton/UpdateAllButton.tsx diff --git a/src/app/(dashboard)/apps/components/UpdateAllModal/UpdateAllModal.tsx b/legacy/src/app/(dashboard)/apps/components/UpdateAllModal/UpdateAllModal.tsx similarity index 100% rename from src/app/(dashboard)/apps/components/UpdateAllModal/UpdateAllModal.tsx rename to legacy/src/app/(dashboard)/apps/components/UpdateAllModal/UpdateAllModal.tsx diff --git a/src/app/(dashboard)/apps/components/UpdateAllModal/index.ts b/legacy/src/app/(dashboard)/apps/components/UpdateAllModal/index.ts similarity index 100% rename from src/app/(dashboard)/apps/components/UpdateAllModal/index.ts rename to legacy/src/app/(dashboard)/apps/components/UpdateAllModal/index.ts diff --git a/src/app/(dashboard)/apps/page.module.css b/legacy/src/app/(dashboard)/apps/page.module.css similarity index 100% rename from src/app/(dashboard)/apps/page.module.css rename to legacy/src/app/(dashboard)/apps/page.module.css diff --git a/src/app/(dashboard)/apps/page.tsx b/legacy/src/app/(dashboard)/apps/page.tsx similarity index 100% rename from src/app/(dashboard)/apps/page.tsx rename to legacy/src/app/(dashboard)/apps/page.tsx diff --git a/src/app/(dashboard)/components/AddLink/AddLinkButton.tsx b/legacy/src/app/(dashboard)/components/AddLink/AddLinkButton.tsx similarity index 100% rename from src/app/(dashboard)/components/AddLink/AddLinkButton.tsx rename to legacy/src/app/(dashboard)/components/AddLink/AddLinkButton.tsx diff --git a/src/app/(dashboard)/components/AddLink/AddLinkModal.tsx b/legacy/src/app/(dashboard)/components/AddLink/AddLinkModal.tsx similarity index 100% rename from src/app/(dashboard)/components/AddLink/AddLinkModal.tsx rename to legacy/src/app/(dashboard)/components/AddLink/AddLinkModal.tsx diff --git a/src/app/(dashboard)/components/AddLink/DeleteLinkModal.tsx b/legacy/src/app/(dashboard)/components/AddLink/DeleteLinkModal.tsx similarity index 100% rename from src/app/(dashboard)/components/AddLink/DeleteLinkModal.tsx rename to legacy/src/app/(dashboard)/components/AddLink/DeleteLinkModal.tsx diff --git a/src/app/(dashboard)/components/AddLink/addLink.module.css b/legacy/src/app/(dashboard)/components/AddLink/addLink.module.css similarity index 100% rename from src/app/(dashboard)/components/AddLink/addLink.module.css rename to legacy/src/app/(dashboard)/components/AddLink/addLink.module.css diff --git a/src/app/(dashboard)/components/AtRiskBanner/AtRiskBanner.tsx b/legacy/src/app/(dashboard)/components/AtRiskBanner/AtRiskBanner.tsx similarity index 100% rename from src/app/(dashboard)/components/AtRiskBanner/AtRiskBanner.tsx rename to legacy/src/app/(dashboard)/components/AtRiskBanner/AtRiskBanner.tsx diff --git a/src/app/(dashboard)/components/Header/Header.tsx b/legacy/src/app/(dashboard)/components/Header/Header.tsx similarity index 100% rename from src/app/(dashboard)/components/Header/Header.tsx rename to legacy/src/app/(dashboard)/components/Header/Header.tsx diff --git a/src/app/(dashboard)/components/Header/index.ts b/legacy/src/app/(dashboard)/components/Header/index.ts similarity index 100% rename from src/app/(dashboard)/components/Header/index.ts rename to legacy/src/app/(dashboard)/components/Header/index.ts diff --git a/src/app/(dashboard)/components/LayoutActions/LayoutActions.tsx b/legacy/src/app/(dashboard)/components/LayoutActions/LayoutActions.tsx similarity index 100% rename from src/app/(dashboard)/components/LayoutActions/LayoutActions.tsx rename to legacy/src/app/(dashboard)/components/LayoutActions/LayoutActions.tsx diff --git a/src/app/(dashboard)/components/NavBar/NavBar.tsx b/legacy/src/app/(dashboard)/components/NavBar/NavBar.tsx similarity index 100% rename from src/app/(dashboard)/components/NavBar/NavBar.tsx rename to legacy/src/app/(dashboard)/components/NavBar/NavBar.tsx diff --git a/src/app/(dashboard)/components/NavBar/index.ts b/legacy/src/app/(dashboard)/components/NavBar/index.ts similarity index 100% rename from src/app/(dashboard)/components/NavBar/index.ts rename to legacy/src/app/(dashboard)/components/NavBar/index.ts diff --git a/src/app/(dashboard)/components/PageTitle/PageTitle.tsx b/legacy/src/app/(dashboard)/components/PageTitle/PageTitle.tsx similarity index 100% rename from src/app/(dashboard)/components/PageTitle/PageTitle.tsx rename to legacy/src/app/(dashboard)/components/PageTitle/PageTitle.tsx diff --git a/src/app/(dashboard)/components/PageTitle/index.ts b/legacy/src/app/(dashboard)/components/PageTitle/index.ts similarity index 100% rename from src/app/(dashboard)/components/PageTitle/index.ts rename to legacy/src/app/(dashboard)/components/PageTitle/index.ts diff --git a/src/app/(dashboard)/components/Welcome/Welcome.tsx b/legacy/src/app/(dashboard)/components/Welcome/Welcome.tsx similarity index 100% rename from src/app/(dashboard)/components/Welcome/Welcome.tsx rename to legacy/src/app/(dashboard)/components/Welcome/Welcome.tsx diff --git a/src/app/(dashboard)/dashboard/components/DashboardContainer/DashboardContainer.tsx b/legacy/src/app/(dashboard)/dashboard/components/DashboardContainer/DashboardContainer.tsx similarity index 100% rename from src/app/(dashboard)/dashboard/components/DashboardContainer/DashboardContainer.tsx rename to legacy/src/app/(dashboard)/dashboard/components/DashboardContainer/DashboardContainer.tsx diff --git a/src/app/(dashboard)/dashboard/components/DashboardContainer/index.ts b/legacy/src/app/(dashboard)/dashboard/components/DashboardContainer/index.ts similarity index 100% rename from src/app/(dashboard)/dashboard/components/DashboardContainer/index.ts rename to legacy/src/app/(dashboard)/dashboard/components/DashboardContainer/index.ts diff --git a/src/app/(dashboard)/dashboard/components/SystemStat/SystemStat.tsx b/legacy/src/app/(dashboard)/dashboard/components/SystemStat/SystemStat.tsx similarity index 100% rename from src/app/(dashboard)/dashboard/components/SystemStat/SystemStat.tsx rename to legacy/src/app/(dashboard)/dashboard/components/SystemStat/SystemStat.tsx diff --git a/src/app/(dashboard)/dashboard/components/SystemStat/index.ts b/legacy/src/app/(dashboard)/dashboard/components/SystemStat/index.ts similarity index 100% rename from src/app/(dashboard)/dashboard/components/SystemStat/index.ts rename to legacy/src/app/(dashboard)/dashboard/components/SystemStat/index.ts diff --git a/src/app/(dashboard)/dashboard/page.tsx b/legacy/src/app/(dashboard)/dashboard/page.tsx similarity index 100% rename from src/app/(dashboard)/dashboard/page.tsx rename to legacy/src/app/(dashboard)/dashboard/page.tsx diff --git a/src/app/(dashboard)/layout.module.scss b/legacy/src/app/(dashboard)/layout.module.scss similarity index 100% rename from src/app/(dashboard)/layout.module.scss rename to legacy/src/app/(dashboard)/layout.module.scss diff --git a/src/app/(dashboard)/layout.tsx b/legacy/src/app/(dashboard)/layout.tsx similarity index 100% rename from src/app/(dashboard)/layout.tsx rename to legacy/src/app/(dashboard)/layout.tsx diff --git a/src/app/(dashboard)/settings/components/ChangePasswordForm/ChangePasswordForm.tsx b/legacy/src/app/(dashboard)/settings/components/ChangePasswordForm/ChangePasswordForm.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/ChangePasswordForm/ChangePasswordForm.tsx rename to legacy/src/app/(dashboard)/settings/components/ChangePasswordForm/ChangePasswordForm.tsx diff --git a/src/app/(dashboard)/settings/components/ChangePasswordForm/index.ts b/legacy/src/app/(dashboard)/settings/components/ChangePasswordForm/index.ts similarity index 100% rename from src/app/(dashboard)/settings/components/ChangePasswordForm/index.ts rename to legacy/src/app/(dashboard)/settings/components/ChangePasswordForm/index.ts diff --git a/src/app/(dashboard)/settings/components/ChangeUsernameForm/ChangeUsernameForm.tsx b/legacy/src/app/(dashboard)/settings/components/ChangeUsernameForm/ChangeUsernameForm.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/ChangeUsernameForm/ChangeUsernameForm.tsx rename to legacy/src/app/(dashboard)/settings/components/ChangeUsernameForm/ChangeUsernameForm.tsx diff --git a/src/app/(dashboard)/settings/components/ChangeUsernameForm/index.ts b/legacy/src/app/(dashboard)/settings/components/ChangeUsernameForm/index.ts similarity index 100% rename from src/app/(dashboard)/settings/components/ChangeUsernameForm/index.ts rename to legacy/src/app/(dashboard)/settings/components/ChangeUsernameForm/index.ts diff --git a/src/app/(dashboard)/settings/components/GeneralActions/GeneralActions.tsx b/legacy/src/app/(dashboard)/settings/components/GeneralActions/GeneralActions.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/GeneralActions/GeneralActions.tsx rename to legacy/src/app/(dashboard)/settings/components/GeneralActions/GeneralActions.tsx diff --git a/src/app/(dashboard)/settings/components/GeneralActions/components/UpdateRepoModal/UpateRepoModal.tsx b/legacy/src/app/(dashboard)/settings/components/GeneralActions/components/UpdateRepoModal/UpateRepoModal.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/GeneralActions/components/UpdateRepoModal/UpateRepoModal.tsx rename to legacy/src/app/(dashboard)/settings/components/GeneralActions/components/UpdateRepoModal/UpateRepoModal.tsx diff --git a/src/app/(dashboard)/settings/components/GeneralActions/index.ts b/legacy/src/app/(dashboard)/settings/components/GeneralActions/index.ts similarity index 100% rename from src/app/(dashboard)/settings/components/GeneralActions/index.ts rename to legacy/src/app/(dashboard)/settings/components/GeneralActions/index.ts diff --git a/src/app/(dashboard)/settings/components/LogsContainer/LogsContainer.tsx b/legacy/src/app/(dashboard)/settings/components/LogsContainer/LogsContainer.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/LogsContainer/LogsContainer.tsx rename to legacy/src/app/(dashboard)/settings/components/LogsContainer/LogsContainer.tsx diff --git a/src/app/(dashboard)/settings/components/LogsContainer/index.ts b/legacy/src/app/(dashboard)/settings/components/LogsContainer/index.ts similarity index 100% rename from src/app/(dashboard)/settings/components/LogsContainer/index.ts rename to legacy/src/app/(dashboard)/settings/components/LogsContainer/index.ts diff --git a/src/app/(dashboard)/settings/components/OtpForm/OtpForm.tsx b/legacy/src/app/(dashboard)/settings/components/OtpForm/OtpForm.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/OtpForm/OtpForm.tsx rename to legacy/src/app/(dashboard)/settings/components/OtpForm/OtpForm.tsx diff --git a/src/app/(dashboard)/settings/components/OtpForm/index.ts b/legacy/src/app/(dashboard)/settings/components/OtpForm/index.ts similarity index 100% rename from src/app/(dashboard)/settings/components/OtpForm/index.ts rename to legacy/src/app/(dashboard)/settings/components/OtpForm/index.ts diff --git a/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.test.tsx b/legacy/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.test.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.test.tsx rename to legacy/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.test.tsx diff --git a/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.tsx b/legacy/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.tsx rename to legacy/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.tsx diff --git a/src/app/(dashboard)/settings/components/SecurityContainer/index.ts b/legacy/src/app/(dashboard)/settings/components/SecurityContainer/index.ts similarity index 100% rename from src/app/(dashboard)/settings/components/SecurityContainer/index.ts rename to legacy/src/app/(dashboard)/settings/components/SecurityContainer/index.ts diff --git a/src/app/(dashboard)/settings/components/SettingsContainer/SettingsContainer.tsx b/legacy/src/app/(dashboard)/settings/components/SettingsContainer/SettingsContainer.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/SettingsContainer/SettingsContainer.tsx rename to legacy/src/app/(dashboard)/settings/components/SettingsContainer/SettingsContainer.tsx diff --git a/src/app/(dashboard)/settings/components/SettingsContainer/index.ts b/legacy/src/app/(dashboard)/settings/components/SettingsContainer/index.ts similarity index 100% rename from src/app/(dashboard)/settings/components/SettingsContainer/index.ts rename to legacy/src/app/(dashboard)/settings/components/SettingsContainer/index.ts diff --git a/src/app/(dashboard)/settings/components/SettingsForm/SettingsForm.test.tsx b/legacy/src/app/(dashboard)/settings/components/SettingsForm/SettingsForm.test.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/SettingsForm/SettingsForm.test.tsx rename to legacy/src/app/(dashboard)/settings/components/SettingsForm/SettingsForm.test.tsx diff --git a/src/app/(dashboard)/settings/components/SettingsForm/SettingsForm.tsx b/legacy/src/app/(dashboard)/settings/components/SettingsForm/SettingsForm.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/SettingsForm/SettingsForm.tsx rename to legacy/src/app/(dashboard)/settings/components/SettingsForm/SettingsForm.tsx diff --git a/src/app/(dashboard)/settings/components/SettingsForm/index.ts b/legacy/src/app/(dashboard)/settings/components/SettingsForm/index.ts similarity index 100% rename from src/app/(dashboard)/settings/components/SettingsForm/index.ts rename to legacy/src/app/(dashboard)/settings/components/SettingsForm/index.ts diff --git a/src/app/(dashboard)/settings/components/SettingsTabTriggers/SettingsTabTriggers.tsx b/legacy/src/app/(dashboard)/settings/components/SettingsTabTriggers/SettingsTabTriggers.tsx similarity index 100% rename from src/app/(dashboard)/settings/components/SettingsTabTriggers/SettingsTabTriggers.tsx rename to legacy/src/app/(dashboard)/settings/components/SettingsTabTriggers/SettingsTabTriggers.tsx diff --git a/src/app/(dashboard)/settings/components/SettingsTabTriggers/index.ts b/legacy/src/app/(dashboard)/settings/components/SettingsTabTriggers/index.ts similarity index 100% rename from src/app/(dashboard)/settings/components/SettingsTabTriggers/index.ts rename to legacy/src/app/(dashboard)/settings/components/SettingsTabTriggers/index.ts diff --git a/src/app/(dashboard)/settings/page.tsx b/legacy/src/app/(dashboard)/settings/page.tsx similarity index 100% rename from src/app/(dashboard)/settings/page.tsx rename to legacy/src/app/(dashboard)/settings/page.tsx diff --git a/src/app/(dashboard)/test-error/page.tsx b/legacy/src/app/(dashboard)/test-error/page.tsx similarity index 100% rename from src/app/(dashboard)/test-error/page.tsx rename to legacy/src/app/(dashboard)/test-error/page.tsx diff --git a/src/app/actions/acknowledge-welcome/acknowledge-welcome.ts b/legacy/src/app/actions/acknowledge-welcome/acknowledge-welcome.ts similarity index 100% rename from src/app/actions/acknowledge-welcome/acknowledge-welcome.ts rename to legacy/src/app/actions/acknowledge-welcome/acknowledge-welcome.ts diff --git a/src/app/actions/app-actions/install-app-action.ts b/legacy/src/app/actions/app-actions/install-app-action.ts similarity index 100% rename from src/app/actions/app-actions/install-app-action.ts rename to legacy/src/app/actions/app-actions/install-app-action.ts diff --git a/src/app/actions/app-actions/reset-app-action.ts b/legacy/src/app/actions/app-actions/reset-app-action.ts similarity index 100% rename from src/app/actions/app-actions/reset-app-action.ts rename to legacy/src/app/actions/app-actions/reset-app-action.ts diff --git a/src/app/actions/app-actions/restart-app-action.ts b/legacy/src/app/actions/app-actions/restart-app-action.ts similarity index 100% rename from src/app/actions/app-actions/restart-app-action.ts rename to legacy/src/app/actions/app-actions/restart-app-action.ts diff --git a/src/app/actions/app-actions/revalidate-app.ts b/legacy/src/app/actions/app-actions/revalidate-app.ts similarity index 100% rename from src/app/actions/app-actions/revalidate-app.ts rename to legacy/src/app/actions/app-actions/revalidate-app.ts diff --git a/src/app/actions/app-actions/start-app-action.ts b/legacy/src/app/actions/app-actions/start-app-action.ts similarity index 100% rename from src/app/actions/app-actions/start-app-action.ts rename to legacy/src/app/actions/app-actions/start-app-action.ts diff --git a/src/app/actions/app-actions/stop-app-action.ts b/legacy/src/app/actions/app-actions/stop-app-action.ts similarity index 100% rename from src/app/actions/app-actions/stop-app-action.ts rename to legacy/src/app/actions/app-actions/stop-app-action.ts diff --git a/src/app/actions/app-actions/uninstall-app-action.ts b/legacy/src/app/actions/app-actions/uninstall-app-action.ts similarity index 100% rename from src/app/actions/app-actions/uninstall-app-action.ts rename to legacy/src/app/actions/app-actions/uninstall-app-action.ts diff --git a/src/app/actions/app-actions/update-all-apps-action.ts b/legacy/src/app/actions/app-actions/update-all-apps-action.ts similarity index 100% rename from src/app/actions/app-actions/update-all-apps-action.ts rename to legacy/src/app/actions/app-actions/update-all-apps-action.ts diff --git a/src/app/actions/app-actions/update-app-action.ts b/legacy/src/app/actions/app-actions/update-app-action.ts similarity index 100% rename from src/app/actions/app-actions/update-app-action.ts rename to legacy/src/app/actions/app-actions/update-app-action.ts diff --git a/src/app/actions/app-actions/update-app-config-action.ts b/legacy/src/app/actions/app-actions/update-app-config-action.ts similarity index 100% rename from src/app/actions/app-actions/update-app-config-action.ts rename to legacy/src/app/actions/app-actions/update-app-config-action.ts diff --git a/src/app/actions/backup/create-app-backup-action.ts b/legacy/src/app/actions/backup/create-app-backup-action.ts similarity index 100% rename from src/app/actions/backup/create-app-backup-action.ts rename to legacy/src/app/actions/backup/create-app-backup-action.ts diff --git a/src/app/actions/backup/delete-app-backup.ts b/legacy/src/app/actions/backup/delete-app-backup.ts similarity index 100% rename from src/app/actions/backup/delete-app-backup.ts rename to legacy/src/app/actions/backup/delete-app-backup.ts diff --git a/src/app/actions/backup/restore-app-backup-action.ts b/legacy/src/app/actions/backup/restore-app-backup-action.ts similarity index 100% rename from src/app/actions/backup/restore-app-backup-action.ts rename to legacy/src/app/actions/backup/restore-app-backup-action.ts diff --git a/src/app/actions/cancel-reset-password/cancel-reset-password-action.ts b/legacy/src/app/actions/cancel-reset-password/cancel-reset-password-action.ts similarity index 100% rename from src/app/actions/cancel-reset-password/cancel-reset-password-action.ts rename to legacy/src/app/actions/cancel-reset-password/cancel-reset-password-action.ts diff --git a/src/app/actions/change-locale/change-locale-action.ts b/legacy/src/app/actions/change-locale/change-locale-action.ts similarity index 100% rename from src/app/actions/change-locale/change-locale-action.ts rename to legacy/src/app/actions/change-locale/change-locale-action.ts diff --git a/src/app/actions/custom-links/add-link-action.ts b/legacy/src/app/actions/custom-links/add-link-action.ts similarity index 100% rename from src/app/actions/custom-links/add-link-action.ts rename to legacy/src/app/actions/custom-links/add-link-action.ts diff --git a/src/app/actions/custom-links/delete-link-action.ts b/legacy/src/app/actions/custom-links/delete-link-action.ts similarity index 100% rename from src/app/actions/custom-links/delete-link-action.ts rename to legacy/src/app/actions/custom-links/delete-link-action.ts diff --git a/src/app/actions/custom-links/edit-link-action.ts b/legacy/src/app/actions/custom-links/edit-link-action.ts similarity index 100% rename from src/app/actions/custom-links/edit-link-action.ts rename to legacy/src/app/actions/custom-links/edit-link-action.ts diff --git a/src/app/actions/login/login-action.ts b/legacy/src/app/actions/login/login-action.ts similarity index 100% rename from src/app/actions/login/login-action.ts rename to legacy/src/app/actions/login/login-action.ts diff --git a/src/app/actions/logout/logout-action.ts b/legacy/src/app/actions/logout/logout-action.ts similarity index 100% rename from src/app/actions/logout/logout-action.ts rename to legacy/src/app/actions/logout/logout-action.ts diff --git a/src/app/actions/register/register-action.ts b/legacy/src/app/actions/register/register-action.ts similarity index 100% rename from src/app/actions/register/register-action.ts rename to legacy/src/app/actions/register/register-action.ts diff --git a/src/app/actions/reset-password/reset-password-action.ts b/legacy/src/app/actions/reset-password/reset-password-action.ts similarity index 100% rename from src/app/actions/reset-password/reset-password-action.ts rename to legacy/src/app/actions/reset-password/reset-password-action.ts diff --git a/src/app/actions/settings/change-password.ts b/legacy/src/app/actions/settings/change-password.ts similarity index 100% rename from src/app/actions/settings/change-password.ts rename to legacy/src/app/actions/settings/change-password.ts diff --git a/src/app/actions/settings/change-username.ts b/legacy/src/app/actions/settings/change-username.ts similarity index 100% rename from src/app/actions/settings/change-username.ts rename to legacy/src/app/actions/settings/change-username.ts diff --git a/src/app/actions/settings/disable-totp.ts b/legacy/src/app/actions/settings/disable-totp.ts similarity index 100% rename from src/app/actions/settings/disable-totp.ts rename to legacy/src/app/actions/settings/disable-totp.ts diff --git a/src/app/actions/settings/get-totp-uri.ts b/legacy/src/app/actions/settings/get-totp-uri.ts similarity index 100% rename from src/app/actions/settings/get-totp-uri.ts rename to legacy/src/app/actions/settings/get-totp-uri.ts diff --git a/src/app/actions/settings/setup-totp-action.ts b/legacy/src/app/actions/settings/setup-totp-action.ts similarity index 100% rename from src/app/actions/settings/setup-totp-action.ts rename to legacy/src/app/actions/settings/setup-totp-action.ts diff --git a/src/app/actions/settings/update-repo-action.ts b/legacy/src/app/actions/settings/update-repo-action.ts similarity index 100% rename from src/app/actions/settings/update-repo-action.ts rename to legacy/src/app/actions/settings/update-repo-action.ts diff --git a/src/app/actions/settings/update-settings.ts b/legacy/src/app/actions/settings/update-settings.ts similarity index 100% rename from src/app/actions/settings/update-settings.ts rename to legacy/src/app/actions/settings/update-settings.ts diff --git a/src/app/actions/utils/ensure-user.ts b/legacy/src/app/actions/utils/ensure-user.ts similarity index 100% rename from src/app/actions/utils/ensure-user.ts rename to legacy/src/app/actions/utils/ensure-user.ts diff --git a/src/app/actions/utils/handle-api-error.ts b/legacy/src/app/actions/utils/handle-api-error.ts similarity index 100% rename from src/app/actions/utils/handle-api-error.ts rename to legacy/src/app/actions/utils/handle-api-error.ts diff --git a/src/app/actions/verify-totp/verify-totp-action.ts b/legacy/src/app/actions/verify-totp/verify-totp-action.ts similarity index 100% rename from src/app/actions/verify-totp/verify-totp-action.ts rename to legacy/src/app/actions/verify-totp/verify-totp-action.ts diff --git a/src/app/api/app-backups/route.ts b/legacy/src/app/api/app-backups/route.ts similarity index 100% rename from src/app/api/app-backups/route.ts rename to legacy/src/app/api/app-backups/route.ts diff --git a/src/app/api/app-image/route.ts b/legacy/src/app/api/app-image/route.ts similarity index 100% rename from src/app/api/app-image/route.ts rename to legacy/src/app/api/app-image/route.ts diff --git a/src/app/api/app-store/route.ts b/legacy/src/app/api/app-store/route.ts similarity index 100% rename from src/app/api/app-store/route.ts rename to legacy/src/app/api/app-store/route.ts diff --git a/src/app/api/certificate/route.ts b/legacy/src/app/api/certificate/route.ts similarity index 100% rename from src/app/api/certificate/route.ts rename to legacy/src/app/api/certificate/route.ts diff --git a/src/app/api/system-status/fetch-system-status.ts b/legacy/src/app/api/system-status/fetch-system-status.ts similarity index 100% rename from src/app/api/system-status/fetch-system-status.ts rename to legacy/src/app/api/system-status/fetch-system-status.ts diff --git a/src/app/api/system-status/route.ts b/legacy/src/app/api/system-status/route.ts similarity index 100% rename from src/app/api/system-status/route.ts rename to legacy/src/app/api/system-status/route.ts diff --git a/src/app/api/test-error/route.ts b/legacy/src/app/api/test-error/route.ts similarity index 100% rename from src/app/api/test-error/route.ts rename to legacy/src/app/api/test-error/route.ts diff --git a/src/app/apple-icon.png b/legacy/src/app/apple-icon.png similarity index 100% rename from src/app/apple-icon.png rename to legacy/src/app/apple-icon.png diff --git a/src/app/components/ClientProviders/AppStatusProvider/app-status-provider.tsx b/legacy/src/app/components/ClientProviders/AppStatusProvider/app-status-provider.tsx similarity index 100% rename from src/app/components/ClientProviders/AppStatusProvider/app-status-provider.tsx rename to legacy/src/app/components/ClientProviders/AppStatusProvider/app-status-provider.tsx diff --git a/src/app/components/ClientProviders/AppStatusProvider/app-status-store.ts b/legacy/src/app/components/ClientProviders/AppStatusProvider/app-status-store.ts similarity index 100% rename from src/app/components/ClientProviders/AppStatusProvider/app-status-store.ts rename to legacy/src/app/components/ClientProviders/AppStatusProvider/app-status-store.ts diff --git a/src/app/components/ClientProviders/ClientProviders.tsx b/legacy/src/app/components/ClientProviders/ClientProviders.tsx similarity index 100% rename from src/app/components/ClientProviders/ClientProviders.tsx rename to legacy/src/app/components/ClientProviders/ClientProviders.tsx diff --git a/src/app/components/ClientProviders/ClientSettingsProvider/ClientSettingsProvider.tsx b/legacy/src/app/components/ClientProviders/ClientSettingsProvider/ClientSettingsProvider.tsx similarity index 100% rename from src/app/components/ClientProviders/ClientSettingsProvider/ClientSettingsProvider.tsx rename to legacy/src/app/components/ClientProviders/ClientSettingsProvider/ClientSettingsProvider.tsx diff --git a/src/app/components/ClientProviders/ClientSettingsProvider/client-settings-store.tsx b/legacy/src/app/components/ClientProviders/ClientSettingsProvider/client-settings-store.tsx similarity index 100% rename from src/app/components/ClientProviders/ClientSettingsProvider/client-settings-store.tsx rename to legacy/src/app/components/ClientProviders/ClientSettingsProvider/client-settings-store.tsx diff --git a/src/app/components/ClientProviders/SocketProvider/SocketProvider.ts b/legacy/src/app/components/ClientProviders/SocketProvider/SocketProvider.ts similarity index 100% rename from src/app/components/ClientProviders/SocketProvider/SocketProvider.ts rename to legacy/src/app/components/ClientProviders/SocketProvider/SocketProvider.ts diff --git a/src/app/components/ClientProviders/ThemeProvider/ThemeProvider.tsx b/legacy/src/app/components/ClientProviders/ThemeProvider/ThemeProvider.tsx similarity index 100% rename from src/app/components/ClientProviders/ThemeProvider/ThemeProvider.tsx rename to legacy/src/app/components/ClientProviders/ThemeProvider/ThemeProvider.tsx diff --git a/src/app/components/ClientProviders/ThemeProvider/index.ts b/legacy/src/app/components/ClientProviders/ThemeProvider/index.ts similarity index 100% rename from src/app/components/ClientProviders/ThemeProvider/index.ts rename to legacy/src/app/components/ClientProviders/ThemeProvider/index.ts diff --git a/src/app/components/ClientProviders/index.ts b/legacy/src/app/components/ClientProviders/index.ts similarity index 100% rename from src/app/components/ClientProviders/index.ts rename to legacy/src/app/components/ClientProviders/index.ts diff --git a/src/app/components/EmptyPage/EmptyPage.module.scss b/legacy/src/app/components/EmptyPage/EmptyPage.module.scss similarity index 100% rename from src/app/components/EmptyPage/EmptyPage.module.scss rename to legacy/src/app/components/EmptyPage/EmptyPage.module.scss diff --git a/src/app/components/EmptyPage/EmptyPage.tsx b/legacy/src/app/components/EmptyPage/EmptyPage.tsx similarity index 100% rename from src/app/components/EmptyPage/EmptyPage.tsx rename to legacy/src/app/components/EmptyPage/EmptyPage.tsx diff --git a/src/app/components/EmptyPage/index.ts b/legacy/src/app/components/EmptyPage/index.ts similarity index 100% rename from src/app/components/EmptyPage/index.ts rename to legacy/src/app/components/EmptyPage/index.ts diff --git a/src/app/components/GuestDashboardApps/GuestDashboardApps.module.css b/legacy/src/app/components/GuestDashboardApps/GuestDashboardApps.module.css similarity index 100% rename from src/app/components/GuestDashboardApps/GuestDashboardApps.module.css rename to legacy/src/app/components/GuestDashboardApps/GuestDashboardApps.module.css diff --git a/src/app/components/GuestDashboardApps/GuestDashboardApps.tsx b/legacy/src/app/components/GuestDashboardApps/GuestDashboardApps.tsx similarity index 100% rename from src/app/components/GuestDashboardApps/GuestDashboardApps.tsx rename to legacy/src/app/components/GuestDashboardApps/GuestDashboardApps.tsx diff --git a/src/app/components/GuestDashboardApps/index.tsx b/legacy/src/app/components/GuestDashboardApps/index.tsx similarity index 100% rename from src/app/components/GuestDashboardApps/index.tsx rename to legacy/src/app/components/GuestDashboardApps/index.tsx diff --git a/src/app/components/LanguageSelector/LanguageSelector.tsx b/legacy/src/app/components/LanguageSelector/LanguageSelector.tsx similarity index 100% rename from src/app/components/LanguageSelector/LanguageSelector.tsx rename to legacy/src/app/components/LanguageSelector/LanguageSelector.tsx diff --git a/src/app/components/LanguageSelector/LanguageSelectorLabel.tsx b/legacy/src/app/components/LanguageSelector/LanguageSelectorLabel.tsx similarity index 100% rename from src/app/components/LanguageSelector/LanguageSelectorLabel.tsx rename to legacy/src/app/components/LanguageSelector/LanguageSelectorLabel.tsx diff --git a/src/app/components/LanguageSelector/index.ts b/legacy/src/app/components/LanguageSelector/index.ts similarity index 100% rename from src/app/components/LanguageSelector/index.ts rename to legacy/src/app/components/LanguageSelector/index.ts diff --git a/src/app/components/LogsTerminal/LogsTerminal.module.scss b/legacy/src/app/components/LogsTerminal/LogsTerminal.module.scss similarity index 100% rename from src/app/components/LogsTerminal/LogsTerminal.module.scss rename to legacy/src/app/components/LogsTerminal/LogsTerminal.module.scss diff --git a/src/app/components/LogsTerminal/LogsTerminal.tsx b/legacy/src/app/components/LogsTerminal/LogsTerminal.tsx similarity index 100% rename from src/app/components/LogsTerminal/LogsTerminal.tsx rename to legacy/src/app/components/LogsTerminal/LogsTerminal.tsx diff --git a/src/app/components/OffCanvas/OffCanvas.tsx b/legacy/src/app/components/OffCanvas/OffCanvas.tsx similarity index 100% rename from src/app/components/OffCanvas/OffCanvas.tsx rename to legacy/src/app/components/OffCanvas/OffCanvas.tsx diff --git a/src/app/components/TablePagination/TablePagination.tsx b/legacy/src/app/components/TablePagination/TablePagination.tsx similarity index 100% rename from src/app/components/TablePagination/TablePagination.tsx rename to legacy/src/app/components/TablePagination/TablePagination.tsx diff --git a/src/app/components/TimeZoneSelector/TimeZoneSelector.tsx b/legacy/src/app/components/TimeZoneSelector/TimeZoneSelector.tsx similarity index 100% rename from src/app/components/TimeZoneSelector/TimeZoneSelector.tsx rename to legacy/src/app/components/TimeZoneSelector/TimeZoneSelector.tsx diff --git a/src/app/favicon.ico b/legacy/src/app/favicon.ico similarity index 100% rename from src/app/favicon.ico rename to legacy/src/app/favicon.ico diff --git a/src/app/global-error.tsx b/legacy/src/app/global-error.tsx similarity index 100% rename from src/app/global-error.tsx rename to legacy/src/app/global-error.tsx diff --git a/src/app/global.css b/legacy/src/app/global.css similarity index 100% rename from src/app/global.css rename to legacy/src/app/global.css diff --git a/src/app/hooks/useAppStatus.tsx b/legacy/src/app/hooks/useAppStatus.tsx similarity index 100% rename from src/app/hooks/useAppStatus.tsx rename to legacy/src/app/hooks/useAppStatus.tsx diff --git a/src/app/hooks/useClientSettings.tsx b/legacy/src/app/hooks/useClientSettings.tsx similarity index 100% rename from src/app/hooks/useClientSettings.tsx rename to legacy/src/app/hooks/useClientSettings.tsx diff --git a/src/app/icon.png b/legacy/src/app/icon.png similarity index 100% rename from src/app/icon.png rename to legacy/src/app/icon.png diff --git a/src/app/layout.tsx b/legacy/src/app/layout.tsx similarity index 100% rename from src/app/layout.tsx rename to legacy/src/app/layout.tsx diff --git a/src/app/not-found.tsx b/legacy/src/app/not-found.tsx similarity index 100% rename from src/app/not-found.tsx rename to legacy/src/app/not-found.tsx diff --git a/src/app/page.tsx b/legacy/src/app/page.tsx similarity index 100% rename from src/app/page.tsx rename to legacy/src/app/page.tsx diff --git a/src/client/components/AppLogo/AppLogo.module.scss b/legacy/src/client/components/AppLogo/AppLogo.module.scss similarity index 100% rename from src/client/components/AppLogo/AppLogo.module.scss rename to legacy/src/client/components/AppLogo/AppLogo.module.scss diff --git a/src/client/components/AppLogo/AppLogo.tsx b/legacy/src/client/components/AppLogo/AppLogo.tsx similarity index 100% rename from src/client/components/AppLogo/AppLogo.tsx rename to legacy/src/client/components/AppLogo/AppLogo.tsx diff --git a/src/client/components/AppLogo/index.ts b/legacy/src/client/components/AppLogo/index.ts similarity index 100% rename from src/client/components/AppLogo/index.ts rename to legacy/src/client/components/AppLogo/index.ts diff --git a/src/client/components/AppStatus/AppStatus.module.scss b/legacy/src/client/components/AppStatus/AppStatus.module.scss similarity index 100% rename from src/client/components/AppStatus/AppStatus.module.scss rename to legacy/src/client/components/AppStatus/AppStatus.module.scss diff --git a/src/client/components/AppStatus/AppStatus.tsx b/legacy/src/client/components/AppStatus/AppStatus.tsx similarity index 100% rename from src/client/components/AppStatus/AppStatus.tsx rename to legacy/src/client/components/AppStatus/AppStatus.tsx diff --git a/src/client/components/AppStatus/index.tsx b/legacy/src/client/components/AppStatus/index.tsx similarity index 100% rename from src/client/components/AppStatus/index.tsx rename to legacy/src/client/components/AppStatus/index.tsx diff --git a/src/client/components/AppTile/AppTile.module.scss b/legacy/src/client/components/AppTile/AppTile.module.scss similarity index 100% rename from src/client/components/AppTile/AppTile.module.scss rename to legacy/src/client/components/AppTile/AppTile.module.scss diff --git a/src/client/components/AppTile/AppTile.tsx b/legacy/src/client/components/AppTile/AppTile.tsx similarity index 100% rename from src/client/components/AppTile/AppTile.tsx rename to legacy/src/client/components/AppTile/AppTile.tsx diff --git a/src/client/components/AppTile/index.tsx b/legacy/src/client/components/AppTile/index.tsx similarity index 100% rename from src/client/components/AppTile/index.tsx rename to legacy/src/client/components/AppTile/index.tsx diff --git a/src/client/components/ClientOnly/ClientOnly.tsx b/legacy/src/client/components/ClientOnly/ClientOnly.tsx similarity index 100% rename from src/client/components/ClientOnly/ClientOnly.tsx rename to legacy/src/client/components/ClientOnly/ClientOnly.tsx diff --git a/src/client/components/DateFormat/DateFormat.tsx b/legacy/src/client/components/DateFormat/DateFormat.tsx similarity index 100% rename from src/client/components/DateFormat/DateFormat.tsx rename to legacy/src/client/components/DateFormat/DateFormat.tsx diff --git a/src/client/components/FileSize/FileSize.tsx b/legacy/src/client/components/FileSize/FileSize.tsx similarity index 100% rename from src/client/components/FileSize/FileSize.tsx rename to legacy/src/client/components/FileSize/FileSize.tsx diff --git a/src/client/components/LinkTile/LinkTile.tsx b/legacy/src/client/components/LinkTile/LinkTile.tsx similarity index 100% rename from src/client/components/LinkTile/LinkTile.tsx rename to legacy/src/client/components/LinkTile/LinkTile.tsx diff --git a/src/client/components/Markdown/Markdown.tsx b/legacy/src/client/components/Markdown/Markdown.tsx similarity index 100% rename from src/client/components/Markdown/Markdown.tsx rename to legacy/src/client/components/Markdown/Markdown.tsx diff --git a/src/client/components/Markdown/index.ts b/legacy/src/client/components/Markdown/index.ts similarity index 100% rename from src/client/components/Markdown/index.ts rename to legacy/src/client/components/Markdown/index.ts diff --git a/src/client/components/StatusScreen/StatusScreen.tsx b/legacy/src/client/components/StatusScreen/StatusScreen.tsx similarity index 100% rename from src/client/components/StatusScreen/StatusScreen.tsx rename to legacy/src/client/components/StatusScreen/StatusScreen.tsx diff --git a/src/client/components/StatusScreen/index.ts b/legacy/src/client/components/StatusScreen/index.ts similarity index 100% rename from src/client/components/StatusScreen/index.ts rename to legacy/src/client/components/StatusScreen/index.ts diff --git a/src/client/components/UnauthenticatedPage/UnauthenticatedPage.tsx b/legacy/src/client/components/UnauthenticatedPage/UnauthenticatedPage.tsx similarity index 100% rename from src/client/components/UnauthenticatedPage/UnauthenticatedPage.tsx rename to legacy/src/client/components/UnauthenticatedPage/UnauthenticatedPage.tsx diff --git a/src/client/components/UnauthenticatedPage/index.ts b/legacy/src/client/components/UnauthenticatedPage/index.ts similarity index 100% rename from src/client/components/UnauthenticatedPage/index.ts rename to legacy/src/client/components/UnauthenticatedPage/index.ts diff --git a/src/client/components/ui/Button/Button.module.css b/legacy/src/client/components/ui/Button/Button.module.css similarity index 100% rename from src/client/components/ui/Button/Button.module.css rename to legacy/src/client/components/ui/Button/Button.module.css diff --git a/src/client/components/ui/Button/Button.test.tsx b/legacy/src/client/components/ui/Button/Button.test.tsx similarity index 100% rename from src/client/components/ui/Button/Button.test.tsx rename to legacy/src/client/components/ui/Button/Button.test.tsx diff --git a/src/client/components/ui/Button/Button.tsx b/legacy/src/client/components/ui/Button/Button.tsx similarity index 100% rename from src/client/components/ui/Button/Button.tsx rename to legacy/src/client/components/ui/Button/Button.tsx diff --git a/src/client/components/ui/Button/index.ts b/legacy/src/client/components/ui/Button/index.ts similarity index 100% rename from src/client/components/ui/Button/index.ts rename to legacy/src/client/components/ui/Button/index.ts diff --git a/src/client/components/ui/ContextMenu/ContextMenu.tsx b/legacy/src/client/components/ui/ContextMenu/ContextMenu.tsx similarity index 100% rename from src/client/components/ui/ContextMenu/ContextMenu.tsx rename to legacy/src/client/components/ui/ContextMenu/ContextMenu.tsx diff --git a/src/client/components/ui/DataGrid/DataGrid.test.tsx b/legacy/src/client/components/ui/DataGrid/DataGrid.test.tsx similarity index 100% rename from src/client/components/ui/DataGrid/DataGrid.test.tsx rename to legacy/src/client/components/ui/DataGrid/DataGrid.test.tsx diff --git a/src/client/components/ui/DataGrid/DataGrid.tsx b/legacy/src/client/components/ui/DataGrid/DataGrid.tsx similarity index 100% rename from src/client/components/ui/DataGrid/DataGrid.tsx rename to legacy/src/client/components/ui/DataGrid/DataGrid.tsx diff --git a/src/client/components/ui/DataGrid/DataGridItem.tsx b/legacy/src/client/components/ui/DataGrid/DataGridItem.tsx similarity index 100% rename from src/client/components/ui/DataGrid/DataGridItem.tsx rename to legacy/src/client/components/ui/DataGrid/DataGridItem.tsx diff --git a/src/client/components/ui/DataGrid/index.ts b/legacy/src/client/components/ui/DataGrid/index.ts similarity index 100% rename from src/client/components/ui/DataGrid/index.ts rename to legacy/src/client/components/ui/DataGrid/index.ts diff --git a/src/client/components/ui/Dialog/Dialog.module.scss b/legacy/src/client/components/ui/Dialog/Dialog.module.scss similarity index 100% rename from src/client/components/ui/Dialog/Dialog.module.scss rename to legacy/src/client/components/ui/Dialog/Dialog.module.scss diff --git a/src/client/components/ui/Dialog/Dialog.tsx b/legacy/src/client/components/ui/Dialog/Dialog.tsx similarity index 100% rename from src/client/components/ui/Dialog/Dialog.tsx rename to legacy/src/client/components/ui/Dialog/Dialog.tsx diff --git a/src/client/components/ui/Dialog/index.ts b/legacy/src/client/components/ui/Dialog/index.ts similarity index 100% rename from src/client/components/ui/Dialog/index.ts rename to legacy/src/client/components/ui/Dialog/index.ts diff --git a/src/client/components/ui/DropdownMenu/DropdownMenu.tsx b/legacy/src/client/components/ui/DropdownMenu/DropdownMenu.tsx similarity index 100% rename from src/client/components/ui/DropdownMenu/DropdownMenu.tsx rename to legacy/src/client/components/ui/DropdownMenu/DropdownMenu.tsx diff --git a/src/client/components/ui/DropdownMenu/index.ts b/legacy/src/client/components/ui/DropdownMenu/index.ts similarity index 100% rename from src/client/components/ui/DropdownMenu/index.ts rename to legacy/src/client/components/ui/DropdownMenu/index.ts diff --git a/src/client/components/ui/EmptyPage/EmptyPage.module.scss b/legacy/src/client/components/ui/EmptyPage/EmptyPage.module.scss similarity index 100% rename from src/client/components/ui/EmptyPage/EmptyPage.module.scss rename to legacy/src/client/components/ui/EmptyPage/EmptyPage.module.scss diff --git a/src/client/components/ui/EmptyPage/EmptyPage.test.tsx b/legacy/src/client/components/ui/EmptyPage/EmptyPage.test.tsx similarity index 100% rename from src/client/components/ui/EmptyPage/EmptyPage.test.tsx rename to legacy/src/client/components/ui/EmptyPage/EmptyPage.test.tsx diff --git a/src/client/components/ui/EmptyPage/EmptyPage.tsx b/legacy/src/client/components/ui/EmptyPage/EmptyPage.tsx similarity index 100% rename from src/client/components/ui/EmptyPage/EmptyPage.tsx rename to legacy/src/client/components/ui/EmptyPage/EmptyPage.tsx diff --git a/src/client/components/ui/EmptyPage/index.ts b/legacy/src/client/components/ui/EmptyPage/index.ts similarity index 100% rename from src/client/components/ui/EmptyPage/index.ts rename to legacy/src/client/components/ui/EmptyPage/index.ts diff --git a/src/client/components/ui/ErrorPage/ErrorPage.module.scss b/legacy/src/client/components/ui/ErrorPage/ErrorPage.module.scss similarity index 100% rename from src/client/components/ui/ErrorPage/ErrorPage.module.scss rename to legacy/src/client/components/ui/ErrorPage/ErrorPage.module.scss diff --git a/src/client/components/ui/ErrorPage/ErrorPage.test.tsx b/legacy/src/client/components/ui/ErrorPage/ErrorPage.test.tsx similarity index 100% rename from src/client/components/ui/ErrorPage/ErrorPage.test.tsx rename to legacy/src/client/components/ui/ErrorPage/ErrorPage.test.tsx diff --git a/src/client/components/ui/ErrorPage/ErrorPage.tsx b/legacy/src/client/components/ui/ErrorPage/ErrorPage.tsx similarity index 100% rename from src/client/components/ui/ErrorPage/ErrorPage.tsx rename to legacy/src/client/components/ui/ErrorPage/ErrorPage.tsx diff --git a/src/client/components/ui/ErrorPage/index.ts b/legacy/src/client/components/ui/ErrorPage/index.ts similarity index 100% rename from src/client/components/ui/ErrorPage/index.ts rename to legacy/src/client/components/ui/ErrorPage/index.ts diff --git a/src/client/components/ui/Input/Input.test.tsx b/legacy/src/client/components/ui/Input/Input.test.tsx similarity index 100% rename from src/client/components/ui/Input/Input.test.tsx rename to legacy/src/client/components/ui/Input/Input.test.tsx diff --git a/src/client/components/ui/Input/Input.tsx b/legacy/src/client/components/ui/Input/Input.tsx similarity index 100% rename from src/client/components/ui/Input/Input.tsx rename to legacy/src/client/components/ui/Input/Input.tsx diff --git a/src/client/components/ui/Input/index.ts b/legacy/src/client/components/ui/Input/index.ts similarity index 100% rename from src/client/components/ui/Input/index.ts rename to legacy/src/client/components/ui/Input/index.ts diff --git a/src/client/components/ui/OtpInput/OtpInput.module.scss b/legacy/src/client/components/ui/OtpInput/OtpInput.module.scss similarity index 100% rename from src/client/components/ui/OtpInput/OtpInput.module.scss rename to legacy/src/client/components/ui/OtpInput/OtpInput.module.scss diff --git a/src/client/components/ui/OtpInput/OtpInput.test.tsx b/legacy/src/client/components/ui/OtpInput/OtpInput.test.tsx similarity index 100% rename from src/client/components/ui/OtpInput/OtpInput.test.tsx rename to legacy/src/client/components/ui/OtpInput/OtpInput.test.tsx diff --git a/src/client/components/ui/OtpInput/OtpInput.tsx b/legacy/src/client/components/ui/OtpInput/OtpInput.tsx similarity index 100% rename from src/client/components/ui/OtpInput/OtpInput.tsx rename to legacy/src/client/components/ui/OtpInput/OtpInput.tsx diff --git a/src/client/components/ui/OtpInput/index.ts b/legacy/src/client/components/ui/OtpInput/index.ts similarity index 100% rename from src/client/components/ui/OtpInput/index.ts rename to legacy/src/client/components/ui/OtpInput/index.ts diff --git a/src/client/components/ui/Pagination/Pagination.module.scss b/legacy/src/client/components/ui/Pagination/Pagination.module.scss similarity index 100% rename from src/client/components/ui/Pagination/Pagination.module.scss rename to legacy/src/client/components/ui/Pagination/Pagination.module.scss diff --git a/src/client/components/ui/Pagination/Pagination.tsx b/legacy/src/client/components/ui/Pagination/Pagination.tsx similarity index 100% rename from src/client/components/ui/Pagination/Pagination.tsx rename to legacy/src/client/components/ui/Pagination/Pagination.tsx diff --git a/src/client/components/ui/ScrollArea/ScrollArea.module.css b/legacy/src/client/components/ui/ScrollArea/ScrollArea.module.css similarity index 100% rename from src/client/components/ui/ScrollArea/ScrollArea.module.css rename to legacy/src/client/components/ui/ScrollArea/ScrollArea.module.css diff --git a/src/client/components/ui/ScrollArea/ScrollArea.tsx b/legacy/src/client/components/ui/ScrollArea/ScrollArea.tsx similarity index 100% rename from src/client/components/ui/ScrollArea/ScrollArea.tsx rename to legacy/src/client/components/ui/ScrollArea/ScrollArea.tsx diff --git a/src/client/components/ui/ScrollArea/index.ts b/legacy/src/client/components/ui/ScrollArea/index.ts similarity index 100% rename from src/client/components/ui/ScrollArea/index.ts rename to legacy/src/client/components/ui/ScrollArea/index.ts diff --git a/src/client/components/ui/Select/Select.tsx b/legacy/src/client/components/ui/Select/Select.tsx similarity index 100% rename from src/client/components/ui/Select/Select.tsx rename to legacy/src/client/components/ui/Select/Select.tsx diff --git a/src/client/components/ui/Select/index.ts b/legacy/src/client/components/ui/Select/index.ts similarity index 100% rename from src/client/components/ui/Select/index.ts rename to legacy/src/client/components/ui/Select/index.ts diff --git a/src/client/components/ui/Switch/Switch.module.scss b/legacy/src/client/components/ui/Switch/Switch.module.scss similarity index 100% rename from src/client/components/ui/Switch/Switch.module.scss rename to legacy/src/client/components/ui/Switch/Switch.module.scss diff --git a/src/client/components/ui/Switch/Switch.test.tsx b/legacy/src/client/components/ui/Switch/Switch.test.tsx similarity index 100% rename from src/client/components/ui/Switch/Switch.test.tsx rename to legacy/src/client/components/ui/Switch/Switch.test.tsx diff --git a/src/client/components/ui/Switch/Switch.tsx b/legacy/src/client/components/ui/Switch/Switch.tsx similarity index 100% rename from src/client/components/ui/Switch/Switch.tsx rename to legacy/src/client/components/ui/Switch/Switch.tsx diff --git a/src/client/components/ui/Switch/index.ts b/legacy/src/client/components/ui/Switch/index.ts similarity index 100% rename from src/client/components/ui/Switch/index.ts rename to legacy/src/client/components/ui/Switch/index.ts diff --git a/src/client/components/ui/Table/Table.tsx b/legacy/src/client/components/ui/Table/Table.tsx similarity index 100% rename from src/client/components/ui/Table/Table.tsx rename to legacy/src/client/components/ui/Table/Table.tsx diff --git a/src/client/components/ui/Table/index.ts b/legacy/src/client/components/ui/Table/index.ts similarity index 100% rename from src/client/components/ui/Table/index.ts rename to legacy/src/client/components/ui/Table/index.ts diff --git a/src/client/components/ui/tabs/index.ts b/legacy/src/client/components/ui/tabs/index.ts similarity index 100% rename from src/client/components/ui/tabs/index.ts rename to legacy/src/client/components/ui/tabs/index.ts diff --git a/src/client/components/ui/tabs/tabs.module.scss b/legacy/src/client/components/ui/tabs/tabs.module.scss similarity index 100% rename from src/client/components/ui/tabs/tabs.module.scss rename to legacy/src/client/components/ui/tabs/tabs.module.scss diff --git a/src/client/components/ui/tabs/tabs.tsx b/legacy/src/client/components/ui/tabs/tabs.tsx similarity index 100% rename from src/client/components/ui/tabs/tabs.tsx rename to legacy/src/client/components/ui/tabs/tabs.tsx diff --git a/src/client/hooks/__tests__/useDisclosure.test.ts b/legacy/src/client/hooks/__tests__/useDisclosure.test.ts similarity index 100% rename from src/client/hooks/__tests__/useDisclosure.test.ts rename to legacy/src/client/hooks/__tests__/useDisclosure.test.ts diff --git a/src/client/hooks/useDisclosure.ts b/legacy/src/client/hooks/useDisclosure.ts similarity index 100% rename from src/client/hooks/useDisclosure.ts rename to legacy/src/client/hooks/useDisclosure.ts diff --git a/src/client/hooks/useInfiniteScroll.ts b/legacy/src/client/hooks/useInfiniteScroll.ts similarity index 100% rename from src/client/hooks/useInfiniteScroll.ts rename to legacy/src/client/hooks/useInfiniteScroll.ts diff --git a/src/client/messages/ach-UG.json b/legacy/src/client/messages/ach-UG.json similarity index 100% rename from src/client/messages/ach-UG.json rename to legacy/src/client/messages/ach-UG.json diff --git a/src/client/messages/af-ZA.json b/legacy/src/client/messages/af-ZA.json similarity index 100% rename from src/client/messages/af-ZA.json rename to legacy/src/client/messages/af-ZA.json diff --git a/src/client/messages/ar-SA.json b/legacy/src/client/messages/ar-SA.json similarity index 100% rename from src/client/messages/ar-SA.json rename to legacy/src/client/messages/ar-SA.json diff --git a/src/client/messages/ca-ES.json b/legacy/src/client/messages/ca-ES.json similarity index 100% rename from src/client/messages/ca-ES.json rename to legacy/src/client/messages/ca-ES.json diff --git a/src/client/messages/cs-CZ.json b/legacy/src/client/messages/cs-CZ.json similarity index 100% rename from src/client/messages/cs-CZ.json rename to legacy/src/client/messages/cs-CZ.json diff --git a/src/client/messages/da-DK.json b/legacy/src/client/messages/da-DK.json similarity index 100% rename from src/client/messages/da-DK.json rename to legacy/src/client/messages/da-DK.json diff --git a/src/client/messages/de-DE.json b/legacy/src/client/messages/de-DE.json similarity index 100% rename from src/client/messages/de-DE.json rename to legacy/src/client/messages/de-DE.json diff --git a/src/client/messages/el-GR.json b/legacy/src/client/messages/el-GR.json similarity index 100% rename from src/client/messages/el-GR.json rename to legacy/src/client/messages/el-GR.json diff --git a/src/client/messages/en-US.json b/legacy/src/client/messages/en-US.json similarity index 100% rename from src/client/messages/en-US.json rename to legacy/src/client/messages/en-US.json diff --git a/src/client/messages/en.json b/legacy/src/client/messages/en.json similarity index 100% rename from src/client/messages/en.json rename to legacy/src/client/messages/en.json diff --git a/src/client/messages/es-ES.json b/legacy/src/client/messages/es-ES.json similarity index 100% rename from src/client/messages/es-ES.json rename to legacy/src/client/messages/es-ES.json diff --git a/src/client/messages/fi-FI.json b/legacy/src/client/messages/fi-FI.json similarity index 100% rename from src/client/messages/fi-FI.json rename to legacy/src/client/messages/fi-FI.json diff --git a/src/client/messages/fr-FR.json b/legacy/src/client/messages/fr-FR.json similarity index 100% rename from src/client/messages/fr-FR.json rename to legacy/src/client/messages/fr-FR.json diff --git a/src/client/messages/he-IL.json b/legacy/src/client/messages/he-IL.json similarity index 100% rename from src/client/messages/he-IL.json rename to legacy/src/client/messages/he-IL.json diff --git a/src/client/messages/hu-HU.json b/legacy/src/client/messages/hu-HU.json similarity index 100% rename from src/client/messages/hu-HU.json rename to legacy/src/client/messages/hu-HU.json diff --git a/src/client/messages/it-IT.json b/legacy/src/client/messages/it-IT.json similarity index 100% rename from src/client/messages/it-IT.json rename to legacy/src/client/messages/it-IT.json diff --git a/src/client/messages/ja-JP.json b/legacy/src/client/messages/ja-JP.json similarity index 100% rename from src/client/messages/ja-JP.json rename to legacy/src/client/messages/ja-JP.json diff --git a/src/client/messages/ko-KR.json b/legacy/src/client/messages/ko-KR.json similarity index 100% rename from src/client/messages/ko-KR.json rename to legacy/src/client/messages/ko-KR.json diff --git a/src/client/messages/nl-NL.json b/legacy/src/client/messages/nl-NL.json similarity index 100% rename from src/client/messages/nl-NL.json rename to legacy/src/client/messages/nl-NL.json diff --git a/src/client/messages/no-NO.json b/legacy/src/client/messages/no-NO.json similarity index 100% rename from src/client/messages/no-NO.json rename to legacy/src/client/messages/no-NO.json diff --git a/src/client/messages/pl-PL.json b/legacy/src/client/messages/pl-PL.json similarity index 100% rename from src/client/messages/pl-PL.json rename to legacy/src/client/messages/pl-PL.json diff --git a/src/client/messages/pt-BR.json b/legacy/src/client/messages/pt-BR.json similarity index 100% rename from src/client/messages/pt-BR.json rename to legacy/src/client/messages/pt-BR.json diff --git a/src/client/messages/pt-PT.json b/legacy/src/client/messages/pt-PT.json similarity index 100% rename from src/client/messages/pt-PT.json rename to legacy/src/client/messages/pt-PT.json diff --git a/src/client/messages/ro-RO.json b/legacy/src/client/messages/ro-RO.json similarity index 100% rename from src/client/messages/ro-RO.json rename to legacy/src/client/messages/ro-RO.json diff --git a/src/client/messages/ru-RU.json b/legacy/src/client/messages/ru-RU.json similarity index 100% rename from src/client/messages/ru-RU.json rename to legacy/src/client/messages/ru-RU.json diff --git a/src/client/messages/sr-SP.json b/legacy/src/client/messages/sr-SP.json similarity index 100% rename from src/client/messages/sr-SP.json rename to legacy/src/client/messages/sr-SP.json diff --git a/src/client/messages/sv-SE.json b/legacy/src/client/messages/sv-SE.json similarity index 100% rename from src/client/messages/sv-SE.json rename to legacy/src/client/messages/sv-SE.json diff --git a/src/client/messages/tr-TR.json b/legacy/src/client/messages/tr-TR.json similarity index 100% rename from src/client/messages/tr-TR.json rename to legacy/src/client/messages/tr-TR.json diff --git a/src/client/messages/uk-UA.json b/legacy/src/client/messages/uk-UA.json similarity index 100% rename from src/client/messages/uk-UA.json rename to legacy/src/client/messages/uk-UA.json diff --git a/src/client/messages/vi-VN.json b/legacy/src/client/messages/vi-VN.json similarity index 100% rename from src/client/messages/vi-VN.json rename to legacy/src/client/messages/vi-VN.json diff --git a/src/client/messages/zh-CN.json b/legacy/src/client/messages/zh-CN.json similarity index 100% rename from src/client/messages/zh-CN.json rename to legacy/src/client/messages/zh-CN.json diff --git a/src/client/messages/zh-TW.json b/legacy/src/client/messages/zh-TW.json similarity index 100% rename from src/client/messages/zh-TW.json rename to legacy/src/client/messages/zh-TW.json diff --git a/src/client/mocks/browser.ts b/legacy/src/client/mocks/browser.ts similarity index 100% rename from src/client/mocks/browser.ts rename to legacy/src/client/mocks/browser.ts diff --git a/src/client/mocks/handlers.ts b/legacy/src/client/mocks/handlers.ts similarity index 100% rename from src/client/mocks/handlers.ts rename to legacy/src/client/mocks/handlers.ts diff --git a/src/client/mocks/index.ts b/legacy/src/client/mocks/index.ts similarity index 100% rename from src/client/mocks/index.ts rename to legacy/src/client/mocks/index.ts diff --git a/src/client/mocks/server.ts b/legacy/src/client/mocks/server.ts similarity index 100% rename from src/client/mocks/server.ts rename to legacy/src/client/mocks/server.ts diff --git a/src/client/state/uiStore.ts b/legacy/src/client/state/uiStore.ts similarity index 100% rename from src/client/state/uiStore.ts rename to legacy/src/client/state/uiStore.ts diff --git a/src/client/utils/__tests__/typescript.test.ts b/legacy/src/client/utils/__tests__/typescript.test.ts similarity index 100% rename from src/client/utils/__tests__/typescript.test.ts rename to legacy/src/client/utils/__tests__/typescript.test.ts diff --git a/src/client/utils/typescript.ts b/legacy/src/client/utils/typescript.ts similarity index 100% rename from src/client/utils/typescript.ts rename to legacy/src/client/utils/typescript.ts diff --git a/src/config/constants.ts b/legacy/src/config/constants.ts similarity index 100% rename from src/config/constants.ts rename to legacy/src/config/constants.ts diff --git a/src/config/index.ts b/legacy/src/config/index.ts similarity index 100% rename from src/config/index.ts rename to legacy/src/config/index.ts diff --git a/src/i18n.ts b/legacy/src/i18n.ts similarity index 100% rename from src/i18n.ts rename to legacy/src/i18n.ts diff --git a/src/instrumentation.ts b/legacy/src/instrumentation.ts similarity index 100% rename from src/instrumentation.ts rename to legacy/src/instrumentation.ts diff --git a/src/inversify.config.ts b/legacy/src/inversify.config.ts similarity index 100% rename from src/inversify.config.ts rename to legacy/src/inversify.config.ts diff --git a/src/lib/get-translator.ts b/legacy/src/lib/get-translator.ts similarity index 100% rename from src/lib/get-translator.ts rename to legacy/src/lib/get-translator.ts diff --git a/src/lib/helpers/castAppConfig.ts b/legacy/src/lib/helpers/castAppConfig.ts similarity index 100% rename from src/lib/helpers/castAppConfig.ts rename to legacy/src/lib/helpers/castAppConfig.ts diff --git a/src/lib/helpers/text-helpers.ts b/legacy/src/lib/helpers/text-helpers.ts similarity index 100% rename from src/lib/helpers/text-helpers.ts rename to legacy/src/lib/helpers/text-helpers.ts diff --git a/src/lib/safe-action.ts b/legacy/src/lib/safe-action.ts similarity index 100% rename from src/lib/safe-action.ts rename to legacy/src/lib/safe-action.ts diff --git a/src/lib/socket/useSocket.test.ts b/legacy/src/lib/socket/useSocket.test.ts similarity index 100% rename from src/lib/socket/useSocket.test.ts rename to legacy/src/lib/socket/useSocket.test.ts diff --git a/src/lib/socket/useSocket.ts b/legacy/src/lib/socket/useSocket.ts similarity index 100% rename from src/lib/socket/useSocket.ts rename to legacy/src/lib/socket/useSocket.ts diff --git a/src/lib/themes.ts b/legacy/src/lib/themes.ts similarity index 100% rename from src/lib/themes.ts rename to legacy/src/lib/themes.ts diff --git a/src/server/common/fs.helpers.ts b/legacy/src/server/common/fs.helpers.ts similarity index 100% rename from src/server/common/fs.helpers.ts rename to legacy/src/server/common/fs.helpers.ts diff --git a/src/server/common/session-manager.ts b/legacy/src/server/common/session-manager.ts similarity index 100% rename from src/server/common/session-manager.ts rename to legacy/src/server/common/session-manager.ts diff --git a/src/server/common/typescript.helpers.ts b/legacy/src/server/common/typescript.helpers.ts similarity index 100% rename from src/server/common/typescript.helpers.ts rename to legacy/src/server/common/typescript.helpers.ts diff --git a/src/server/core/EventDispatcher/EventDispatcher.ts b/legacy/src/server/core/EventDispatcher/EventDispatcher.ts similarity index 100% rename from src/server/core/EventDispatcher/EventDispatcher.ts rename to legacy/src/server/core/EventDispatcher/EventDispatcher.ts diff --git a/src/server/core/EventDispatcher/index.ts b/legacy/src/server/core/EventDispatcher/index.ts similarity index 100% rename from src/server/core/EventDispatcher/index.ts rename to legacy/src/server/core/EventDispatcher/index.ts diff --git a/src/server/core/TipiConfig/TipiConfig.test.ts b/legacy/src/server/core/TipiConfig/TipiConfig.test.ts similarity index 100% rename from src/server/core/TipiConfig/TipiConfig.test.ts rename to legacy/src/server/core/TipiConfig/TipiConfig.test.ts diff --git a/src/server/core/TipiConfig/TipiConfig.ts b/legacy/src/server/core/TipiConfig/TipiConfig.ts similarity index 100% rename from src/server/core/TipiConfig/TipiConfig.ts rename to legacy/src/server/core/TipiConfig/TipiConfig.ts diff --git a/src/server/core/TipiConfig/index.ts b/legacy/src/server/core/TipiConfig/index.ts similarity index 100% rename from src/server/core/TipiConfig/index.ts rename to legacy/src/server/core/TipiConfig/index.ts diff --git a/src/server/queries/apps/apps.queries.ts b/legacy/src/server/queries/apps/apps.queries.ts similarity index 100% rename from src/server/queries/apps/apps.queries.ts rename to legacy/src/server/queries/apps/apps.queries.ts diff --git a/src/server/queries/auth/auth.queries.ts b/legacy/src/server/queries/auth/auth.queries.ts similarity index 100% rename from src/server/queries/auth/auth.queries.ts rename to legacy/src/server/queries/auth/auth.queries.ts diff --git a/src/server/queries/links/links.queries.ts b/legacy/src/server/queries/links/links.queries.ts similarity index 100% rename from src/server/queries/links/links.queries.ts rename to legacy/src/server/queries/links/links.queries.ts diff --git a/src/server/services/app-backup/app-backup.service.ts b/legacy/src/server/services/app-backup/app-backup.service.ts similarity index 100% rename from src/server/services/app-backup/app-backup.service.ts rename to legacy/src/server/services/app-backup/app-backup.service.ts diff --git a/src/server/services/app-backup/commands/create-app-backup-command.ts b/legacy/src/server/services/app-backup/commands/create-app-backup-command.ts similarity index 100% rename from src/server/services/app-backup/commands/create-app-backup-command.ts rename to legacy/src/server/services/app-backup/commands/create-app-backup-command.ts diff --git a/src/server/services/app-backup/commands/delete-app-backup-command.ts b/legacy/src/server/services/app-backup/commands/delete-app-backup-command.ts similarity index 100% rename from src/server/services/app-backup/commands/delete-app-backup-command.ts rename to legacy/src/server/services/app-backup/commands/delete-app-backup-command.ts diff --git a/src/server/services/app-backup/commands/get-app-backups-command.ts b/legacy/src/server/services/app-backup/commands/get-app-backups-command.ts similarity index 100% rename from src/server/services/app-backup/commands/get-app-backups-command.ts rename to legacy/src/server/services/app-backup/commands/get-app-backups-command.ts diff --git a/src/server/services/app-backup/commands/index.ts b/legacy/src/server/services/app-backup/commands/index.ts similarity index 100% rename from src/server/services/app-backup/commands/index.ts rename to legacy/src/server/services/app-backup/commands/index.ts diff --git a/src/server/services/app-backup/commands/restore-app-backup-command.ts b/legacy/src/server/services/app-backup/commands/restore-app-backup-command.ts similarity index 100% rename from src/server/services/app-backup/commands/restore-app-backup-command.ts rename to legacy/src/server/services/app-backup/commands/restore-app-backup-command.ts diff --git a/src/server/services/app-backup/commands/types.ts b/legacy/src/server/services/app-backup/commands/types.ts similarity index 100% rename from src/server/services/app-backup/commands/types.ts rename to legacy/src/server/services/app-backup/commands/types.ts diff --git a/src/server/services/app-catalog/app-catalog-cache.ts b/legacy/src/server/services/app-catalog/app-catalog-cache.ts similarity index 100% rename from src/server/services/app-catalog/app-catalog-cache.ts rename to legacy/src/server/services/app-catalog/app-catalog-cache.ts diff --git a/src/server/services/app-catalog/app-catalog.service.test.ts b/legacy/src/server/services/app-catalog/app-catalog.service.test.ts similarity index 100% rename from src/server/services/app-catalog/app-catalog.service.test.ts rename to legacy/src/server/services/app-catalog/app-catalog.service.test.ts diff --git a/src/server/services/app-catalog/app-catalog.service.ts b/legacy/src/server/services/app-catalog/app-catalog.service.ts similarity index 100% rename from src/server/services/app-catalog/app-catalog.service.ts rename to legacy/src/server/services/app-catalog/app-catalog.service.ts diff --git a/src/server/services/app-catalog/apps.helpers.test.ts b/legacy/src/server/services/app-catalog/apps.helpers.test.ts similarity index 100% rename from src/server/services/app-catalog/apps.helpers.test.ts rename to legacy/src/server/services/app-catalog/apps.helpers.test.ts diff --git a/src/server/services/app-lifecycle/app-lifecycle.service.test.ts b/legacy/src/server/services/app-lifecycle/app-lifecycle.service.test.ts similarity index 100% rename from src/server/services/app-lifecycle/app-lifecycle.service.test.ts rename to legacy/src/server/services/app-lifecycle/app-lifecycle.service.test.ts diff --git a/src/server/services/app-lifecycle/app-lifecycle.service.ts b/legacy/src/server/services/app-lifecycle/app-lifecycle.service.ts similarity index 100% rename from src/server/services/app-lifecycle/app-lifecycle.service.ts rename to legacy/src/server/services/app-lifecycle/app-lifecycle.service.ts diff --git a/src/server/services/app-lifecycle/commands/__tests__/install-app-command.test.ts b/legacy/src/server/services/app-lifecycle/commands/__tests__/install-app-command.test.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/__tests__/install-app-command.test.ts rename to legacy/src/server/services/app-lifecycle/commands/__tests__/install-app-command.test.ts diff --git a/src/server/services/app-lifecycle/commands/__tests__/reset-app-command.test.ts b/legacy/src/server/services/app-lifecycle/commands/__tests__/reset-app-command.test.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/__tests__/reset-app-command.test.ts rename to legacy/src/server/services/app-lifecycle/commands/__tests__/reset-app-command.test.ts diff --git a/src/server/services/app-lifecycle/commands/__tests__/restart-app-command.test.ts b/legacy/src/server/services/app-lifecycle/commands/__tests__/restart-app-command.test.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/__tests__/restart-app-command.test.ts rename to legacy/src/server/services/app-lifecycle/commands/__tests__/restart-app-command.test.ts diff --git a/src/server/services/app-lifecycle/commands/__tests__/start-app-command.test.ts b/legacy/src/server/services/app-lifecycle/commands/__tests__/start-app-command.test.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/__tests__/start-app-command.test.ts rename to legacy/src/server/services/app-lifecycle/commands/__tests__/start-app-command.test.ts diff --git a/src/server/services/app-lifecycle/commands/__tests__/stop-app-command.test.ts b/legacy/src/server/services/app-lifecycle/commands/__tests__/stop-app-command.test.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/__tests__/stop-app-command.test.ts rename to legacy/src/server/services/app-lifecycle/commands/__tests__/stop-app-command.test.ts diff --git a/src/server/services/app-lifecycle/commands/__tests__/uninstall-app-command.test.ts b/legacy/src/server/services/app-lifecycle/commands/__tests__/uninstall-app-command.test.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/__tests__/uninstall-app-command.test.ts rename to legacy/src/server/services/app-lifecycle/commands/__tests__/uninstall-app-command.test.ts diff --git a/src/server/services/app-lifecycle/commands/__tests__/update-app-command.test.ts b/legacy/src/server/services/app-lifecycle/commands/__tests__/update-app-command.test.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/__tests__/update-app-command.test.ts rename to legacy/src/server/services/app-lifecycle/commands/__tests__/update-app-command.test.ts diff --git a/src/server/services/app-lifecycle/commands/__tests__/update-app-config-command.test.ts b/legacy/src/server/services/app-lifecycle/commands/__tests__/update-app-config-command.test.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/__tests__/update-app-config-command.test.ts rename to legacy/src/server/services/app-lifecycle/commands/__tests__/update-app-config-command.test.ts diff --git a/src/server/services/app-lifecycle/commands/index.ts b/legacy/src/server/services/app-lifecycle/commands/index.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/index.ts rename to legacy/src/server/services/app-lifecycle/commands/index.ts diff --git a/src/server/services/app-lifecycle/commands/install-app-command.ts b/legacy/src/server/services/app-lifecycle/commands/install-app-command.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/install-app-command.ts rename to legacy/src/server/services/app-lifecycle/commands/install-app-command.ts diff --git a/src/server/services/app-lifecycle/commands/reset-app-command.ts b/legacy/src/server/services/app-lifecycle/commands/reset-app-command.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/reset-app-command.ts rename to legacy/src/server/services/app-lifecycle/commands/reset-app-command.ts diff --git a/src/server/services/app-lifecycle/commands/restart-app-command.ts b/legacy/src/server/services/app-lifecycle/commands/restart-app-command.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/restart-app-command.ts rename to legacy/src/server/services/app-lifecycle/commands/restart-app-command.ts diff --git a/src/server/services/app-lifecycle/commands/start-app-command.ts b/legacy/src/server/services/app-lifecycle/commands/start-app-command.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/start-app-command.ts rename to legacy/src/server/services/app-lifecycle/commands/start-app-command.ts diff --git a/src/server/services/app-lifecycle/commands/stop-app-command.ts b/legacy/src/server/services/app-lifecycle/commands/stop-app-command.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/stop-app-command.ts rename to legacy/src/server/services/app-lifecycle/commands/stop-app-command.ts diff --git a/src/server/services/app-lifecycle/commands/types.ts b/legacy/src/server/services/app-lifecycle/commands/types.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/types.ts rename to legacy/src/server/services/app-lifecycle/commands/types.ts diff --git a/src/server/services/app-lifecycle/commands/uninstall-app-command.ts b/legacy/src/server/services/app-lifecycle/commands/uninstall-app-command.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/uninstall-app-command.ts rename to legacy/src/server/services/app-lifecycle/commands/uninstall-app-command.ts diff --git a/src/server/services/app-lifecycle/commands/update-app-command.ts b/legacy/src/server/services/app-lifecycle/commands/update-app-command.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/update-app-command.ts rename to legacy/src/server/services/app-lifecycle/commands/update-app-command.ts diff --git a/src/server/services/app-lifecycle/commands/update-app-config-command.ts b/legacy/src/server/services/app-lifecycle/commands/update-app-config-command.ts similarity index 100% rename from src/server/services/app-lifecycle/commands/update-app-config-command.ts rename to legacy/src/server/services/app-lifecycle/commands/update-app-config-command.ts diff --git a/src/server/services/auth/auth.service.test.ts b/legacy/src/server/services/auth/auth.service.test.ts similarity index 100% rename from src/server/services/auth/auth.service.test.ts rename to legacy/src/server/services/auth/auth.service.test.ts diff --git a/src/server/services/auth/auth.service.ts b/legacy/src/server/services/auth/auth.service.ts similarity index 100% rename from src/server/services/auth/auth.service.ts rename to legacy/src/server/services/auth/auth.service.ts diff --git a/src/server/services/custom-links/custom-links.service.ts b/legacy/src/server/services/custom-links/custom-links.service.ts similarity index 100% rename from src/server/services/custom-links/custom-links.service.ts rename to legacy/src/server/services/custom-links/custom-links.service.ts diff --git a/src/server/services/system/index.ts b/legacy/src/server/services/system/index.ts similarity index 100% rename from src/server/services/system/index.ts rename to legacy/src/server/services/system/index.ts diff --git a/src/server/services/system/system.service.test.ts b/legacy/src/server/services/system/system.service.test.ts similarity index 100% rename from src/server/services/system/system.service.test.ts rename to legacy/src/server/services/system/system.service.test.ts diff --git a/src/server/services/system/system.service.ts b/legacy/src/server/services/system/system.service.ts similarity index 100% rename from src/server/services/system/system.service.ts rename to legacy/src/server/services/system/system.service.ts diff --git a/src/server/tests/apps.factory.ts b/legacy/src/server/tests/apps.factory.ts similarity index 100% rename from src/server/tests/apps.factory.ts rename to legacy/src/server/tests/apps.factory.ts diff --git a/src/server/tests/test-utils.ts b/legacy/src/server/tests/test-utils.ts similarity index 100% rename from src/server/tests/test-utils.ts rename to legacy/src/server/tests/test-utils.ts diff --git a/src/server/tests/user.factory.ts b/legacy/src/server/tests/user.factory.ts similarity index 100% rename from src/server/tests/user.factory.ts rename to legacy/src/server/tests/user.factory.ts diff --git a/src/server/utils/__tests__/encryption.test.ts b/legacy/src/server/utils/__tests__/encryption.test.ts similarity index 100% rename from src/server/utils/__tests__/encryption.test.ts rename to legacy/src/server/utils/__tests__/encryption.test.ts diff --git a/src/server/utils/encryption.ts b/legacy/src/server/utils/encryption.ts similarity index 100% rename from src/server/utils/encryption.ts rename to legacy/src/server/utils/encryption.ts diff --git a/src/server/utils/errors.ts b/legacy/src/server/utils/errors.ts similarity index 100% rename from src/server/utils/errors.ts rename to legacy/src/server/utils/errors.ts diff --git a/src/server/utils/network.ts b/legacy/src/server/utils/network.ts similarity index 100% rename from src/server/utils/network.ts rename to legacy/src/server/utils/network.ts diff --git a/src/server/utils/totp.ts b/legacy/src/server/utils/totp.ts similarity index 100% rename from src/server/utils/totp.ts rename to legacy/src/server/utils/totp.ts diff --git a/src/shared/internationalization/locales.ts b/legacy/src/shared/internationalization/locales.ts similarity index 100% rename from src/shared/internationalization/locales.ts rename to legacy/src/shared/internationalization/locales.ts diff --git a/src/utils/getCurrentLocale.ts b/legacy/src/utils/getCurrentLocale.ts similarity index 100% rename from src/utils/getCurrentLocale.ts rename to legacy/src/utils/getCurrentLocale.ts diff --git a/start.dev.sh b/legacy/start.dev.sh similarity index 100% rename from start.dev.sh rename to legacy/start.dev.sh diff --git a/start.prod.sh b/legacy/start.prod.sh similarity index 100% rename from start.prod.sh rename to legacy/start.prod.sh diff --git a/tests/client/test.setup.tsx b/legacy/tests/client/test.setup.tsx similarity index 100% rename from tests/client/test.setup.tsx rename to legacy/tests/client/test.setup.tsx diff --git a/tests/mocks/fs.ts b/legacy/tests/mocks/fs.ts similarity index 100% rename from tests/mocks/fs.ts rename to legacy/tests/mocks/fs.ts diff --git a/tests/server/test.setup.ts b/legacy/tests/server/test.setup.ts similarity index 100% rename from tests/server/test.setup.ts rename to legacy/tests/server/test.setup.ts diff --git a/tests/test-utils.tsx b/legacy/tests/test-utils.tsx similarity index 100% rename from tests/test-utils.tsx rename to legacy/tests/test-utils.tsx diff --git a/tsconfig.json b/legacy/tsconfig.json similarity index 100% rename from tsconfig.json rename to legacy/tsconfig.json diff --git a/vitest.workspace.ts b/legacy/vitest.workspace.ts similarity index 100% rename from vitest.workspace.ts rename to legacy/vitest.workspace.ts From c7d9b0c577ee8e4368af535cb387880e8001fc5a Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Fri, 8 Nov 2024 22:34:17 +0100 Subject: [PATCH 033/241] feat: add nest app --- .dockerignore | 19 + .gitignore | 82 + Dockerfile | 79 + Dockerfile.dev | 50 + biome.json | 42 +- docker-compose.dev.yml | 76 + docker-compose.prod.yml | 151 + openapi-ts.config.ts | 14 + package.json | 22 + packages/backend/README.md | 85 + .../assets/traefik/dynamic/dynamic.yml | 14 + packages/backend/assets/traefik/traefik.yml | 32 + packages/backend/nest-cli.json | 19 + packages/backend/package.json | 94 + .../backend/src/@types/express/index.d.ts | 9 + packages/backend/src/app.controller.ts | 68 + packages/backend/src/app.dto.ts | 46 + packages/backend/src/app.module.ts | 82 + packages/backend/src/app.service.ts | 230 + packages/backend/src/common/constants.ts | 6 + .../src/common/error/exception.filter.ts | 41 + .../src/common/error/translatable-error.ts | 10 + .../backend/src/common/helpers/env-helpers.ts | 125 + .../src/common/helpers/error-helpers.ts | 78 + .../src/common/helpers/exec-helpers.ts | 20 + .../src/common/helpers/file-helpers.ts | 29 + .../src/core/archive/archive.module.ts | 9 + .../src/core/archive/archive.service.ts | 13 + .../backend/src/core/cache/cache.module.ts | 11 + .../backend/src/core/cache/cache.service.ts | 55 + .../src/core/config/configuration.module.ts | 12 + .../src/core/config/configuration.service.ts | 135 + .../database/database-migrator.service.ts | 51 + .../src/core/database/database.module.ts | 12 + .../src/core/database/database.service.ts | 37 + .../00000-create-migrations-table.sql | 6 + .../database/migrations/00001-initial.sql | 69 + .../migrations/00002-add-app-version.sql | 32 + .../migrations/00003-add-status-updating.sql | 15 + .../00004-add-exposed-domain-fields.sql | 23 + .../migrations/00005-add-user-operator.sql | 18 + .../migrations/00006-add-totp-user-fields.sql | 23 + .../migrations/00007-add-locale-user-col.sql | 15 + ...8-merge-config-with-domain-and-exposed.sql | 14 + .../migrations/00009-add-guest-dashboard.sql | 15 + .../migrations/00010-add-status-resetting.sql | 15 + .../migrations/00011-create-link-table.sql | 11 + .../00012-link-table-description.sql | 2 + .../migrations/00013-app-local-exposed.sql | 31 + .../migrations/00014-restarting-state.sql | 15 + .../migrations/00015-backingup-state.sql | 31 + .../00016-user-has-seen-welcome.sql | 15 + packages/backend/src/core/database/schema.ts | 95 + .../src/core/encryption/encryption.module.ts | 10 + .../src/core/encryption/encryption.service.ts | 49 + .../src/core/filesystem/filesystem.module.ts | 10 + .../src/core/filesystem/filesystem.service.ts | 155 + .../src/core/health/health.controller.ts | 19 + .../backend/src/core/health/health.module.ts | 12 + .../backend/src/core/logger/logger.module.ts | 19 + .../backend/src/core/logger/logger.service.ts | 118 + .../backend/src/core/socket/socket-schemas.ts | 82 + .../backend/src/core/socket/socket.health.ts | 25 + .../backend/src/core/socket/socket.module.ts | 10 + .../backend/src/core/socket/socket.service.ts | 61 + packages/backend/src/exports/index.ts | 3 + packages/backend/src/main.ts | 56 + packages/backend/src/metadata.ts | 12 + .../app-lifecycle-command.factory.ts | 52 + .../app-lifecycle/app-lifecycle.controller.ts | 42 + .../app-lifecycle/app-lifecycle.module.ts | 18 + .../app-lifecycle/app-lifecycle.service.ts | 262 + .../commands/backup-app-command.ts | 36 + .../modules/app-lifecycle/commands/command.ts | 54 + .../commands/install-app-command.ts | 53 + .../commands/reset-app-command.ts | 61 + .../commands/restart-app-command.ts | 50 + .../commands/restore-app-command.ts | 37 + .../commands/start-app-command.ts | 41 + .../commands/stop-app-command.ts | 47 + .../commands/uninstall-app-command.ts | 42 + .../app-lifecycle/dto/app-lifecycle.dto.ts | 15 + .../src/modules/apps/app-catalog.service.ts | 217 + .../src/modules/apps/app-files-manager.ts | 402 + .../backend/src/modules/apps/app.helpers.ts | 101 + .../src/modules/apps/apps.controller.ts | 64 + .../backend/src/modules/apps/apps.module.ts | 16 + .../src/modules/apps/apps.repository.ts | 94 + .../src/modules/apps/dto/app-info.dto.ts | 106 + .../backend/src/modules/apps/dto/app.dto.ts | 80 + .../src/modules/auth/auth.controller.ts | 163 + .../backend/src/modules/auth/auth.guard.ts | 15 + .../src/modules/auth/auth.middleware.ts | 26 + .../backend/src/modules/auth/auth.module.ts | 13 + .../backend/src/modules/auth/auth.service.ts | 393 + .../backend/src/modules/auth/dto/auth.dto.ts | 100 + .../src/modules/auth/session.manager.ts | 59 + .../modules/auth/utils/totp-authenticator.ts | 5 + .../src/modules/backups/backup.manager.ts | 150 + .../src/modules/backups/backups.controller.ts | 38 + .../src/modules/backups/backups.module.ts | 17 + .../src/modules/backups/backups.service.ts | 110 + .../src/modules/backups/dto/backups.dto.ts | 38 + .../docker/builders/compose.builder.ts | 40 + .../src/modules/docker/builders/schemas.ts | 67 + .../docker/builders/service.builder.ts | 384 + .../docker/builders/traefik-labels.builder.ts | 55 + .../modules/docker/commands/app-logs-init.ts | 60 + .../docker/commands/runtipi-logs-init.ts | 61 + .../src/modules/docker/commands/type.ts | 6 + .../modules/docker/docker-command.factory.ts | 19 + .../src/modules/docker/docker.module.ts | 12 + .../src/modules/docker/docker.service.ts | 178 + .../modules/docker/helpers/colorize-logs.ts | 14 + .../backend/src/modules/env/env.module.ts | 9 + packages/backend/src/modules/env/env.utils.ts | 102 + .../src/modules/i18n/i18n.controller.ts | 13 + .../backend/src/modules/i18n/i18n.module.ts | 10 + .../backend/src/modules/i18n/i18n.service.ts | 26 + .../src/modules/i18n/translations/en.json | 344 + .../src/modules/links/dto/links.dto.ts | 41 + .../src/modules/links/links.controller.ts | 49 + .../backend/src/modules/links/links.module.ts | 11 + .../src/modules/links/links.repository.ts | 56 + .../src/modules/links/links.service.ts | 35 + .../src/modules/queue/entities/app-events.ts | 40 + .../src/modules/queue/entities/repo-events.ts | 16 + .../backend/src/modules/queue/queue.entity.ts | 118 + .../src/modules/queue/queue.factory.ts | 32 + .../backend/src/modules/queue/queue.health.ts | 22 + .../backend/src/modules/queue/queue.module.ts | 37 + .../backend/src/modules/repos/repos.module.ts | 11 + .../src/modules/repos/repos.service.ts | 163 + .../src/modules/system/dto/system.dto.ts | 14 + .../src/modules/system/system.controller.ts | 31 + .../src/modules/system/system.module.ts | 11 + .../src/modules/system/system.service.ts | 54 + .../backend/src/modules/user/dto/user.dto.ts | 13 + .../backend/src/modules/user/user.module.ts | 10 + .../src/modules/user/user.repository.ts | 79 + packages/backend/src/schemas/queue-schemas.ts | 33 + packages/backend/src/swagger.json | 2739 ++++ packages/backend/src/types/express/index.d.ts | 9 + packages/backend/tsconfig.json | 37 + packages/frontend/.gitignore | 27 + packages/frontend/README.md | 50 + packages/frontend/index.html | 23 + packages/frontend/package.json | 85 + packages/frontend/public/app-not-found.jpg | Bin 0 -> 9582 bytes packages/frontend/public/empty.svg | 1 + packages/frontend/public/error.png | Bin 0 -> 33481 bytes .../public/icons/apple-touch-icon.png | Bin 0 -> 6728 bytes .../frontend/public/icons/favicon-96x96.png | Bin 0 -> 3905 bytes packages/frontend/public/icons/favicon.ico | Bin 0 -> 15086 bytes packages/frontend/public/icons/favicon.svg | 3 + .../frontend/public/icons/site.webmanifest | 21 + .../public/icons/web-app-manifest-192x192.png | Bin 0 -> 6289 bytes .../public/icons/web-app-manifest-512x512.png | Bin 0 -> 20132 bytes packages/frontend/public/js/.gitkeep | 0 packages/frontend/public/mockServiceWorker.js | 284 + packages/frontend/public/placeholder.png | Bin 0 -> 136 bytes packages/frontend/public/tipi-christmas.png | Bin 0 -> 44889 bytes packages/frontend/public/tipi.png | Bin 0 -> 9462 bytes packages/frontend/public/vite.svg | 1 + packages/frontend/scripts/postinstall.sh | 3 + packages/frontend/src/App.css | 17 + packages/frontend/src/App.tsx | 16 + .../api-client/@tanstack/react-query.gen.ts | 983 ++ packages/frontend/src/api-client/index.ts | 4 + .../frontend/src/api-client/schemas.gen.ts | 1456 ++ .../frontend/src/api-client/services.gen.ts | 368 + packages/frontend/src/api-client/types.gen.ts | 846 + packages/frontend/src/assets/react.svg | 1 + .../src/components/app-logo/app-logo.css | 3 + .../src/components/app-logo/app-logo.tsx | 25 + .../app-store-layout-actions.css | 5 + .../app-store-layout-actions.tsx | 31 + .../category-selector/category-selector.tsx | 45 + .../components/date-format/date-format.tsx | 35 + .../src/components/empty-page/empty-page.css | 4 + .../src/components/empty-page/empty-page.tsx | 42 + .../src/components/error/error-page.tsx | 37 + .../src/components/file-size/file-size.tsx | 21 + .../src/components/header/guest-header.tsx | 91 + .../frontend/src/components/header/header.tsx | 105 + .../language-selector/language-selector.tsx | 52 + .../src/components/layouts/auth/layout.tsx | 27 + .../layouts/dashboard/layout-actions.tsx | 21 + .../components/layouts/dashboard/layout.css | 5 + .../components/layouts/dashboard/layout.tsx | 67 + .../logs-terminal/logs-terminal.css | 9 + .../logs-terminal/logs-terminal.tsx | 75 + .../src/components/markdown/markdown.tsx | 12 + .../frontend/src/components/navbar/navbar.tsx | 45 + .../src/components/page-title/page-title.tsx | 41 + .../providers/i18n/i18n-provider.tsx | 23 + .../location-listener/location-listener.tsx | 15 + .../src/components/providers/providers.tsx | 58 + .../providers/socket/socket-provider.tsx | 80 + .../providers/theme/theme-provider.tsx | 33 + .../components/routes/authenticated-route.tsx | 49 + .../src/components/routes/route-error.tsx | 21 + .../src/components/routes/route-wrapper.tsx | 11 + .../routes/unauthenticated-route.tsx | 16 + .../timezone-selector/timezone-selector.tsx | 48 + .../components/ui/Button/Button.module.css | 3 + .../src/components/ui/Button/Button.test.tsx | 54 + .../src/components/ui/Button/Button.tsx | 91 + .../src/components/ui/Button/index.ts | 4 + .../components/ui/ContextMenu/ContextMenu.tsx | 124 + .../components/ui/DataGrid/DataGrid.test.tsx | 32 + .../src/components/ui/DataGrid/DataGrid.tsx | 13 + .../components/ui/DataGrid/DataGridItem.tsx | 13 + .../src/components/ui/DataGrid/index.ts | 2 + .../src/components/ui/Dialog/Dialog.css | 36 + .../src/components/ui/Dialog/Dialog.tsx | 80 + .../src/components/ui/Dialog/index.ts | 1 + .../ui/DropdownMenu/DropdownMenu.tsx | 68 + .../src/components/ui/DropdownMenu/index.ts | 1 + .../src/components/ui/Input/Input.test.tsx | 149 + .../src/components/ui/Input/Input.tsx | 48 + .../frontend/src/components/ui/Input/index.ts | 1 + .../src/components/ui/OtpInput/OtpInput.css | 16 + .../components/ui/OtpInput/OtpInput.test.tsx | 262 + .../src/components/ui/OtpInput/OtpInput.tsx | 158 + .../src/components/ui/OtpInput/index.ts | 1 + .../components/ui/Pagination/Pagination.css | 9 + .../components/ui/Pagination/Pagination.tsx | 60 + .../ui/ScrollArea/ScrollArea.module.css | 26 + .../components/ui/ScrollArea/ScrollArea.tsx | 41 + .../src/components/ui/ScrollArea/index.ts | 1 + .../src/components/ui/Select/Select.tsx | 82 + .../src/components/ui/Select/index.ts | 1 + .../src/components/ui/Skeleton/Skeleton.tsx | 43 + .../src/components/ui/Skeleton/skeleton.css | 56 + .../components/ui/Skeleton/skeleton.props.ts | 14 + .../src/components/ui/Switch/Switch.css | 10 + .../src/components/ui/Switch/Switch.test.tsx | 71 + .../src/components/ui/Switch/Switch.tsx | 24 + .../src/components/ui/Switch/index.ts | 1 + .../src/components/ui/Table/Table.tsx | 46 + .../frontend/src/components/ui/Table/index.ts | 1 + .../ui/TablePagination/TablePagination.tsx | 86 + .../components/ui/helpers/component-props.ts | 18 + .../components/ui/helpers/extract-props.ts | 123 + .../ui/helpers/get-responsive-styles.ts | 148 + .../components/ui/helpers/has-own-property.ts | 7 + .../src/components/ui/helpers/inert.ts | 5 + .../ui/helpers/is-responsive-object.ts | 9 + .../src/components/ui/helpers/merge-styles.ts | 14 + .../src/components/ui/props/height.props.ts | 64 + .../src/components/ui/props/margin.props.ts | 155 + .../src/components/ui/props/prop-def.ts | 107 + .../src/components/ui/props/width.props.ts | 64 + .../frontend/src/components/ui/tabs/index.ts | 1 + .../frontend/src/components/ui/tabs/tabs.css | 23 + .../frontend/src/components/ui/tabs/tabs.tsx | 47 + .../src/components/welcome/welcome.tsx | 76 + packages/frontend/src/context/app-context.tsx | 61 + .../frontend/src/context/user-context.tsx | 50 + .../frontend/src/lib/api/get-app-backups.ts | 15 + packages/frontend/src/lib/api/search-apps.ts | 28 + .../frontend/src/lib/helpers/text-helpers.ts | 1 + .../frontend/src/lib/hooks/use-disclosure.ts | 17 + .../src/lib/hooks/use-infinite-scroll.ts | 34 + packages/frontend/src/lib/hooks/use-socket.ts | 100 + packages/frontend/src/lib/i18n/locales.ts | 64 + packages/frontend/src/lib/theme/theme.ts | 38 + packages/frontend/src/main.tsx | 131 + .../add-link-tile/add-link-tile.css | 11 + .../add-link-tile/add-link-tile.tsx | 34 + .../app/components/app-status/app-status.tsx | 28 + .../app/components/app-tile/app-tile.tsx | 57 + .../dialogs/add-link/add-link-dialog.tsx | 132 + .../backup-app-dialog/backup-app-dialog.tsx | 34 + .../delete-backup-dialog.tsx | 39 + .../delete-link/delete-link-dialog.tsx | 50 + .../dialogs/install-dialog/install-dialog.tsx | 52 + .../dialogs/reset-dialog/reset-dialog.tsx | 52 + .../dialogs/restart-dialog/restart-dialog.tsx | 49 + .../restore-app-dialog/restore-app-dialog.tsx | 40 + .../dialogs/stop-dialog/stop-dialog.tsx | 50 + .../uninstall-dialog/uninstall-dialog.tsx | 52 + .../install-form/form-validators.ts | 106 + .../install-form/install-form-field.tsx | 113 + .../components/install-form/install-form.tsx | 221 + .../app/components/link-tile/link-tile.tsx | 66 + .../app/components/store-tile/store-tile.css | 18 + .../app/components/store-tile/store-tile.tsx | 43 + .../containers/app-actions/app-actions.css | 4 + .../containers/app-actions/app-actions.tsx | 249 + .../containers/app-backups/app-backups.tsx | 164 + .../app-details-tabs/app-details-tabs.tsx | 114 + .../app/containers/app-logs/app-logs.tsx | 41 + .../src/modules/app/helpers/table-helpers.ts | 59 + .../src/modules/app/helpers/use-app-status.ts | 25 + .../modules/app/pages/app-details-page.tsx | 42 + .../src/modules/app/pages/app-store-page.tsx | 51 + .../src/modules/app/pages/my-apps-page.tsx | 56 + .../frontend/src/modules/app/pages/page.css | 8 + .../modules/auth/components/login-form.tsx | 69 + .../modules/auth/components/register-form.tsx | 76 + .../reset-password-form.tsx | 71 + .../auth/components/totp-form/totp-form.tsx | 34 + .../src/modules/auth/pages/login-page.tsx | 60 + .../src/modules/auth/pages/register-page.tsx | 42 + .../auth/pages/reset-password-page.tsx | 64 + .../dashboard/components/system-stat.tsx | 48 + .../src/modules/dashboard/pages/dashboard.tsx | 43 + .../dashboard/pages/guest-dashboard.css | 8 + .../dashboard/pages/guest-dashboard.tsx | 101 + .../change-password-form.tsx | 85 + .../change-username-form.tsx | 84 + .../settings/components/otp-form/otp-form.tsx | 176 + .../user-settings-form/user-settings-form.tsx | 232 + .../settings/containers/general-actions.tsx | 50 + .../src/modules/settings/containers/logs.tsx | 41 + .../modules/settings/containers/security.tsx | 37 + .../settings/containers/user-settings.tsx | 41 + .../modules/settings/pages/settings-page.tsx | 63 + packages/frontend/src/stores/app-store.ts | 31 + packages/frontend/src/stores/ui-store.ts | 24 + packages/frontend/src/tests/test-utils.tsx | 17 + packages/frontend/src/types/app.types.ts | 14 + packages/frontend/src/types/error.types.ts | 9 + packages/frontend/src/vite-env.d.ts | 1 + packages/frontend/tsconfig.json | 34 + packages/frontend/vite.config.ts | 31 + pnpm-lock.yaml | 12702 ++++++++++++++++ pnpm-workspace.yaml | 3 + supervisord.dev.conf | 20 + supervisord.prod.conf | 20 + turbo.json | 19 + 333 files changed, 35885 insertions(+), 17 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Dockerfile.dev create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.prod.yml create mode 100644 openapi-ts.config.ts create mode 100644 package.json create mode 100644 packages/backend/README.md create mode 100644 packages/backend/assets/traefik/dynamic/dynamic.yml create mode 100644 packages/backend/assets/traefik/traefik.yml create mode 100644 packages/backend/nest-cli.json create mode 100644 packages/backend/package.json create mode 100644 packages/backend/src/@types/express/index.d.ts create mode 100644 packages/backend/src/app.controller.ts create mode 100644 packages/backend/src/app.dto.ts create mode 100644 packages/backend/src/app.module.ts create mode 100644 packages/backend/src/app.service.ts create mode 100644 packages/backend/src/common/constants.ts create mode 100644 packages/backend/src/common/error/exception.filter.ts create mode 100644 packages/backend/src/common/error/translatable-error.ts create mode 100644 packages/backend/src/common/helpers/env-helpers.ts create mode 100644 packages/backend/src/common/helpers/error-helpers.ts create mode 100644 packages/backend/src/common/helpers/exec-helpers.ts create mode 100644 packages/backend/src/common/helpers/file-helpers.ts create mode 100644 packages/backend/src/core/archive/archive.module.ts create mode 100644 packages/backend/src/core/archive/archive.service.ts create mode 100644 packages/backend/src/core/cache/cache.module.ts create mode 100644 packages/backend/src/core/cache/cache.service.ts create mode 100644 packages/backend/src/core/config/configuration.module.ts create mode 100644 packages/backend/src/core/config/configuration.service.ts create mode 100644 packages/backend/src/core/database/database-migrator.service.ts create mode 100644 packages/backend/src/core/database/database.module.ts create mode 100644 packages/backend/src/core/database/database.service.ts create mode 100644 packages/backend/src/core/database/migrations/00000-create-migrations-table.sql create mode 100644 packages/backend/src/core/database/migrations/00001-initial.sql create mode 100644 packages/backend/src/core/database/migrations/00002-add-app-version.sql create mode 100644 packages/backend/src/core/database/migrations/00003-add-status-updating.sql create mode 100644 packages/backend/src/core/database/migrations/00004-add-exposed-domain-fields.sql create mode 100644 packages/backend/src/core/database/migrations/00005-add-user-operator.sql create mode 100644 packages/backend/src/core/database/migrations/00006-add-totp-user-fields.sql create mode 100644 packages/backend/src/core/database/migrations/00007-add-locale-user-col.sql create mode 100644 packages/backend/src/core/database/migrations/00008-merge-config-with-domain-and-exposed.sql create mode 100644 packages/backend/src/core/database/migrations/00009-add-guest-dashboard.sql create mode 100644 packages/backend/src/core/database/migrations/00010-add-status-resetting.sql create mode 100644 packages/backend/src/core/database/migrations/00011-create-link-table.sql create mode 100644 packages/backend/src/core/database/migrations/00012-link-table-description.sql create mode 100644 packages/backend/src/core/database/migrations/00013-app-local-exposed.sql create mode 100644 packages/backend/src/core/database/migrations/00014-restarting-state.sql create mode 100644 packages/backend/src/core/database/migrations/00015-backingup-state.sql create mode 100644 packages/backend/src/core/database/migrations/00016-user-has-seen-welcome.sql create mode 100644 packages/backend/src/core/database/schema.ts create mode 100644 packages/backend/src/core/encryption/encryption.module.ts create mode 100644 packages/backend/src/core/encryption/encryption.service.ts create mode 100644 packages/backend/src/core/filesystem/filesystem.module.ts create mode 100644 packages/backend/src/core/filesystem/filesystem.service.ts create mode 100644 packages/backend/src/core/health/health.controller.ts create mode 100644 packages/backend/src/core/health/health.module.ts create mode 100644 packages/backend/src/core/logger/logger.module.ts create mode 100644 packages/backend/src/core/logger/logger.service.ts create mode 100644 packages/backend/src/core/socket/socket-schemas.ts create mode 100644 packages/backend/src/core/socket/socket.health.ts create mode 100644 packages/backend/src/core/socket/socket.module.ts create mode 100644 packages/backend/src/core/socket/socket.service.ts create mode 100644 packages/backend/src/exports/index.ts create mode 100644 packages/backend/src/main.ts create mode 100644 packages/backend/src/metadata.ts create mode 100644 packages/backend/src/modules/app-lifecycle/app-lifecycle-command.factory.ts create mode 100644 packages/backend/src/modules/app-lifecycle/app-lifecycle.controller.ts create mode 100644 packages/backend/src/modules/app-lifecycle/app-lifecycle.module.ts create mode 100644 packages/backend/src/modules/app-lifecycle/app-lifecycle.service.ts create mode 100644 packages/backend/src/modules/app-lifecycle/commands/backup-app-command.ts create mode 100644 packages/backend/src/modules/app-lifecycle/commands/command.ts create mode 100644 packages/backend/src/modules/app-lifecycle/commands/install-app-command.ts create mode 100644 packages/backend/src/modules/app-lifecycle/commands/reset-app-command.ts create mode 100644 packages/backend/src/modules/app-lifecycle/commands/restart-app-command.ts create mode 100644 packages/backend/src/modules/app-lifecycle/commands/restore-app-command.ts create mode 100644 packages/backend/src/modules/app-lifecycle/commands/start-app-command.ts create mode 100644 packages/backend/src/modules/app-lifecycle/commands/stop-app-command.ts create mode 100644 packages/backend/src/modules/app-lifecycle/commands/uninstall-app-command.ts create mode 100644 packages/backend/src/modules/app-lifecycle/dto/app-lifecycle.dto.ts create mode 100644 packages/backend/src/modules/apps/app-catalog.service.ts create mode 100644 packages/backend/src/modules/apps/app-files-manager.ts create mode 100644 packages/backend/src/modules/apps/app.helpers.ts create mode 100644 packages/backend/src/modules/apps/apps.controller.ts create mode 100644 packages/backend/src/modules/apps/apps.module.ts create mode 100644 packages/backend/src/modules/apps/apps.repository.ts create mode 100644 packages/backend/src/modules/apps/dto/app-info.dto.ts create mode 100644 packages/backend/src/modules/apps/dto/app.dto.ts create mode 100644 packages/backend/src/modules/auth/auth.controller.ts create mode 100644 packages/backend/src/modules/auth/auth.guard.ts create mode 100644 packages/backend/src/modules/auth/auth.middleware.ts create mode 100644 packages/backend/src/modules/auth/auth.module.ts create mode 100644 packages/backend/src/modules/auth/auth.service.ts create mode 100644 packages/backend/src/modules/auth/dto/auth.dto.ts create mode 100644 packages/backend/src/modules/auth/session.manager.ts create mode 100644 packages/backend/src/modules/auth/utils/totp-authenticator.ts create mode 100644 packages/backend/src/modules/backups/backup.manager.ts create mode 100644 packages/backend/src/modules/backups/backups.controller.ts create mode 100644 packages/backend/src/modules/backups/backups.module.ts create mode 100644 packages/backend/src/modules/backups/backups.service.ts create mode 100644 packages/backend/src/modules/backups/dto/backups.dto.ts create mode 100644 packages/backend/src/modules/docker/builders/compose.builder.ts create mode 100644 packages/backend/src/modules/docker/builders/schemas.ts create mode 100644 packages/backend/src/modules/docker/builders/service.builder.ts create mode 100644 packages/backend/src/modules/docker/builders/traefik-labels.builder.ts create mode 100644 packages/backend/src/modules/docker/commands/app-logs-init.ts create mode 100644 packages/backend/src/modules/docker/commands/runtipi-logs-init.ts create mode 100644 packages/backend/src/modules/docker/commands/type.ts create mode 100644 packages/backend/src/modules/docker/docker-command.factory.ts create mode 100644 packages/backend/src/modules/docker/docker.module.ts create mode 100644 packages/backend/src/modules/docker/docker.service.ts create mode 100644 packages/backend/src/modules/docker/helpers/colorize-logs.ts create mode 100644 packages/backend/src/modules/env/env.module.ts create mode 100644 packages/backend/src/modules/env/env.utils.ts create mode 100644 packages/backend/src/modules/i18n/i18n.controller.ts create mode 100644 packages/backend/src/modules/i18n/i18n.module.ts create mode 100644 packages/backend/src/modules/i18n/i18n.service.ts create mode 100644 packages/backend/src/modules/i18n/translations/en.json create mode 100644 packages/backend/src/modules/links/dto/links.dto.ts create mode 100644 packages/backend/src/modules/links/links.controller.ts create mode 100644 packages/backend/src/modules/links/links.module.ts create mode 100644 packages/backend/src/modules/links/links.repository.ts create mode 100644 packages/backend/src/modules/links/links.service.ts create mode 100644 packages/backend/src/modules/queue/entities/app-events.ts create mode 100644 packages/backend/src/modules/queue/entities/repo-events.ts create mode 100644 packages/backend/src/modules/queue/queue.entity.ts create mode 100644 packages/backend/src/modules/queue/queue.factory.ts create mode 100644 packages/backend/src/modules/queue/queue.health.ts create mode 100644 packages/backend/src/modules/queue/queue.module.ts create mode 100644 packages/backend/src/modules/repos/repos.module.ts create mode 100644 packages/backend/src/modules/repos/repos.service.ts create mode 100644 packages/backend/src/modules/system/dto/system.dto.ts create mode 100644 packages/backend/src/modules/system/system.controller.ts create mode 100644 packages/backend/src/modules/system/system.module.ts create mode 100644 packages/backend/src/modules/system/system.service.ts create mode 100644 packages/backend/src/modules/user/dto/user.dto.ts create mode 100644 packages/backend/src/modules/user/user.module.ts create mode 100644 packages/backend/src/modules/user/user.repository.ts create mode 100644 packages/backend/src/schemas/queue-schemas.ts create mode 100644 packages/backend/src/swagger.json create mode 100644 packages/backend/src/types/express/index.d.ts create mode 100644 packages/backend/tsconfig.json create mode 100644 packages/frontend/.gitignore create mode 100644 packages/frontend/README.md create mode 100644 packages/frontend/index.html create mode 100644 packages/frontend/package.json create mode 100644 packages/frontend/public/app-not-found.jpg create mode 100644 packages/frontend/public/empty.svg create mode 100644 packages/frontend/public/error.png create mode 100644 packages/frontend/public/icons/apple-touch-icon.png create mode 100644 packages/frontend/public/icons/favicon-96x96.png create mode 100644 packages/frontend/public/icons/favicon.ico create mode 100644 packages/frontend/public/icons/favicon.svg create mode 100644 packages/frontend/public/icons/site.webmanifest create mode 100644 packages/frontend/public/icons/web-app-manifest-192x192.png create mode 100644 packages/frontend/public/icons/web-app-manifest-512x512.png create mode 100644 packages/frontend/public/js/.gitkeep create mode 100644 packages/frontend/public/mockServiceWorker.js create mode 100644 packages/frontend/public/placeholder.png create mode 100644 packages/frontend/public/tipi-christmas.png create mode 100644 packages/frontend/public/tipi.png create mode 100644 packages/frontend/public/vite.svg create mode 100755 packages/frontend/scripts/postinstall.sh create mode 100644 packages/frontend/src/App.css create mode 100644 packages/frontend/src/App.tsx create mode 100644 packages/frontend/src/api-client/@tanstack/react-query.gen.ts create mode 100644 packages/frontend/src/api-client/index.ts create mode 100644 packages/frontend/src/api-client/schemas.gen.ts create mode 100644 packages/frontend/src/api-client/services.gen.ts create mode 100644 packages/frontend/src/api-client/types.gen.ts create mode 100644 packages/frontend/src/assets/react.svg create mode 100644 packages/frontend/src/components/app-logo/app-logo.css create mode 100644 packages/frontend/src/components/app-logo/app-logo.tsx create mode 100644 packages/frontend/src/components/app-store-layout-actions/app-store-layout-actions.css create mode 100644 packages/frontend/src/components/app-store-layout-actions/app-store-layout-actions.tsx create mode 100644 packages/frontend/src/components/category-selector/category-selector.tsx create mode 100644 packages/frontend/src/components/date-format/date-format.tsx create mode 100644 packages/frontend/src/components/empty-page/empty-page.css create mode 100644 packages/frontend/src/components/empty-page/empty-page.tsx create mode 100644 packages/frontend/src/components/error/error-page.tsx create mode 100644 packages/frontend/src/components/file-size/file-size.tsx create mode 100644 packages/frontend/src/components/header/guest-header.tsx create mode 100644 packages/frontend/src/components/header/header.tsx create mode 100644 packages/frontend/src/components/language-selector/language-selector.tsx create mode 100644 packages/frontend/src/components/layouts/auth/layout.tsx create mode 100644 packages/frontend/src/components/layouts/dashboard/layout-actions.tsx create mode 100644 packages/frontend/src/components/layouts/dashboard/layout.css create mode 100644 packages/frontend/src/components/layouts/dashboard/layout.tsx create mode 100644 packages/frontend/src/components/logs-terminal/logs-terminal.css create mode 100644 packages/frontend/src/components/logs-terminal/logs-terminal.tsx create mode 100644 packages/frontend/src/components/markdown/markdown.tsx create mode 100644 packages/frontend/src/components/navbar/navbar.tsx create mode 100644 packages/frontend/src/components/page-title/page-title.tsx create mode 100644 packages/frontend/src/components/providers/i18n/i18n-provider.tsx create mode 100644 packages/frontend/src/components/providers/location-listener/location-listener.tsx create mode 100644 packages/frontend/src/components/providers/providers.tsx create mode 100644 packages/frontend/src/components/providers/socket/socket-provider.tsx create mode 100644 packages/frontend/src/components/providers/theme/theme-provider.tsx create mode 100644 packages/frontend/src/components/routes/authenticated-route.tsx create mode 100644 packages/frontend/src/components/routes/route-error.tsx create mode 100644 packages/frontend/src/components/routes/route-wrapper.tsx create mode 100644 packages/frontend/src/components/routes/unauthenticated-route.tsx create mode 100644 packages/frontend/src/components/timezone-selector/timezone-selector.tsx create mode 100644 packages/frontend/src/components/ui/Button/Button.module.css create mode 100644 packages/frontend/src/components/ui/Button/Button.test.tsx create mode 100644 packages/frontend/src/components/ui/Button/Button.tsx create mode 100644 packages/frontend/src/components/ui/Button/index.ts create mode 100644 packages/frontend/src/components/ui/ContextMenu/ContextMenu.tsx create mode 100644 packages/frontend/src/components/ui/DataGrid/DataGrid.test.tsx create mode 100644 packages/frontend/src/components/ui/DataGrid/DataGrid.tsx create mode 100644 packages/frontend/src/components/ui/DataGrid/DataGridItem.tsx create mode 100644 packages/frontend/src/components/ui/DataGrid/index.ts create mode 100644 packages/frontend/src/components/ui/Dialog/Dialog.css create mode 100644 packages/frontend/src/components/ui/Dialog/Dialog.tsx create mode 100644 packages/frontend/src/components/ui/Dialog/index.ts create mode 100644 packages/frontend/src/components/ui/DropdownMenu/DropdownMenu.tsx create mode 100644 packages/frontend/src/components/ui/DropdownMenu/index.ts create mode 100644 packages/frontend/src/components/ui/Input/Input.test.tsx create mode 100644 packages/frontend/src/components/ui/Input/Input.tsx create mode 100644 packages/frontend/src/components/ui/Input/index.ts create mode 100644 packages/frontend/src/components/ui/OtpInput/OtpInput.css create mode 100644 packages/frontend/src/components/ui/OtpInput/OtpInput.test.tsx create mode 100644 packages/frontend/src/components/ui/OtpInput/OtpInput.tsx create mode 100644 packages/frontend/src/components/ui/OtpInput/index.ts create mode 100644 packages/frontend/src/components/ui/Pagination/Pagination.css create mode 100644 packages/frontend/src/components/ui/Pagination/Pagination.tsx create mode 100644 packages/frontend/src/components/ui/ScrollArea/ScrollArea.module.css create mode 100644 packages/frontend/src/components/ui/ScrollArea/ScrollArea.tsx create mode 100644 packages/frontend/src/components/ui/ScrollArea/index.ts create mode 100644 packages/frontend/src/components/ui/Select/Select.tsx create mode 100644 packages/frontend/src/components/ui/Select/index.ts create mode 100644 packages/frontend/src/components/ui/Skeleton/Skeleton.tsx create mode 100644 packages/frontend/src/components/ui/Skeleton/skeleton.css create mode 100644 packages/frontend/src/components/ui/Skeleton/skeleton.props.ts create mode 100644 packages/frontend/src/components/ui/Switch/Switch.css create mode 100644 packages/frontend/src/components/ui/Switch/Switch.test.tsx create mode 100644 packages/frontend/src/components/ui/Switch/Switch.tsx create mode 100644 packages/frontend/src/components/ui/Switch/index.ts create mode 100644 packages/frontend/src/components/ui/Table/Table.tsx create mode 100644 packages/frontend/src/components/ui/Table/index.ts create mode 100644 packages/frontend/src/components/ui/TablePagination/TablePagination.tsx create mode 100644 packages/frontend/src/components/ui/helpers/component-props.ts create mode 100644 packages/frontend/src/components/ui/helpers/extract-props.ts create mode 100644 packages/frontend/src/components/ui/helpers/get-responsive-styles.ts create mode 100644 packages/frontend/src/components/ui/helpers/has-own-property.ts create mode 100644 packages/frontend/src/components/ui/helpers/inert.ts create mode 100644 packages/frontend/src/components/ui/helpers/is-responsive-object.ts create mode 100644 packages/frontend/src/components/ui/helpers/merge-styles.ts create mode 100644 packages/frontend/src/components/ui/props/height.props.ts create mode 100644 packages/frontend/src/components/ui/props/margin.props.ts create mode 100644 packages/frontend/src/components/ui/props/prop-def.ts create mode 100644 packages/frontend/src/components/ui/props/width.props.ts create mode 100644 packages/frontend/src/components/ui/tabs/index.ts create mode 100644 packages/frontend/src/components/ui/tabs/tabs.css create mode 100644 packages/frontend/src/components/ui/tabs/tabs.tsx create mode 100644 packages/frontend/src/components/welcome/welcome.tsx create mode 100644 packages/frontend/src/context/app-context.tsx create mode 100644 packages/frontend/src/context/user-context.tsx create mode 100644 packages/frontend/src/lib/api/get-app-backups.ts create mode 100644 packages/frontend/src/lib/api/search-apps.ts create mode 100644 packages/frontend/src/lib/helpers/text-helpers.ts create mode 100644 packages/frontend/src/lib/hooks/use-disclosure.ts create mode 100644 packages/frontend/src/lib/hooks/use-infinite-scroll.ts create mode 100644 packages/frontend/src/lib/hooks/use-socket.ts create mode 100644 packages/frontend/src/lib/i18n/locales.ts create mode 100644 packages/frontend/src/lib/theme/theme.ts create mode 100644 packages/frontend/src/main.tsx create mode 100644 packages/frontend/src/modules/app/components/add-link-tile/add-link-tile.css create mode 100644 packages/frontend/src/modules/app/components/add-link-tile/add-link-tile.tsx create mode 100644 packages/frontend/src/modules/app/components/app-status/app-status.tsx create mode 100644 packages/frontend/src/modules/app/components/app-tile/app-tile.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/add-link/add-link-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/backup-app-dialog/backup-app-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/delete-backup-dialog/delete-backup-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/delete-link/delete-link-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/install-dialog/install-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/reset-dialog/reset-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/restart-dialog/restart-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/restore-app-dialog/restore-app-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/stop-dialog/stop-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/dialogs/uninstall-dialog/uninstall-dialog.tsx create mode 100644 packages/frontend/src/modules/app/components/install-form/form-validators.ts create mode 100644 packages/frontend/src/modules/app/components/install-form/install-form-field.tsx create mode 100644 packages/frontend/src/modules/app/components/install-form/install-form.tsx create mode 100644 packages/frontend/src/modules/app/components/link-tile/link-tile.tsx create mode 100644 packages/frontend/src/modules/app/components/store-tile/store-tile.css create mode 100644 packages/frontend/src/modules/app/components/store-tile/store-tile.tsx create mode 100644 packages/frontend/src/modules/app/containers/app-actions/app-actions.css create mode 100644 packages/frontend/src/modules/app/containers/app-actions/app-actions.tsx create mode 100644 packages/frontend/src/modules/app/containers/app-backups/app-backups.tsx create mode 100644 packages/frontend/src/modules/app/containers/app-details-tabs/app-details-tabs.tsx create mode 100644 packages/frontend/src/modules/app/containers/app-logs/app-logs.tsx create mode 100644 packages/frontend/src/modules/app/helpers/table-helpers.ts create mode 100644 packages/frontend/src/modules/app/helpers/use-app-status.ts create mode 100644 packages/frontend/src/modules/app/pages/app-details-page.tsx create mode 100644 packages/frontend/src/modules/app/pages/app-store-page.tsx create mode 100644 packages/frontend/src/modules/app/pages/my-apps-page.tsx create mode 100644 packages/frontend/src/modules/app/pages/page.css create mode 100644 packages/frontend/src/modules/auth/components/login-form.tsx create mode 100644 packages/frontend/src/modules/auth/components/register-form.tsx create mode 100644 packages/frontend/src/modules/auth/components/reset-password-form/reset-password-form.tsx create mode 100644 packages/frontend/src/modules/auth/components/totp-form/totp-form.tsx create mode 100644 packages/frontend/src/modules/auth/pages/login-page.tsx create mode 100644 packages/frontend/src/modules/auth/pages/register-page.tsx create mode 100644 packages/frontend/src/modules/auth/pages/reset-password-page.tsx create mode 100644 packages/frontend/src/modules/dashboard/components/system-stat.tsx create mode 100644 packages/frontend/src/modules/dashboard/pages/dashboard.tsx create mode 100644 packages/frontend/src/modules/dashboard/pages/guest-dashboard.css create mode 100644 packages/frontend/src/modules/dashboard/pages/guest-dashboard.tsx create mode 100644 packages/frontend/src/modules/settings/components/change-password-form/change-password-form.tsx create mode 100644 packages/frontend/src/modules/settings/components/change-username-form/change-username-form.tsx create mode 100644 packages/frontend/src/modules/settings/components/otp-form/otp-form.tsx create mode 100644 packages/frontend/src/modules/settings/components/user-settings-form/user-settings-form.tsx create mode 100644 packages/frontend/src/modules/settings/containers/general-actions.tsx create mode 100644 packages/frontend/src/modules/settings/containers/logs.tsx create mode 100644 packages/frontend/src/modules/settings/containers/security.tsx create mode 100644 packages/frontend/src/modules/settings/containers/user-settings.tsx create mode 100644 packages/frontend/src/modules/settings/pages/settings-page.tsx create mode 100644 packages/frontend/src/stores/app-store.ts create mode 100644 packages/frontend/src/stores/ui-store.ts create mode 100644 packages/frontend/src/tests/test-utils.tsx create mode 100644 packages/frontend/src/types/app.types.ts create mode 100644 packages/frontend/src/types/error.types.ts create mode 100644 packages/frontend/src/vite-env.d.ts create mode 100644 packages/frontend/tsconfig.json create mode 100644 packages/frontend/vite.config.ts create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 supervisord.dev.conf create mode 100644 supervisord.prod.conf create mode 100644 turbo.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..aab5ea2c0c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +* + +### Includes ### +!packages/**/src/** +!packages/**/public/** +!packages/**/assets/** +!packages/**/scripts/** +!pnpm-*.yaml +!package.json +!turbo.json +!supervisord.*.conf + +!**/package.json +!**/tsconfig.json +!**/nest-cli.json +!**/tsup.config.ts +!**/vite.config.ts +!**/index.html + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..9afe1f55f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +/node_modules +**/dist/** +.turbo + +*.swo +*.swp + +.DS_Store +.vscode +.idea + +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build +/dist +server-preload.js + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo + +logs +.pnpm-debug.log +.env* +!.env.example +!.env.test +github.secrets +node_modules/ +/app-data/ +/data/ +/repos/ +/apps/ +/traefik/ +/backups/ + +# media folder +media + +/state/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +temp + +./traefik/ +/user-config/ + +# Sentry Config File +.sentryclirc + +public/js/* +!public/js/.gitkeep + +/cache diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..0e3003a664 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,79 @@ +ARG NODE_VERSION="iron" +ARG ALPINE_VERSION="3.20" + +FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS node_base + +# ---- BUILDER BASE ---- +FROM node_base AS builder_base + +RUN npm install pnpm@9.12.2 -g +RUN apk add --no-cache curl python3 make g++ git + +WORKDIR /deps + +COPY ./pnpm-lock.yaml ./ +RUN pnpm fetch + +# ---- RUNNER BASE ---- +FROM node_base AS runner_base + +RUN apk add --no-cache curl openssl git rabbitmq-server supervisor + +# ---- BUILDER ---- +FROM builder_base AS builder + +ARG TARGETARCH +ARG DOCKER_COMPOSE_VERSION="v2.29.2" +ENV TARGETARCH=${TARGETARCH} + +WORKDIR /app + +RUN echo "Building for ${TARGETARCH}" + +RUN if [ "${TARGETARCH}" = "arm64" ]; then \ + curl -L -o docker-binary "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-aarch64"; \ + elif [ "${TARGETARCH}" = "amd64" ]; then \ + curl -L -o docker-binary "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-x86_64"; \ + fi + +RUN chmod +x docker-binary + +COPY ./pnpm-workspace.yaml ./ +COPY ./pnpm-lock.yaml ./ +COPY ./package.json ./ +COPY ./packages/backend/package.json ./packages/backend/package.json +COPY ./packages/frontend/package.json ./packages/frontend/package.json +COPY ./packages/frontend/scripts ./packages/frontend/scripts +COPY ./packages/frontend/public ./packages/frontend/public + +RUN pnpm install -r --prefer-offline + +COPY ./turbo.json ./turbo.json +COPY ./packages ./packages + +RUN npm run build && pnpm -r bundle + +# ---- RUNNER ---- +FROM runner_base AS runner + +ENV NODE_ENV="production" + +WORKDIR /app + +RUN npm install argon2 + +COPY --from=builder /app/package.json ./ +COPY --from=builder /app/packages/backend/dist/bundle ./ +COPY --from=builder /app/docker-binary /usr/local/bin/docker-compose + +# Assets +COPY --from=builder /app/packages/backend/assets ./assets +COPY --from=builder /app/packages/backend/src/core/database/migrations ./assets/migrations +COPY --from=builder /app/packages/backend/src/modules/i18n/translations ./assets/translations +COPY --from=builder /app/packages/frontend/dist ./assets/frontend + +COPY ./supervisord.prod.conf /etc/supervisord.conf + +EXPOSE 3000 5001 + +CMD ["supervisord", "-c", "/etc/supervisord.conf"] diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000000..25c312887d --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,50 @@ +ARG NODE_VERSION="iron" +ARG ALPINE_VERSION="3.20" + +FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} + +RUN apk add --no-cache curl git +RUN npm install pnpm@9.12.2 -g + +RUN apk add --no-cache curl openssl git rabbitmq-server supervisor + +ARG TARGETARCH +ARG DOCKER_COMPOSE_VERSION="v2.29.2" +ENV TARGETARCH=${TARGETARCH} +ENV NODE_ENV="development" + +WORKDIR /app + +RUN echo "Building for ${TARGETARCH}" + +RUN if [ "${TARGETARCH}" = "arm64" ]; then \ + curl -L -o docker-binary "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-aarch64"; \ + elif [ "${TARGETARCH}" = "amd64" ]; then \ + curl -L -o docker-binary "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-x86_64"; \ + fi + +RUN chmod +x docker-binary +RUN mv docker-binary /usr/local/bin/docker-compose + +COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml +COPY ./pnpm-lock.yaml ./ +RUN pnpm fetch + +COPY ./packages/backend/package.json ./packages/backend/package.json +COPY ./packages/frontend/package.json ./packages/frontend/package.json +COPY ./packages/frontend/scripts ./packages/frontend/scripts +COPY ./packages/frontend/public ./packages/frontend/public +COPY ./package.json ./package.json + +RUN pnpm install + +COPY ./turbo.json ./turbo.json +COPY ./packages ./packages +COPY ./packages/backend/assets ./assets +COPY ./packages/backend/src/core/database/migrations ./assets/migrations + +COPY ./supervisord.dev.conf /etc/supervisord.conf + +EXPOSE 3000 5001 + +CMD ["supervisord", "-c", "/etc/supervisord.conf"] diff --git a/biome.json b/biome.json index 01d25e77c1..3dfcbfea7f 100644 --- a/biome.json +++ b/biome.json @@ -1,22 +1,9 @@ { - "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", "files": { - "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "**/*.json"], "ignore": [ - "node_modules/**", "dist/**", - "coverage/**", - ".next/**", - "public/**", - "app-data/**", - "apps/*/**", - "logs/**", - "media/**", - "repos/**", - "state/**", - "traefik/**", - "user-config/**", - "playwright-report/**" + "public/**" ] }, "formatter": { @@ -30,7 +17,7 @@ "ignore": [] }, "organizeImports": { - "enabled": false + "enabled": true }, "linter": { "enabled": true, @@ -76,7 +63,10 @@ }, "overrides": [ { - "include": ["**/*.test.ts", "**/*.test.tsx"], + "include": [ + "**/*.test.ts", + "**/*.test.tsx" + ], "linter": { "rules": { "suspicious": { @@ -86,6 +76,24 @@ } } } + }, + { + "include": [ + "packages/backend/**" + ], + "linter": { + "rules": { + "style": { + "useImportType": "off" + }, + "correctness": { + "useHookAtTopLevel": { + "level": "off", + "options": {} + } + } + } + } } ], "javascript": { diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000000..8f09b2f066 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,76 @@ +services: + runtipi-db: + container_name: runtipi-db + image: postgres:14 + restart: unless-stopped + stop_grace_period: 1m + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - 5432:5432 + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: tipi + POSTGRES_DB: tipi + healthcheck: + test: ["CMD-SHELL", "pg_isready -d tipi -U tipi"] + interval: 5s + timeout: 10s + retries: 120 + networks: + - tipi_main_network + + runtipi: + build: + context: . + dockerfile: Dockerfile.dev + depends_on: + runtipi-db: + condition: service_healthy + container_name: runtipi + restart: unless-stopped + ports: + - 3000:8080 + - 5001:5001 + volumes: + # Hot reload + - ./packages/backend/src:/app/packages/backend/src + - ./packages/frontend/src:/app/packages/frontend/src + # Data + - ${RUNTIPI_MEDIA_PATH:-.}/media:/data/media + - ${RUNTIPI_STATE_PATH:-.}/state:/data/state + - ${RUNTIPI_REPOS_PATH:-.}/repos:/data/repos + - ${RUNTIPI_APPS_PATH:-.}/apps:/data/apps + - ${RUNTIPI_LOGS_PATH:-.}/logs:/data/logs + - ${RUNTIPI_TRAEFIK_PATH:-.}/traefik:/data/traefik + - ${RUNTIPI_USER_CONFIG_PATH:-.}/user-config:/data/user-config + - ${RUNTIPI_APP_DATA_PATH:-.}/app-data:/app-data + - ${RUNTIPI_BACKUPS_PATH:-.}/backups:/data/backups + # Static + - ./.env:/data/.env + - ./cache:/cache + - ./docker-compose.dev.yml:/data/docker-compose.yml + - /var/run/docker.sock:/var/run/docker.sock:ro + - /proc/meminfo:/host/proc/meminfo:ro + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + networks: + - tipi_main_network + environment: + LOCAL: true + NODE_ENV: development + ROOT_FOLDER_HOST: ${PWD} + POSTGRES_PASSWORD: postgres + INTERNAL_IP: 127.0.0.1 + TIPI_VERSION: 0.0.0 + LOG_LEVEL: debug + env_file: + - .env + +networks: + tipi_main_network: + driver: bridge + name: runtipi_tipi_main_network + +volumes: + pgdata: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000000..c93470beb6 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,151 @@ +services: + runtipi-reverse-proxy: + container_name: runtipi-reverse-proxy + depends_on: + runtipi: + condition: service_healthy + image: traefik:v3.1.4 + restart: unless-stopped + ports: + - 80:80 + - 443:443 + - 8080:8080 + command: --providers.docker + volumes: + - ./traefik:/etc/traefik + - ./traefik/shared:/shared + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - tipi_main_network + + runtipi-db: + container_name: runtipi-db + image: postgres:14 + restart: unless-stopped + stop_grace_period: 1m + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - 5432:5432 + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: tipi + POSTGRES_DB: tipi + healthcheck: + test: ["CMD-SHELL", "pg_isready -d tipi -U tipi"] + interval: 5s + timeout: 10s + retries: 120 + networks: + - tipi_main_network + + runtipi: + build: + context: . + dockerfile: Dockerfile + depends_on: + runtipi-db: + condition: service_healthy + container_name: runtipi + restart: unless-stopped + healthcheck: + start_period: 10s + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] + interval: 5s + timeout: 3s + retries: 20 + volumes: + # Data + - ${RUNTIPI_MEDIA_PATH:-.}/media:/data/media + - ${RUNTIPI_STATE_PATH:-.}/state:/data/state + - ${RUNTIPI_REPOS_PATH:-.}/repos:/data/repos + - ${RUNTIPI_APPS_PATH:-.}/apps:/data/apps + - ${RUNTIPI_LOGS_PATH:-.}/logs:/data/logs + - ${RUNTIPI_TRAEFIK_PATH:-.}/traefik:/data/traefik + - ${RUNTIPI_USER_CONFIG_PATH:-.}/user-config:/data/user-config + - ${RUNTIPI_APP_DATA_PATH:-.}/app-data:/app-data + - ${RUNTIPI_BACKUPS_PATH:-.}/backups:/data/backups + # Static + - ./.env:/data/.env + - ./cache:/cache + - /var/run/docker.sock:/var/run/docker.sock:ro + - /proc/meminfo:/host/proc/meminfo:ro + - ./docker-compose.prod.yml:/data/docker-compose.yml + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + networks: + - tipi_main_network + environment: + LOCAL: true + NODE_ENV: production + ROOT_FOLDER_HOST: ${PWD} + POSTGRES_PASSWORD: postgres + INTERNAL_IP: 127.0.0.1 + TIPI_VERSION: 0.0.0 + LOG_LEVEL: debug + env_file: + - .env + labels: + # ---- General ----- # + traefik.enable: true + traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: https + + # ---- Dashboard ----- # + traefik.http.services.dashboard.loadbalancer.server.port: 3000 + # Local ip + traefik.http.routers.dashboard.rule: PathPrefix("/") + traefik.http.routers.dashboard.service: dashboard + traefik.http.routers.dashboard.entrypoints: web + # Websecure + traefik.http.routers.dashboard-insecure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`) + traefik.http.routers.dashboard-insecure.service: dashboard + traefik.http.routers.dashboard-insecure.entrypoints: web + traefik.http.routers.dashboard-insecure.middlewares: redirect-to-https + traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`) + traefik.http.routers.dashboard-secure.service: dashboard + traefik.http.routers.dashboard-secure.entrypoints: websecure + traefik.http.routers.dashboard-secure.tls.certresolver: myresolver + # Local domain + traefik.http.routers.dashboard-local-insecure.rule: Host(`${LOCAL_DOMAIN}`) + traefik.http.routers.dashboard-local-insecure.entrypoints: web + traefik.http.routers.dashboard-local-insecure.service: dashboard + traefik.http.routers.dashboard-local-insecure.middlewares: redirect-to-https + # Secure + traefik.http.routers.dashboard-local.rule: Host(`${LOCAL_DOMAIN}`) + traefik.http.routers.dashboard-local.entrypoints: websecure + traefik.http.routers.dashboard-local.tls: true + traefik.http.routers.dashboard-local.service: dashboard + + # ---- socket ----- # + traefik.http.services.socket.loadbalancer.server.port: 5001 + # Local ip + traefik.http.routers.socket.rule: PathPrefix("/api/socket.io") + traefik.http.routers.socket.service: socket + traefik.http.routers.socket.entrypoints: web + # Websecure + traefik.http.routers.socket-insecure.rule: Host(`${DOMAIN}`) && PathPrefix(`/api/socket.io`) + traefik.http.routers.socket-insecure.service: socket + traefik.http.routers.socket-insecure.entrypoints: web + traefik.http.routers.socket-insecure.middlewares: redirect-to-https + traefik.http.routers.socket-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/api/socket.io`) + traefik.http.routers.socket-secure.service: socket + traefik.http.routers.socket-secure.entrypoints: websecure + traefik.http.routers.socket-secure.tls.certresolver: myresolver + # Local domain + traefik.http.routers.socket-local-insecure.rule: Host(`${LOCAL_DOMAIN}`) && PathPrefix("/api/socket.io") + traefik.http.routers.socket-local-insecure.entrypoints: web + traefik.http.routers.socket-local-insecure.service: socket + traefik.http.routers.socket-local-insecure.middlewares: redirect-to-https + # Secure + traefik.http.routers.socket-local.rule: Host(`${LOCAL_DOMAIN}`) && PathPrefix("/api/socket.io") + traefik.http.routers.socket-local.entrypoints: websecure + traefik.http.routers.socket-local.tls: true + traefik.http.routers.socket-local.service: socket + +networks: + tipi_main_network: + driver: bridge + name: runtipi_tipi_main_network + +volumes: + pgdata: diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts new file mode 100644 index 0000000000..c7672c6533 --- /dev/null +++ b/openapi-ts.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@hey-api/openapi-ts'; + +export default defineConfig({ + client: '@hey-api/client-fetch', + input: './packages/backend/src/swagger.json', + types: { + export: true, + }, + output: { + path: './packages/frontend/src/api-client', + format: 'biome', + }, + plugins: ['@tanstack/react-query'], +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..58b52c6d8b --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "runtipi", + "version": "1.0.0", + "description": "", + "packageManager": "pnpm@9.12.2", + "scripts": { + "dev": "turbo run dev", + "build": "turbo build", + "start": "node ./index.js", + "start:dev": "docker compose --project-name runtipi -f docker-compose.dev.yml up --build", + "start:prod": "docker compose --project-name runtipi -f docker-compose.prod.yml up --build ", + "gen:api-client": "openapi-ts" + }, + "keywords": [], + "author": "", + "license": "GNU General Public License v3.0", + "devDependencies": { + "@biomejs/biome": "^1.9.2", + "@hey-api/openapi-ts": "^0.53.7", + "turbo": "^2.2.3" + } +} diff --git a/packages/backend/README.md b/packages/backend/README.md new file mode 100644 index 0000000000..48dcbf8ba8 --- /dev/null +++ b/packages/backend/README.md @@ -0,0 +1,85 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Coverage +Discord +Backers on Open Collective +Sponsors on Open Collective + Donate us + Support us + Follow us on Twitter +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Project setup + +```bash +$ pnpm install +``` + +## Compile and run the project + +```bash +# development +$ pnpm run start + +# watch mode +$ pnpm run start:dev + +# production mode +$ pnpm run start:prod +``` + +## Run tests + +```bash +# unit tests +$ pnpm run test + +# e2e tests +$ pnpm run test:e2e + +# test coverage +$ pnpm run test:cov +``` + +## Resources + +Check out a few resources that may come in handy when working with NestJS: + +- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. +- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). +- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). +- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). +- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). +- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). +- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). diff --git a/packages/backend/assets/traefik/dynamic/dynamic.yml b/packages/backend/assets/traefik/dynamic/dynamic.yml new file mode 100644 index 0000000000..3bce274402 --- /dev/null +++ b/packages/backend/assets/traefik/dynamic/dynamic.yml @@ -0,0 +1,14 @@ +http: + serversTransports: + insecuretransport: + insecureSkipVerify: true + +tls: + stores: + default: + defaultCertificate: + certFile: /etc/traefik/tls/cert.pem + keyFile: /etc/traefik/tls/key.pem + certificates: + - certFile: /etc/traefik/tls/cert.pem + keyFile: /etc/traefik/tls/key.pem diff --git a/packages/backend/assets/traefik/traefik.yml b/packages/backend/assets/traefik/traefik.yml new file mode 100644 index 0000000000..49ffb32cd0 --- /dev/null +++ b/packages/backend/assets/traefik/traefik.yml @@ -0,0 +1,32 @@ +api: + dashboard: true + insecure: true + +providers: + docker: + endpoint: "unix:///var/run/docker.sock" + watch: true + exposedByDefault: false + file: + directory: /etc/traefik/dynamic + watch: true + +entryPoints: + web: + address: ":80" + websecure: + address: ":443" + http: + tls: + certResolver: myresolver + +certificatesResolvers: + myresolver: + acme: + email: acme@thisprops.com + storage: /shared/acme.json + httpChallenge: + entryPoint: web + +log: + level: ERROR diff --git a/packages/backend/nest-cli.json b/packages/backend/nest-cli.json new file mode 100644 index 0000000000..a7506bc6fc --- /dev/null +++ b/packages/backend/nest-cli.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "monorepo": true, + "compilerOptions": { + "plugins": ["@nestjs/swagger"], + "deleteOutDir": true, + "typeCheck": true, + "builder": "swc", + "assets": [ + { + "include": "./src/core/database/migrations/*.sql", + "outDir": "./dist/assets/migrations", + "watchAssets": true + } + ] + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json new file mode 100644 index 0000000000..e90c484c07 --- /dev/null +++ b/packages/backend/package.json @@ -0,0 +1,94 @@ +{ + "name": "backend", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "main": "./src/exports/index.ts", + "scripts": { + "dev": "nest start --watch --preserveWatchOutput", + "build": "nest build", + "bundle": "ncc build dist/main.js -o dist/bundle --external argon2", + "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "tsc": "tsc --noEmit" + }, + "dependencies": { + "@keyv/sqlite": "^4.0.1", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.2.3", + "@nestjs/core": "^10.0.0", + "@nestjs/mapped-types": "*", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/serve-static": "^4.0.2", + "@nestjs/swagger": "^7.4.2", + "@nestjs/terminus": "^10.2.3", + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1", + "@runtipi/postgres-migrations": "^5.3.0", + "@sentry/nestjs": "^8.37.1", + "ansi-to-html": "^0.7.2", + "argon2": "^0.41.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "cookie-parser": "^1.4.6", + "dotenv": "^16.4.5", + "dotenv-cli": "^7.4.2", + "drizzle-orm": "^0.33.0", + "i18next": "^23.15.1", + "i18next-fs-backend": "^2.3.2", + "keyv": "^5.1.2", + "lodash.clonedeep": "^4.5.0", + "minisearch": "^7.1.0", + "nestjs-zod": "^4.1.0", + "node-cron": "^3.0.3", + "pg": "^8.12.0", + "rabbitmq-client": "^5.0.0", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1", + "semver": "^7.6.3", + "socket.io": "^4.8.0", + "sqlite3": "^5.1.7", + "systeminformation": "^5.23.5", + "uuid": "^10.0.0", + "validator": "^13.12.0", + "web-push": "^3.6.7", + "winston": "^3.14.2", + "yaml": "^2.6.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@sentry/types": "^8.37.1", + "@swc/cli": "0.4.1-nightly.20240914", + "@swc/core": "^1.7.26", + "@types/cookie-parser": "^1.4.7", + "@types/express": "^4.17.17", + "@types/lodash.clonedeep": "^4.5.9", + "@types/node": "^20.3.1", + "@types/node-cron": "^3.0.11", + "@types/pg": "^8.11.10", + "@types/semver": "^7.5.8", + "@types/supertest": "^6.0.0", + "@types/uuid": "^10.0.0", + "@types/validator": "^13.12.2", + "@types/web-push": "^3.6.3", + "@vercel/ncc": "^0.38.2", + "esbuild": "^0.24.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3", + "vitest": "^2.1.1" + } +} diff --git a/packages/backend/src/@types/express/index.d.ts b/packages/backend/src/@types/express/index.d.ts new file mode 100644 index 0000000000..cfb5dfd804 --- /dev/null +++ b/packages/backend/src/@types/express/index.d.ts @@ -0,0 +1,9 @@ +import type { UserDto } from '@/modules/user/dto/user.dto'; + +declare global { + namespace Express { + interface Request { + user?: UserDto; + } + } +} diff --git a/packages/backend/src/app.controller.ts b/packages/backend/src/app.controller.ts new file mode 100644 index 0000000000..b66141f099 --- /dev/null +++ b/packages/backend/src/app.controller.ts @@ -0,0 +1,68 @@ +import { ConfigurationService } from '@/core/config/configuration.service'; +import { UserRepository } from '@/modules/user/user.repository'; +import { Body, Controller, Get, Patch, Req, UseGuards } from '@nestjs/common'; +import type { Request } from 'express'; +import { ZodSerializerDto } from 'nestjs-zod'; +import { AcknowledgeWelcomeBody, AppContextDto, PartialUserSettingsDto, UserContextDto } from './app.dto'; +import { AppService } from './app.service'; +import { AppCatalogService } from './modules/apps/app-catalog.service'; +import { AuthGuard } from './modules/auth/auth.guard'; +import type { UserDto } from './modules/user/dto/user.dto'; + +@Controller() +export class AppController { + constructor( + private readonly appService: AppService, + private readonly userRepository: UserRepository, + private readonly configuration: ConfigurationService, + private readonly appCatalog: AppCatalogService, + ) {} + + @Get('/user-context') + @ZodSerializerDto(UserContextDto) + async userContext(@Req() req: Request): Promise { + const { guestDashboard } = this.configuration.get('userSettings'); + const operator = await this.userRepository.getFirstOperator(); + + return { + isLoggedIn: Boolean(req.user), + isConfigured: Boolean(operator), + isGuestDashboardEnabled: guestDashboard, + }; + } + + @Get('/app-context') + @UseGuards(AuthGuard) + @ZodSerializerDto(AppContextDto) + async appContext(@Req() req: Request): Promise { + const version = await this.appService.getVersion(); + + const { userSettings } = this.configuration.getConfig(); + + const apps = await this.appCatalog.getAvailableApps(); + + return { version, userSettings, user: req.user as UserDto, apps }; + } + + @Patch('/user-settings') + @UseGuards(AuthGuard) + async updateUserSettings(@Body() body: PartialUserSettingsDto): Promise { + await this.configuration.setUserSettings(body); + } + + @Patch('/acknowledge-welcome') + @UseGuards(AuthGuard) + async acknowledgeWelcome(@Req() req: Request, @Body() body: AcknowledgeWelcomeBody): Promise { + if (!req.user) { + return; + } + + await this.userRepository.updateUser(req.user.id, { hasSeenWelcome: true }); + await this.configuration.setUserSettings({ allowErrorMonitoring: body.allowErrorMonitoring }); + } + + @Get('/debug-sentry') + getError() { + throw new Error('My first Sentry error!'); + } +} diff --git a/packages/backend/src/app.dto.ts b/packages/backend/src/app.dto.ts new file mode 100644 index 0000000000..ea24f853d6 --- /dev/null +++ b/packages/backend/src/app.dto.ts @@ -0,0 +1,46 @@ +import { createZodDto } from 'nestjs-zod'; +import { AppInfoSimpleDto } from './modules/apps/dto/app-info.dto'; +import { UserDto } from './modules/user/dto/user.dto'; + +import { z } from 'zod'; + +export const settingsSchema = z.object({ + dnsIp: z.string().ip().trim(), + internalIp: z.string().ip().trim(), + postgresPort: z.coerce.number(), + appsRepoUrl: z.string().url().trim(), + domain: z.string().trim(), + appDataPath: z.string().trim(), + localDomain: z.string().trim(), + demoMode: z.boolean(), + guestDashboard: z.boolean(), + allowAutoThemes: z.boolean(), + allowErrorMonitoring: z.boolean(), + persistTraefikConfig: z.boolean(), + port: z.coerce.number(), + sslPort: z.coerce.number(), + listenIp: z.string().ip().trim(), + timeZone: z.string().trim(), +}); + +export class UserSettingsDto extends createZodDto(settingsSchema) {} +export class PartialUserSettingsDto extends createZodDto(settingsSchema.partial()) {} + +export class AppContextDto extends createZodDto( + z.object({ + version: z.object({ current: z.string(), latest: z.string(), body: z.string() }), + userSettings: UserSettingsDto.schema, + user: UserDto.schema, + apps: AppInfoSimpleDto.schema.array(), + }), +) {} + +export class UserContextDto extends createZodDto( + z.object({ + isLoggedIn: z.boolean().describe('Indicates if the user is logged in'), + isConfigured: z.boolean().describe('Indicates if the app is already configured'), + isGuestDashboardEnabled: z.boolean().describe('Indicates if the guest dashboard is enabled'), + }), +) {} + +export class AcknowledgeWelcomeBody extends createZodDto(z.object({ allowErrorMonitoring: z.boolean() })) {} diff --git a/packages/backend/src/app.module.ts b/packages/backend/src/app.module.ts new file mode 100644 index 0000000000..2f6e85b447 --- /dev/null +++ b/packages/backend/src/app.module.ts @@ -0,0 +1,82 @@ +import path from 'node:path'; +import { CacheModule } from '@/core/cache/cache.module'; +import { ConfigurationModule } from '@/core/config/configuration.module'; +import { DatabaseModule } from '@/core/database/database.module'; +import { AuthModule } from '@/modules/auth/auth.module'; +import { I18nModule } from '@/modules/i18n/i18n.module'; +import { type DynamicModule, type MiddlewareConsumer, Module, type NestModule } from '@nestjs/common'; +import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; +import { ServeStaticModule } from '@nestjs/serve-static'; +import { SentryModule } from '@sentry/nestjs/setup'; +import { ZodSerializerInterceptor, ZodValidationPipe } from 'nestjs-zod'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { APP_DIR } from './common/constants'; +import { MainExceptionFilter } from './common/error/exception.filter'; +import { FilesystemModule } from './core/filesystem/filesystem.module'; +import { HealthModule } from './core/health/health.module'; +import { LoggerModule } from './core/logger/logger.module'; +import { LoggerService } from './core/logger/logger.service'; +import { SocketModule } from './core/socket/socket.module'; +import { AppLifecycleModule } from './modules/app-lifecycle/app-lifecycle.module'; +import { AppsModule } from './modules/apps/apps.module'; +import { AuthMiddleware } from './modules/auth/auth.middleware'; +import { BackupsModule } from './modules/backups/backups.module'; +import { LinksModule } from './modules/links/links.module'; +import { QueueModule } from './modules/queue/queue.module'; +import { ReposModule } from './modules/repos/repos.module'; +import { SystemModule } from './modules/system/system.module'; +import { UserModule } from './modules/user/user.module'; + +const imports: (DynamicModule | typeof I18nModule)[] = [ + SentryModule.forRoot(), + SystemModule, + I18nModule, + AuthModule, + UserModule, + ConfigurationModule, + DatabaseModule, + CacheModule, + LoggerModule, + AppsModule, + FilesystemModule, + ReposModule, + QueueModule, + AppLifecycleModule, + SocketModule, + LinksModule, + BackupsModule, + HealthModule, +]; + +if (process.env.NODE_ENV === 'production') { + imports.push( + ServeStaticModule.forRoot({ + rootPath: path.join(APP_DIR, 'assets', 'frontend'), + exclude: ['/api*'], + }), + ); +} + +@Module({ + imports, + providers: [ + AppService, + { provide: APP_INTERCEPTOR, useClass: ZodSerializerInterceptor }, + { + provide: APP_PIPE, + useClass: ZodValidationPipe, + }, + { + provide: APP_FILTER, + useFactory: (logger: LoggerService) => new MainExceptionFilter(logger), + inject: [LoggerService], + }, + ], + controllers: [AppController], +}) +export class AppModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(AuthMiddleware).forRoutes('*'); + } +} diff --git a/packages/backend/src/app.service.ts b/packages/backend/src/app.service.ts new file mode 100644 index 0000000000..29b4ae76cb --- /dev/null +++ b/packages/backend/src/app.service.ts @@ -0,0 +1,230 @@ +import path from 'node:path'; + +import { Injectable } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; +import { z } from 'zod'; +import { cleanseErrorData } from './common/helpers/error-helpers'; +import { execAsync } from './common/helpers/exec-helpers'; +import { CacheService } from './core/cache/cache.service'; +import { ConfigurationService } from './core/config/configuration.service'; +import { DatabaseMigrator } from './core/database/database-migrator.service'; +import { FilesystemService } from './core/filesystem/filesystem.service'; +import { LoggerService } from './core/logger/logger.service'; +import { SocketManager } from './core/socket/socket.service'; +import { RepoEventsQueue } from './modules/queue/entities/repo-events'; +import { ReposService } from './modules/repos/repos.service'; + +@Injectable() +export class AppService { + constructor( + private readonly cache: CacheService, + private readonly configuration: ConfigurationService, + private readonly logger: LoggerService, + private readonly migrator: DatabaseMigrator, + private readonly repos: ReposService, + private readonly repoQueue: RepoEventsQueue, + private readonly socketManager: SocketManager, + private readonly filesystem: FilesystemService, + ) {} + + public async bootstrap() { + const { directories, version, appsRepoUrl, userSettings } = this.configuration.getConfig(); + const { appDir } = directories; + + this.initSentry({ release: version, allowSentry: userSettings.allowErrorMonitoring }); + + await this.logger.flush(); + + this.logger.info(`Running version: ${process.env.TIPI_VERSION}`); + this.logger.info('Generating system env file...'); + + // Run migrations + await this.migrator.runPostgresMigrations(path.join(appDir, 'assets')); + + const clone = await this.repos.cloneRepo(appsRepoUrl); + if (!clone.success) { + this.logger.error(`Failed to clone repo ${appsRepoUrl}`); + } + + if (process.env.NODE_ENV !== 'development') { + const pull = await this.repos.pullRepo(appsRepoUrl); + if (!pull.success) { + this.logger.error(`Failed to pull repo ${appsRepoUrl}`); + } + } + + // Every 15 minutes, check for updates to the apps repo + this.repoQueue.publishRepeatable({ command: 'update', url: appsRepoUrl }, '*/15 * * * *'); + + this.socketManager.init(); + + await this.copyAssets(); + await this.generateTlsCertificates({ localDomain: userSettings.localDomain }); + } + + public async initSentry(params: { release: string; allowSentry: boolean }) { + const { release, allowSentry } = params; + + if (allowSentry) { + Sentry.init({ + release, + dsn: 'https://6cc88df40d1cdd0222ff30d996ca457c@o4504242900238336.ingest.us.sentry.io/4508264534835200', + environment: process.env.NODE_ENV, + beforeSend: cleanseErrorData, + includeLocalVariables: true, + integrations: [], + initialScope: { + tags: { version: release }, + }, + }); + } + } + + public async getVersion() { + const { version: currentVersion } = this.configuration.getConfig(); + + try { + let version = (await this.cache.get('latestVersion')) ?? ''; + let body = (await this.cache.get('latestVersionBody')) ?? ''; + + if (!version) { + const response = await fetch('https://api.github.com/repos/runtipi/runtipi/releases/latest'); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const data = await response.json(); + + const res = z.object({ tag_name: z.string(), body: z.string() }).parse(data); + + version = res.tag_name; + body = res.body; + + await this.cache.set('latestVersion', version, 60 * 60); + await this.cache.set('latestVersionBody', body, 60 * 60); + } + + return { current: currentVersion, latest: version, body }; + } catch (e) { + this.logger.error(e); + return { + current: currentVersion, + latest: currentVersion, + body: '', + }; + } + } + + public async copyAssets() { + const { directories, userSettings } = this.configuration.getConfig(); + const { appDir, dataDir, appDataDir } = directories; + + const assetsFolder = path.join(appDir, 'assets'); + + this.logger.info('Creating traefik folders'); + + await this.filesystem.createDirectories([ + path.join(dataDir, 'traefik', 'dynamic'), + path.join(dataDir, 'traefik', 'shared'), + path.join(dataDir, 'traefik', 'tls'), + ]); + + if (userSettings.persistTraefikConfig) { + this.logger.warn('Skipping the copy of traefik files because persistTraefikConfig is set to true'); + } else { + this.logger.info('Copying traefik files'); + await this.filesystem.copyFile(path.join(assetsFolder, 'traefik', 'traefik.yml'), path.join(dataDir, 'traefik', 'traefik.yml')); + await this.filesystem.copyFile( + path.join(assetsFolder, 'traefik', 'dynamic', 'dynamic.yml'), + path.join(dataDir, 'traefik', 'dynamic', 'dynamic.yml'), + ); + } + + // Create base folders + this.logger.info('Creating base folders'); + await this.filesystem.createDirectories([ + path.join(dataDir, 'apps'), + path.join(dataDir, 'state'), + path.join(dataDir, 'repos'), + path.join(dataDir, 'backups'), + path.join(appDataDir), + ]); + + // Create media folders + this.logger.info('Creating media folders'); + await this.filesystem.createDirectories([ + path.join(dataDir, 'media', 'torrents', 'watch'), + path.join(dataDir, 'media', 'torrents', 'complete'), + path.join(dataDir, 'media', 'torrents', 'incomplete'), + path.join(dataDir, 'media', 'usenet', 'watch'), + path.join(dataDir, 'media', 'usenet', 'complete'), + path.join(dataDir, 'media', 'usenet', 'incomplete'), + path.join(dataDir, 'media', 'downloads', 'watch'), + path.join(dataDir, 'media', 'downloads', 'complete'), + path.join(dataDir, 'media', 'downloads', 'incomplete'), + path.join(dataDir, 'media', 'data', 'books'), + path.join(dataDir, 'media', 'data', 'comics'), + path.join(dataDir, 'media', 'data', 'movies'), + path.join(dataDir, 'media', 'data', 'music'), + path.join(dataDir, 'media', 'data', 'tv'), + path.join(dataDir, 'media', 'data', 'podcasts'), + path.join(dataDir, 'media', 'data', 'images'), + path.join(dataDir, 'media', 'data', 'roms'), + ]); + } + + /** + * Given a domain, generates the TLS certificates for it to be used with Traefik + * + * @param {string} data.domain The domain to generate the certificates for + */ + public generateTlsCertificates = async (data: { localDomain?: string }) => { + if (!data.localDomain) { + return; + } + + const { dataDir } = this.configuration.get('directories'); + + const tlsFolder = path.join(dataDir, 'traefik', 'tls'); + + // If the certificate already exists, don't generate it again + if ( + (await this.filesystem.pathExists(path.join(tlsFolder, `${data.localDomain}.txt`))) && + (await this.filesystem.pathExists(path.join(tlsFolder, 'cert.pem'))) && + (await this.filesystem.pathExists(path.join(tlsFolder, 'key.pem'))) + ) { + this.logger.info(`TLS certificate for ${data.localDomain} already exists`); + return; + } + + // Empty out the folder + const files = await this.filesystem.listFiles(tlsFolder); + await Promise.all( + files.map(async (file) => { + this.logger.info(`Removing file ${file}`); + await this.filesystem.removeFile(path.join(tlsFolder, file)); + }), + ); + + const subject = `/O=runtipi.io/OU=IT/CN=*.${data.localDomain}/emailAddress=webmaster@${data.localDomain}`; + const subjectAltName = `DNS:*.${data.localDomain},DNS:${data.localDomain}`; + + try { + this.logger.info(`Generating TLS certificate for ${data.localDomain}`); + const { stderr } = await execAsync( + `openssl req -x509 -newkey rsa:4096 -keyout ${dataDir}/traefik/tls/key.pem -out ${dataDir}/traefik/tls/cert.pem -days 365 -subj "${subject}" -addext "subjectAltName = ${subjectAltName}" -nodes`, + ); + if ( + !(await this.filesystem.pathExists(path.join(tlsFolder, 'cert.pem'))) || + !(await this.filesystem.pathExists(path.join(tlsFolder, 'key.pem'))) + ) { + this.logger.error(`Failed to generate TLS certificate for ${data.localDomain}`); + this.logger.error(stderr); + } else { + this.logger.info(`Writing txt file for ${data.localDomain}`); + } + await this.filesystem.writeTextFile(path.join(tlsFolder, `${data.localDomain}.txt`), ''); + } catch (error) { + this.logger.error(error); + } + }; +} diff --git a/packages/backend/src/common/constants.ts b/packages/backend/src/common/constants.ts new file mode 100644 index 0000000000..654d1b66a5 --- /dev/null +++ b/packages/backend/src/common/constants.ts @@ -0,0 +1,6 @@ +export const APP_DIR = '/app'; +export const DATA_DIR = '/data'; +export const APP_DATA_DIR = '/app-data'; + +export const SESSION_COOKIE_NAME = 'tipi.sid'; +export const SESSION_COOKIE_MAX_AGE = 1000 * 60 * 60 * 24; diff --git a/packages/backend/src/common/error/exception.filter.ts b/packages/backend/src/common/error/exception.filter.ts new file mode 100644 index 0000000000..5a5dd557ed --- /dev/null +++ b/packages/backend/src/common/error/exception.filter.ts @@ -0,0 +1,41 @@ +import type { LoggerService } from '@/core/logger/logger.service'; +import { type ArgumentsHost, Catch, type ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common'; +import { WithSentry } from '@sentry/nestjs'; +import type { Request, Response } from 'express'; +import { ZodError } from 'zod'; +import { TranslatableError } from './translatable-error'; + +@Catch() +export class MainExceptionFilter implements ExceptionFilter { + constructor(private readonly logger: LoggerService) {} + + @WithSentry() + catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; + + let message = 'Internal server error'; + + if (status === HttpStatus.INTERNAL_SERVER_ERROR) { + this.logger.error(`An error occured while calling: ${request.url}`, exception); + } + + // @ts-expect-error + const error = exception?.error; + if (error instanceof ZodError) { + this.logger.error('Schema validation failed: ', request.path, JSON.stringify(error.errors, null, 2)); + } + + if ((exception instanceof Error && status !== HttpStatus.INTERNAL_SERVER_ERROR) || exception instanceof TranslatableError) { + message = exception.message; + } + + response.status(status).json({ + statusCode: status, + message, + path: request.url, + }); + } +} diff --git a/packages/backend/src/common/error/translatable-error.ts b/packages/backend/src/common/error/translatable-error.ts new file mode 100644 index 0000000000..96490f9106 --- /dev/null +++ b/packages/backend/src/common/error/translatable-error.ts @@ -0,0 +1,10 @@ +import { HttpException, type HttpExceptionOptions } from '@nestjs/common'; +import messages from '@/modules/i18n/translations/en.json'; + +type TranslationKey = keyof typeof messages; + +export class TranslatableError extends HttpException { + constructor(message: TranslationKey, intlParams?: Record, statusCode = 500, options?: HttpExceptionOptions) { + super({ message, intlParams }, statusCode, options); + } +} diff --git a/packages/backend/src/common/helpers/env-helpers.ts b/packages/backend/src/common/helpers/env-helpers.ts new file mode 100644 index 0000000000..ad1a41374c --- /dev/null +++ b/packages/backend/src/common/helpers/env-helpers.ts @@ -0,0 +1,125 @@ +import crypto from 'node:crypto'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { settingsSchema } from '@/app.dto'; +import { EnvUtils } from '@/modules/env/env.utils'; +import dotenv from 'dotenv'; +import { DATA_DIR } from '../constants'; + +const OLD_DEFAULT_REPO_URL = 'https://github.com/meienberger/runtipi-appstore'; +export const DEFAULT_REPO_URL = 'https://github.com/runtipi/runtipi-appstore'; + +/** + * Generates a random seed if it does not exist yet + */ +const generateSeed = async () => { + const seedFilePath = path.join(DATA_DIR, 'state', 'seed'); + if (!fs.existsSync(seedFilePath)) { + const randomBytes = crypto.randomBytes(32); + const seed = randomBytes.toString('hex'); + await fs.promises.writeFile(seedFilePath, seed); + } +}; + +/** + * Returns the architecture of the current system + */ +const getArchitecture = () => { + const arch = os.arch(); + + if (arch === 'arm64') return 'arm64'; + if (arch === 'x64') return 'amd64'; + + throw new Error(`Unsupported architecture: ${arch}`); +}; + +export const generateSystemEnvFile = async (): Promise> => { + const envUtils = new EnvUtils(); + + await fs.promises.mkdir(path.join(DATA_DIR, 'state'), { recursive: true }); + + const settingsFilePath = path.join(DATA_DIR, 'state', 'settings.json'); + const envFilePath = path.join(DATA_DIR, '.env'); + + if (!fs.existsSync(envFilePath)) { + await fs.promises.writeFile(envFilePath, ''); + } + + const envFile = await fs.promises.readFile(envFilePath, 'utf-8'); + + const envMap: Map = envUtils.envStringToMap(envFile); + envMap.set('NODE_ENV', process.env.NODE_ENV || 'production'); + + if (!fs.existsSync(settingsFilePath)) { + await fs.promises.writeFile(settingsFilePath, JSON.stringify({})); + } + + const settingsFile = await fs.promises.readFile(settingsFilePath, 'utf-8'); + + const settings = settingsSchema.partial().safeParse(JSON.parse(settingsFile)); + + if (!settings.success) { + throw new Error(`Invalid settings.json file: ${settings.error.message}`); + } + + await generateSeed(); + + const { data } = settings; + + if (data.appsRepoUrl === OLD_DEFAULT_REPO_URL) { + data.appsRepoUrl = DEFAULT_REPO_URL; + } + + const jwtSecret = envMap.get('JWT_SECRET') || envUtils.deriveEntropy('jwt_secret'); + + const repoUrl = data.appsRepoUrl || envMap.get('APPS_REPO_URL') || DEFAULT_REPO_URL; + const hash = crypto.createHash('sha256'); + hash.update(repoUrl); + const repoId = hash.digest('hex'); + + const rootFolderHost = envMap.get('ROOT_FOLDER_HOST') ?? process.env.ROOT_FOLDER_HOST; + const internalIp = envMap.get('INTERNAL_IP') ?? '127.0.0.1'; + + if (!rootFolderHost) { + throw new Error( + 'Failed to determine root folder host. If you are not running via the CLI, please set the ROOT_FOLDER_HOST environment variable.', + ); + } + + envMap.set('ROOT_FOLDER_HOST', rootFolderHost); + envMap.set('APPS_REPO_ID', repoId); + envMap.set('APPS_REPO_URL', data.appsRepoUrl || envMap.get('APPS_REPO_URL') || DEFAULT_REPO_URL); + envMap.set('TZ', data.timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone); + envMap.set('INTERNAL_IP', data.listenIp || internalIp); + envMap.set('DNS_IP', data.dnsIp || envMap.get('DNS_IP') || '9.9.9.9'); + envMap.set('ARCHITECTURE', getArchitecture()); + envMap.set('JWT_SECRET', jwtSecret); + envMap.set('DOMAIN', data.domain || envMap.get('DOMAIN') || 'example.com'); + envMap.set('RUNTIPI_APP_DATA_PATH', data.appDataPath || envMap.get('RUNTIPI_APP_DATA_PATH') || path.join(rootFolderHost, 'app-data')); + envMap.set('POSTGRES_HOST', 'runtipi-db'); + envMap.set('POSTGRES_DBNAME', 'tipi'); + envMap.set('POSTGRES_USERNAME', 'tipi'); + envMap.set('POSTGRES_PORT', String(5432)); + envMap.set('DEMO_MODE', typeof data.demoMode === 'boolean' ? String(data.demoMode) : envMap.get('DEMO_MODE') || 'false'); + envMap.set('GUEST_DASHBOARD', typeof data.guestDashboard === 'boolean' ? String(data.guestDashboard) : envMap.get('GUEST_DASHBOARD') || 'false'); + envMap.set('LOCAL_DOMAIN', data.localDomain || envMap.get('LOCAL_DOMAIN') || 'tipi.lan'); + envMap.set( + 'ALLOW_AUTO_THEMES', + typeof data.allowAutoThemes === 'boolean' ? String(data.allowAutoThemes) : envMap.get('ALLOW_AUTO_THEMES') || 'true', + ); + envMap.set( + 'ALLOW_ERROR_MONITORING', + typeof data.allowErrorMonitoring === 'boolean' ? String(data.allowErrorMonitoring) : envMap.get('ALLOW_ERROR_MONITORING') || 'false', + ); + envMap.set( + 'PERSIST_TRAEFIK_CONFIG', + typeof data.persistTraefikConfig === 'boolean' ? String(data.persistTraefikConfig) : envMap.get('PERSIST_TRAEFIK_CONFIG') || 'false', + ); + + await fs.promises.writeFile(envFilePath, envUtils.envMapToString(envMap)); + + dotenv.config({ path: envFilePath, override: true }); + + return envMap; +}; diff --git a/packages/backend/src/common/helpers/error-helpers.ts b/packages/backend/src/common/helpers/error-helpers.ts new file mode 100644 index 0000000000..0823d3a113 --- /dev/null +++ b/packages/backend/src/common/helpers/error-helpers.ts @@ -0,0 +1,78 @@ +import type { ErrorEvent, EventHint } from '@sentry/types'; +import cloneDeep from 'lodash.clonedeep'; +import validator from 'validator'; + +const IgnoreErrors = [ + // Innocuous browser errors + /ResizeObserver loop limit exceeded/, + /ResizeObserver loop completed with undelivered notifications/, + // Dark reader extension + /WeakMap key undefined must be an object or an unregistered symbol/, + // Docker-compose error + /no space left on device/, + /port is already allocated/, + /address already in use/, + /Error with your custom app/, +]; + +const cleanseUrl = (url: string) => { + if (validator.isURL(url)) { + const { pathname, search } = new URL(url); + + return `${pathname}${search}`; + } + + return url; +}; + +const shouldIgnoreException = (s: string) => { + return s && IgnoreErrors.find((pattern) => pattern.test(s)); +}; + +export const cleanseErrorData = (event: ErrorEvent, hint: EventHint) => { + const result = cloneDeep(event); + + const error = hint && (hint.originalException as Error); + + if (result.transaction) { + result.transaction = cleanseUrl(result.transaction); + } + + if (result.breadcrumbs) { + for (const breadcrumb of result.breadcrumbs) { + if (breadcrumb.data?.url) { + breadcrumb.data.url = cleanseUrl(breadcrumb.data.url); + } + } + } + + if (result.exception?.values) { + for (const exception of result.exception.values) { + if (exception.stacktrace?.frames) { + for (const frame of exception.stacktrace.frames) { + if (frame.filename) { + frame.filename = cleanseUrl(frame.filename); + } + } + } + } + } + + if (error?.message && shouldIgnoreException(error.message)) { + return null; + } + + // IF error message starts with 'Command failed: docker-compose' then grab only the 200 last characters + if (error?.message?.startsWith('Command failed: docker-compose')) { + // Command failed: docker-compose --env-file /storage/app-data//app.env + const appName = error.message.split('/')[3]; + const message = error.message.slice(-200); + result.message = `Error with ${appName}: ${message}`; + } + + if (result.request?.url) { + result.request.url = cleanseUrl(result.request.url); + } + + return result; +}; diff --git a/packages/backend/src/common/helpers/exec-helpers.ts b/packages/backend/src/common/helpers/exec-helpers.ts new file mode 100644 index 0000000000..292aeedfef --- /dev/null +++ b/packages/backend/src/common/helpers/exec-helpers.ts @@ -0,0 +1,20 @@ +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; + +type ExecAsyncParams = [command: string]; + +type ExecResult = { stdout: string; stderr: string }; + +export const execAsync = async (...args: ExecAsyncParams): Promise => { + try { + const { stdout, stderr } = await promisify(exec)(...args); + + return { stdout, stderr }; + } catch (error) { + if (error instanceof Error) { + return { stderr: error.message, stdout: '' }; + } + + return { stderr: String(error), stdout: '' }; + } +}; diff --git a/packages/backend/src/common/helpers/file-helpers.ts b/packages/backend/src/common/helpers/file-helpers.ts new file mode 100644 index 0000000000..e4f3e16fac --- /dev/null +++ b/packages/backend/src/common/helpers/file-helpers.ts @@ -0,0 +1,29 @@ +export const pLimit = (concurrency: number) => { + const queue: (() => Promise)[] = []; + let activeCount = 0; + + const next = () => { + if (queue.length > 0 && activeCount < concurrency) { + activeCount++; + const fn = queue.shift(); + fn?.().finally(() => { + activeCount--; + next(); + }); + } + }; + + return (fn: () => Promise): Promise => + new Promise((resolve, reject) => { + const run = () => + fn() + .then(resolve) + .catch(reject) + .finally(() => next()); + + queue.push(run); + next(); + }); +}; + +export const notEmpty = (value: TValue | null | undefined): value is TValue => value !== null && value !== undefined; diff --git a/packages/backend/src/core/archive/archive.module.ts b/packages/backend/src/core/archive/archive.module.ts new file mode 100644 index 0000000000..a02bea331a --- /dev/null +++ b/packages/backend/src/core/archive/archive.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ArchiveService } from './archive.service'; + +@Module({ + imports: [], + providers: [ArchiveService], + exports: [ArchiveService], +}) +export class ArchiveModule {} diff --git a/packages/backend/src/core/archive/archive.service.ts b/packages/backend/src/core/archive/archive.service.ts new file mode 100644 index 0000000000..569d8bd677 --- /dev/null +++ b/packages/backend/src/core/archive/archive.service.ts @@ -0,0 +1,13 @@ +import { execAsync } from '@/common/helpers/exec-helpers'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ArchiveService { + createTarGz = async (sourceDir: string, destinationFile: string) => { + return execAsync(`tar -czf ${destinationFile} -C ${sourceDir} .`); + }; + + extractTarGz = async (sourceFile: string, destinationDir: string) => { + return execAsync(`tar -xzf ${sourceFile} -C ${destinationDir}`); + }; +} diff --git a/packages/backend/src/core/cache/cache.module.ts b/packages/backend/src/core/cache/cache.module.ts new file mode 100644 index 0000000000..9874379176 --- /dev/null +++ b/packages/backend/src/core/cache/cache.module.ts @@ -0,0 +1,11 @@ +import { Global, Module } from '@nestjs/common'; +import { CacheService } from './cache.service'; + +@Global() +@Module({ + imports: [], + controllers: [], + providers: [CacheService], + exports: [CacheService], +}) +export class CacheModule {} diff --git a/packages/backend/src/core/cache/cache.service.ts b/packages/backend/src/core/cache/cache.service.ts new file mode 100644 index 0000000000..8cdbac3141 --- /dev/null +++ b/packages/backend/src/core/cache/cache.service.ts @@ -0,0 +1,55 @@ +import KeyvSqlite from '@keyv/sqlite'; +import { Injectable } from '@nestjs/common'; +import Keyv from 'keyv'; +import sqlite3 from 'sqlite3'; + +const ONE_DAY_IN_SECONDS = 60 * 60 * 24; + +@Injectable() +export class CacheService { + private client: Keyv; + private backend: KeyvSqlite; + + constructor() { + this.backend = new KeyvSqlite('sqlite:///cache/cache.sqlite'); + this.client = new Keyv({ + store: this.backend, + ttl: ONE_DAY_IN_SECONDS, + namespace: 'cache', + }); + } + + public getClient() { + return this.client; + } + + public set(key: string, value: string, expiration = ONE_DAY_IN_SECONDS) { + return this.client.set(key, value, expiration * 1000); + } + + public get(key: string) { + return this.client.get(key); + } + + public del(key: string) { + return this.client.delete(key); + } + + public async getByPrefix(prefix: string) { + const db = new sqlite3.Database('/cache/cache.sqlite'); + + return new Promise<{ key: string; val: string }[]>((resolve, reject) => { + db.all('SELECT * FROM keyv WHERE key LIKE ?', [`cache:${prefix}%`], (err, rows: { key: string; value: string }[]) => { + if (err) { + reject(err); + } else { + resolve(rows.map((row) => ({ key: row.key, val: JSON.parse(row.value).value }))); + } + }); + }); + } + + public clear() { + return this.client.clear(); + } +} diff --git a/packages/backend/src/core/config/configuration.module.ts b/packages/backend/src/core/config/configuration.module.ts new file mode 100644 index 0000000000..c32ce7bcf8 --- /dev/null +++ b/packages/backend/src/core/config/configuration.module.ts @@ -0,0 +1,12 @@ +import { Global, Module } from '@nestjs/common'; +import { ConfigurationService } from '../config/configuration.service'; +import { EnvModule } from '@/modules/env/env.module'; + +@Global() +@Module({ + imports: [EnvModule], + controllers: [], + providers: [ConfigurationService], + exports: [ConfigurationService], +}) +export class ConfigurationModule {} diff --git a/packages/backend/src/core/config/configuration.service.ts b/packages/backend/src/core/config/configuration.service.ts new file mode 100644 index 0000000000..0943092cc5 --- /dev/null +++ b/packages/backend/src/core/config/configuration.service.ts @@ -0,0 +1,135 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import type { PartialUserSettingsDto } from '@/app.dto'; +import { APP_DATA_DIR, APP_DIR, DATA_DIR } from '@/common/constants'; +import { TranslatableError } from '@/common/error/translatable-error'; +import { EnvUtils } from '@/modules/env/env.utils'; +import { Injectable } from '@nestjs/common'; +import dotenv from 'dotenv'; +import { z } from 'zod'; + +export const ARCHITECTURES = ['arm64', 'amd64'] as const; +export type Architecture = (typeof ARCHITECTURES)[number]; + +const envSchema = z.object({ + POSTGRES_HOST: z.string(), + POSTGRES_DBNAME: z.string(), + POSTGRES_USERNAME: z.string(), + POSTGRES_PASSWORD: z.string(), + POSTGRES_PORT: z.string().transform(Number), + ARCHITECTURE: z.enum(ARCHITECTURES).default('amd64'), + INTERNAL_IP: z.string(), + TIPI_VERSION: z.string(), + JWT_SECRET: z.string(), + APPS_REPO_ID: z.string(), + APPS_REPO_URL: z.string(), + DOMAIN: z.string(), + LOCAL_DOMAIN: z.string(), + DNS_IP: z.string().default('9.9.9.9'), + RUNTIPI_APP_DATA_PATH: z.string(), + DEMO_MODE: z.string().transform((val) => val.toLowerCase() === 'true'), + GUEST_DASHBOARD: z.string().transform((val) => val.toLowerCase() === 'true'), + ALLOW_ERROR_MONITORING: z.string().transform((val) => val.toLowerCase() === 'true'), + ALLOW_AUTO_THEMES: z.string().transform((val) => val.toLowerCase() === 'true'), + PERSIST_TRAEFIK_CONFIG: z.string().transform((val) => val.toLowerCase() === 'true'), + LOG_LEVEL: z.string().default('info'), + TZ: z.string(), + ROOT_FOLDER_HOST: z.string(), + NGINX_PORT: z.string().default('80').transform(Number), + NGINX_PORT_SSL: z.string().default('443').transform(Number), +}); + +@Injectable() +export class ConfigurationService { + private config: ReturnType; + private envPath = path.join(DATA_DIR, '.env'); + + // Lowest level, cannot use any other service or module to avoid circular dependencies + constructor(private readonly envUtils: EnvUtils) { + dotenv.config({ path: this.envPath }); + this.config = this.configure(); + } + + private configure() { + let envFile = ''; + try { + envFile = fs.readFileSync(this.envPath).toString(); + } catch (e) { + console.error('❌ .env file not found'); + } + + const envMap = this.envUtils.envStringToMap(envFile.toString()); + const conf = { ...Object.fromEntries(envMap), ...process.env } as Record; + + const env = envSchema.safeParse(conf); + + if (!env.success) { + console.error(env.error.errors); + throw new Error(`❌ Invalid environment variables ${JSON.stringify(env.error.flatten(), null, 2)}`); + } + + return { + database: { + host: env.data.POSTGRES_HOST, + port: env.data.POSTGRES_PORT, + username: env.data.POSTGRES_USERNAME, + password: env.data.POSTGRES_PASSWORD, + database: env.data.POSTGRES_DBNAME, + }, + directories: { + dataDir: DATA_DIR, + appDataDir: APP_DATA_DIR, + appDir: APP_DIR, + }, + logLevel: env.data.LOG_LEVEL, + version: env.data.TIPI_VERSION, + userSettings: { + allowAutoThemes: env.data.ALLOW_AUTO_THEMES, + allowErrorMonitoring: env.data.ALLOW_ERROR_MONITORING, + demoMode: env.data.DEMO_MODE, + guestDashboard: env.data.GUEST_DASHBOARD, + timeZone: env.data.TZ, + domain: env.data.DOMAIN, + localDomain: env.data.LOCAL_DOMAIN, + port: env.data.NGINX_PORT || 80, + sslPort: env.data.NGINX_PORT_SSL || 443, + listenIp: env.data.INTERNAL_IP, // TODO: Check if this is correct + internalIp: env.data.INTERNAL_IP, + appsRepoUrl: env.data.APPS_REPO_URL, + postgresPort: env.data.POSTGRES_PORT, + dnsIp: env.data.DNS_IP, + appDataPath: env.data.RUNTIPI_APP_DATA_PATH, // TODO: Check how it's used + persistTraefikConfig: env.data.PERSIST_TRAEFIK_CONFIG, + }, + appsRepoId: env.data.APPS_REPO_ID, + appsRepoUrl: env.data.APPS_REPO_URL, + architecture: env.data.ARCHITECTURE, + demoMode: env.data.DEMO_MODE, + rootFolderHost: env.data.ROOT_FOLDER_HOST, + envFilePath: this.envPath, + internalIp: env.data.INTERNAL_IP, + jwtSecret: env.data.JWT_SECRET, + }; + } + + public getConfig() { + return this.config; + } + + public get>(key: T) { + return this.config[key]; + } + + public async setUserSettings(settings: PartialUserSettingsDto) { + if (this.config.demoMode) { + throw new TranslatableError('SERVER_ERROR_NOT_ALLOWED_IN_DEMO'); + } + + await fs.promises.writeFile(`${DATA_DIR}/state/settings.json`, JSON.stringify(settings, null, 2)); + + this.config.userSettings = { + ...this.config.userSettings, + ...settings, + }; + } +} diff --git a/packages/backend/src/core/database/database-migrator.service.ts b/packages/backend/src/core/database/database-migrator.service.ts new file mode 100644 index 0000000000..db0b23a90b --- /dev/null +++ b/packages/backend/src/core/database/database-migrator.service.ts @@ -0,0 +1,51 @@ +import path from 'node:path'; +import { migrate } from '@runtipi/postgres-migrations'; +import pg from 'pg'; + +import { Injectable } from '@nestjs/common'; +import { ConfigurationService } from '../config/configuration.service'; +import { LoggerService } from '../logger/logger.service'; + +@Injectable() +export class DatabaseMigrator { + constructor( + private configurationService: ConfigurationService, + private logger: LoggerService, + ) {} + + public runPostgresMigrations = async (migrationsFolder: string) => { + const { database, host, username, password, port } = this.configurationService.getConfig().database; + + this.logger.info('Starting database migration'); + + this.logger.info(`Connecting to database ${database} on ${host} as ${username} on port ${port}`); + + const client = new pg.Client({ user: username, host, database, password, port: Number(port) }); + await client.connect(); + + this.logger.info('Client connected'); + + try { + const { rows } = await client.query('SELECT * FROM migrations'); + // if rows contains a migration with name 'Initial1657299198975' (legacy typeorm) delete table migrations. As all migrations are idempotent we can safely delete the table and start over. + if (rows.find((row) => row.name === 'Initial1657299198975')) { + this.logger.info('Found legacy migration. Deleting table migrations'); + await client.query('DROP TABLE migrations'); + } + } catch (e) { + this.logger.info('Migrations table not found, creating it', e); + } + + this.logger.info('Running migrations'); + try { + await migrate({ client }, path.join(migrationsFolder, 'migrations'), { skipCreateMigrationTable: true }); + } catch (e) { + this.logger.error('Error running migrations. Dropping table migrations and trying again', e); + await client.query('DROP TABLE migrations'); + await migrate({ client }, path.join(migrationsFolder, 'migrations'), { skipCreateMigrationTable: true }); + } + + this.logger.info('Migration complete'); + await client.end(); + }; +} diff --git a/packages/backend/src/core/database/database.module.ts b/packages/backend/src/core/database/database.module.ts new file mode 100644 index 0000000000..f9f2f953b9 --- /dev/null +++ b/packages/backend/src/core/database/database.module.ts @@ -0,0 +1,12 @@ +import { Global, Module } from '@nestjs/common'; +import { DatabaseMigrator } from './database-migrator.service'; +import { DatabaseService } from './database.service'; + +@Global() +@Module({ + imports: [], + controllers: [], + providers: [DatabaseService, DatabaseMigrator], + exports: [DatabaseService, DatabaseMigrator], +}) +export class DatabaseModule {} diff --git a/packages/backend/src/core/database/database.service.ts b/packages/backend/src/core/database/database.service.ts new file mode 100644 index 0000000000..c784df71eb --- /dev/null +++ b/packages/backend/src/core/database/database.service.ts @@ -0,0 +1,37 @@ +import { Pool } from 'pg'; +import * as schema from './schema'; +import { type NodePgDatabase, drizzle } from 'drizzle-orm/node-postgres'; +import { ConfigurationService } from '../config/configuration.service'; +import { Injectable } from '@nestjs/common'; +import { LoggerService } from '../logger/logger.service'; + +@Injectable() +export class DatabaseService { + public db: NodePgDatabase; + + constructor( + private configurationService: ConfigurationService, + private logger: LoggerService, + ) { + const { username, port, database, host, password } = this.configurationService.getConfig().database; + const connectionString = `postgresql://${username}:${password}@${host}:${port}/${database}?connect_timeout=300`; + + const pool = new Pool({ + connectionString, + }); + + pool.on('error', async (err) => { + this.logger.error('Unexpected error on idle client:', err); + }); + + pool.on('connect', () => { + this.logger.debug('Connected to the database successfully.'); + }); + + pool.on('remove', () => { + this.logger.debug('Client removed from the pool.'); + }); + + this.db = drizzle(pool, { schema }); + } +} diff --git a/packages/backend/src/core/database/migrations/00000-create-migrations-table.sql b/packages/backend/src/core/database/migrations/00000-create-migrations-table.sql new file mode 100644 index 0000000000..9a9d1c1ce6 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00000-create-migrations-table.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS migrations ( + id integer PRIMARY KEY, + name varchar(100) UNIQUE NOT NULL, + hash varchar(40) NOT NULL, -- sha1 hex encoded hash of the file name and contents, to ensure it hasn't been altered since applying the migration + executed_at timestamp DEFAULT CURRENT_TIMESTAMP +); diff --git a/packages/backend/src/core/database/migrations/00001-initial.sql b/packages/backend/src/core/database/migrations/00001-initial.sql new file mode 100644 index 0000000000..27f69b33a5 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00001-initial.sql @@ -0,0 +1,69 @@ +DO $$ +BEGIN + -- check if enum update_status_enum exists + IF NOT EXISTS ( + SELECT + 1 + FROM + pg_type + WHERE + typname = 'update_status_enum') THEN + -- create enum + CREATE TYPE "public"."update_status_enum" AS ENUM ( + 'FAILED', + 'SUCCESS' +); +END IF; + -- check if enum app_status_enum exists + IF NOT EXISTS ( + SELECT + 1 + FROM + pg_type + WHERE + typname = 'app_status_enum') THEN + -- create enum + CREATE TYPE "public"."app_status_enum" AS ENUM ( + 'running', + 'stopped', + 'installing', + 'uninstalling', + 'stopping', + 'starting', + 'missing' +); +END IF; +END +$$; + +CREATE TABLE IF NOT EXISTS "update" ( + "id" serial NOT NULL, + "name" character varying NOT NULL, + "status" "public"."update_status_enum" NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "UQ_6e7d7ecccdc972caa0ad33cb014" UNIQUE ("name"), + CONSTRAINT "PK_575f77a0576d6293bc1cb752847" PRIMARY KEY ("id") +); + +CREATE TABLE IF NOT EXISTS "user" ( + "id" serial NOT NULL, + "username" character varying NOT NULL, + "password" character varying NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), + CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id") +); + +CREATE TABLE IF NOT EXISTS "app" ( + "id" character varying NOT NULL, + "status" "public"."app_status_enum" NOT NULL DEFAULT 'stopped', + "lastOpened" timestamp with time zone DEFAULT now(), + "numOpened" integer NOT NULL DEFAULT '0', + "config" jsonb NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "UQ_9478629fc093d229df09e560aea" UNIQUE ("id"), + CONSTRAINT "PK_9478629fc093d229df09e560aea" PRIMARY KEY ("id") +); diff --git a/packages/backend/src/core/database/migrations/00002-add-app-version.sql b/packages/backend/src/core/database/migrations/00002-add-app-version.sql new file mode 100644 index 0000000000..4d287f21f3 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00002-add-app-version.sql @@ -0,0 +1,32 @@ +-- Create version field if it doesn't exist +ALTER TABLE "app" + ADD COLUMN IF NOT EXISTS "version" integer DEFAULT '0'; + +-- Set version field to 1 for all apps that have no version +UPDATE + "app" +SET + "version" = '1' +WHERE + "version" IS NULL + OR "version" = '0'; + +-- Set version field to not null +ALTER TABLE "app" + ALTER COLUMN "version" SET NOT NULL; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT + * + FROM + information_schema.table_constraints + WHERE + constraint_name = 'UQ_9478629fc093d229df09e560aea' + AND table_name = 'app') THEN + ALTER TABLE "app" + ADD CONSTRAINT "UQ_9478629fc093d229df09e560aea" UNIQUE ("id"); +END IF; +END +$$; diff --git a/packages/backend/src/core/database/migrations/00003-add-status-updating.sql b/packages/backend/src/core/database/migrations/00003-add-status-updating.sql new file mode 100644 index 0000000000..02ed9cd817 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00003-add-status-updating.sql @@ -0,0 +1,15 @@ +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT + 1 + FROM + pg_enum + WHERE + enumlabel = 'updating'::text + AND enumtypid = 'public.app_status_enum'::regtype) THEN + ALTER TYPE "public"."app_status_enum" + ADD VALUE 'updating'; +END IF; +END +$$; diff --git a/packages/backend/src/core/database/migrations/00004-add-exposed-domain-fields.sql b/packages/backend/src/core/database/migrations/00004-add-exposed-domain-fields.sql new file mode 100644 index 0000000000..a56cd26e7d --- /dev/null +++ b/packages/backend/src/core/database/migrations/00004-add-exposed-domain-fields.sql @@ -0,0 +1,23 @@ +-- Create exposed field if it doesn't exist +ALTER TABLE "app" + ADD COLUMN IF NOT EXISTS "exposed" boolean DEFAULT FALSE; + +-- Select all apps that have not the exposed field and put it to false +UPDATE + "app" +SET + "exposed" = FALSE +WHERE + "exposed" IS NULL; + +-- Set exposed column to not null constraint +ALTER TABLE "app" + ALTER COLUMN "exposed" SET NOT NULL; + +-- Create domain column if it doesn't exist +ALTER TABLE "app" + ADD COLUMN IF NOT EXISTS "domain" character varying; + +-- Set default version to 1 +ALTER TABLE "app" + ALTER COLUMN "version" SET DEFAULT '1'; diff --git a/packages/backend/src/core/database/migrations/00005-add-user-operator.sql b/packages/backend/src/core/database/migrations/00005-add-user-operator.sql new file mode 100644 index 0000000000..0880343656 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00005-add-user-operator.sql @@ -0,0 +1,18 @@ +-- Create operator field if it doesn't exist +ALTER TABLE "user" + ADD COLUMN IF NOT EXISTS "operator" boolean DEFAULT NULL; + +UPDATE + "user" +SET + "operator" = TRUE +WHERE + "operator" IS NULL; + +-- Set operator column to default false +ALTER TABLE "user" + ALTER COLUMN "operator" SET DEFAULT FALSE; + +-- Set operator column to not null constraint +ALTER TABLE "user" + ALTER COLUMN "operator" SET NOT NULL; diff --git a/packages/backend/src/core/database/migrations/00006-add-totp-user-fields.sql b/packages/backend/src/core/database/migrations/00006-add-totp-user-fields.sql new file mode 100644 index 0000000000..4bca28f573 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00006-add-totp-user-fields.sql @@ -0,0 +1,23 @@ +-- Create totp_secret field if it doesn't exist +ALTER TABLE "user" + ADD COLUMN IF NOT EXISTS "totp_secret" text DEFAULT NULL; + +-- Create totp_enabled field if it doesn't exist +ALTER TABLE "user" + ADD COLUMN IF NOT EXISTS "totp_enabled" boolean DEFAULT FALSE; + +-- Add salt field to user table +ALTER TABLE "user" + ADD COLUMN IF NOT EXISTS "salt" text DEFAULT NULL; + +-- Set all users to have totp enabled false +UPDATE + "user" +SET + "totp_enabled" = FALSE +WHERE + "totp_enabled" IS NULL; + +-- Set totp_enabled column to not null constraint +ALTER TABLE "user" + ALTER COLUMN "totp_enabled" SET NOT NULL; diff --git a/packages/backend/src/core/database/migrations/00007-add-locale-user-col.sql b/packages/backend/src/core/database/migrations/00007-add-locale-user-col.sql new file mode 100644 index 0000000000..65f5ef5380 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00007-add-locale-user-col.sql @@ -0,0 +1,15 @@ +-- Create locale field if it doesn't exist +ALTER TABLE "user" + ADD COLUMN IF NOT EXISTS "locale" character varying DEFAULT 'en'; + +-- Set default locale to en +UPDATE + "user" +SET + "locale" = 'en' +WHERE + "locale" IS NULL; + +-- Set locale column to not null constraint +ALTER TABLE "user" + ALTER COLUMN "locale" SET NOT NULL; diff --git a/packages/backend/src/core/database/migrations/00008-merge-config-with-domain-and-exposed.sql b/packages/backend/src/core/database/migrations/00008-merge-config-with-domain-and-exposed.sql new file mode 100644 index 0000000000..997f55a72e --- /dev/null +++ b/packages/backend/src/core/database/migrations/00008-merge-config-with-domain-and-exposed.sql @@ -0,0 +1,14 @@ +-- For all apps that have a domain or exposed field set merge those values into the config jsonb +UPDATE + app +SET + config = jsonb_set(config, '{domain}', to_jsonb (DOMAIN)) +WHERE + DOMAIN IS NOT NULL; + +UPDATE + app +SET + config = jsonb_set(config, '{exposed}', to_jsonb (exposed)) +WHERE + exposed IS NOT NULL; diff --git a/packages/backend/src/core/database/migrations/00009-add-guest-dashboard.sql b/packages/backend/src/core/database/migrations/00009-add-guest-dashboard.sql new file mode 100644 index 0000000000..81b281b426 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00009-add-guest-dashboard.sql @@ -0,0 +1,15 @@ +-- Update app table to add "is_visible_on_guest_dashboard" column +ALTER TABLE "app" + ADD COLUMN IF NOT EXISTS "is_visible_on_guest_dashboard" boolean DEFAULT FALSE; + +-- Set default value to false +UPDATE + "app" +SET + "is_visible_on_guest_dashboard" = FALSE +WHERE + "is_visible_on_guest_dashboard" IS NULL; + +-- Set is_visible_on_guest_dashboard column to not null constraint +ALTER TABLE "app" + ALTER COLUMN "is_visible_on_guest_dashboard" SET NOT NULL; diff --git a/packages/backend/src/core/database/migrations/00010-add-status-resetting.sql b/packages/backend/src/core/database/migrations/00010-add-status-resetting.sql new file mode 100644 index 0000000000..44f88673c8 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00010-add-status-resetting.sql @@ -0,0 +1,15 @@ +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT + 1 + FROM + pg_enum + WHERE + enumlabel = 'resetting'::text + AND enumtypid = 'public.app_status_enum'::regtype) THEN + ALTER TYPE "public"."app_status_enum" + ADD VALUE 'resetting'; +END IF; +END +$$; diff --git a/packages/backend/src/core/database/migrations/00011-create-link-table.sql b/packages/backend/src/core/database/migrations/00011-create-link-table.sql new file mode 100644 index 0000000000..a55536f57f --- /dev/null +++ b/packages/backend/src/core/database/migrations/00011-create-link-table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS "link" ( + "id" serial NOT NULL, + "title" character varying(20) NOT NULL, + "url" character varying NOT NULL, + "icon_url" character varying, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + "user_id" integer NOT NULL, + CONSTRAINT "PK_link" PRIMARY KEY ("id"), + CONSTRAINT "FK_link_user_id" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE +); diff --git a/packages/backend/src/core/database/migrations/00012-link-table-description.sql b/packages/backend/src/core/database/migrations/00012-link-table-description.sql new file mode 100644 index 0000000000..1fcd90d404 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00012-link-table-description.sql @@ -0,0 +1,2 @@ +ALTER TABLE "link" + ADD COLUMN IF NOT EXISTS "description" character varying(50) \ No newline at end of file diff --git a/packages/backend/src/core/database/migrations/00013-app-local-exposed.sql b/packages/backend/src/core/database/migrations/00013-app-local-exposed.sql new file mode 100644 index 0000000000..370075173e --- /dev/null +++ b/packages/backend/src/core/database/migrations/00013-app-local-exposed.sql @@ -0,0 +1,31 @@ +-- Create open_port field if it doesn't exist +ALTER TABLE "app" + ADD COLUMN IF NOT EXISTS "open_port" boolean DEFAULT TRUE; + +-- Select all apps that have not the open_port field and put it to true +UPDATE + "app" +SET + "open_port" = TRUE +WHERE + "open_port" IS NULL; + +-- Set exposed column to not null constraint +ALTER TABLE "app" + ALTER COLUMN "open_port" SET NOT NULL; + +-- Create exposed_local field if it doesn't exist +ALTER TABLE "app" + ADD COLUMN IF NOT EXISTS "exposed_local" boolean DEFAULT TRUE; + +-- Select all apps that have not the exposed_local field and put it to false +UPDATE + "app" +SET + "exposed_local" = TRUE +WHERE + "exposed_local" IS NULL; + +-- Set exposed_local column to not null constraint +ALTER TABLE "app" + ALTER COLUMN "exposed_local" SET NOT NULL; diff --git a/packages/backend/src/core/database/migrations/00014-restarting-state.sql b/packages/backend/src/core/database/migrations/00014-restarting-state.sql new file mode 100644 index 0000000000..949dc45a83 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00014-restarting-state.sql @@ -0,0 +1,15 @@ +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT + 1 + FROM + pg_enum + WHERE + enumlabel = 'restarting'::text + AND enumtypid = 'public.app_status_enum'::regtype) THEN + ALTER TYPE "public"."app_status_enum" + ADD VALUE 'restarting'; +END IF; +END +$$; diff --git a/packages/backend/src/core/database/migrations/00015-backingup-state.sql b/packages/backend/src/core/database/migrations/00015-backingup-state.sql new file mode 100644 index 0000000000..1a6fc93360 --- /dev/null +++ b/packages/backend/src/core/database/migrations/00015-backingup-state.sql @@ -0,0 +1,31 @@ +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT + 1 + FROM + pg_enum + WHERE + enumlabel = 'backing_up'::text + AND enumtypid = 'public.app_status_enum'::regtype) THEN + ALTER TYPE "public"."app_status_enum" + ADD VALUE 'backing_up'; +END IF; +END +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT + 1 + FROM + pg_enum + WHERE + enumlabel = 'restoring'::text + AND enumtypid = 'public.app_status_enum'::regtype) THEN + ALTER TYPE "public"."app_status_enum" + ADD VALUE 'restoring'; +END IF; +END +$$; diff --git a/packages/backend/src/core/database/migrations/00016-user-has-seen-welcome.sql b/packages/backend/src/core/database/migrations/00016-user-has-seen-welcome.sql new file mode 100644 index 0000000000..cb6914340a --- /dev/null +++ b/packages/backend/src/core/database/migrations/00016-user-has-seen-welcome.sql @@ -0,0 +1,15 @@ +-- Create locale field if it doesn't exist +ALTER TABLE "user" + ADD COLUMN IF NOT EXISTS "has_seen_welcome" boolean DEFAULT false; + +-- Set default locale to en +UPDATE + "user" +SET + "has_seen_welcome" = false +WHERE + "has_seen_welcome" IS NULL; + +-- Set locale column to not null constraint +ALTER TABLE "user" + ALTER COLUMN "has_seen_welcome" SET NOT NULL; diff --git a/packages/backend/src/core/database/schema.ts b/packages/backend/src/core/database/schema.ts new file mode 100644 index 0000000000..a7a338e868 --- /dev/null +++ b/packages/backend/src/core/database/schema.ts @@ -0,0 +1,95 @@ +import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; +import { customType } from 'drizzle-orm/pg-core'; +import { boolean, integer, pgEnum, pgTable, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core'; + +const updateStatusEnum = pgEnum('update_status_enum', ['SUCCESS', 'FAILED']); +const appStatusEnum = pgEnum('app_status_enum', [ + 'running', + 'stopped', + 'starting', + 'stopping', + 'updating', + 'missing', + 'installing', + 'uninstalling', + 'resetting', + 'restarting', + 'backing_up', + 'restoring', +]); + +export const APP_STATUS = appStatusEnum.enumValues; +export type AppStatus = (typeof APP_STATUS)[number]; + +export const migrations = pgTable('migrations', { + id: integer('id').notNull(), + name: varchar('name', { length: 100 }).notNull(), + hash: varchar('hash', { length: 40 }).notNull(), + executedAt: timestamp('executed_at', { mode: 'string' }).defaultNow(), +}); + +export const userTable = pgTable('user', { + id: serial('id').notNull(), + username: varchar('username').notNull(), + password: varchar('password').notNull(), + createdAt: timestamp('createdAt', { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp('updatedAt', { mode: 'string' }).defaultNow().notNull(), + operator: boolean('operator').notNull().default(false), + totpSecret: text('totp_secret'), + totpEnabled: boolean('totp_enabled').default(false).notNull(), + salt: text('salt'), + locale: varchar('locale').default('en').notNull(), + hasSeenWelcome: boolean('has_seen_welcome').default(false).notNull(), +}); +export type User = InferSelectModel; +export type NewUser = InferInsertModel & Partial; + +export const update = pgTable('update', { + id: serial('id').notNull(), + name: varchar('name').notNull(), + status: updateStatusEnum('status').notNull(), + createdAt: timestamp('createdAt', { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp('updatedAt', { mode: 'string' }).defaultNow().notNull(), +}); + +const appConfig = customType<{ data: Record; driverData: string }>({ + dataType() { + return 'jsonb'; + }, + toDriver(value: Record): string { + return JSON.stringify(value); + }, +}); + +export const appTable = pgTable('app', { + id: varchar('id').notNull(), + status: appStatusEnum('status').default('stopped').notNull(), + lastOpened: timestamp('lastOpened', { withTimezone: true, mode: 'string' }).defaultNow(), + numOpened: integer('numOpened').default(0).notNull(), + config: appConfig('config').notNull(), + createdAt: timestamp('createdAt', { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp('updatedAt', { mode: 'string' }).defaultNow().notNull(), + version: integer('version').default(1).notNull(), + exposed: boolean('exposed').notNull(), + exposedLocal: boolean('exposed_local').notNull(), + openPort: boolean('open_port').notNull(), + domain: varchar('domain'), + isVisibleOnGuestDashboard: boolean('is_visible_on_guest_dashboard').default(false).notNull(), +}); + +export const linkTable = pgTable('link', { + id: serial('id').notNull(), + title: varchar('title', { length: 20 }).notNull(), + description: varchar('description', { length: 50 }), + url: varchar('url').notNull(), + iconUrl: varchar('icon_url'), + createdAt: timestamp('createdAt').defaultNow().notNull(), + updatedAt: timestamp('updatedAt').defaultNow().notNull(), + userId: integer('user_id') + .notNull() + .references(() => userTable.id, { onDelete: 'cascade' }), +}); + +export type App = InferSelectModel; +export type NewApp = InferInsertModel; +export type Link = InferSelectModel; diff --git a/packages/backend/src/core/encryption/encryption.module.ts b/packages/backend/src/core/encryption/encryption.module.ts new file mode 100644 index 0000000000..c221172767 --- /dev/null +++ b/packages/backend/src/core/encryption/encryption.module.ts @@ -0,0 +1,10 @@ +import { EnvUtils } from '@/modules/env/env.utils'; +import { Module } from '@nestjs/common'; +import { ConfigurationService } from '../config/configuration.service'; +import { EncryptionService } from './encryption.service'; + +@Module({ + providers: [ConfigurationService, EncryptionService, EnvUtils], + exports: [EncryptionService], +}) +export class EncryptionModule {} diff --git a/packages/backend/src/core/encryption/encryption.service.ts b/packages/backend/src/core/encryption/encryption.service.ts new file mode 100644 index 0000000000..7ec32d2a35 --- /dev/null +++ b/packages/backend/src/core/encryption/encryption.service.ts @@ -0,0 +1,49 @@ +import crypto from 'node:crypto'; + +import { Injectable } from '@nestjs/common'; +import { ConfigurationService } from '../config/configuration.service'; + +@Injectable() +export class EncryptionService { + algorithm = 'aes-256-gcm' as const; + keyLength = 32; + + constructor(private readonly config: ConfigurationService) {} + + /** + * Given a string, encrypts it using the provided salt + */ + encrypt = (data: string, salt: string) => { + const jwtSecret = this.config.get('jwtSecret'); + + const key = crypto.pbkdf2Sync(jwtSecret, salt, 100000, this.keyLength, 'sha256'); + const iv = crypto.randomBytes(12); + + const cipher = crypto.createCipheriv(this.algorithm, key, iv); + const encrypted = Buffer.concat([cipher.update(data), cipher.final()]); + + const tag = cipher.getAuthTag(); + return `${iv.toString('hex')}:${encrypted.toString('hex')}:${tag.toString('hex')}`; + }; + + /** + * Given an encrypted string, decrypts it using the provided salt + */ + decrypt = (encryptedData: string, salt: string) => { + const jwtSecret = this.config.get('jwtSecret'); + + const key = crypto.pbkdf2Sync(jwtSecret, salt, 100000, this.keyLength, 'sha256'); + const parts = encryptedData.split(':'); + const iv = Buffer.from(parts.shift() as string, 'hex'); + const encrypted = Buffer.from(parts.shift() as string, 'hex'); + const tag = Buffer.from(parts.shift() as string, 'hex'); + const decipher = crypto.createDecipheriv(this.algorithm, key, iv); + + decipher.setAuthTag(tag); + + let decrypted = decipher.update(encrypted); + decrypted = Buffer.concat([decrypted, decipher.final()]); + + return decrypted.toString(); + }; +} diff --git a/packages/backend/src/core/filesystem/filesystem.module.ts b/packages/backend/src/core/filesystem/filesystem.module.ts new file mode 100644 index 0000000000..c9901f56d5 --- /dev/null +++ b/packages/backend/src/core/filesystem/filesystem.module.ts @@ -0,0 +1,10 @@ +import { Global, Module } from '@nestjs/common'; +import { FilesystemService } from './filesystem.service'; + +@Global() +@Module({ + imports: [], + providers: [FilesystemService], + exports: [FilesystemService], +}) +export class FilesystemModule {} diff --git a/packages/backend/src/core/filesystem/filesystem.service.ts b/packages/backend/src/core/filesystem/filesystem.service.ts new file mode 100644 index 0000000000..108a7137ee --- /dev/null +++ b/packages/backend/src/core/filesystem/filesystem.service.ts @@ -0,0 +1,155 @@ +import fs from 'node:fs'; +import { LoggerService } from '@/core/logger/logger.service'; +import { Injectable } from '@nestjs/common'; +import { ZodSchema } from 'zod'; + +@Injectable() +export class FilesystemService { + constructor(private readonly logger: LoggerService) {} + + async readJsonFile(filePath: string, schema?: ZodSchema): Promise { + try { + const fileContent = await fs.promises.readFile(filePath, 'utf8'); + const parsedContent = JSON.parse(fileContent); + + if (schema) { + const validatedContent = schema.safeParse(parsedContent); + if (!validatedContent.success) { + this.logger.debug(`File ${filePath} validation error:`, validatedContent.error); + return null; + } + return validatedContent.data; + } + + return parsedContent; + } catch (error) { + this.logger.error(`Error reading file ${filePath}: ${error}`); + return null; + } + } + + async readTextFile(filePath: string): Promise { + try { + return await fs.promises.readFile(filePath, 'utf8'); + } catch (error) { + this.logger.error(`Error reading file ${filePath}: ${error}`); + return null; + } + } + + async readBinaryFile(filePath: string): Promise { + try { + return await fs.promises.readFile(filePath); + } catch (error) { + this.logger.error(`Error reading file ${filePath}: ${error}`); + return null; + } + } + + async writeJsonFile(filePath: string, data: T): Promise { + try { + await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'); + return true; + } catch (error) { + this.logger.error(`Error writing file ${filePath}: ${error}`); + return false; + } + } + + async writeTextFile(filePath: string, content: string): Promise { + try { + await fs.promises.writeFile(filePath, content, 'utf8'); + return true; + } catch (error) { + this.logger.error(`Error writing file ${filePath}: ${error}`); + return false; + } + } + + async pathExists(filePath: string): Promise { + return fs.promises + .access(filePath) + .then(() => true) + .catch(() => false); + } + + async copyFile(src: string, dest: string): Promise { + try { + await fs.promises.copyFile(src, dest); + return true; + } catch (error) { + this.logger.error(`Error copying file from ${src} to ${dest}: ${error}`); + return false; + } + } + + async createDirectory(dirPath: string): Promise { + try { + await fs.promises.mkdir(dirPath, { recursive: true }); + return true; + } catch (error) { + this.logger.error(`Error creating directory ${dirPath}: ${error}`); + return false; + } + } + + async createDirectories(dirPaths: string[]): Promise { + for (const dirPath of dirPaths) { + if (!(await this.createDirectory(dirPath))) { + return false; + } + } + return true; + } + + async copyDirectory(src: string, dest: string, options: fs.CopyOptions = {}): Promise { + try { + await fs.promises.cp(src, dest, { recursive: true, ...options }); + return true; + } catch (error) { + this.logger.error(`Error copying directory from ${src} to ${dest}: ${error}`); + return false; + } + } + + async removeDirectory(dirPath: string): Promise { + try { + await fs.promises.rm(dirPath, { recursive: true, force: true }); + return true; + } catch (error) { + this.logger.error(`Error removing directory ${dirPath}: ${error}`); + return false; + } + } + + async removeFile(filePath: string): Promise { + try { + await fs.promises.unlink(filePath); + return true; + } catch (error) { + this.logger.error(`Error removing file ${filePath}: ${error}`); + return false; + } + } + + async listFiles(dirPath: string): Promise { + try { + return await fs.promises.readdir(dirPath); + } catch (error) { + this.logger.error(`Error listing files in ${dirPath}: ${error}`); + return []; + } + } + + async isDirectory(dirPath: string): Promise { + return (await fs.promises.lstat(dirPath)).isDirectory(); + } + + async createTempDirectory(prefix: string): Promise { + return fs.promises.mkdtemp(prefix); + } + + async getStats(filePath: string) { + return await fs.promises.stat(filePath); + } +} diff --git a/packages/backend/src/core/health/health.controller.ts b/packages/backend/src/core/health/health.controller.ts new file mode 100644 index 0000000000..a68c73cf93 --- /dev/null +++ b/packages/backend/src/core/health/health.controller.ts @@ -0,0 +1,19 @@ +import { QueueHealthIndicator } from '@/modules/queue/queue.health'; +import { Controller, Get } from '@nestjs/common'; +import { HealthCheck, HealthCheckService } from '@nestjs/terminus'; +import { SocketHealthIndicator } from '../socket/socket.health'; + +@Controller('health') +export class HealthController { + constructor( + private health: HealthCheckService, + private queueHealthIndicator: QueueHealthIndicator, + private socketHealthIndicator: SocketHealthIndicator, + ) {} + + @Get() + @HealthCheck() + check() { + return this.health.check([() => this.queueHealthIndicator.isHealthy('queue'), () => this.socketHealthIndicator.isHealthy('socket')]); + } +} diff --git a/packages/backend/src/core/health/health.module.ts b/packages/backend/src/core/health/health.module.ts new file mode 100644 index 0000000000..107be7c7d7 --- /dev/null +++ b/packages/backend/src/core/health/health.module.ts @@ -0,0 +1,12 @@ +import { QueueModule } from '@/modules/queue/queue.module'; +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; +import { SocketModule } from '../socket/socket.module'; +import { HealthController } from './health.controller'; + +@Module({ + controllers: [HealthController], + imports: [TerminusModule, QueueModule, SocketModule], + providers: [], +}) +export class HealthModule {} diff --git a/packages/backend/src/core/logger/logger.module.ts b/packages/backend/src/core/logger/logger.module.ts new file mode 100644 index 0000000000..186567c42a --- /dev/null +++ b/packages/backend/src/core/logger/logger.module.ts @@ -0,0 +1,19 @@ +import { Global, Module } from '@nestjs/common'; +import { LoggerService } from './logger.service'; +import { DATA_DIR } from '@/common/constants'; +import path from 'node:path'; +import { ConfigurationService } from '../config/configuration.service'; + +@Global() +@Module({ + imports: [], + providers: [ + { + provide: LoggerService, + useFactory: (configurationService: ConfigurationService) => new LoggerService('backend', path.join(DATA_DIR, 'logs'), configurationService), + inject: [ConfigurationService], + }, + ], + exports: [LoggerService], +}) +export class LoggerModule {} diff --git a/packages/backend/src/core/logger/logger.service.ts b/packages/backend/src/core/logger/logger.service.ts new file mode 100644 index 0000000000..13f62b892f --- /dev/null +++ b/packages/backend/src/core/logger/logger.service.ts @@ -0,0 +1,118 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { Injectable } from '@nestjs/common'; +import winston, { createLogger, format, transports } from 'winston'; +import { ConfigurationService } from '../config/configuration.service'; + +const { printf, timestamp, combine, colorize, align, label } = format; + +type Transports = transports.ConsoleTransportInstance | transports.FileTransportInstance; + +/** + * Given an id and a logs folder, creates a new winston logger + * + * @param {string} id - The id of the logger, used to identify the logger in the logs + * @param {string} logsFolder - The folder where the logs will be stored + */ +export const newLogger = (id: string, logsFolder: string, logLevel = 'info') => { + const tr: Transports[] = []; + const exceptionHandlers: Transports[] = [new transports.Console()]; + + try { + tr.push( + new transports.File({ + filename: path.join(logsFolder, 'error.log'), + level: 'error', + }), + ); + tr.push( + new transports.File({ + filename: path.join(logsFolder, 'app.log'), + level: logLevel, + }), + ); + + tr.push(new transports.Console({ level: logLevel })); + } catch (error) { + // no-op + } + + return createLogger({ + level: logLevel, + format: combine( + label({ label: id }), + colorize(), + timestamp(), + align(), + printf((info) => `${id}: ${info.timestamp} - ${info.level} > ${info.message}`), + ), + transports: tr, + exceptionHandlers, + exitOnError: false, + }); +}; + +@Injectable() +export class LoggerService { + private winstonLogger: winston.Logger; + + private logsFolder: string; + + constructor(id: string, folder: string, configurationService: ConfigurationService) { + const { logLevel } = configurationService.getConfig(); + + this.winstonLogger = newLogger(id, folder, logLevel); + this.logsFolder = folder; + } + + private streamLogToHistory(logFile: string) { + return new Promise((resolve, reject) => { + const appLogReadStream = fs.createReadStream(path.join(this.logsFolder, logFile), 'utf-8'); + const appLogHistoryWriteStream = fs.createWriteStream(path.join(this.logsFolder, `${logFile}.history`), { flags: 'a' }); + + appLogReadStream + .pipe(appLogHistoryWriteStream) + .on('finish', () => { + fs.writeFileSync(path.join(this.logsFolder, logFile), ''); + resolve(true); + }) + .on('error', (error) => { + reject(error); + }); + }); + } + + public flush = async () => { + try { + if (fs.existsSync(path.join(this.logsFolder, 'app.log'))) { + await this.streamLogToHistory('app.log'); + } + if (fs.existsSync(path.join(this.logsFolder, 'error.log'))) { + await this.streamLogToHistory('error.log'); + } + this.winstonLogger.info('Logs flushed'); + } catch (error) { + this.winstonLogger.error('Error flushing logs', error); + } + }; + + private log = (level: string, messages: unknown[]) => { + this.winstonLogger.log(level, messages.join(' ')); + }; + + public error = (...message: unknown[]) => { + this.log('error', message); + }; + + public info = (...message: unknown[]) => { + this.log('info', message); + }; + + public warn = (...message: unknown[]) => { + this.log('warn', message); + }; + + public debug = (...message: unknown[]) => { + this.log('debug', message); + }; +} diff --git a/packages/backend/src/core/socket/socket-schemas.ts b/packages/backend/src/core/socket/socket-schemas.ts new file mode 100644 index 0000000000..1c8308f98b --- /dev/null +++ b/packages/backend/src/core/socket/socket-schemas.ts @@ -0,0 +1,82 @@ +import { z } from 'zod'; + +export const socketEventSchema = z.union([ + z.object({ + type: z.literal('app'), + event: z.union([ + z.literal('status_change'), + z.literal('install_success'), + z.literal('install_error'), + z.literal('uninstall_success'), + z.literal('uninstall_error'), + z.literal('reset_success'), + z.literal('reset_error'), + z.literal('update_success'), + z.literal('update_error'), + z.literal('start_success'), + z.literal('start_error'), + z.literal('stop_success'), + z.literal('stop_error'), + z.literal('restart_success'), + z.literal('restart_error'), + z.literal('generate_env_success'), + z.literal('generate_env_error'), + z.literal('backup_success'), + z.literal('backup_error'), + z.literal('restore_success'), + z.literal('restore_error'), + ]), + data: z.object({ + appId: z.string(), + appStatus: z + .enum([ + 'running', + 'stopped', + 'starting', + 'stopping', + 'updating', + 'missing', + 'installing', + 'uninstalling', + 'resetting', + 'restarting', + 'backing_up', + 'restoring', + ]) + .optional(), + error: z.string().optional(), + }), + }), + z.object({ + type: z.literal('app-logs-init'), + event: z.literal('initLogs'), + data: z.object({ + appId: z.string(), + maxLines: z.number().optional(), + }), + }), + z.object({ + type: z.literal('app-logs'), + event: z.union([z.literal('newLogs'), z.literal('stopLogs')]), + data: z.object({ + appId: z.string(), + lines: z.array(z.string()).optional(), + }), + }), + z.object({ + type: z.literal('runtipi-logs-init'), + event: z.literal('initLogs'), + data: z.object({ + maxLines: z.number().optional(), + }), + }), + z.object({ + type: z.literal('runtipi-logs'), + event: z.union([z.literal('newLogs'), z.literal('stopLogs')]), + data: z.object({ + lines: z.array(z.string()).optional(), + }), + }), +]); + +export type SocketEvent = z.infer; diff --git a/packages/backend/src/core/socket/socket.health.ts b/packages/backend/src/core/socket/socket.health.ts new file mode 100644 index 0000000000..f304c331cc --- /dev/null +++ b/packages/backend/src/core/socket/socket.health.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { HealthCheckError, HealthIndicator, type HealthIndicatorResult } from '@nestjs/terminus'; +import { SocketManager } from './socket.service'; + +@Injectable() +export class SocketHealthIndicator extends HealthIndicator { + constructor(private readonly socketManager: SocketManager) { + super(); + } + + async isHealthy(key: string): Promise { + const isConnected = await this.socketManager.isConnected(); + + if (isConnected) { + return Promise.resolve(this.getStatus(key, true)); + } + + throw new HealthCheckError( + 'Websocket check failed', + this.getStatus(key, false, { + message: 'Not connected to websocket server', + }), + ); + } +} diff --git a/packages/backend/src/core/socket/socket.module.ts b/packages/backend/src/core/socket/socket.module.ts new file mode 100644 index 0000000000..dc9264ff1a --- /dev/null +++ b/packages/backend/src/core/socket/socket.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { SocketHealthIndicator } from './socket.health'; +import { SocketManager } from './socket.service'; + +@Module({ + imports: [], + providers: [SocketManager, SocketHealthIndicator], + exports: [SocketManager, SocketHealthIndicator], +}) +export class SocketModule {} diff --git a/packages/backend/src/core/socket/socket.service.ts b/packages/backend/src/core/socket/socket.service.ts new file mode 100644 index 0000000000..ae3aaa6170 --- /dev/null +++ b/packages/backend/src/core/socket/socket.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@nestjs/common'; +import { Server } from 'socket.io'; +import { LoggerService } from '../logger/logger.service'; +import type { SocketEvent } from './socket-schemas'; + +@Injectable() +export class SocketManager { + public io: Server | null = null; + + constructor(private logger: LoggerService) {} + + init() { + if (this.io) { + return this.io; + } + + const io = new Server(5001, { cors: { origin: '*' }, path: '/api/socket.io' }); + this.logger.info('SocketManager initialized'); + + io.on('disconnect', (socket) => { + this.logger.debug('Client disconnected from socket', socket.id); + }); + + io.on('error', (error) => { + this.logger.error('SocketManager error:', error); + }); + + this.io = io; + + return io; + } + + async isConnected() { + if (!this.io) { + return false; + } + + return this.io.httpServer.listening; + } + + async emit(event: SocketEvent) { + if (!this.io) { + this.logger.error('SocketManager is not initialized'); + return; + } + + try { + const sockets = await this.io.fetchSockets(); + + for (const socket of sockets) { + try { + socket.emit(event.type, event); + } catch (error) { + this.logger.error('Error sending socket event:', error); + } + } + } catch (error) { + this.logger.error('Error emitting socket event:', error); + } + } +} diff --git a/packages/backend/src/exports/index.ts b/packages/backend/src/exports/index.ts new file mode 100644 index 0000000000..061e798502 --- /dev/null +++ b/packages/backend/src/exports/index.ts @@ -0,0 +1,3 @@ +import { socketEventSchema, type SocketEvent } from '../core/socket/socket-schemas'; + +export { socketEventSchema, type SocketEvent }; diff --git a/packages/backend/src/main.ts b/packages/backend/src/main.ts new file mode 100644 index 0000000000..3d7fc55f88 --- /dev/null +++ b/packages/backend/src/main.ts @@ -0,0 +1,56 @@ +import { patchNestJsSwagger } from 'nestjs-zod'; + +import fs from 'node:fs'; +import path from 'node:path'; +import { type INestApplication, ValidationPipe } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import cookieParser from 'cookie-parser'; +import { AppModule } from './app.module'; +import { AppService } from './app.service'; +import { APP_DIR } from './common/constants'; +import { generateSystemEnvFile } from './common/helpers/env-helpers'; +import metadata from './metadata'; + +async function setupSwagger(app: INestApplication) { + const config = new DocumentBuilder().setTitle('Runtipi API').setDescription('API specs for Runtipi').setVersion('1.0').build(); + await SwaggerModule.loadPluginMetadata(metadata); + + const document = SwaggerModule.createDocument(app, config, { + operationIdFactory: (_: string, methodKey: string) => methodKey, + }); + SwaggerModule.setup('api/docs', app, document); + + // write the swagger.json file to the assets folder + if (process.env.NODE_ENV !== 'production') { + await fs.promises.writeFile(path.join(APP_DIR, 'packages', 'backend', 'src', 'swagger.json'), JSON.stringify(document, null, 2)); + } +} + +async function bootstrap() { + patchNestJsSwagger(); + + await generateSystemEnvFile(); + + const app = await NestFactory.create(AppModule, { + abortOnError: true, + logger: ['error', 'warn', 'fatal'], + }); + + const appService = app.get(AppService); + await appService.bootstrap(); + + app.setGlobalPrefix('/api'); + app.useGlobalPipes(new ValidationPipe()); + app.enableCors(); + app.use(cookieParser()); + + await setupSwagger(app); + + await app.listen(3000); +} + +bootstrap().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/packages/backend/src/metadata.ts b/packages/backend/src/metadata.ts new file mode 100644 index 0000000000..ccd1664dbb --- /dev/null +++ b/packages/backend/src/metadata.ts @@ -0,0 +1,12 @@ +/* eslint-disable */ +export default async () => { + const t = { + ["./app.dto"]: await import("./app.dto"), + ["./modules/auth/dto/auth.dto"]: await import("./modules/auth/dto/auth.dto"), + ["./modules/apps/dto/app.dto"]: await import("./modules/apps/dto/app.dto"), + ["./modules/backups/dto/backups.dto"]: await import("./modules/backups/dto/backups.dto"), + ["./modules/links/dto/links.dto"]: await import("./modules/links/dto/links.dto"), + ["./modules/system/dto/system.dto"]: await import("./modules/system/dto/system.dto") + }; + return { "@nestjs/swagger": { "models": [[import("./modules/apps/dto/app-info.dto"), { "AppInfoSimpleDto": {}, "AppInfoDto": {} }], [import("./modules/user/dto/user.dto"), { "UserDto": {} }], [import("./app.dto"), { "UserSettingsDto": {}, "PartialUserSettingsDto": {}, "AppContextDto": {}, "UserContextDto": {}, "AcknowledgeWelcomeBody": {} }], [import("./modules/queue/queue.entity"), { "Queue": { queueNameResponse: { required: true, type: () => String } } }], [import("./modules/auth/dto/auth.dto"), { "LoginBody": {}, "VerifyTotpBody": {}, "LoginDto": {}, "RegisterBody": {}, "RegisterDto": {}, "ChangeUsernameBody": {}, "ChangePasswordBody": {}, "GetTotpUriBody": {}, "GetTotpUriDto": {}, "SetupTotpBody": {}, "DisableTotpBody": {}, "ResetPasswordBody": {}, "ResetPasswordDto": {}, "CheckResetPasswordRequestDto": {} }], [import("./modules/app-lifecycle/dto/app-lifecycle.dto"), { "AppFormBody": {} }], [import("./modules/apps/dto/app.dto"), { "SearchAppsQueryDto": {}, "SearchAppsDto": {}, "UpdateInfoDto": {}, "AppDto": {}, "MyAppsDto": {}, "GuestAppsDto": {}, "AppDetailsDto": {} }], [import("./modules/backups/dto/backups.dto"), { "BackupDto": {}, "RestoreAppBackupDto": {}, "GetAppBackupsDto": {}, "GetAppBackupsQueryDto": {}, "DeleteAppBackupBodyDto": {} }], [import("./modules/links/dto/links.dto"), { "LinkBodyDto": {}, "EditLinkBodyDto": {}, "LinksDto": {} }], [import("./modules/system/dto/system.dto"), { "LoadDto": {} }]], "controllers": [[import("./app.controller"), { "AppController": { "userContext": { type: t["./app.dto"].UserContextDto }, "appContext": { type: t["./app.dto"].AppContextDto }, "updateUserSettings": {}, "acknowledgeWelcome": {}, "getError": {} } }], [import("./modules/auth/auth.controller"), { "AuthController": { "login": { type: t["./modules/auth/dto/auth.dto"].LoginDto }, "verifyTotp": { type: t["./modules/auth/dto/auth.dto"].LoginDto }, "register": { type: t["./modules/auth/dto/auth.dto"].RegisterDto }, "logout": {}, "changeUsername": {}, "changePassword": {}, "getTotpUri": { type: t["./modules/auth/dto/auth.dto"].GetTotpUriDto }, "setupTotp": {}, "disableTotp": {}, "resetPassword": { type: t["./modules/auth/dto/auth.dto"].ResetPasswordDto }, "cancelResetPassword": {}, "checkResetPasswordRequest": { type: t["./modules/auth/dto/auth.dto"].CheckResetPasswordRequestDto } } }], [import("./modules/i18n/i18n.controller"), { "I18nController": { "getTranslation": { type: Object } } }], [import("./core/health/health.controller"), { "HealthController": { "check": { type: Object } } }], [import("./modules/apps/apps.controller"), { "AppsController": { "getInstalledApps": { type: t["./modules/apps/dto/app.dto"].MyAppsDto }, "getGuestApps": { type: t["./modules/apps/dto/app.dto"].GuestAppsDto }, "searchApps": { type: t["./modules/apps/dto/app.dto"].SearchAppsDto }, "getAppDetails": { type: t["./modules/apps/dto/app.dto"].AppDetailsDto }, "getImage": {} } }], [import("./modules/backups/backups.controller"), { "BackupsController": { "backupApp": {}, "restoreAppBackup": {}, "getAppBackups": { type: t["./modules/backups/dto/backups.dto"].GetAppBackupsDto }, "deleteAppBackup": {} } }], [import("./modules/app-lifecycle/app-lifecycle.controller"), { "AppLifecycleController": { "installApp": {}, "startApp": {}, "stopApp": {}, "restartApp": {}, "uninstallApp": {}, "resetApp": {} } }], [import("./modules/links/links.controller"), { "LinksController": { "getLinks": { type: t["./modules/links/dto/links.dto"].LinksDto }, "createLink": {}, "editLink": {}, "deleteLink": {} } }], [import("./modules/system/system.controller"), { "SystemController": { "systemLoad": { type: t["./modules/system/dto/system.dto"].LoadDto }, "downloadLocalCertificate": {} } }]] } }; +}; \ No newline at end of file diff --git a/packages/backend/src/modules/app-lifecycle/app-lifecycle-command.factory.ts b/packages/backend/src/modules/app-lifecycle/app-lifecycle-command.factory.ts new file mode 100644 index 0000000000..5c765d947a --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/app-lifecycle-command.factory.ts @@ -0,0 +1,52 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { Injectable } from '@nestjs/common'; +import type { z } from 'zod'; +import { AppFilesManager } from '../apps/app-files-manager'; +import { AppHelpers } from '../apps/app.helpers'; +import { BackupManager } from '../backups/backup.manager'; +import { DockerService } from '../docker/docker.service'; +import { EnvUtils } from '../env/env.utils'; +import type { appEventSchema } from '../queue/entities/app-events'; +import { BackupAppCommand } from './commands/backup-app-command'; +import { InstallAppCommand } from './commands/install-app-command'; +import { ResetAppCommand } from './commands/reset-app-command'; +import { RestartAppCommand } from './commands/restart-app-command'; +import { RestoreAppCommand } from './commands/restore-app-command'; +import { StartAppCommand } from './commands/start-app-command'; +import { StopAppCommand } from './commands/stop-app-command'; +import { UninstallAppCommand } from './commands/uninstall-app-command'; + +@Injectable() +export class AppLifecycleCommandFactory { + constructor( + private readonly appFilesManager: AppFilesManager, + private readonly logger: LoggerService, + private readonly appHelpers: AppHelpers, + private readonly envUtils: EnvUtils, + private readonly dockerService: DockerService, + private readonly backupManager: BackupManager, + ) {} + + createCommand(eventData: z.infer) { + switch (eventData.command) { + case 'install': + return new InstallAppCommand(this.logger, this.appFilesManager, this.dockerService, this.appHelpers, this.envUtils); + case 'start': + return new StartAppCommand(this.logger, this.appFilesManager, this.dockerService, this.appHelpers); + case 'stop': + return new StopAppCommand(this.logger, this.appFilesManager, this.dockerService, this.appHelpers); + case 'restart': + return new RestartAppCommand(this.logger, this.appFilesManager, this.dockerService, this.appHelpers); + case 'uninstall': + return new UninstallAppCommand(this.logger, this.appFilesManager, this.dockerService); + case 'reset': + return new ResetAppCommand(this.logger, this.appFilesManager, this.dockerService, this.appHelpers, this.envUtils); + case 'backup': + return new BackupAppCommand(this.logger, this.appFilesManager, this.dockerService, this.backupManager); + case 'restore': + return new RestoreAppCommand(this.logger, this.appFilesManager, this.dockerService, this.backupManager, eventData.filename); + default: + throw new Error(`Unknown command: ${eventData.command}`); + } + } +} diff --git a/packages/backend/src/modules/app-lifecycle/app-lifecycle.controller.ts b/packages/backend/src/modules/app-lifecycle/app-lifecycle.controller.ts new file mode 100644 index 0000000000..a66b4020ed --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/app-lifecycle.controller.ts @@ -0,0 +1,42 @@ +import { Body, Controller, Delete, Param, Post, UseGuards } from '@nestjs/common'; +import { AuthGuard } from '../auth/auth.guard'; +import { AppLifecycleService } from './app-lifecycle.service'; +import { AppFormBody, appFormSchema } from './dto/app-lifecycle.dto'; + +@UseGuards(AuthGuard) +@Controller('app-lifecycle') +export class AppLifecycleController { + constructor(private readonly appLifecycleService: AppLifecycleService) {} + + @Post(':id/install') + async installApp(@Param('id') id: string, @Body() body: AppFormBody) { + const form = appFormSchema.parse(body); + + return this.appLifecycleService.installApp({ appId: id, form }); + } + + @Post(':id/start') + async startApp(@Param('id') id: string) { + return this.appLifecycleService.startApp({ appId: id }); + } + + @Post(':id/stop') + async stopApp(@Param('id') id: string) { + return this.appLifecycleService.stopApp({ appId: id }); + } + + @Post(':id/restart') + async restartApp(@Param('id') id: string) { + return this.appLifecycleService.restartApp({ appId: id }); + } + + @Delete(':id/uninstall') + async uninstallApp(@Param('id') id: string) { + return this.appLifecycleService.uninstallApp({ appId: id }); + } + + @Post(':id/reset') + async resetApp(@Param('id') id: string) { + return this.appLifecycleService.resetApp({ appId: id }); + } +} diff --git a/packages/backend/src/modules/app-lifecycle/app-lifecycle.module.ts b/packages/backend/src/modules/app-lifecycle/app-lifecycle.module.ts new file mode 100644 index 0000000000..6c41117878 --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/app-lifecycle.module.ts @@ -0,0 +1,18 @@ +import { SocketModule } from '@/core/socket/socket.module'; +import { Module, forwardRef } from '@nestjs/common'; +import { AppsModule } from '../apps/apps.module'; +import { BackupsModule } from '../backups/backups.module'; +import { DockerModule } from '../docker/docker.module'; +import { EnvModule } from '../env/env.module'; +import { QueueModule } from '../queue/queue.module'; +import { AppLifecycleCommandFactory } from './app-lifecycle-command.factory'; +import { AppLifecycleController } from './app-lifecycle.controller'; +import { AppLifecycleService } from './app-lifecycle.service'; + +@Module({ + imports: [QueueModule, AppsModule, EnvModule, DockerModule, SocketModule, forwardRef(() => BackupsModule)], + providers: [AppLifecycleService, AppLifecycleCommandFactory], + controllers: [AppLifecycleController], + exports: [AppLifecycleService], +}) +export class AppLifecycleModule {} diff --git a/packages/backend/src/modules/app-lifecycle/app-lifecycle.service.ts b/packages/backend/src/modules/app-lifecycle/app-lifecycle.service.ts new file mode 100644 index 0000000000..c7c5fc410e --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/app-lifecycle.service.ts @@ -0,0 +1,262 @@ +import { TranslatableError } from '@/common/error/translatable-error'; +import { ConfigurationService } from '@/core/config/configuration.service'; +import { LoggerService } from '@/core/logger/logger.service'; +import { SocketManager } from '@/core/socket/socket.service'; +import { HttpStatus, Injectable } from '@nestjs/common'; +import { lt, valid } from 'semver'; +import { isFQDN } from 'validator'; +import type { z } from 'zod'; +import { AppFilesManager } from '../apps/app-files-manager'; +import { AppsRepository } from '../apps/apps.repository'; +import { type AppEventFormInput, AppEventsQueue, appEventSchema } from '../queue/entities/app-events'; +import { AppLifecycleCommandFactory } from './app-lifecycle-command.factory'; + +@Injectable() +export class AppLifecycleService { + constructor( + private readonly logger: LoggerService, + private readonly appEventsQueue: AppEventsQueue, + private readonly commandFactory: AppLifecycleCommandFactory, + private readonly appRepository: AppsRepository, + private readonly config: ConfigurationService, + private readonly appFilesManager: AppFilesManager, + private readonly socketManager: SocketManager, + ) { + this.logger.debug('Subscribing to app events...'); + this.appEventsQueue.onEvent(({ eventId, ...data }) => this.invokeCommand(eventId, data)); + } + + async invokeCommand(eventId: string, data: z.infer) { + try { + const command = this.commandFactory.createCommand(data); + const { success, message } = await command.execute(data.appid, data.form); + this.appEventsQueue.sendEventResponse(eventId, { success, message }); + } catch (err) { + this.logger.error(`Error invoking command: ${err}`); + this.appEventsQueue.sendEventResponse(eventId, { success: false, message: String(err) }); + } + } + + async startApp(params: { appId: string }): Promise { + const { appId } = params; + const app = await this.appRepository.getApp(appId); + + if (!app) { + throw new TranslatableError('APP_ERROR_APP_NOT_FOUND', { id: appId }, HttpStatus.NOT_FOUND); + } + + await this.appRepository.updateApp(appId, { status: 'starting' }); + this.socketManager.emit({ type: 'app', event: 'status_change', data: { appId, appStatus: 'starting' } }); + + this.appEventsQueue.publishAsync({ appid: appId, command: 'start', form: app.config }).then(async ({ success, message }) => { + if (success) { + this.logger.info(`App ${appId} started successfully`); + this.socketManager.emit({ type: 'app', event: 'start_success', data: { appId, appStatus: 'running' } }); + await this.appRepository.updateApp(appId, { status: 'running' }); + } else { + this.logger.error(`Failed to start app ${appId}: ${message}`); + this.socketManager.emit({ type: 'app', event: 'start_error', data: { appId, appStatus: 'stopped' } }); + await this.appRepository.updateApp(appId, { status: 'stopped' }); + } + }); + } + + async installApp(params: { appId: string; form: AppEventFormInput }): Promise { + const { appId, form } = params; + const { demoMode, version, architecture } = this.config.getConfig(); + + this.socketManager.emit({ type: 'app', event: 'status_change', data: { appId, appStatus: 'installing' } }); + + const app = await this.appRepository.getApp(appId); + + if (app) { + return this.startApp({ appId }); + } + + const { exposed, exposedLocal, openPort, domain, isVisibleOnGuestDashboard } = form; + const apps = await this.appRepository.getApps(); + + if (apps.length >= 6 && demoMode) { + throw new TranslatableError('SYSTEM_ERROR_DEMO_MODE_LIMIT'); + } + + if (exposed && !domain) { + throw new TranslatableError('APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP'); + } + + if (domain && !isFQDN(domain)) { + throw new TranslatableError('APP_ERROR_DOMAIN_NOT_VALID', { domain }); + } + + const appInfo = await this.appFilesManager.getAppInfoFromAppStore(appId); + + if (!appInfo) { + throw new TranslatableError('APP_ERROR_APP_NOT_FOUND', { id: appId }, HttpStatus.NOT_FOUND); + } + + if (appInfo.supported_architectures?.length && !appInfo.supported_architectures.includes(architecture)) { + throw new TranslatableError('APP_ERROR_ARCHITECTURE_NOT_SUPPORTED', { id: appId, arch: architecture }); + } + + if (!appInfo.exposable && exposed) { + throw new TranslatableError('APP_ERROR_APP_NOT_EXPOSABLE', { id: appId }); + } + + if (appInfo.force_expose && !exposed) { + throw new TranslatableError('APP_ERROR_APP_FORCE_EXPOSED', { id: appId }); + } + + if (exposed && domain) { + const appsWithSameDomain = await this.appRepository.getAppsByDomain(domain, appId); + + if (appsWithSameDomain.length > 0) { + throw new TranslatableError('APP_ERROR_DOMAIN_ALREADY_IN_USE', { domain, id: appsWithSameDomain[0]?.id }); + } + } + + if (appInfo?.min_tipi_version && valid(version) && lt(version, appInfo.min_tipi_version)) { + throw new TranslatableError('APP_UPDATE_ERROR_MIN_TIPI_VERSION', { id: appId, minVersion: appInfo.min_tipi_version }); + } + + await this.appRepository.createApp({ + id: appId, + status: 'installing', + config: form, + version: appInfo.tipi_version, + exposed: exposed || false, + domain: domain || null, + openPort: openPort || false, + exposedLocal: exposedLocal || false, + isVisibleOnGuestDashboard, + }); + + // Send install command to the queue + this.appEventsQueue.publishAsync({ appid: appId, command: 'install', form }).then(async ({ success, message }) => { + if (success) { + this.logger.info(`App ${appId} installed successfully`); + await this.socketManager.emit({ type: 'app', event: 'install_success', data: { appId, appStatus: 'running' } }); + await this.appRepository.updateApp(appId, { status: 'running' }); + } else { + this.socketManager.emit({ type: 'app', event: 'install_error', data: { appId, appStatus: 'missing' } }); + this.logger.error(`Failed to install app ${appId}: ${message}`); + await this.appRepository.deleteApp(appId); + } + }); + } + + /** + * Stop an app by its ID + */ + public async stopApp(params: { appId: string }) { + const { appId } = params; + const app = await this.appRepository.getApp(appId); + + if (!app) { + throw new TranslatableError('APP_ERROR_APP_NOT_FOUND', { id: appId }, HttpStatus.NOT_FOUND); + } + + this.socketManager.emit({ type: 'app', event: 'status_change', data: { appId, appStatus: 'stopping' } }); + + await this.appRepository.updateApp(appId, { status: 'stopping' }); + + // Send stop command to the queue + this.appEventsQueue.publishAsync({ command: 'stop', appid: appId, form: app.config }).then(async ({ success, message }) => { + if (success) { + this.socketManager.emit({ type: 'app', event: 'stop_success', data: { appId, appStatus: 'stopped' } }); + this.logger.info(`App ${appId} stopped successfully`); + await this.appRepository.updateApp(appId, { status: 'stopped' }); + } else { + this.socketManager.emit({ type: 'app', event: 'stop_error', data: { appId, appStatus: 'running' } }); + this.logger.error(`Failed to stop app ${appId}: ${message}`); + await this.appRepository.updateApp(appId, { status: 'running' }); + } + }); + } + + /** + * Restart an app by its ID + */ + public async restartApp(params: { appId: string }) { + const { appId } = params; + const app = await this.appRepository.getApp(appId); + + if (!app) { + throw new TranslatableError('APP_ERROR_APP_NOT_FOUND'); + } + + this.socketManager.emit({ type: 'app', event: 'status_change', data: { appId, appStatus: 'restarting' } }); + await this.appRepository.updateApp(appId, { status: 'restarting' }); + + this.appEventsQueue.publishAsync({ command: 'restart', appid: appId, form: app.config }).then(async ({ success, message }) => { + if (success) { + this.logger.info(`App ${appId} restarted successfully`); + this.socketManager.emit({ type: 'app', event: 'restart_success', data: { appId, appStatus: 'running' } }); + await this.appRepository.updateApp(appId, { status: 'running' }); + } else { + this.logger.error(`Failed to restart app ${appId}: ${message}`); + this.socketManager.emit({ type: 'app', event: 'restart_error', data: { appId, appStatus: 'running' } }); + await this.appRepository.updateApp(appId, { status: 'stopped' }); + } + }); + } + + /** + * Uninstall an app by its ID + */ + public async uninstallApp(params: { appId: string }) { + const { appId } = params; + + const app = await this.appRepository.getApp(appId); + + if (!app) { + throw new TranslatableError('APP_ERROR_APP_NOT_FOUND', { id: appId }); + } + + await this.appRepository.updateApp(appId, { status: 'uninstalling' }); + this.socketManager.emit({ type: 'app', event: 'status_change', data: { appId, appStatus: 'uninstalling' } }); + + this.appEventsQueue.publishAsync({ command: 'uninstall', appid: appId, form: app.config }).then(async ({ success, message }) => { + if (success) { + this.logger.info(`App ${appId} uninstalled successfully`); + await this.appRepository.deleteApp(appId); + await this.socketManager.emit({ type: 'app', event: 'uninstall_success', data: { appId, appStatus: 'missing' } }); + } else { + this.logger.error(`Failed to uninstall app ${appId}: ${message}`); + await this.appRepository.updateApp(appId, { status: 'stopped' }); + await this.socketManager.emit({ type: 'app', event: 'uninstall_error', data: { appId, appStatus: 'stopped' } }); + } + }); + } + + /** + * Reset an app by its ID + */ + public async resetApp(params: { appId: string }) { + const { appId } = params; + const app = await this.appRepository.getApp(appId); + + if (!app) { + throw new TranslatableError('APP_ERROR_APP_NOT_FOUND', { id: appId }); + } + + const appStatusBeforeReset = app?.status; + this.socketManager.emit({ type: 'app', event: 'status_change', data: { appId, appStatus: 'resetting' } }); + await this.appRepository.updateApp(appId, { status: 'resetting' }); + + this.appEventsQueue.publishAsync({ command: 'reset', appid: appId, form: app.config }).then(async ({ success, message }) => { + if (success) { + this.logger.info(`App ${appId} reset successfully`); + await this.socketManager.emit({ type: 'app', event: 'reset_success', data: { appId, appStatus: 'stopped' } }); + if (appStatusBeforeReset === 'running') { + this.startApp({ appId }); + } else { + await this.appRepository.updateApp(appId, { status: 'running' }); + } + } else { + this.logger.error(`Failed to reset app ${appId}: ${message}`); + await this.socketManager.emit({ type: 'app', event: 'reset_error', data: { appId, appStatus: 'running' } }); + await this.appRepository.updateApp(appId, { status: 'running' }); + } + }); + } +} diff --git a/packages/backend/src/modules/app-lifecycle/commands/backup-app-command.ts b/packages/backend/src/modules/app-lifecycle/commands/backup-app-command.ts new file mode 100644 index 0000000000..4fc12a2009 --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/commands/backup-app-command.ts @@ -0,0 +1,36 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { AppFilesManager } from '@/modules/apps/app-files-manager'; +import { BackupManager } from '@/modules/backups/backup.manager'; +import { DockerService } from '@/modules/docker/docker.service'; +import { AppLifecycleCommand } from './command'; + +export class BackupAppCommand extends AppLifecycleCommand { + constructor( + logger: LoggerService, + appFilesManager: AppFilesManager, + dockerService: DockerService, + private readonly backupManager: BackupManager, + ) { + super(logger, appFilesManager, dockerService); + + this.logger = logger; + this.appFilesManager = appFilesManager; + } + + public async execute(appId: string): Promise<{ success: boolean; message: string }> { + try { + this.logger.info(`Stopping app ${appId}`); + await this.dockerService.composeApp(appId, 'rm --force --stop'); + this.logger.info('App stopped!'); + + await this.backupManager.backupApp(appId); + + // Done + this.logger.info('Backup completed!'); + + return { success: true, message: `App ${appId} backed up successfully` }; + } catch (err) { + return this.handleAppError(err, appId, 'backup'); + } + } +} diff --git a/packages/backend/src/modules/app-lifecycle/commands/command.ts b/packages/backend/src/modules/app-lifecycle/commands/command.ts new file mode 100644 index 0000000000..aa3b4bb5d9 --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/commands/command.ts @@ -0,0 +1,54 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { AppFilesManager } from '@/modules/apps/app-files-manager'; +import { dynamicComposeSchema } from '@/modules/docker/builders/schemas'; +import { DockerService } from '@/modules/docker/docker.service'; +import type { AppEventFormInput } from '@/modules/queue/entities/app-events'; +import * as Sentry from '@sentry/nestjs'; + +export class AppLifecycleCommand { + constructor( + protected logger: LoggerService, + protected appFilesManager: AppFilesManager, + protected dockerService: DockerService, + ) {} + + protected async ensureAppDir(appId: string, form: AppEventFormInput): Promise { + const composeYaml = await this.appFilesManager.getDockerComposeYaml(appId); + + if (!composeYaml.content) { + await this.appFilesManager.copyAppFromRepoToInstalled(appId); + } + + const appInfo = await this.appFilesManager.getAppInfoFromAppStore(appId); + const composeJson = await this.appFilesManager.getDockerComposeJson(appId); + + if (composeJson.content && appInfo?.dynamic_config) { + try { + const { services } = dynamicComposeSchema.parse(composeJson.content); + const composeFile = this.dockerService.getDockerCompose(services, form); + + await this.appFilesManager.writeDockerComposeYml(appId, composeFile); + } catch (err) { + this.logger.error(`Error generating docker-compose.yml file for app ${appId}. Falling back to default docker-compose.yml`); + this.logger.error(err); + Sentry.captureException(err); + } + } + + // Set permissions + await this.appFilesManager.setAppDataDirPermissions(appId); + } + + protected handleAppError = async (err: unknown, appId: string, event: string): Promise<{ success: false; message: string }> => { + Sentry.captureException(err, { + tags: { appId, event }, + }); + + if (err instanceof Error) { + this.logger.error(`An error occurred: ${err.message}`); + return { success: false, message: err.message }; + } + + return { success: false, message: `An error occurred: ${String(err)}` }; + }; +} diff --git a/packages/backend/src/modules/app-lifecycle/commands/install-app-command.ts b/packages/backend/src/modules/app-lifecycle/commands/install-app-command.ts new file mode 100644 index 0000000000..7eadc1660e --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/commands/install-app-command.ts @@ -0,0 +1,53 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { AppFilesManager } from '@/modules/apps/app-files-manager'; +import { AppHelpers } from '@/modules/apps/app.helpers'; +import { DockerService } from '@/modules/docker/docker.service'; +import { EnvUtils } from '@/modules/env/env.utils'; +import type { AppEventFormInput } from '@/modules/queue/entities/app-events'; +import { AppLifecycleCommand } from './command'; + +export class InstallAppCommand extends AppLifecycleCommand { + constructor( + logger: LoggerService, + appFilesManager: AppFilesManager, + dockerService: DockerService, + private readonly appHelpers: AppHelpers, + private readonly envUtils: EnvUtils, + ) { + super(logger, appFilesManager, dockerService); + + this.logger = logger; + this.appFilesManager = appFilesManager; + } + + public async execute(appId: string, form: AppEventFormInput): Promise<{ success: boolean; message: string }> { + try { + if (process.getuid && process.getgid) { + this.logger.info(`Installing app ${appId} as User ID: ${process.getuid()}, Group ID: ${process.getgid()}`); + } else { + this.logger.info(`Installing app ${appId}. No User ID or Group ID found.`); + } + + await this.appFilesManager.copyAppFromRepoToInstalled(appId); + + // Create app.env file + this.logger.info(`Creating app.env file for app ${appId}`); + await this.appHelpers.generateEnvFile(appId, form); + + // Copy data dir + this.logger.info(`Copying data dir for app ${appId}`); + const appEnv = await this.appFilesManager.getAppEnv(appId); + const envMap = this.envUtils.envStringToMap(appEnv.content); + await this.appFilesManager.copyDataDir(appId, envMap); + + await this.ensureAppDir(appId, form); + + // run docker-compose up + await this.dockerService.composeApp(appId, 'up --detach --force-recreate --remove-orphans --pull always'); + + return { success: true, message: `App ${appId} installed successfully` }; + } catch (err) { + return this.handleAppError(err, appId, 'install'); + } + } +} diff --git a/packages/backend/src/modules/app-lifecycle/commands/reset-app-command.ts b/packages/backend/src/modules/app-lifecycle/commands/reset-app-command.ts new file mode 100644 index 0000000000..575c2827b3 --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/commands/reset-app-command.ts @@ -0,0 +1,61 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { AppFilesManager } from '@/modules/apps/app-files-manager'; +import { AppHelpers } from '@/modules/apps/app.helpers'; +import { DockerService } from '@/modules/docker/docker.service'; +import type { EnvUtils } from '@/modules/env/env.utils'; +import type { AppEventFormInput } from '@/modules/queue/entities/app-events'; +import { AppLifecycleCommand } from './command'; + +export class ResetAppCommand extends AppLifecycleCommand { + constructor( + logger: LoggerService, + appFilesManager: AppFilesManager, + dockerService: DockerService, + private readonly appHelpers: AppHelpers, + private readonly envUtils: EnvUtils, + ) { + super(logger, appFilesManager, dockerService); + + this.logger = logger; + this.appFilesManager = appFilesManager; + } + + public async execute(appId: string, form: AppEventFormInput): Promise<{ success: boolean; message: string }> { + try { + this.logger.info(`Resetting app ${appId}`); + + await this.ensureAppDir(appId, form); + await this.appHelpers.generateEnvFile(appId, form); + + // Stop app + try { + await this.dockerService.composeApp(appId, 'down --remove-orphans --volumes'); + } catch (err) { + if (err instanceof Error && err.message.includes('conflict')) { + this.logger.warn(`Could not reset app ${appId}. Most likely there have been made changes to the compose file.`); + } else { + throw err; + } + } + + // Delete app data directory + await this.appFilesManager.deleteAppDataDir(appId); + + // Create app.env file + this.logger.info(`Creating app.env file for app ${appId}`); + await this.appHelpers.generateEnvFile(appId, form); + + // Copy data dir + this.logger.info(`Copying data dir for app ${appId}`); + const env = await this.appFilesManager.getAppEnv(appId); + const envMap = this.envUtils.envStringToMap(env.content); + + await this.appFilesManager.copyDataDir(appId, envMap); + await this.ensureAppDir(appId, form); + + return { success: true, message: `App ${appId} reset successfully` }; + } catch (err) { + return this.handleAppError(err, appId, 'reset'); + } + } +} diff --git a/packages/backend/src/modules/app-lifecycle/commands/restart-app-command.ts b/packages/backend/src/modules/app-lifecycle/commands/restart-app-command.ts new file mode 100644 index 0000000000..1e6deb51cf --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/commands/restart-app-command.ts @@ -0,0 +1,50 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { AppFilesManager } from '@/modules/apps/app-files-manager'; +import { AppHelpers } from '@/modules/apps/app.helpers'; +import { DockerService } from '@/modules/docker/docker.service'; +import type { AppEventFormInput } from '@/modules/queue/entities/app-events'; +import { AppLifecycleCommand } from './command'; + +export class RestartAppCommand extends AppLifecycleCommand { + constructor( + logger: LoggerService, + appFilesManager: AppFilesManager, + dockerService: DockerService, + private readonly appHelpers: AppHelpers, + ) { + super(logger, appFilesManager, dockerService); + + this.logger = logger; + this.appFilesManager = appFilesManager; + } + + public async execute(appId: string, form: AppEventFormInput, skipEnvGeneration = false): Promise<{ success: boolean; message: string }> { + try { + const config = await this.appFilesManager.getInstalledAppInfo(appId); + + if (!config) { + return { success: true, message: 'App config not found. Skipping...' }; + } + + await this.ensureAppDir(appId, form); + + this.logger.info(`Stopping app ${appId}`); + + await this.dockerService.composeApp(appId, 'rm --force --stop'); + await this.ensureAppDir(appId, form); + + if (!skipEnvGeneration) { + this.logger.info(`Regenerating app.env file for app ${appId}`); + await this.appHelpers.generateEnvFile(appId, form); + } + + await this.dockerService.composeApp(appId, 'up --detach --force-recreate --remove-orphans --pull always'); + + this.logger.info(`App ${appId} restarted`); + + return { success: true, message: `App ${appId} restarted successfully` }; + } catch (err) { + return this.handleAppError(err, appId, 'restart'); + } + } +} diff --git a/packages/backend/src/modules/app-lifecycle/commands/restore-app-command.ts b/packages/backend/src/modules/app-lifecycle/commands/restore-app-command.ts new file mode 100644 index 0000000000..abd492e5aa --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/commands/restore-app-command.ts @@ -0,0 +1,37 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { AppFilesManager } from '@/modules/apps/app-files-manager'; +import { BackupManager } from '@/modules/backups/backup.manager'; +import { DockerService } from '@/modules/docker/docker.service'; +import { AppLifecycleCommand } from './command'; + +export class RestoreAppCommand extends AppLifecycleCommand { + constructor( + logger: LoggerService, + appFilesManager: AppFilesManager, + dockerService: DockerService, + private readonly backupManager: BackupManager, + private readonly filename: string, + ) { + super(logger, appFilesManager, dockerService); + + this.logger = logger; + this.appFilesManager = appFilesManager; + } + + public async execute(appId: string): Promise<{ success: boolean; message: string }> { + try { + // Stop the app + this.logger.info(`Stopping app ${appId}`); + await this.dockerService.composeApp(appId, 'rm --force --stop'); + this.logger.info('App stopped!'); + + await this.backupManager.restoreApp(appId, this.filename); + + // Done + this.logger.info(`App ${appId} restored!`); + return { success: true, message: `App ${appId} restored successfully` }; + } catch (err) { + return this.handleAppError(err, appId, 'restore'); + } + } +} diff --git a/packages/backend/src/modules/app-lifecycle/commands/start-app-command.ts b/packages/backend/src/modules/app-lifecycle/commands/start-app-command.ts new file mode 100644 index 0000000000..e7fe67405f --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/commands/start-app-command.ts @@ -0,0 +1,41 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { AppFilesManager } from '@/modules/apps/app-files-manager'; +import { AppHelpers } from '@/modules/apps/app.helpers'; +import { DockerService } from '@/modules/docker/docker.service'; +import type { AppEventFormInput } from '@/modules/queue/entities/app-events'; +import { AppLifecycleCommand } from './command'; + +export class StartAppCommand extends AppLifecycleCommand { + constructor( + logger: LoggerService, + appFilesManager: AppFilesManager, + dockerService: DockerService, + private readonly appHelpers: AppHelpers, + ) { + super(logger, appFilesManager, dockerService); + + this.logger = logger; + this.appFilesManager = appFilesManager; + } + + public async execute(appId: string, form: AppEventFormInput, skipEnvGeneration = false) { + try { + this.logger.info(`Starting app ${appId}`); + + await this.ensureAppDir(appId, form); + + if (!skipEnvGeneration) { + this.logger.info(`Regenerating app.env file for app ${appId}`); + await this.appHelpers.generateEnvFile(appId, form); + } + + await this.dockerService.composeApp(appId, 'up --detach --force-recreate --remove-orphans --pull always'); + + this.logger.info(`App ${appId} started`); + + return { success: true, message: `App ${appId} started successfully` }; + } catch (err) { + return this.handleAppError(err, appId, 'start'); + } + } +} diff --git a/packages/backend/src/modules/app-lifecycle/commands/stop-app-command.ts b/packages/backend/src/modules/app-lifecycle/commands/stop-app-command.ts new file mode 100644 index 0000000000..6a81431261 --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/commands/stop-app-command.ts @@ -0,0 +1,47 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { AppFilesManager } from '@/modules/apps/app-files-manager'; +import { AppHelpers } from '@/modules/apps/app.helpers'; +import { DockerService } from '@/modules/docker/docker.service'; +import type { AppEventFormInput } from '@/modules/queue/entities/app-events'; +import { AppLifecycleCommand } from './command'; + +export class StopAppCommand extends AppLifecycleCommand { + constructor( + logger: LoggerService, + appFilesManager: AppFilesManager, + dockerService: DockerService, + private readonly appHelpers: AppHelpers, + ) { + super(logger, appFilesManager, dockerService); + + this.logger = logger; + this.appFilesManager = appFilesManager; + } + + public async execute(appId: string, form: AppEventFormInput, skipEnvGeneration = false) { + try { + const config = await this.appFilesManager.getInstalledAppInfo(appId); + + if (!config) { + return { success: true, message: 'App config not found. Skipping...' }; + } + + this.logger.info(`Stopping app ${appId}`); + + await this.ensureAppDir(appId, form); + + if (!skipEnvGeneration) { + this.logger.info(`Regenerating app.env file for app ${appId}`); + await this.appHelpers.generateEnvFile(appId, form); + } + + await this.dockerService.composeApp(appId, 'rm --force --stop'); + + this.logger.info(`App ${appId} stopped`); + + return { success: true, message: `App ${appId} stopped successfully` }; + } catch (err) { + return this.handleAppError(err, appId, 'stop'); + } + } +} diff --git a/packages/backend/src/modules/app-lifecycle/commands/uninstall-app-command.ts b/packages/backend/src/modules/app-lifecycle/commands/uninstall-app-command.ts new file mode 100644 index 0000000000..c467778bf4 --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/commands/uninstall-app-command.ts @@ -0,0 +1,42 @@ +import { LoggerService } from '@/core/logger/logger.service'; +import { AppFilesManager } from '@/modules/apps/app-files-manager'; +import { DockerService } from '@/modules/docker/docker.service'; +import type { AppEventFormInput } from '@/modules/queue/entities/app-events'; +import { AppLifecycleCommand } from './command'; + +export class UninstallAppCommand extends AppLifecycleCommand { + constructor(logger: LoggerService, appFilesManager: AppFilesManager, dockerService: DockerService) { + super(logger, appFilesManager, dockerService); + + this.logger = logger; + this.appFilesManager = appFilesManager; + } + + public async execute(appId: string, form: AppEventFormInput) { + try { + this.logger.info(`Uninstalling app ${appId}`); + await this.ensureAppDir(appId, form); + + try { + await this.dockerService.composeApp(appId, 'down --remove-orphans --volumes --rmi all'); + } catch (err) { + if (err instanceof Error && err.message.includes('conflict')) { + this.logger.warn( + `Could not fully uninstall app ${appId}. Some images are in use by other apps. Consider cleaning unused images docker system prune -a`, + ); + } else { + throw err; + } + } + + await this.appFilesManager.deleteAppFolder(appId); + await this.appFilesManager.deleteAppDataDir(appId); + + this.logger.info(`App ${appId} uninstalled`); + + return { success: true, message: `App ${appId} uninstalled successfully` }; + } catch (err) { + return this.handleAppError(err, appId, 'uninstall'); + } + } +} diff --git a/packages/backend/src/modules/app-lifecycle/dto/app-lifecycle.dto.ts b/packages/backend/src/modules/app-lifecycle/dto/app-lifecycle.dto.ts new file mode 100644 index 0000000000..ed5f28e0b7 --- /dev/null +++ b/packages/backend/src/modules/app-lifecycle/dto/app-lifecycle.dto.ts @@ -0,0 +1,15 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod'; + +export const appFormSchema = z + .object({ + exposed: z.boolean().optional(), + exposedLocal: z.boolean().optional(), + openPort: z.boolean().optional().default(true), + domain: z.string().optional(), + isVisibleOnGuestDashboard: z.boolean().optional(), + }) + .extend({}) + .catchall(z.unknown()); + +export class AppFormBody extends createZodDto(appFormSchema) {} diff --git a/packages/backend/src/modules/apps/app-catalog.service.ts b/packages/backend/src/modules/apps/app-catalog.service.ts new file mode 100644 index 0000000000..e5823a234e --- /dev/null +++ b/packages/backend/src/modules/apps/app-catalog.service.ts @@ -0,0 +1,217 @@ +import { TranslatableError } from '@/common/error/translatable-error'; +import { notEmpty, pLimit } from '@/common/helpers/file-helpers'; +import { type Architecture, ConfigurationService } from '@/core/config/configuration.service'; +import type { App } from '@/core/database/schema'; +import { Injectable } from '@nestjs/common'; +import MiniSearch from 'minisearch'; +import { AppFilesManager } from './app-files-manager'; +import { AppsRepository } from './apps.repository'; + +type AppList = Awaited['getAllAvailableApps']>>; + +const sortApps = (a: AppList[number], b: AppList[number]) => a.id.localeCompare(b.id); +const filterApp = + (architecture: Architecture) => + (app: AppList[number]): boolean => { + if (app.deprecated) { + return false; + } + + if (!app.supported_architectures) { + return true; + } + + return app.supported_architectures.includes(architecture); + }; + +@Injectable() +export class AppCatalogService { + constructor( + private readonly filesManager: AppFilesManager, + private readonly configuration: ConfigurationService, + private readonly appsRepository: AppsRepository, + ) {} + + private appsAvailable: AppList | null = null; + private miniSearch: MiniSearch | null = null; + private cacheTimeout = 1000 * 60 * 15; // 15 minutes + private cacheLastUpdated = 0; + + private async constructSingleApp(app: App) { + try { + const info = await this.filesManager.getInstalledAppInfo(app.id); + const updateInfo = await this.filesManager.getAppUpdateInfo(app.id); + return info ? { app, info, updateInfo } : null; + } catch (e) { + return null; + } + } + + private async constructAppList(apps: App[]) { + const limit = pLimit(10); + + const installedApps = await Promise.all( + apps.map(async (app) => { + return limit(() => this.constructSingleApp(app)); + }), + ); + + return installedApps.filter(notEmpty); + } + + private async getAppInfoFromInstalledOrAppStore(id: string) { + const info = await this.filesManager.getInstalledAppInfo(id); + if (!info) { + return this.filesManager.getAppInfoFromAppStore(id); + } + return info; + } + + /** + * Get all available apps from the catalog + * @returns All available apps + */ + private async getAllAvailableApps() { + const appIds = await this.filesManager.getAvailableAppIds(); + + const limit = pLimit(10); + const apps = await Promise.all( + appIds.map(async (app) => { + return limit(() => this.filesManager.getAppInfoFromAppStore(app)); + }), + ); + + return apps.filter(notEmpty); + } + + /** + * Invalidate the cache + */ + private invalidateCache() { + this.appsAvailable = null; + if (this.miniSearch) { + this.miniSearch.removeAll(); + } + } + + /** + * Filter the apps based on the architecture + * @param apps - The apps to filter + * @returns The filtered apps + */ + private filterApps(apps: AppList): AppList { + const { architecture } = this.configuration.getConfig(); + return apps.sort(sortApps).filter(filterApp(architecture)); + } + + /** + * Get all available apps from the catalog + * @returns All available apps + */ + public async getAvailableApps(): Promise { + if (this.cacheLastUpdated && Date.now() - this.cacheLastUpdated > this.cacheTimeout) { + this.invalidateCache(); + } + + if (!this.appsAvailable) { + const apps = await this.getAllAvailableApps(); + this.appsAvailable = this.filterApps(apps); + + this.miniSearch = new MiniSearch<(typeof this.appsAvailable)[number]>({ + fields: ['name', 'short_desc', 'categories'], + storeFields: ['id'], + idField: 'id', + searchOptions: { + boost: { name: 2 }, + fuzzy: 0.2, + prefix: true, + }, + }); + this.miniSearch.addAll(this.appsAvailable); + + this.cacheLastUpdated = Date.now(); + } + + return this.appsAvailable; + } + + /** + * Search for apps in the catalog + * @param params - The search parameters + * @returns The search results + */ + public async searchApps(params: { search?: string | null; category?: string | null; pageSize?: number; cursor?: string | null }) { + const { search, category, pageSize, cursor } = params; + + let filteredApps = await this.getAvailableApps(); + + if (category) { + filteredApps = filteredApps.filter((app) => app.categories.some((c) => c === category)); + } + + if (search && this.miniSearch) { + const result = this.miniSearch.search(search); + const searchIds = result.map((app) => app.id); + filteredApps = filteredApps.filter((app) => searchIds.includes(app.id)).sort((a, b) => searchIds.indexOf(a.id) - searchIds.indexOf(b.id)); + } + + const start = cursor ? filteredApps.findIndex((app) => app.id === cursor) : 0; + const end = start + (pageSize ?? 24); + const data = filteredApps.slice(start, end); + + return { data, total: filteredApps.length, nextCursor: filteredApps[end]?.id }; + } + + /** + * Get the image of an app + * @param id - The ID of the app + * @returns The image of the app + */ + public async getAppImage(id: string) { + return this.filesManager.getAppImage(id); + } + + public async getApp(appId: string) { + let app = await this.appsRepository.getApp(appId); + const info = await this.getAppInfoFromInstalledOrAppStore(appId); + + const updateInfo = await this.filesManager.getAppUpdateInfo(appId); + + if (!info) { + throw new TranslatableError('APP_ERROR_INVALID_CONFIG', { id: appId }); + } + + if (!app) { + app = { + id: appId, + status: 'missing', + config: {}, + exposed: false, + domain: '', + version: 1, + lastOpened: null, + openPort: false, + createdAt: '', + numOpened: 0, + updatedAt: '', + exposedLocal: false, + isVisibleOnGuestDashboard: false, + } satisfies App; + } + + return { app, updateInfo, info }; + } + + /** + * Get the installed apps + */ + public async getInstalledApps() { + const apps = await this.appsRepository.getApps(); + return this.constructAppList(apps); + } + + public async getGuestDashboardApps() { + const apps = await this.appsRepository.getGuestDashboardApps(); + return this.constructAppList(apps); + } +} diff --git a/packages/backend/src/modules/apps/app-files-manager.ts b/packages/backend/src/modules/apps/app-files-manager.ts new file mode 100644 index 0000000000..aa8f45c211 --- /dev/null +++ b/packages/backend/src/modules/apps/app-files-manager.ts @@ -0,0 +1,402 @@ +import path from 'node:path'; +import { execAsync } from '@/common/helpers/exec-helpers'; +import { ConfigurationService } from '@/core/config/configuration.service'; +import { FilesystemService } from '@/core/filesystem/filesystem.service'; +import { LoggerService } from '@/core/logger/logger.service'; +import { Injectable } from '@nestjs/common'; +import { appInfoSchema } from './dto/app-info.dto'; + +@Injectable() +export class AppFilesManager { + constructor( + private readonly configuration: ConfigurationService, + private readonly filesystem: FilesystemService, + private readonly logger: LoggerService, + ) {} + + private getInstalledAppsFolder() { + const { directories } = this.configuration.getConfig(); + + return path.join(directories.dataDir, 'apps'); + } + + private getAppsRepoFolder() { + const { directories, appsRepoId } = this.configuration.getConfig(); + return path.join(directories.dataDir, 'repos', appsRepoId, 'apps'); + } + + public getAppPaths(appId: string) { + const { directories } = this.configuration.getConfig(); + + return { + appDataDir: path.join(directories.appDataDir, appId), + appRepoDir: path.join(this.getAppsRepoFolder(), appId), + appInstalledDir: path.join(this.getInstalledAppsFolder(), appId), + }; + } + + /** + * Get the app info from the app store + * @param id - The app id + */ + public async getAppInfoFromAppStore(id: string) { + try { + const { appRepoDir } = this.getAppPaths(id); + + if (await this.filesystem.pathExists(path.join(appRepoDir, 'config.json'))) { + const configFile = await this.filesystem.readTextFile(path.join(appRepoDir, 'config.json')); + const parsedConfig = appInfoSchema.safeParse(JSON.parse(configFile ?? '')); + + if (!parsedConfig.success) { + this.logger.debug(`App ${id} config error:`); + this.logger.debug(parsedConfig.error); + } + + if (parsedConfig.success && parsedConfig.data.available) { + const description = (await this.filesystem.readTextFile(path.join(appRepoDir, 'metadata', 'description.md'))) ?? ''; + return { ...parsedConfig.data, description }; + } + } + } catch (error) { + this.logger.error(`Error getting app info from app store for ${id}: ${error}`); + } + } + + /** + * Get the app info from the installed apps apps + * @param id - The app id + */ + public async getInstalledAppInfo(id: string) { + try { + const { appInstalledDir } = this.getAppPaths(id); + + if (await this.filesystem.pathExists(path.join(appInstalledDir, 'config.json'))) { + const configFile = await this.filesystem.readTextFile(path.join(appInstalledDir, 'config.json')); + const parsedConfig = appInfoSchema.safeParse(JSON.parse(configFile ?? '')); + + if (!parsedConfig.success) { + this.logger.debug(`App ${id} config error:`); + this.logger.debug(parsedConfig.error); + } + + if (parsedConfig.success && parsedConfig.data.available) { + const description = (await this.filesystem.readTextFile(path.join(appInstalledDir, 'metadata', 'description.md'))) ?? ''; + return { ...parsedConfig.data, description }; + } + } + } catch (error) { + return null; + } + } + + /** + * Get the docker-compose.json file content from the installed app + * @param id - The app id + * @returns The content of docker-compose.yml as a string, or null if not found + */ + public async getDockerComposeYaml(id: string) { + const arch = this.configuration.get('architecture'); + const { appInstalledDir } = this.getAppPaths(id); + let dockerComposePath = path.join(appInstalledDir, 'docker-compose.yml'); + + if (arch === 'arm64' && (await this.filesystem.pathExists(path.join(appInstalledDir, 'docker-compose.arm64.yml')))) { + dockerComposePath = path.join(appInstalledDir, 'docker-compose.arm64.yml'); + } + + let content = null; + try { + if (await this.filesystem.pathExists(dockerComposePath)) { + content = await this.filesystem.readTextFile(dockerComposePath); + } + } catch (error) { + this.logger.error(`Error getting docker-compose.yml for installed app ${id}: ${error}`); + } + + return { path: dockerComposePath, content }; + } + + /** + * Get the docker-compose.json file content from the installed app + * @param id - The app id + * @returns The content of docker-compose.json as a string, or null if not found + */ + public async getDockerComposeJson(id: string) { + const { appInstalledDir } = this.getAppPaths(id); + const dockerComposePath = path.join(appInstalledDir, 'docker-compose.json'); + + let content = null; + try { + if (await this.filesystem.pathExists(dockerComposePath)) { + content = await this.filesystem.readJsonFile(dockerComposePath); + } + } catch (error) { + this.logger.error(`Error getting docker-compose.json for installed app ${id}: ${error}`); + } + + return { path: dockerComposePath, content }; + } + + /** + /** + * Copy the app from the repo to the installed apps folder + * @param id - The app id + */ + public async copyAppFromRepoToInstalled(id: string) { + const appsRepoId = this.configuration.get('appsRepoId'); + const { appRepoDir, appDataDir, appInstalledDir } = this.getAppPaths(id); + + if (!(await this.filesystem.pathExists(appRepoDir))) { + this.logger.error(`App ${id} not found in repo ${appsRepoId}`); + throw new Error(`App ${id} not found in repo ${appsRepoId}`); + } + + // delete eventual app folder if exists + this.logger.info(`Deleting app ${id} folder if exists`); + await this.filesystem.removeDirectory(appInstalledDir); + + // Create app folder + this.logger.info(`Creating app ${id} folder`); + await this.filesystem.createDirectory(appInstalledDir); + + // Create app data folder + this.logger.info(`Creating app ${id} data folder`); + await this.filesystem.createDirectory(appDataDir); + + // Copy app folder from repo + this.logger.info(`Copying app ${id} from repo ${appsRepoId}`); + await this.filesystem.copyDirectory(appRepoDir, appInstalledDir); + } + + /** + * This function returns an object containing information about the updates available for the app with the provided id. + * It checks if the app is installed or not and looks for the config.json file in the appropriate directory. + * If the config.json file is invalid, it returns null. + * If the app is not found, it returns null. + * + * @param {string} id - The app id. + */ + public async getAppUpdateInfo(id: string) { + const config = await this.getAppInfoFromAppStore(id); + + if (config) { + return { + latestVersion: config.tipi_version, + minTipiVersion: config.min_tipi_version, + latestDockerVersion: config.version, + }; + } + + return { latestVersion: 0, latestDockerVersion: '0.0.0' }; + } + + /** + * Get the list of available app ids + * @returns The list of app ids + */ + public async getAvailableAppIds() { + const appsRepoFolder = this.getAppsRepoFolder(); + const appsRepoId = this.configuration.get('appsRepoId'); + + if (!(await this.filesystem.pathExists(appsRepoFolder))) { + this.logger.error(`Apps repo ${appsRepoId} not found. Make sure your repo is configured correctly.`); + return []; + } + + const appsDir = await this.filesystem.listFiles(appsRepoFolder); + const skippedFiles = ['__tests__', 'docker-compose.common.yml', 'schema.json', '.DS_Store']; + + return appsDir.filter((app) => !skippedFiles.includes(app)); + } + + /** + * Write the docker-compose.yml file to the installed app folder + * @param appId - The app id + * @param composeFile - The content of the docker-compose.yml file + */ + public async writeDockerComposeYml(appId: string, composeFile: string) { + const { appInstalledDir } = this.getAppPaths(appId); + const dockerComposePath = path.join(appInstalledDir, 'docker-compose.yml'); + + await this.filesystem.writeTextFile(dockerComposePath, composeFile); + } + + public async deleteAppFolder(appId: string) { + const { appInstalledDir } = this.getAppPaths(appId); + await this.filesystem.removeDirectory(appInstalledDir); + } + + public async deleteAppDataDir(appId: string) { + const { appDataDir } = this.getAppPaths(appId); + await this.filesystem.removeDirectory(appDataDir); + } + + /** + * Set the permissions for the app data directory + * @param appId - The app id + */ + public async setAppDataDirPermissions(appId: string) { + const { appDataDir } = this.getAppPaths(appId); + + await execAsync(`chmod -Rf a+rwx ${appDataDir}`).catch(() => { + this.logger.error(`Error setting permissions for app ${appId}`); + }); + } + + /** + * Given a template and a map of variables, this function replaces all instances of the variables in the template with their values. + * + * @param {string} template - The template to be rendered. + * @param {Map} envMap - The map of variables and their values. + */ + private renderTemplate(template: string, envMap: Map) { + let renderedTemplate = template; + + envMap.forEach((value, key) => { + const safeKey = key.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + renderedTemplate = renderedTemplate.replace(new RegExp(`{{${safeKey}}}`, 'g'), value); + }); + + return renderedTemplate; + } + + public async copyDataDir(appId: string, envMap: Map) { + const { appInstalledDir, appDataDir } = this.getAppPaths(appId); + + // return if app does not have a data directory + if (!(await this.filesystem.pathExists(path.join(appInstalledDir, 'data')))) { + return; + } + + // Return if app has already a data directory + if (await this.filesystem.pathExists(path.join(appDataDir, 'data'))) { + return; + } + + // Create app-data folder if it doesn't exist + if (!(await this.filesystem.pathExists(path.join(appDataDir, 'data')))) { + await this.filesystem.createDirectory(path.join(appDataDir, 'data')); + } + + const dataDir = await this.filesystem.listFiles(path.join(appInstalledDir, 'data')); + + const processFile = async (file: string) => { + if (file.endsWith('.template')) { + const template = await this.filesystem.readTextFile(path.join(appInstalledDir, 'data', file)); + if (template) { + const renderedTemplate = this.renderTemplate(template, envMap); + + await this.filesystem.writeTextFile(path.join(appDataDir, 'data', file.replace('.template', '')), renderedTemplate); + } + } else { + await this.filesystem.copyFile(path.join(appInstalledDir, 'data', file), path.join(appDataDir, 'data', file)); + } + }; + + const processDir = async (p: string) => { + await this.filesystem.createDirectory(path.join(appDataDir, 'data', p)); + + const files = await this.filesystem.listFiles(path.join(appInstalledDir, 'data', p)); + + await Promise.all( + files.map(async (file) => { + const fullPath = path.join(appInstalledDir, 'data', p, file); + + if (await this.filesystem.isDirectory(fullPath)) { + await processDir(path.join(p, file)); + } else { + await processFile(path.join(p, file)); + } + }), + ); + }; + + await Promise.all( + dataDir.map(async (file) => { + const fullPath = path.join(appInstalledDir, 'data', file); + + if (await this.filesystem.isDirectory(fullPath)) { + await processDir(file); + } else { + await processFile(file); + } + }), + ); + + // Remove any .gitkeep files from the app-data folder at any level + if (await this.filesystem.pathExists(path.join(appDataDir, 'data'))) { + await execAsync(`find ${appDataDir}/data -name .gitkeep -delete`).catch(() => { + this.logger.error(`Error removing .gitkeep files from ${appDataDir}/data`); + }); + } + } + + public async getAppImage(appId: string) { + const { appInstalledDir, appRepoDir } = this.getAppPaths(appId); + const { appDir } = this.configuration.get('directories'); + + const defaultFilePath = path.join(appInstalledDir, 'metadata', 'logo.jpg'); + const appRepoFilePath = path.join(appRepoDir, 'metadata', 'logo.jpg'); + + let filePath = path.join(appDir, 'public', 'app-not-found.jpg'); + + if (await this.filesystem.pathExists(defaultFilePath)) { + filePath = defaultFilePath; + } else if (await this.filesystem.pathExists(appRepoFilePath)) { + filePath = appRepoFilePath; + } + + const file = await this.filesystem.readBinaryFile(filePath); + return file; + } + + public async getAppEnv(appId: string) { + const { appDataDir } = this.getAppPaths(appId); + + const envPath = path.join(appDataDir, 'app.env'); + + let env = ''; + if (await this.filesystem.pathExists(envPath)) { + env = (await this.filesystem.readTextFile(envPath)) ?? ''; + } + + return { path: envPath, content: env }; + } + + public async writeAppEnv(appId: string, env: string) { + const { appDataDir } = this.getAppPaths(appId); + + const envPath = path.join(appDataDir, 'app.env'); + + await this.filesystem.writeTextFile(envPath, env); + } + + /** + * Get the user env file content + * @param appId - The app id + */ + public async getUserEnv(appId: string) { + const { directories } = this.configuration.getConfig(); + + const userEnvFile = path.join(directories.dataDir, 'user-config', appId, 'app.env'); + let content = null; + + if (await this.filesystem.pathExists(userEnvFile)) { + content = await this.filesystem.readTextFile(userEnvFile); + } + + return { path: userEnvFile, content }; + } + + public async getUserComposeFile(appId: string) { + const { directories } = this.configuration.getConfig(); + + const userComposeFile = path.join(directories.dataDir, 'user-config', appId, 'docker-compose.yml'); + let content = null; + + if (await this.filesystem.pathExists(userComposeFile)) { + content = await this.filesystem.readTextFile(userComposeFile); + } + + return { path: userComposeFile, content }; + } +} diff --git a/packages/backend/src/modules/apps/app.helpers.ts b/packages/backend/src/modules/apps/app.helpers.ts new file mode 100644 index 0000000000..5fcf153343 --- /dev/null +++ b/packages/backend/src/modules/apps/app.helpers.ts @@ -0,0 +1,101 @@ +import path from 'path'; +import { ConfigurationService } from '@/core/config/configuration.service'; +import { FilesystemService } from '@/core/filesystem/filesystem.service'; +import { Injectable } from '@nestjs/common'; +import { EnvUtils } from '../env/env.utils'; +import type { AppEventFormInput } from '../queue/entities/app-events'; +import { AppFilesManager } from './app-files-manager'; + +@Injectable() +export class AppHelpers { + constructor( + private readonly appFilesManager: AppFilesManager, + private readonly config: ConfigurationService, + private readonly filesytem: FilesystemService, + private readonly envUtils: EnvUtils, + ) {} + + /** + * This function generates an env file for the provided app. + * It reads the config.json file for the app, parses it, + * and uses the app's form fields and domain to generate the env file + * if the app is exposed and has a domain set, it adds the domain to the env file, + * otherwise, it adds the internal IP address to the env file + * It also creates the app-data folder for the app if it does not exist + * + * @param {string} appId - The id of the app to generate the env file for. + * @param {AppEventFormInput} form - The config object for the app. + * @throws Will throw an error if the app has an invalid config.json file or if a required variable is missing. + */ + public generateEnvFile = async (appId: string, form: AppEventFormInput) => { + const { internalIp, envFilePath, rootFolderHost } = this.config.getConfig(); + + const config = await this.appFilesManager.getInstalledAppInfo(appId); + + if (!config) { + throw new Error(`App ${appId} not found`); + } + + const baseEnvFile = await this.filesytem.readTextFile(envFilePath); + const envMap = this.envUtils.envStringToMap(baseEnvFile?.toString() ?? ''); + + // Default always present env variables + envMap.set('APP_PORT', String(config.port)); + envMap.set('APP_ID', appId); + envMap.set('ROOT_FOLDER_HOST', rootFolderHost); + envMap.set('APP_DATA_DIR', path.join(this.config.get('userSettings').appDataPath, appId)); + + const appEnv = await this.appFilesManager.getAppEnv(appId); + const existingAppEnvMap = this.envUtils.envStringToMap(appEnv.content); + + if (config.generate_vapid_keys) { + if (existingAppEnvMap.has('VAPID_PUBLIC_KEY') && existingAppEnvMap.has('VAPID_PRIVATE_KEY')) { + envMap.set('VAPID_PUBLIC_KEY', existingAppEnvMap.get('VAPID_PUBLIC_KEY') as string); + envMap.set('VAPID_PRIVATE_KEY', existingAppEnvMap.get('VAPID_PRIVATE_KEY') as string); + } else { + const vapidKeys = this.envUtils.generateVapidKeys(); + envMap.set('VAPID_PUBLIC_KEY', vapidKeys.publicKey); + envMap.set('VAPID_PRIVATE_KEY', vapidKeys.privateKey); + } + } + + await Promise.all( + config.form_fields.map(async (field) => { + const formValue = form[field.env_variable]; + const envVar = field.env_variable; + + if (formValue || typeof formValue === 'boolean') { + envMap.set(envVar, String(formValue)); + } else if (field.type === 'random') { + if (existingAppEnvMap.has(envVar)) { + envMap.set(envVar, existingAppEnvMap.get(envVar) as string); + } else { + const length = field.min || 32; + const randomString = this.envUtils.createRandomString(field.env_variable, length, field.encoding); + + envMap.set(envVar, randomString); + } + } else if (field.required) { + throw new Error(`Variable ${field.label || field.env_variable} is required`); + } + }), + ); + + if (form.exposed && form.domain && typeof form.domain === 'string') { + envMap.set('APP_EXPOSED', 'true'); + envMap.set('APP_DOMAIN', form.domain); + envMap.set('APP_HOST', form.domain); + envMap.set('APP_PROTOCOL', 'https'); + } else if (form.exposedLocal && !form.openPort) { + envMap.set('APP_DOMAIN', `${config.id}.${envMap.get('LOCAL_DOMAIN')}`); + envMap.set('APP_HOST', `${config.id}.${envMap.get('LOCAL_DOMAIN')}`); + envMap.set('APP_PROTOCOL', 'https'); + } else { + envMap.set('APP_DOMAIN', `${internalIp}:${config.port}`); + envMap.set('APP_HOST', internalIp); + envMap.set('APP_PROTOCOL', 'http'); + } + + await this.appFilesManager.writeAppEnv(appId, this.envUtils.envMapToString(envMap)); + }; +} diff --git a/packages/backend/src/modules/apps/apps.controller.ts b/packages/backend/src/modules/apps/apps.controller.ts new file mode 100644 index 0000000000..a6f5914983 --- /dev/null +++ b/packages/backend/src/modules/apps/apps.controller.ts @@ -0,0 +1,64 @@ +import { Controller, Get, Param, Query, Res, UseGuards } from '@nestjs/common'; +import { ApiQuery } from '@nestjs/swagger'; +import type { Response } from 'express'; +import { ZodSerializerDto } from 'nestjs-zod'; +import { AuthGuard } from '../auth/auth.guard'; +import { AppCatalogService } from './app-catalog.service'; +import { APP_CATEGORIES } from './dto/app-info.dto'; +import { AppDetailsDto, GuestAppsDto, MyAppsDto, SearchAppsDto, SearchAppsQueryDto } from './dto/app.dto'; + +@Controller('apps') +export class AppsController { + constructor(private readonly appCatalog: AppCatalogService) {} + + @Get('installed') + @UseGuards(AuthGuard) + @ZodSerializerDto(MyAppsDto) + async getInstalledApps(): Promise { + const installed = await this.appCatalog.getInstalledApps(); + return { installed }; + } + + @Get('guest') + @ZodSerializerDto(GuestAppsDto) + async getGuestApps(): Promise { + const guest = await this.appCatalog.getGuestDashboardApps(); + return { installed: guest }; + } + + @Get('search') + @UseGuards(AuthGuard) + @ZodSerializerDto(SearchAppsDto) + @ApiQuery({ name: 'search', type: String, required: false }) + @ApiQuery({ name: 'pageSize', type: Number, required: false }) + @ApiQuery({ name: 'cursor', type: String, required: false }) + @ApiQuery({ name: 'category', required: false, enum: APP_CATEGORIES }) + async searchApps(@Query() query: SearchAppsQueryDto): Promise { + const { search, pageSize, cursor, category } = query; + + const res = await this.appCatalog.searchApps({ search, pageSize: Number(pageSize), cursor, category }); + + return res; + } + + @Get(':id') + @UseGuards(AuthGuard) + @ZodSerializerDto(AppDetailsDto) + async getAppDetails(@Param('id') id: string): Promise { + const res = await this.appCatalog.getApp(id); + + return res; + } + + @Get(':id/image') + async getImage(@Param('id') id: string, @Res({ passthrough: true }) res: Response) { + const image = await this.appCatalog.getAppImage(id); + + res.set({ + 'Content-Type': 'image/jpeg', + 'Cache-Control': 'public, max-age=86400', + }); + + res.send(image); + } +} diff --git a/packages/backend/src/modules/apps/apps.module.ts b/packages/backend/src/modules/apps/apps.module.ts new file mode 100644 index 0000000000..1127f1d113 --- /dev/null +++ b/packages/backend/src/modules/apps/apps.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { EnvModule } from '../env/env.module'; +import { QueueModule } from '../queue/queue.module'; +import { AppCatalogService } from './app-catalog.service'; +import { AppFilesManager } from './app-files-manager'; +import { AppHelpers } from './app.helpers'; +import { AppsController } from './apps.controller'; +import { AppsRepository } from './apps.repository'; + +@Module({ + imports: [QueueModule, EnvModule], + controllers: [AppsController], + providers: [AppFilesManager, AppCatalogService, AppsRepository, AppHelpers], + exports: [AppCatalogService, AppsRepository, AppFilesManager, AppHelpers], +}) +export class AppsModule {} diff --git a/packages/backend/src/modules/apps/apps.repository.ts b/packages/backend/src/modules/apps/apps.repository.ts new file mode 100644 index 0000000000..980c037757 --- /dev/null +++ b/packages/backend/src/modules/apps/apps.repository.ts @@ -0,0 +1,94 @@ +import { DatabaseService } from '@/core/database/database.service'; +import { type AppStatus, appTable, type NewApp } from '@/core/database/schema'; +import { Injectable } from '@nestjs/common'; +import { and, asc, eq, ne, notInArray } from 'drizzle-orm'; + +@Injectable() +export class AppsRepository { + constructor(private db: DatabaseService) {} + + /** + * Given an app id, return the app + * + * @param {string} appId - The id of the app to return + */ + public async getApp(appId: string) { + return this.db.db.query.appTable.findFirst({ where: eq(appTable.id, appId) }); + } + + /** + * Given an app id, update the app with the given data + * + * @param {string} appId - The id of the app to update + * @param {Partial} data - The data to update the app with + */ + public async updateApp(appId: string, data: Partial) { + const updatedApps = await this.db.db.update(appTable).set(data).where(eq(appTable.id, appId)).returning().execute(); + return updatedApps[0]; + } + + /** + * Given an app id, delete the app + * + * @param {string} appId - The id of the app to delete + */ + public async deleteApp(appId: string) { + await this.db.db.delete(appTable).where(eq(appTable.id, appId)).execute(); + } + + /** + * Given app data, creates a new app + * + * @param {NewApp} data - The data to create the app with + */ + public async createApp(data: NewApp) { + const newApps = await this.db.db.insert(appTable).values(data).returning().execute(); + return newApps[0]; + } + + /** + * Returns all apps installed with the given status sorted by id ascending + * + * @param {AppStatus} status - The status of the apps to return + */ + public async getAppsByStatus(status: AppStatus) { + return this.db.db.query.appTable.findMany({ where: eq(appTable.status, status), orderBy: asc(appTable.id) }); + } + + /** + * Returns all apps installed sorted by id ascending + */ + public async getApps() { + return this.db.db.query.appTable.findMany({ orderBy: asc(appTable.id) }); + } + + /** + * Returns all apps that are running and visible on guest dashboard sorted by id ascending + */ + public async getGuestDashboardApps() { + return this.db.db.query.appTable.findMany({ + where: and(eq(appTable.status, 'running'), eq(appTable.isVisibleOnGuestDashboard, true)), + orderBy: asc(appTable.id), + }); + } + + /** + * Given a domain, return all apps that have this domain, are exposed and not the given id + * + * @param {string} domain - The domain to search for + * @param {string} id - The id of the app to exclude + */ + public async getAppsByDomain(domain: string, id: string) { + return this.db.db.query.appTable.findMany({ where: and(eq(appTable.domain, domain), eq(appTable.exposed, true), ne(appTable.id, id)) }); + } + + /** + * Given an array of app status, update all apps that have a status not in the array with new values + * + * @param {AppStatus[]} statuses - The statuses to exclude from the update + * @param {Partial} data - The data to update the apps with + */ + public async updateAppsByStatusNotIn(statuses: AppStatus[], data: Partial) { + return this.db.db.update(appTable).set(data).where(notInArray(appTable.status, statuses)).returning().execute(); + } +} diff --git a/packages/backend/src/modules/apps/dto/app-info.dto.ts b/packages/backend/src/modules/apps/dto/app-info.dto.ts new file mode 100644 index 0000000000..e5db5fce20 --- /dev/null +++ b/packages/backend/src/modules/apps/dto/app-info.dto.ts @@ -0,0 +1,106 @@ +import { ARCHITECTURES } from '@/core/config/configuration.service'; +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod'; + +export const APP_CATEGORIES = [ + 'network', + 'media', + 'development', + 'automation', + 'social', + 'utilities', + 'photography', + 'security', + 'featured', + 'books', + 'data', + 'music', + 'finance', + 'gaming', + 'ai', +] as const; +export type AppCategory = (typeof APP_CATEGORIES)[number]; + +export const FIELD_TYPES = ['text', 'password', 'email', 'number', 'fqdn', 'ip', 'fqdnip', 'url', 'random', 'boolean'] as const; +export type FieldType = (typeof FIELD_TYPES)[number]; + +export const RANDOM_ENCODINGS = ['hex', 'base64'] as const; +export type RandomEncoding = (typeof RANDOM_ENCODINGS)[number]; + +export const formFieldSchema = z.object({ + type: z.enum(FIELD_TYPES), + label: z.string(), + placeholder: z.string().optional(), + max: z.number().optional(), + min: z.number().optional(), + hint: z.string().optional(), + options: z.object({ label: z.string(), value: z.string() }).array().optional(), + required: z.boolean().optional().default(false), + default: z.union([z.boolean(), z.string(), z.number()]).optional(), + regex: z.string().optional(), + pattern_error: z.string().optional(), + env_variable: z.string(), + encoding: z.enum(RANDOM_ENCODINGS).optional(), +}); + +export const appInfoSchema = z.object({ + id: z.string(), + available: z.boolean(), + deprecated: z.boolean().optional().default(false), + port: z.number().min(1).max(65535), + name: z.string(), + description: z.string().optional().default(''), + version: z.string().optional().default('latest'), + tipi_version: z.number(), + short_desc: z.string(), + author: z.string(), + source: z.string(), + website: z.string().optional(), + force_expose: z.boolean().optional().default(false), + generate_vapid_keys: z.boolean().optional().default(false), + categories: z.enum(APP_CATEGORIES).array().default([]), + url_suffix: z.string().optional(), + form_fields: z.array(formFieldSchema).optional().default([]), + https: z.boolean().optional().default(false), + exposable: z.boolean().optional().default(false), + no_gui: z.boolean().optional().default(false), + supported_architectures: z.enum(ARCHITECTURES).array().optional(), + uid: z.number().optional(), + gid: z.number().optional(), + dynamic_config: z.boolean().optional().default(false), + min_tipi_version: z.string().optional(), + created_at: z + .number() + .int() + .min(0) + .refine((v) => v < Date.now()) + .optional() + .default(0), + updated_at: z + .number() + .int() + .min(0) + .refine((v) => v < Date.now()) + .optional() + .default(0), +}); + +// Derived types +export type AppInfo = z.output; +export type FormField = z.output; + +// App info +export class AppInfoSimpleDto extends createZodDto( + appInfoSchema.pick({ + id: true, + name: true, + short_desc: true, + categories: true, + deprecated: true, + created_at: true, + supported_architectures: true, + available: true, + }), +) {} + +export class AppInfoDto extends createZodDto(appInfoSchema) {} diff --git a/packages/backend/src/modules/apps/dto/app.dto.ts b/packages/backend/src/modules/apps/dto/app.dto.ts new file mode 100644 index 0000000000..93c4662bd8 --- /dev/null +++ b/packages/backend/src/modules/apps/dto/app.dto.ts @@ -0,0 +1,80 @@ +import { APP_STATUS } from '@/core/database/schema'; +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod'; +import { APP_CATEGORIES, AppInfoDto, AppInfoSimpleDto } from './app-info.dto'; + +// Search apps +export class SearchAppsQueryDto extends createZodDto( + z.object({ + search: z.string().optional(), + pageSize: z.coerce.number().optional(), + cursor: z.string().optional(), + category: z.enum(APP_CATEGORIES).optional(), + }), +) {} + +export class SearchAppsDto extends createZodDto( + z.object({ + data: AppInfoSimpleDto.schema.array(), + nextCursor: z.string().optional(), + total: z.number(), + }), +) {} + +export class UpdateInfoDto extends createZodDto( + z.object({ + latestVersion: z.number(), + minTipiVersion: z.string().optional(), + latestDockerVersion: z.string().optional(), + }), +) {} + +export class AppDto extends createZodDto( + z.object({ + id: z.string(), + status: z.enum(APP_STATUS), + lastOpened: z.string().nullable(), + numOpened: z.number().default(0), + createdAt: z.string().optional(), + updatedAt: z.string().optional(), + version: z.number(), + exposed: z.boolean(), + openPort: z.boolean(), + exposedLocal: z.boolean(), + domain: z.string().nullable(), + isVisibleOnGuestDashboard: z.boolean(), + }), +) {} + +export class MyAppsDto extends createZodDto( + z.object({ + installed: z + .object({ + app: AppDto.schema, + info: AppInfoSimpleDto.schema, + updateInfo: UpdateInfoDto.schema, + }) + .array(), + }), +) {} + +export class GuestAppsDto extends createZodDto( + z.object({ + installed: z + .object({ + app: AppDto.schema, + info: AppInfoDto.schema, + updateInfo: UpdateInfoDto.schema, + }) + .array(), + }), +) {} + +// App details +export class AppDetailsDto extends createZodDto( + z.object({ + info: AppInfoDto.schema, + app: AppDto.schema, + updateInfo: UpdateInfoDto.schema, + }), +) {} diff --git a/packages/backend/src/modules/auth/auth.controller.ts b/packages/backend/src/modules/auth/auth.controller.ts new file mode 100644 index 0000000000..40d46ce97f --- /dev/null +++ b/packages/backend/src/modules/auth/auth.controller.ts @@ -0,0 +1,163 @@ +import { SESSION_COOKIE_MAX_AGE, SESSION_COOKIE_NAME } from '@/common/constants'; +import { TranslatableError } from '@/common/error/translatable-error'; +import { Body, Controller, Delete, Get, Patch, Post, Req, Res, UseGuards } from '@nestjs/common'; +import type { Request, Response } from 'express'; +import { ZodSerializerDto } from 'nestjs-zod'; +import { AuthGuard } from './auth.guard'; +import { AuthService } from './auth.service'; +import { + ChangePasswordBody, + ChangeUsernameBody, + CheckResetPasswordRequestDto, + DisableTotpBody, + GetTotpUriBody, + GetTotpUriDto, + LoginBody, + LoginDto, + RegisterBody, + RegisterDto, + ResetPasswordBody, + ResetPasswordDto, + SetupTotpBody, + VerifyTotpBody, +} from './dto/auth.dto'; + +@Controller('auth') +export class AuthController { + constructor(private readonly authService: AuthService) {} + + @Post('/login') + @ZodSerializerDto(LoginDto) + async login(@Body() body: LoginBody, @Res({ passthrough: true }) res: Response): Promise { + const { sessionId, totpSessionId } = await this.authService.login(body); + + if (totpSessionId) { + return { success: true, totpSessionId }; + } + + res.cookie(SESSION_COOKIE_NAME, sessionId, { httpOnly: true, secure: false, sameSite: false, maxAge: SESSION_COOKIE_MAX_AGE }); + + return { success: true }; + } + + @Post('/verify-totp') + @ZodSerializerDto(LoginDto) + async verifyTotp(@Body() body: VerifyTotpBody, @Res({ passthrough: true }) res: Response): Promise { + const { sessionId } = await this.authService.verifyTotp(body); + + res.cookie(SESSION_COOKIE_NAME, sessionId, { httpOnly: true, secure: false, sameSite: false, maxAge: SESSION_COOKIE_MAX_AGE }); + + return { success: true }; + } + + @Post('/register') + @ZodSerializerDto(RegisterDto) + async register(@Body() body: RegisterBody, @Res({ passthrough: true }) res: Response): Promise { + const { sessionId } = await this.authService.register(body); + + res.cookie(SESSION_COOKIE_NAME, sessionId, { httpOnly: true, secure: false, sameSite: false, maxAge: SESSION_COOKIE_MAX_AGE }); + + return { success: true }; + } + + @Post('/logout') + async logout(@Res({ passthrough: true }) res: Response, @Req() req: Request): Promise { + res.clearCookie(SESSION_COOKIE_NAME); + const sessionId = req.cookies['tipi.sid']; + + if (!sessionId) { + return; + } + + await this.authService.logout(sessionId); + + return; + } + + @Patch('/username') + @UseGuards(AuthGuard) + async changeUsername(@Body() body: ChangeUsernameBody, @Req() req: Request, @Res() res: Response): Promise { + const userId = req.user?.id; + + if (!userId) { + throw new TranslatableError('SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN'); + } + + await this.authService.changeUsername({ userId, ...body }); + + res.clearCookie(SESSION_COOKIE_NAME); + res.status(204).send(); + } + + @Patch('/password') + @UseGuards(AuthGuard) + async changePassword(@Body() body: ChangePasswordBody, @Req() req: Request, @Res() res: Response): Promise { + const userId = req.user?.id; + + if (!userId) { + throw new TranslatableError('SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN'); + } + + await this.authService.changePassword({ userId, ...body }); + + res.clearCookie(SESSION_COOKIE_NAME); + res.status(204).send(); + } + + @Patch('/totp/get-uri') + @UseGuards(AuthGuard) + @ZodSerializerDto(GetTotpUriDto) + async getTotpUri(@Body() body: GetTotpUriBody, @Req() req: Request): Promise { + const userId = req.user?.id; + + if (!userId) { + throw new TranslatableError('SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN'); + } + + return this.authService.getTotpUri({ userId, ...body }); + } + + @Patch('/totp/setup') + @UseGuards(AuthGuard) + async setupTotp(@Body() body: SetupTotpBody, @Req() req: Request): Promise { + const userId = req.user?.id; + + if (!userId) { + throw new TranslatableError('SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN'); + } + + await this.authService.setupTotp({ userId, totpCode: body.code }); + } + + @Patch('/totp/disable') + @UseGuards(AuthGuard) + async disableTotp(@Body() body: DisableTotpBody, @Req() req: Request): Promise { + const userId = req.user?.id; + + if (!userId) { + throw new TranslatableError('SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN'); + } + + await this.authService.disableTotp({ userId, ...body }); + } + + @Post('/reset-password') + @ZodSerializerDto(ResetPasswordDto) + async resetPassword(@Body() body: ResetPasswordBody): Promise { + const { email } = await this.authService.changeOperatorPassword(body); + + return { success: true, email }; + } + + @Delete('/reset-password') + async cancelResetPassword(): Promise { + await this.authService.cancelPasswordChangeRequest(); + } + + @Get('/reset-password') + async checkResetPasswordRequest(): Promise { + const isPending = await this.authService.checkPasswordChangeRequest(); + + return { isRequestPending: isPending }; + } +} diff --git a/packages/backend/src/modules/auth/auth.guard.ts b/packages/backend/src/modules/auth/auth.guard.ts new file mode 100644 index 0000000000..2345c9384f --- /dev/null +++ b/packages/backend/src/modules/auth/auth.guard.ts @@ -0,0 +1,15 @@ +import type { Request } from 'express'; +import { Injectable, type CanActivate, type ExecutionContext, UnauthorizedException } from '@nestjs/common'; + +@Injectable() +export class AuthGuard implements CanActivate { + async canActivate(context: ExecutionContext) { + const request = context.switchToHttp().getRequest() as Request; + + if (!request.user) { + throw new UnauthorizedException(); + } + + return true; + } +} diff --git a/packages/backend/src/modules/auth/auth.middleware.ts b/packages/backend/src/modules/auth/auth.middleware.ts new file mode 100644 index 0000000000..df713b2305 --- /dev/null +++ b/packages/backend/src/modules/auth/auth.middleware.ts @@ -0,0 +1,26 @@ +import { CacheService } from '@/core/cache/cache.service'; +import { Injectable, type NestMiddleware } from '@nestjs/common'; +import type { NextFunction, Request, Response } from 'express'; +import { UserRepository } from '../user/user.repository'; + +@Injectable() +export class AuthMiddleware implements NestMiddleware { + constructor( + private readonly cache: CacheService, + private readonly userRepository: UserRepository, + ) {} + + async use(req: Request, _: Response, next: NextFunction) { + const sessionId = req.cookies['tipi.sid']; + + if (sessionId) { + const userId = await this.cache.get(`session:${sessionId}`); + if (!Number.isNaN(Number(userId))) { + const user = await this.userRepository.getUserDtoById(Number(userId)); + req.user = user; + } + } + + next(); + } +} diff --git a/packages/backend/src/modules/auth/auth.module.ts b/packages/backend/src/modules/auth/auth.module.ts new file mode 100644 index 0000000000..de2317a7a7 --- /dev/null +++ b/packages/backend/src/modules/auth/auth.module.ts @@ -0,0 +1,13 @@ +import { EncryptionModule } from '@/core/encryption/encryption.module'; +import { UserModule } from '@/modules/user/user.module'; +import { Module } from '@nestjs/common'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; +import { SessionManager } from './session.manager'; + +@Module({ + imports: [UserModule, EncryptionModule], + controllers: [AuthController], + providers: [AuthService, SessionManager], +}) +export class AuthModule {} diff --git a/packages/backend/src/modules/auth/auth.service.ts b/packages/backend/src/modules/auth/auth.service.ts new file mode 100644 index 0000000000..8d7f7ce0cd --- /dev/null +++ b/packages/backend/src/modules/auth/auth.service.ts @@ -0,0 +1,393 @@ +import path from 'node:path'; +import { TranslatableError } from '@/common/error/translatable-error'; +import { CacheService } from '@/core/cache/cache.service'; +import { ConfigurationService } from '@/core/config/configuration.service'; +import { EncryptionService } from '@/core/encryption/encryption.service'; +import { FilesystemService } from '@/core/filesystem/filesystem.service'; +import { UserRepository } from '@/modules/user/user.repository'; +import { HttpStatus, Injectable } from '@nestjs/common'; +import * as argon2 from 'argon2'; +import { v4 } from 'uuid'; +import validator from 'validator'; +import type { LoginBody, RegisterBody } from './dto/auth.dto'; +import { SessionManager } from './session.manager'; +import { TotpAuthenticator } from './utils/totp-authenticator'; + +@Injectable() +export class AuthService { + constructor( + private userRepository: UserRepository, + private sessionManager: SessionManager, + private config: ConfigurationService, + private encryption: EncryptionService, + private cache: CacheService, + private filesystem: FilesystemService, + ) {} + + /** + * Given a username and password, login the user and return the session ID. + * + * @param username - The username of the user to login. + * @param password - The password of the user to login. + * @returns The session ID. + */ + public login = async (input: LoginBody) => { + const { username, password } = input; + + const user = await this.userRepository.getUserByUsername(username); + + if (!user) { + throw new TranslatableError('AUTH_ERROR_USER_NOT_FOUND', {}, HttpStatus.BAD_REQUEST); + } + + const isPasswordValid = await argon2.verify(user.password, password); + + if (!isPasswordValid) { + throw new TranslatableError('AUTH_ERROR_INVALID_CREDENTIALS', {}, HttpStatus.BAD_REQUEST); + } + + if (user.totpEnabled) { + const totpSessionId = v4(); + await this.cache.set(totpSessionId, user.id.toString()); + return { totpSessionId }; + } + + const sessionId = await this.sessionManager.createSession(user.id); + + return { + sessionId, + }; + }; + + /** + * Verify TOTP code and return a JWT token + * + * @param {object} params - An object containing the TOTP session ID and the TOTP code + * @param {string} params.totpSessionId - The TOTP session ID + * @param {string} params.totpCode - The TOTP code + */ + public verifyTotp = async (params: { totpSessionId: string; totpCode: string }) => { + const { totpSessionId, totpCode } = params; + const userId = await this.cache.get(totpSessionId); + + if (!userId) { + throw new TranslatableError('AUTH_ERROR_TOTP_SESSION_NOT_FOUND'); + } + + const user = await this.userRepository.getUserById(Number(userId)); + + if (!user) { + throw new TranslatableError('AUTH_ERROR_USER_NOT_FOUND'); + } + + if (!user.totpEnabled || !user.totpSecret || !user.salt) { + throw new TranslatableError('AUTH_ERROR_TOTP_NOT_ENABLED'); + } + + const totpSecret = this.encryption.decrypt(user.totpSecret, user.salt); + const isValid = TotpAuthenticator.check(totpCode, totpSecret); + + if (!isValid) { + throw new TranslatableError('AUTH_ERROR_TOTP_INVALID_CODE'); + } + + const sessionId = await this.sessionManager.createSession(user.id); + + await this.cache.del(totpSessionId); + + return { + sessionId, + }; + }; + + /** + * Creates a new user with the provided email and password and returns a session token + * + * @param {LoginBody} input - An object containing the email and password fields + */ + public register = async (input: RegisterBody) => { + const operators = await this.userRepository.getOperators(); + + if (operators.length > 0) { + throw new TranslatableError('AUTH_ERROR_ADMIN_ALREADY_EXISTS', {}, HttpStatus.FORBIDDEN); + } + + const { password, username } = input; + const email = username.trim().toLowerCase(); + + if (!username || !password) { + throw new TranslatableError('AUTH_ERROR_MISSING_EMAIL_OR_PASSWORD', {}, HttpStatus.BAD_REQUEST); + } + + if (username.length < 3 || !validator.isEmail(email)) { + throw new TranslatableError('AUTH_ERROR_INVALID_USERNAME', {}, HttpStatus.BAD_REQUEST); + } + + const user = await this.userRepository.getUserByUsername(email); + + if (user) { + throw new TranslatableError('AUTH_ERROR_USER_ALREADY_EXISTS', {}, HttpStatus.BAD_REQUEST); + } + + const hash = await argon2.hash(password); + const newUser = await this.userRepository.createUser({ username: email, password: hash, operator: true }); + + if (!newUser) { + throw new TranslatableError('AUTH_ERROR_ERROR_CREATING_USER', {}, HttpStatus.INTERNAL_SERVER_ERROR); + } + + const sessionId = await this.sessionManager.createSession(newUser.id); + + return { + sessionId, + }; + }; + + /** + * Logs out the currently logged in user. + */ + public logout = async (sessionId: string) => { + await this.sessionManager.deleteSession(sessionId); + }; + + /** + * Change the username of the currently logged in user. + */ + public changeUsername = async (params: { password: string; userId: number; newUsername: string }) => { + if (this.config.get('demoMode')) { + throw new TranslatableError('SERVER_ERROR_NOT_ALLOWED_IN_DEMO'); + } + + const { newUsername, password, userId } = params; + + const user = await this.userRepository.getUserById(userId); + + if (!user) { + throw new TranslatableError('AUTH_ERROR_USER_NOT_FOUND'); + } + + const valid = await argon2.verify(user.password, password); + + if (!valid) { + throw new TranslatableError('AUTH_ERROR_INVALID_PASSWORD'); + } + + const email = newUsername.trim().toLowerCase(); + + if (!validator.isEmail(email)) { + throw new TranslatableError('AUTH_ERROR_INVALID_USERNAME'); + } + + const existingUser = await this.userRepository.getUserByUsername(email); + + if (existingUser) { + throw new TranslatableError('AUTH_ERROR_USER_ALREADY_EXISTS'); + } + + await this.userRepository.updateUser(user.id, { username: email }); + await this.sessionManager.destroyAllSessionsByUserId(user.id); + + return true; + }; + + public changePassword = async (params: { currentPassword: string; newPassword: string; userId: number }) => { + if (this.config.get('demoMode')) { + throw new TranslatableError('SERVER_ERROR_NOT_ALLOWED_IN_DEMO'); + } + + const { currentPassword, newPassword, userId } = params; + + const user = await this.userRepository.getUserById(userId); + + if (!user) { + throw new TranslatableError('AUTH_ERROR_USER_NOT_FOUND'); + } + + const valid = await argon2.verify(user.password, currentPassword); + + if (!valid) { + throw new TranslatableError('AUTH_ERROR_INVALID_PASSWORD'); + } + + if (newPassword.length < 8) { + throw new TranslatableError('AUTH_ERROR_INVALID_PASSWORD_LENGTH'); + } + + const hash = await argon2.hash(newPassword); + await this.userRepository.updateUser(user.id, { password: hash }); + await this.sessionManager.destroyAllSessionsByUserId(user.id); + + return true; + }; + + /** + * Given a userId returns the TOTP URI and the secret key + * + * @param {object} params - An object containing the userId and the user's password + * @param {number} params.userId - The user's ID + * @param {string} params.password - The user's password + */ + public getTotpUri = async (params: { userId: number; password: string }) => { + if (this.config.get('demoMode')) { + throw new TranslatableError('SERVER_ERROR_NOT_ALLOWED_IN_DEMO'); + } + + const { userId, password } = params; + + const user = await this.userRepository.getUserById(userId); + + if (!user) { + throw new TranslatableError('AUTH_ERROR_USER_NOT_FOUND'); + } + + const isPasswordValid = await argon2.verify(user.password, password); + if (!isPasswordValid) { + throw new TranslatableError('AUTH_ERROR_INVALID_PASSWORD'); + } + + if (user.totpEnabled) { + throw new TranslatableError('AUTH_ERROR_TOTP_ALREADY_ENABLED'); + } + + let { salt } = user; + const newTotpSecret = TotpAuthenticator.generateSecret(); + + if (!salt) { + salt = this.sessionManager.generateSalt(); + } + + const encryptedTotpSecret = this.encryption.encrypt(newTotpSecret, salt); + + await this.userRepository.updateUser(userId, { totpSecret: encryptedTotpSecret, salt }); + + const uri = TotpAuthenticator.keyuri(user.username, 'Runtipi', newTotpSecret); + + return { uri, key: newTotpSecret }; + }; + + public setupTotp = async (params: { userId: number; totpCode: string }) => { + if (this.config.get('demoMode')) { + throw new TranslatableError('SERVER_ERROR_NOT_ALLOWED_IN_DEMO'); + } + + const { userId, totpCode } = params; + const user = await this.userRepository.getUserById(userId); + + if (!user) { + throw new TranslatableError('AUTH_ERROR_USER_NOT_FOUND'); + } + + if (user.totpEnabled || !user.totpSecret || !user.salt) { + throw new TranslatableError('AUTH_ERROR_TOTP_ALREADY_ENABLED'); + } + + const totpSecret = this.encryption.decrypt(user.totpSecret, user.salt); + const isValid = TotpAuthenticator.check(totpCode, totpSecret); + + if (!isValid) { + throw new TranslatableError('AUTH_ERROR_TOTP_INVALID_CODE'); + } + + await this.userRepository.updateUser(userId, { totpEnabled: true }); + + return true; + }; + + public disableTotp = async (params: { userId: number; password: string }) => { + const { userId, password } = params; + + const user = await this.userRepository.getUserById(userId); + + if (!user) { + throw new TranslatableError('AUTH_ERROR_USER_NOT_FOUND'); + } + + if (!user.totpEnabled) { + throw new TranslatableError('AUTH_ERROR_TOTP_NOT_ENABLED'); + } + + const isPasswordValid = await argon2.verify(user.password, password); + if (!isPasswordValid) { + throw new TranslatableError('AUTH_ERROR_INVALID_PASSWORD'); + } + + await this.userRepository.updateUser(userId, { totpEnabled: false, totpSecret: null }); + + return true; + }; + + /** + * Change the password of the operator user + * + * @param {object} params - An object containing the new password + * @param {string} params.newPassword - The new password + */ + public changeOperatorPassword = async (params: { newPassword: string }) => { + const isRequested = await this.checkPasswordChangeRequest(); + + if (!isRequested) { + throw new TranslatableError('AUTH_ERROR_NO_CHANGE_PASSWORD_REQUEST'); + } + + const { newPassword } = params; + + const user = await this.userRepository.getFirstOperator(); + + if (!user) { + throw new TranslatableError('AUTH_ERROR_OPERATOR_NOT_FOUND'); + } + + const hash = await argon2.hash(newPassword); + + await this.userRepository.updateUser(user.id, { password: hash, totpEnabled: false, totpSecret: null }); + + const { dataDir } = this.config.get('directories'); + await this.filesystem.removeFile(path.join(dataDir, 'state', 'password-change-request')); + + await this.sessionManager.destroyAllSessionsByUserId(user.id); + + return { email: user.username }; + }; + + /* + * Check if there is a pending password change request for the given email + * Returns true if there is a file in the password change requests folder with the given email + * + * @returns {boolean} - A boolean indicating if there is a password change request or not + */ + public checkPasswordChangeRequest = async () => { + const REQUEST_TIMEOUT_SECS = 15 * 60; // 15 minutes + + const { dataDir } = this.config.get('directories'); + const resetPasswordFilePath = path.join(dataDir, 'state', 'password-change-request'); + + try { + const timestamp = await this.filesystem.readTextFile(resetPasswordFilePath); + + if (!timestamp) { + return false; + } + + const requestCreation = Number(timestamp); + return requestCreation + REQUEST_TIMEOUT_SECS > Date.now() / 1000; + } catch { + return false; + } + }; + + /* + * If there is a pending password change request, remove it + * Returns true if the file is removed successfully + * + * @returns {boolean} - A boolean indicating if the file is removed successfully or not + * @throws {Error} - If the file cannot be removed + */ + public cancelPasswordChangeRequest = async () => { + const { dataDir } = this.config.get('directories'); + const changeRequestPath = path.join(dataDir, 'state', 'password-change-request'); + + await this.filesystem.removeFile(changeRequestPath); + + return true; + }; +} diff --git a/packages/backend/src/modules/auth/dto/auth.dto.ts b/packages/backend/src/modules/auth/dto/auth.dto.ts new file mode 100644 index 0000000000..d6b0040f61 --- /dev/null +++ b/packages/backend/src/modules/auth/dto/auth.dto.ts @@ -0,0 +1,100 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod'; + +// Login +export class LoginBody extends createZodDto( + z.object({ + username: z.string(), + password: z.string(), + }), +) {} + +export class VerifyTotpBody extends createZodDto( + z.object({ + totpCode: z.string(), + totpSessionId: z.string(), + }), +) {} + +export class LoginDto extends createZodDto( + z.object({ + success: z.boolean(), + totpSessionId: z.string().optional(), + }), +) {} + +// Register +export class RegisterBody extends createZodDto( + z.object({ + username: z.string(), + password: z.string(), + }), +) {} + +export class RegisterDto extends createZodDto( + z.object({ + success: z.boolean(), + }), +) {} + +// Change username +export class ChangeUsernameBody extends createZodDto( + z.object({ + newUsername: z.string(), + password: z.string(), + }), +) {} + +// Change password +export class ChangePasswordBody extends createZodDto( + z.object({ + currentPassword: z.string(), + newPassword: z.string(), + }), +) {} + +// TOTP +export class GetTotpUriBody extends createZodDto( + z.object({ + password: z.string(), + }), +) {} + +export class GetTotpUriDto extends createZodDto( + z.object({ + key: z.string(), + uri: z.string(), + }), +) {} + +export class SetupTotpBody extends createZodDto( + z.object({ + code: z.string(), + }), +) {} + +export class DisableTotpBody extends createZodDto( + z.object({ + password: z.string(), + }), +) {} + +// Reset password +export class ResetPasswordBody extends createZodDto( + z.object({ + newPassword: z.string(), + }), +) {} + +export class ResetPasswordDto extends createZodDto( + z.object({ + success: z.boolean(), + email: z.string(), + }), +) {} + +export class CheckResetPasswordRequestDto extends createZodDto( + z.object({ + isRequestPending: z.boolean(), + }), +) {} diff --git a/packages/backend/src/modules/auth/session.manager.ts b/packages/backend/src/modules/auth/session.manager.ts new file mode 100644 index 0000000000..c490ee32b8 --- /dev/null +++ b/packages/backend/src/modules/auth/session.manager.ts @@ -0,0 +1,59 @@ +import { CacheService } from '@/core/cache/cache.service'; +import { Injectable } from '@nestjs/common'; +import { v4 } from 'uuid'; + +@Injectable() +export class SessionManager { + private COOKIE_MAX_AGE = 60 * 60 * 24; // 1 day + + constructor(private cache: CacheService) {} + + /** + * Create a new session for the given user. + * @param userId - The ID of the user to create a session for. + * @returns The session ID. + */ + public async createSession(userId: number) { + const sessionId = v4(); + const sessionKey = `session:${sessionId}`; + + await this.cache.set(sessionKey, userId.toString(), this.COOKIE_MAX_AGE * 7); + await this.cache.set(`session:${userId}:${sessionId}`, sessionKey, this.COOKIE_MAX_AGE * 7); + + return sessionId; + } + + public generateSalt() { + return v4(); + } + + /** + * Delete a session by its ID. + * @param sessionId - The ID of the session to delete. + */ + public async deleteSession(sessionId: string) { + const sessionKey = `session:${sessionId}`; + const userId = await this.cache.get(sessionKey); + + await this.cache.del(sessionKey); + if (userId) { + await this.cache.del(`session:${userId}:${sessionId}`); + } + } + + /** + * Given a user ID, destroy all sessions for that user + * + * @param {number} userId - The user ID + */ + public destroyAllSessionsByUserId = async (userId: number) => { + const sessions = await this.cache.getByPrefix(`session:${userId}:`); + + await Promise.all( + sessions.map(async (session) => { + await this.cache.del(session.key); + if (session.val) await this.cache.del(session.val); + }), + ); + }; +} diff --git a/packages/backend/src/modules/auth/utils/totp-authenticator.ts b/packages/backend/src/modules/auth/utils/totp-authenticator.ts new file mode 100644 index 0000000000..b6f092b62b --- /dev/null +++ b/packages/backend/src/modules/auth/utils/totp-authenticator.ts @@ -0,0 +1,5 @@ +import { Authenticator } from '@otplib/core'; +import { createDigest, createRandomBytes } from '@otplib/plugin-crypto'; +import { keyDecoder, keyEncoder } from '@otplib/plugin-thirty-two'; + +export const TotpAuthenticator = new Authenticator({ createDigest, createRandomBytes, keyEncoder, keyDecoder }); diff --git a/packages/backend/src/modules/backups/backup.manager.ts b/packages/backend/src/modules/backups/backup.manager.ts new file mode 100644 index 0000000000..2238adbe70 --- /dev/null +++ b/packages/backend/src/modules/backups/backup.manager.ts @@ -0,0 +1,150 @@ +import path from 'node:path'; +import { ArchiveService } from '@/core/archive/archive.service'; +import { ConfigurationService } from '@/core/config/configuration.service'; +import { FilesystemService } from '@/core/filesystem/filesystem.service'; +import { LoggerService } from '@/core/logger/logger.service'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BackupManager { + constructor( + private readonly archiveManager: ArchiveService, + private readonly logger: LoggerService, + private readonly config: ConfigurationService, + private readonly filesystem: FilesystemService, + ) {} + + public backupApp = async (appId: string) => { + const { dataDir, appDataDir } = this.config.get('directories'); + const backupName = `${appId}-${new Date().getTime()}`; + const backupDir = path.join(dataDir, 'backups', appId); + + const tempDir = await this.filesystem.createTempDirectory(appId); + + if (!tempDir) { + throw new Error('Failed to create temp directory'); + } + + this.logger.info('Copying files to backup location...'); + + // Ensure backup directory exists + await this.filesystem.createDirectory(tempDir); + + // Move app data and app directories + await this.filesystem.copyDirectory(path.join(appDataDir, appId), path.join(tempDir, 'app-data'), { + recursive: true, + filter: (src) => !src.includes('backups'), + }); + + await this.filesystem.copyDirectory(path.join(dataDir, 'apps', appId), path.join(tempDir, 'app')); + + this.logger.info('Creating archive...'); + + // Create the archive + const { stdout, stderr } = await this.archiveManager.createTarGz(tempDir, `${path.join(tempDir, backupName)}.tar.gz`); + this.logger.debug('--- archiveManager.createTarGz ---'); + this.logger.debug('stderr:', stderr); + this.logger.debug('stdout:', stdout); + + this.logger.info('Moving archive to backup directory...'); + + // Move the archive to the backup directory + await this.filesystem.createDirectory(backupDir); + await this.filesystem.copyFile(`${path.join(tempDir, backupName)}.tar.gz`, path.join(backupDir, `${backupName}.tar.gz`)); + + // Remove the temp backup folder + await this.filesystem.removeDirectory(tempDir); + + this.logger.info('Backup completed!'); + }; + + public restoreApp = async (appId: string, filename: string) => { + const { dataDir, appDataDir } = this.config.get('directories'); + const restoreDir = await this.filesystem.createTempDirectory(appId); + + if (!restoreDir) { + throw new Error('Failed to create temp directory'); + } + + const archive = path.join(dataDir, 'backups', appId, filename); + + this.logger.info('Restoring app from backup...'); + + // Verify the app has a backup + if (!(await this.filesystem.pathExists(archive))) { + throw new Error('The backup file does not exist'); + } + + // Unzip the archive + await this.filesystem.createDirectory(restoreDir); + + this.logger.info('Extracting archive...'); + const { stderr, stdout } = await this.archiveManager.extractTarGz(archive, restoreDir); + this.logger.debug('--- archiveManager.extractTarGz ---'); + this.logger.debug('stderr:', stderr); + this.logger.debug('stdout:', stdout); + + const appDataDirPath = path.join(appDataDir, appId); + const appDirPath = path.join(dataDir, 'apps', appId); + + // Remove old data directories + await this.filesystem.removeDirectory(appDataDirPath); + await this.filesystem.removeDirectory(appDirPath); + + await this.filesystem.createDirectory(appDataDirPath); + await this.filesystem.createDirectory(appDirPath); + + // Copy data from the backup folder + await this.filesystem.copyDirectory(path.join(restoreDir, 'app-data'), appDataDirPath); + await this.filesystem.copyDirectory(path.join(restoreDir, 'app'), appDirPath); + + // Delete restore folder + await this.filesystem.removeDirectory(restoreDir); + }; + + /** + * Delete a backup file + * @param appId - The app id + * @param filename - The filename of the backup + */ + public async deleteBackup(appId: string, filename: string) { + const { dataDir } = this.config.get('directories'); + + const backupPath = path.join(dataDir, 'backups', appId, filename); + + if (await this.filesystem.pathExists(backupPath)) { + await this.filesystem.removeFile(backupPath); + } + } + + /** + * List the backups for an app + * @param appId - The app id + * @returns The list of backups + */ + public async listBackupsByAppId(appId: string) { + const { dataDir } = this.config.get('directories'); + + const backupsDir = path.join(dataDir, 'backups', appId); + + if (!(await this.filesystem.pathExists(backupsDir))) { + return []; + } + + try { + const list = await this.filesystem.listFiles(backupsDir); + + const backups = await Promise.all( + list.map(async (backup) => { + const stats = await this.filesystem.getStats(path.join(backupsDir, backup)); + return { id: backup, size: stats.size, date: stats.mtime.getTime() }; + }), + ); + + return backups; + } catch (error) { + this.logger.error(`Error listing backups for app ${appId}: ${error}`); + return []; + } + } +} diff --git a/packages/backend/src/modules/backups/backups.controller.ts b/packages/backend/src/modules/backups/backups.controller.ts new file mode 100644 index 0000000000..fef8fcea35 --- /dev/null +++ b/packages/backend/src/modules/backups/backups.controller.ts @@ -0,0 +1,38 @@ +import { Body, Controller, Delete, Get, Injectable, Param, Post, Query, UseGuards } from '@nestjs/common'; +import { ApiQuery } from '@nestjs/swagger'; +import { ZodSerializerDto } from 'nestjs-zod'; +import { AuthGuard } from '../auth/auth.guard'; +import { BackupsService } from './backups.service'; +import { DeleteAppBackupBodyDto, GetAppBackupsDto, GetAppBackupsQueryDto, RestoreAppBackupDto } from './dto/backups.dto'; + +@Injectable() +@UseGuards(AuthGuard) +@Controller('backups') +export class BackupsController { + constructor(private readonly backupsService: BackupsService) {} + + @Post(':appid/backup') + async backupApp(@Param('appid') appId: string) { + return this.backupsService.backupApp({ appId }); + } + + @Post(':appid/restore') + async restoreAppBackup(@Param('appid') id: string, @Body() body: RestoreAppBackupDto) { + return this.backupsService.restoreApp({ appId: id, filename: body.filename }); + } + + @Get(':id') + @ApiQuery({ name: 'pageSize', type: Number, required: false }) + @ApiQuery({ name: 'page', type: Number, required: false }) + @ZodSerializerDto(GetAppBackupsDto) + async getAppBackups(@Param('id') id: string, @Query() query: GetAppBackupsQueryDto): Promise { + const backups = await this.backupsService.getAppBackups({ appId: id, page: query.page ?? 0, pageSize: query.pageSize ?? 10 }); + + return backups; + } + + @Delete(':appid') + async deleteAppBackup(@Param('appid') appid: string, @Body() body: DeleteAppBackupBodyDto) { + return this.backupsService.deleteAppBackup({ appId: appid, filename: body.filename }); + } +} diff --git a/packages/backend/src/modules/backups/backups.module.ts b/packages/backend/src/modules/backups/backups.module.ts new file mode 100644 index 0000000000..2ed6b680bb --- /dev/null +++ b/packages/backend/src/modules/backups/backups.module.ts @@ -0,0 +1,17 @@ +import { ArchiveModule } from '@/core/archive/archive.module'; +import { SocketModule } from '@/core/socket/socket.module'; +import { Module, forwardRef } from '@nestjs/common'; +import { AppLifecycleModule } from '../app-lifecycle/app-lifecycle.module'; +import { AppsModule } from '../apps/apps.module'; +import { QueueModule } from '../queue/queue.module'; +import { BackupManager } from './backup.manager'; +import { BackupsController } from './backups.controller'; +import { BackupsService } from './backups.service'; + +@Module({ + imports: [forwardRef(() => AppLifecycleModule), AppsModule, QueueModule, SocketModule, ArchiveModule], + controllers: [BackupsController], + providers: [BackupsService, BackupManager], + exports: [BackupsService, BackupManager], +}) +export class BackupsModule {} diff --git a/packages/backend/src/modules/backups/backups.service.ts b/packages/backend/src/modules/backups/backups.service.ts new file mode 100644 index 0000000000..94c2ed73b8 --- /dev/null +++ b/packages/backend/src/modules/backups/backups.service.ts @@ -0,0 +1,110 @@ +import { TranslatableError } from '@/common/error/translatable-error'; +import { ConfigurationService } from '@/core/config/configuration.service'; +import { LoggerService } from '@/core/logger/logger.service'; +import { SocketManager } from '@/core/socket/socket.service'; +import { Injectable } from '@nestjs/common'; +import { AppLifecycleService } from '../app-lifecycle/app-lifecycle.service'; +import { AppsRepository } from '../apps/apps.repository'; +import { AppEventsQueue } from '../queue/entities/app-events'; +import { BackupManager } from './backup.manager'; + +@Injectable() +export class BackupsService { + constructor( + private appsRepository: AppsRepository, + private logger: LoggerService, + private socketManager: SocketManager, + private config: ConfigurationService, + private appEventsQueue: AppEventsQueue, + private appLifecycle: AppLifecycleService, + private backupManager: BackupManager, + ) {} + + public async backupApp(params: { appId: string }) { + if (this.config.get('demoMode')) { + throw new TranslatableError('SERVER_ERROR_NOT_ALLOWED_IN_DEMO'); + } + + const { appId } = params; + const app = await this.appsRepository.getApp(appId); + + if (!app) { + throw new TranslatableError('APP_ERROR_APP_NOT_FOUND', { id: appId }); + } + + const appStatusBeforeUpdate = app.status; + + // Run script + await this.appsRepository.updateApp(appId, { status: 'backing_up' }); + this.socketManager.emit({ type: 'app', event: 'status_change', data: { appId, appStatus: 'starting' } }); + + this.appEventsQueue.publishAsync({ appid: appId, command: 'backup', form: app.config }, 1000 * 60 * 15).then(async ({ success, message }) => { + if (success) { + if (appStatusBeforeUpdate === 'running') { + await this.appLifecycle.startApp({ appId }); + } else { + await this.appsRepository.updateApp(appId, { status: appStatusBeforeUpdate }); + this.socketManager.emit({ type: 'app', event: 'backup_success', data: { appId, appStatus: 'stopped' } }); + } + } else { + this.logger.error(`Failed to backup app ${appId}: ${message}`); + await this.appsRepository.updateApp(appId, { status: 'stopped' }); + } + }); + } + + public async restoreApp(params: { appId: string; filename: string }) { + const { appId, filename } = params; + const app = await this.appsRepository.getApp(appId); + + if (!app) { + throw new TranslatableError('APP_ERROR_APP_NOT_FOUND', { id: appId }); + } + + const appStatusBeforeUpdate = app.status; + + // Run script + await this.appsRepository.updateApp(appId, { status: 'restoring' }); + await this.socketManager.emit({ type: 'app', event: 'status_change', data: { appId, appStatus: 'restoring' } }); + + this.appEventsQueue + .publishAsync({ appid: appId, command: 'restore', filename, form: app.config }, 1000 * 60 * 15) + .then(async ({ success, message }) => { + if (success) { + if (appStatusBeforeUpdate === 'running') { + await this.appLifecycle.startApp({ appId }); + } else { + await this.appsRepository.updateApp(appId, { status: appStatusBeforeUpdate }); + this.socketManager.emit({ type: 'app', event: 'restore_success', data: { appId, appStatus: 'stopped' } }); + } + } else { + this.logger.error(`Failed to restore app ${appId}: ${message}`); + await this.appsRepository.updateApp(appId, { status: 'stopped' }); + } + }); + } + + public async getAppBackups(params: { appId: string; page: number; pageSize: number }) { + const { appId, page, pageSize } = params; + const backups = await this.backupManager.listBackupsByAppId(appId); + + backups.sort((a, b) => b.date - a.date); + + const start = (page - 1) * pageSize; + const end = start + pageSize; + const data = backups.slice(start, end); + + return { + data, + total: backups.length, + currentPage: Math.floor(start / pageSize) + 1, + lastPage: Math.ceil(backups.length / pageSize), + }; + } + + public async deleteAppBackup(params: { appId: string; filename: string }): Promise { + const { appId, filename } = params; + + await this.backupManager.deleteBackup(appId, filename); + } +} diff --git a/packages/backend/src/modules/backups/dto/backups.dto.ts b/packages/backend/src/modules/backups/dto/backups.dto.ts new file mode 100644 index 0000000000..34c4232071 --- /dev/null +++ b/packages/backend/src/modules/backups/dto/backups.dto.ts @@ -0,0 +1,38 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod'; + +export class BackupDto extends createZodDto( + z.object({ + id: z.string(), + size: z.number(), + date: z.number(), + }), +) {} + +export class RestoreAppBackupDto extends createZodDto( + z.object({ + filename: z.string(), + }), +) {} + +export class GetAppBackupsDto extends createZodDto( + z.object({ + data: BackupDto.schema.array(), + total: z.number(), + currentPage: z.number(), + lastPage: z.number(), + }), +) {} + +export class GetAppBackupsQueryDto extends createZodDto( + z.object({ + page: z.coerce.number().optional(), + pageSize: z.coerce.number().optional(), + }), +) {} + +export class DeleteAppBackupBodyDto extends createZodDto( + z.object({ + filename: z.string(), + }), +) {} diff --git a/packages/backend/src/modules/docker/builders/compose.builder.ts b/packages/backend/src/modules/docker/builders/compose.builder.ts new file mode 100644 index 0000000000..1d7a4c44bd --- /dev/null +++ b/packages/backend/src/modules/docker/builders/compose.builder.ts @@ -0,0 +1,40 @@ +import * as yaml from 'yaml'; +import type { BuiltService } from './service.builder'; + +interface Network { + key?: string; + name: string; + external: boolean; +} + +export class DockerComposeBuilder { + private services: Record = {}; + private networks: Record = {}; + + addService(service: BuiltService) { + this.services[service.container_name] = service; + return this; + } + + addServices(services: BuiltService[]) { + for (const service of services) { + this.addService(service); + } + return this; + } + + addNetwork(network: Network) { + this.networks[network.key || network.name] = { + name: network.name, + external: network.external, + }; + return this; + } + + build() { + return yaml.stringify({ + services: this.services, + networks: this.networks, + }); + } +} diff --git a/packages/backend/src/modules/docker/builders/schemas.ts b/packages/backend/src/modules/docker/builders/schemas.ts new file mode 100644 index 0000000000..71d3fca482 --- /dev/null +++ b/packages/backend/src/modules/docker/builders/schemas.ts @@ -0,0 +1,67 @@ +import { z } from 'zod'; + +const dependsOnSchema = z.union([ + z.array(z.string()), + z.record( + z.string(), + z.object({ + condition: z.enum(['service_healthy', 'service_started', 'service_completed_successfully']), + }), + ), +]); + +const ulimitsSchema = z.object({ + nproc: z.number().or(z.object({ soft: z.number(), hard: z.number() })), + nofile: z.number().or(z.object({ soft: z.number(), hard: z.number() })), +}); + +export const serviceSchema = z.object({ + image: z.string(), + name: z.string(), + internalPort: z.number().optional(), + isMain: z.boolean().optional(), + networkMode: z.string().optional(), + extraHosts: z.array(z.string()).optional(), + ulimits: ulimitsSchema.optional(), + addPorts: z + .array( + z.object({ + containerPort: z.number(), + hostPort: z.number(), + udp: z.boolean().optional(), + tcp: z.boolean().optional(), + interface: z.string().optional(), + }), + ) + .optional(), + command: z.string().optional().or(z.array(z.string()).optional()), + volumes: z + .array( + z.object({ + hostPath: z.string(), + containerPath: z.string(), + readOnly: z.boolean().optional(), + }), + ) + .optional(), + environment: z.record(z.string()).optional(), + healthCheck: z + .object({ + test: z.string(), + interval: z.string().optional(), + timeout: z.string().optional(), + retries: z.number().optional(), + startInterval: z.string().optional(), + startPeriod: z.string().optional(), + }) + .optional(), + dependsOn: dependsOnSchema.optional(), +}); + +export const dynamicComposeSchema = z.object({ + services: serviceSchema.array(), +}); + +export type DependsOn = z.output; +export type ServiceInput = z.input; +export type Service = z.output; diff --git a/packages/backend/src/modules/docker/builders/service.builder.ts b/packages/backend/src/modules/docker/builders/service.builder.ts new file mode 100644 index 0000000000..41ce2d02fb --- /dev/null +++ b/packages/backend/src/modules/docker/builders/service.builder.ts @@ -0,0 +1,384 @@ +import type { z } from 'zod'; +import type { DependsOn, serviceSchema } from './schemas'; + +interface ServicePort { + containerPort: number; + hostPort: number | string; + tcp?: boolean; + udp?: boolean; + interface?: string; +} + +interface ServiceVolume { + hostPath: string; + containerPath: string; + readOnly?: boolean; +} + +interface HealthCheck { + test: string; + interval?: string; + timeout?: string; + retries?: number; + start_interval?: string; + start_period?: string; +} + +interface Ulimits { + nproc: number | { soft: number; hard: number }; + nofile: number | { soft: number; hard: number }; +} + +export interface BuilderService { + image: string; + containerName: string; + restart: 'always' | 'unless-stopped' | 'on-failure'; + environment?: Record; + command?: string | string[]; + volumes?: string[]; + ports?: string[]; + healthCheck?: HealthCheck; + labels?: Record; + dependsOn?: DependsOn; + networks?: string[]; + networkMode?: string; + extraHosts?: string[]; + ulimits?: Ulimits; +} + +export type BuiltService = ReturnType; + +/** + * This class is a builder for the service object in the docker-compose file. + */ +export class ServiceBuilder { + private service: Partial = {}; + + /** + * Sets the image for the service. + * @param image The image to use for the service. + * + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.setImage('nginx:latest'); + * ``` + */ + setImage(image: string) { + this.service.image = image; + return this; + } + + /** + * Sets the name of the container. Will be used as the `container_name` property as well as the key + * @param name The name of the container. + * + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.setName('nginx-container'); + * ``` + */ + setName(name: string) { + this.service.containerName = name; + return this; + } + + /** + * Sets the restart policy for the service. + * @param {string} policy The restart policy for the service. Can be one of ['always', 'unless-stopped', 'on-failure'] + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.setRestartPolicy('always'); + * ``` + */ + setRestartPolicy(policy: 'always' | 'unless-stopped' | 'on-failure') { + this.service.restart = policy; + return this; + } + + /** + * Adds a network to the service. + * @param {string} network The network to add to the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.addNetwork('tipi_main_network'); + * ``` + */ + addNetwork(network: string) { + this.service.networks = [network]; + return this; + } + + /** + * Adds a port to the service. + * + * @param {ServicePort} port The port to add to the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.addPort({ containerPort: 80, hostPort: 8080 }); + */ + addPort(port?: ServicePort) { + if (!port) { + return this; + } + + if (!this.service.ports) { + this.service.ports = []; + } + + let port_string = `${port.hostPort}:${port.containerPort}`; + + if (port.interface) { + port_string = `${port.interface}:${port_string}`; + } + + if (!port.tcp && !port.udp) { + this.service.ports.push(port_string); + return this; + } + + if (port.tcp) { + this.service.ports.push(`${port_string}/tcp`); + } + + if (port.udp) { + this.service.ports.push(`${port_string}/udp`); + } + + return this; + } + + /** + * Adds multiple ports to the service. + * + * @param {ServicePort[]} ports The ports to add to the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.addPorts([ + * { containerPort: 80, hostPort: 8080 }, + * { containerPort: 443, hostPort: 8443, tcp: true }, + * ]); + */ + addPorts(ports?: ServicePort[]) { + if (ports) { + for (const port of ports) { + this.addPort(port); + } + } + return this; + } + + /** + * Adds a volume to the service. + * @param {ServiceVolume} volume The volume to add to the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.addVolume({ hostPath: '/path/to/host', containerPath: '/path/to/container' }); + * ``` + */ + addVolume(volume: ServiceVolume) { + if (!this.service.volumes) { + this.service.volumes = []; + } + + const readOnly = volume.readOnly ? ':ro' : ''; + this.service.volumes.push(`${volume.hostPath}:${volume.containerPath}${readOnly}`); + return this; + } + + /** + * Adds multiple volumes to the service. + * @param {ServiceVolume[]} volumes The volumes to add to the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.addVolumes([ + * { hostPath: '/path/to/host', containerPath: '/path/to/container' }, + * { hostPath: '/path/to/host2', containerPath: '/path/to/container2', readOnly: true }, + * ]); + * ``` + */ + addVolumes(volumes?: ServiceVolume[]) { + if (volumes && volumes.length > 0) { + for (const volume of volumes) { + this.addVolume(volume); + } + } + return this; + } + + /** + * Sets the environment variables for the service. + * @param {Record} environment The environment variables to set for the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.setEnvironment({ key: 'value' }); + * ``` + */ + setEnvironment(environment?: Record) { + if (environment) { + this.service.environment = { ...this.service.environment, ...environment }; + } + return this; + } + + /** + * Sets the command for the service. + * @param {string} command The command to run in the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.setCommand('npm run start'); + * ``` + */ + setCommand(command?: string | string[]) { + if (command) { + this.service.command = command; + } + + return this; + } + + /** + * Sets the health check for the service. + * @param {HealthCheck} healthCheck The health check to set for the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.setHealthCheck({ + * test: 'curl --fail http://localhost:3000 || exit 1', + * retries: 3, + * interval: '30s', + * timeout: '10s', + * }); + * ``` + */ + setHealthCheck(healthCheck?: z.infer['healthCheck']) { + if (healthCheck) { + this.service.healthCheck = { + test: healthCheck.test, + interval: healthCheck.interval, + timeout: healthCheck.timeout, + retries: healthCheck.retries, + start_interval: healthCheck.startInterval, + start_period: healthCheck.startPeriod, + }; + } + + return this; + } + + /** + * Sets the labels for the service. + * @param {Record} labels The labels to set for the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.setLabels({ key: 'value' }); + * ``` + */ + setLabels(labels: Record) { + if (labels) { + this.service.labels = { ...this.service.labels, ...labels }; + } + return this; + } + + /** + * Sets the depends on for the service. + * @param {DependsOn} dependsOn The depends on to set for the service. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.setDependsOn({ + * serviceName: { + * condition: 'service_healthy', + * } + * }); + * ``` + */ + setDependsOn(dependsOn?: DependsOn) { + if (!dependsOn) { + return this; + } + + this.service.dependsOn = dependsOn; + return this; + } + + setNetworkMode(networkMode?: string) { + if (!networkMode) { + return this; + } + + this.service.networkMode = networkMode; + + return this; + } + + addExtraHosts(extraHosts?: string[]) { + if (!extraHosts) { + return this; + } + + this.service.extraHosts = extraHosts; + return this; + } + + addUlimits(ulimits?: Ulimits) { + if (!ulimits) { + return this; + } + + this.service.ulimits = ulimits; + return this; + } + + /** + * Builds the service object. + * @returns The built service object. + * @example + * ```typescript + * const service = new ServiceBuilder(); + * service.setImage('nginx:latest') + * .setName('nginx-container') + * .setRestartPolicy('always') + * .addNetwork('tipi_main_network') + * .build(); + * ``` + */ + build() { + if (!this.service.containerName || !this.service.image) { + throw new Error('Service name and image are required'); + } + + if (this.service.networkMode) { + this.service.ports = undefined; + this.service.networks = undefined; + } + + return { + image: this.service.image, + command: this.service.command, + container_name: this.service.containerName, + restart: this.service.restart, + networks: this.service.networks, + network_mode: this.service.networkMode, + extra_hosts: this.service.extraHosts, + ulimits: this.service.ulimits, + healthcheck: this.service.healthCheck, + environment: this.service.environment, + ports: this.service.ports, + volumes: this.service.volumes, + depends_on: this.service.dependsOn, + labels: this.service.labels, + }; + } +} diff --git a/packages/backend/src/modules/docker/builders/traefik-labels.builder.ts b/packages/backend/src/modules/docker/builders/traefik-labels.builder.ts new file mode 100644 index 0000000000..322dde32e1 --- /dev/null +++ b/packages/backend/src/modules/docker/builders/traefik-labels.builder.ts @@ -0,0 +1,55 @@ +interface TraefikLabelsArgs { + internalPort: number; + appId: string; + exposedLocal?: boolean; + exposed?: boolean; +} + +export class TraefikLabelsBuilder { + private labels: Record = {}; + + constructor(private params: TraefikLabelsArgs) { + this.labels = { + generated: true, + 'traefik.enable': false, + [`traefik.http.middlewares.${params.appId}-web-redirect.redirectscheme.scheme`]: 'https', + [`traefik.http.services.${params.appId}.loadbalancer.server.port`]: `${params.internalPort}`, + }; + } + + addExposedLabels() { + if (this.params.exposed) { + Object.assign(this.labels, { + 'traefik.enable': true, + [`traefik.http.routers.${this.params.appId}-insecure.rule`]: 'Host(`${APP_DOMAIN}`)', + [`traefik.http.routers.${this.params.appId}-insecure.service`]: this.params.appId, + [`traefik.http.routers.${this.params.appId}-insecure.middlewares`]: `${this.params.appId}-web-redirect`, + [`traefik.http.routers.${this.params.appId}.rule`]: 'Host(`${APP_DOMAIN}`)', + [`traefik.http.routers.${this.params.appId}.entrypoints`]: 'websecure', + [`traefik.http.routers.${this.params.appId}.tls.certresolver`]: 'myresolver', + }); + } + return this; + } + + addExposedLocalLabels() { + if (this.params.exposedLocal) { + Object.assign(this.labels, { + 'traefik.enable': true, + [`traefik.http.routers.${this.params.appId}-local-insecure.rule`]: `Host(\`${this.params.appId}.\${LOCAL_DOMAIN}\`)`, + [`traefik.http.routers.${this.params.appId}-local-insecure.entrypoints`]: 'web', + [`traefik.http.routers.${this.params.appId}-local-insecure.service`]: this.params.appId, + [`traefik.http.routers.${this.params.appId}-local-insecure.middlewares`]: `${this.params.appId}-web-redirect`, + [`traefik.http.routers.${this.params.appId}-local.rule`]: `Host(\`${this.params.appId}.\${LOCAL_DOMAIN}\`)`, + [`traefik.http.routers.${this.params.appId}-local.entrypoints`]: 'websecure', + [`traefik.http.routers.${this.params.appId}-local.service`]: this.params.appId, + [`traefik.http.routers.${this.params.appId}-local.tls`]: true, + }); + } + return this; + } + + build() { + return this.labels; + } +} diff --git a/packages/backend/src/modules/docker/commands/app-logs-init.ts b/packages/backend/src/modules/docker/commands/app-logs-init.ts new file mode 100644 index 0000000000..ac9df22c21 --- /dev/null +++ b/packages/backend/src/modules/docker/commands/app-logs-init.ts @@ -0,0 +1,60 @@ +import { spawn } from 'node:child_process'; +import type { Socket } from 'socket.io'; +import { type SocketEvent, socketEventSchema } from '../../../core/socket/socket-schemas'; +import type { DockerService } from '../docker.service'; +import { colorizeLogs } from '../helpers/colorize-logs'; +import type { DockerCommand } from './type'; + +export class AppLogsCommand implements DockerCommand { + constructor(private readonly dockerService: DockerService) {} + + async execute(socket: Socket, event: SocketEvent, emit: (event: SocketEvent) => Promise) { + const parsedEvent = socketEventSchema.safeParse(event); + + if (!parsedEvent.success) { + return; + } + + if (parsedEvent.data.type !== 'app-logs-init') { + return; + } + + const { appId, maxLines } = parsedEvent.data.data; + const { args } = await this.dockerService.getBaseComposeArgsApp(appId); + + args.push(`logs --follow -n ${maxLines || 25}`); + + const logsCommand = `docker-compose ${args.join(' ')}`; + + const logs = spawn('sh', ['-c', logsCommand]); + + socket.on('disconnect', () => { + logs.kill('SIGINT'); + }); + + socket.on('app-logs', (data) => { + if (data.event === 'stopLogs') { + logs.kill('SIGINT'); + } + }); + + logs.on('error', () => { + logs.kill('SIGINT'); + }); + + logs.stdout.on('data', async (data) => { + const lines = await colorizeLogs( + data + .toString() + .split(/(?:\r\n|\r|\n)/g) + .filter(Boolean), + ); + + await emit({ + type: 'app-logs', + event: 'newLogs', + data: { lines, appId }, + }); + }); + } +} diff --git a/packages/backend/src/modules/docker/commands/runtipi-logs-init.ts b/packages/backend/src/modules/docker/commands/runtipi-logs-init.ts new file mode 100644 index 0000000000..ce7c523ed1 --- /dev/null +++ b/packages/backend/src/modules/docker/commands/runtipi-logs-init.ts @@ -0,0 +1,61 @@ +import { spawn } from 'node:child_process'; +import type { Socket } from 'socket.io'; +import { type SocketEvent, socketEventSchema } from '../../../core/socket/socket-schemas'; +import type { DockerService } from '../docker.service'; +import { colorizeLogs } from '../helpers/colorize-logs'; +import type { DockerCommand } from './type'; + +export class RuntipiLogsCommand implements DockerCommand { + constructor(private readonly dockerService: DockerService) {} + + async execute(socket: Socket, event: SocketEvent, emit: (event: SocketEvent) => Promise) { + const { success, data } = socketEventSchema.safeParse(event); + + if (!success) { + return; + } + + if (data.type !== 'runtipi-logs-init') { + return; + } + + const { maxLines } = data.data; + + const args = await this.dockerService.getBaseComposeArgsRuntipi(); + + args.push(`logs --follow -n ${maxLines || 25}`); + + const logsCommand = `docker-compose ${args.join(' ')}`; + + const logs = spawn('sh', ['-c', logsCommand]); + + socket.on('disconnect', () => { + logs.kill('SIGINT'); + }); + + socket.on('runtipi-logs', (data) => { + if (data.event === 'stopLogs') { + logs.kill('SIGINT'); + } + }); + + logs.on('error', () => { + logs.kill('SIGINT'); + }); + + logs.stdout.on('data', async (data) => { + const lines = await colorizeLogs( + data + .toString() + .split(/(?:\r\n|\r|\n)/g) + .filter(Boolean), + ); + + await emit({ + type: 'runtipi-logs', + event: 'newLogs', + data: { lines }, + }); + }); + } +} diff --git a/packages/backend/src/modules/docker/commands/type.ts b/packages/backend/src/modules/docker/commands/type.ts new file mode 100644 index 0000000000..40e70f9b86 --- /dev/null +++ b/packages/backend/src/modules/docker/commands/type.ts @@ -0,0 +1,6 @@ +import type { Socket } from 'socket.io'; +import { type SocketEvent } from '../../../core/socket/socket-schemas'; + +export interface DockerCommand { + execute(Socket: Socket, event: SocketEvent, emit: (event: SocketEvent) => Promise): Promise; +} diff --git a/packages/backend/src/modules/docker/docker-command.factory.ts b/packages/backend/src/modules/docker/docker-command.factory.ts new file mode 100644 index 0000000000..3b572c9f4d --- /dev/null +++ b/packages/backend/src/modules/docker/docker-command.factory.ts @@ -0,0 +1,19 @@ +import { AppLogsCommand } from './commands/app-logs-init'; +import { RuntipiLogsCommand } from './commands/runtipi-logs-init'; +import type { DockerCommand } from './commands/type'; +import type { DockerService } from './docker.service'; + +export class DockerCommandFactory { + constructor(private readonly dockerService: DockerService) {} + + createCommand(event: string): DockerCommand | null { + switch (event) { + case 'app-logs-init': + return new AppLogsCommand(this.dockerService); + case 'runtipi-logs-init': + return new RuntipiLogsCommand(this.dockerService); + default: + return null; + } + } +} diff --git a/packages/backend/src/modules/docker/docker.module.ts b/packages/backend/src/modules/docker/docker.module.ts new file mode 100644 index 0000000000..adc4511886 --- /dev/null +++ b/packages/backend/src/modules/docker/docker.module.ts @@ -0,0 +1,12 @@ +import { SocketModule } from '@/core/socket/socket.module'; +import { Module } from '@nestjs/common'; +import { AppsModule } from '../apps/apps.module'; +import { ReposModule } from '../repos/repos.module'; +import { DockerService } from './docker.service'; + +@Module({ + imports: [AppsModule, ReposModule, SocketModule], + providers: [DockerService], + exports: [DockerService], +}) +export class DockerModule {} diff --git a/packages/backend/src/modules/docker/docker.service.ts b/packages/backend/src/modules/docker/docker.service.ts new file mode 100644 index 0000000000..4505e25d4d --- /dev/null +++ b/packages/backend/src/modules/docker/docker.service.ts @@ -0,0 +1,178 @@ +import path from 'node:path'; +import { DEFAULT_REPO_URL } from '@/common/helpers/env-helpers'; +import { execAsync } from '@/common/helpers/exec-helpers'; +import { ConfigurationService } from '@/core/config/configuration.service'; +import { FilesystemService } from '@/core/filesystem/filesystem.service'; +import { LoggerService } from '@/core/logger/logger.service'; +import { SocketManager } from '@/core/socket/socket.service'; +import { Injectable } from '@nestjs/common'; +import { AppFilesManager } from '../apps/app-files-manager'; +import type { AppEventFormInput } from '../queue/entities/app-events'; +import { ReposService } from '../repos/repos.service'; +import { DockerComposeBuilder } from './builders/compose.builder'; +import { type Service, type ServiceInput, serviceSchema } from './builders/schemas'; +import { ServiceBuilder } from './builders/service.builder'; +import { TraefikLabelsBuilder } from './builders/traefik-labels.builder'; +import { DockerCommandFactory } from './docker-command.factory'; + +@Injectable() +export class DockerService { + dockerCommandFactory: DockerCommandFactory; + + constructor( + private readonly logger: LoggerService, + private readonly config: ConfigurationService, + private readonly appFilesManager: AppFilesManager, + private readonly repoService: ReposService, + private readonly socketManager: SocketManager, + private readonly filesystem: FilesystemService, + ) { + this.dockerCommandFactory = new DockerCommandFactory(this); + + const io = this.socketManager.init(); + + io.on('connection', async (socket) => { + socket.onAny((event, body) => { + const command = this.dockerCommandFactory.createCommand(event); + + if (command) { + command.execute(socket, body, this.socketManager.emit.bind(this.socketManager)); + } + }); + }); + } + + /** + * Get the base compose args for an app + * @param {string} appId - App name + */ + public getBaseComposeArgsApp = async (appId: string) => { + const { appsRepoId, directories } = this.config.getConfig(); + + let isCustomConfig = appsRepoId !== this.repoService.getRepoHash(DEFAULT_REPO_URL); + + const appEnv = await this.appFilesManager.getAppEnv(appId); + const args: string[] = [`--env-file ${appEnv.path}`]; + + // User custom env file + const userEnvFile = await this.appFilesManager.getUserEnv(appId); + if (userEnvFile.content) { + args.push(`--env-file ${userEnvFile.path}`); + } + + args.push(`--project-name ${appId}`); + + const composeFile = await this.appFilesManager.getDockerComposeYaml(appId); + args.push(`-f ${composeFile.path}`); + + const commonComposeFile = path.join(directories.dataDir, 'repos', appsRepoId, 'apps', 'docker-compose.common.yml'); + args.push(`-f ${commonComposeFile}`); + + // User defined overrides + const userComposeFile = await this.appFilesManager.getUserComposeFile(appId); + if (userComposeFile.content) { + isCustomConfig = true; + args.push(`--file ${userComposeFile.path}`); + } + + return { args, isCustomConfig }; + }; + + public getBaseComposeArgsRuntipi = async () => { + const { dataDir } = this.config.get('directories'); + const args: string[] = [`--env-file ${path.join(dataDir, '.env')}`]; + + args.push('--project-name runtipi'); + + const composeFile = path.join(dataDir, 'docker-compose.yml'); + args.push(`-f ${composeFile}`); + + // User defined overrides + const userComposeFile = path.join(dataDir, 'user-config', 'tipi-compose.yml'); + if (await this.filesystem.pathExists(userComposeFile)) { + args.push(`--file ${userComposeFile}`); + } + + return args; + }; + + /** + * Helpers to execute docker compose commands + * @param {string} appId - App name + * @param {string} command - Command to execute + */ + public composeApp = async (appId: string, command: string) => { + const { args, isCustomConfig } = await this.getBaseComposeArgsApp(appId); + args.push(command); + + this.logger.info(`Running docker compose with args ${args.join(' ')}`); + const { stdout, stderr } = await execAsync(`docker-compose ${args.join(' ')}`); + + if (stderr?.includes('Command failed:')) { + if (isCustomConfig) { + throw new Error( + `Error with your custom app: ${stderr}. Before opening an issue try to remove any user-config files or any custom app-store repo and try again.`, + ); + } + throw new Error(stderr); + } + + return { stdout, stderr }; + }; + + public getDockerCompose = (services: ServiceInput[], form: AppEventFormInput) => { + const myServices = services.map((service) => this.buildService(serviceSchema.parse(service), form)); + + const dockerCompose = new DockerComposeBuilder().addServices(myServices).addNetwork({ + key: 'tipi_main_network', + name: 'runtipi_tipi_main_network', + external: true, + }); + + return dockerCompose.build(); + }; + + private buildService = (params: Service, form: AppEventFormInput) => { + const service = new ServiceBuilder(); + service + .setImage(params.image) + .setName(params.name) + .setEnvironment(params.environment) + .setCommand(params.command) + .setHealthCheck(params.healthCheck) + .setDependsOn(params.dependsOn) + .addVolumes(params.volumes) + .setRestartPolicy('unless-stopped') + .addExtraHosts(params.extraHosts) + .addUlimits(params.ulimits) + .addPorts(params.addPorts) + .addNetwork('tipi_main_network') + .setNetworkMode(params.networkMode); + + if (params.isMain) { + if (!params.internalPort) { + throw new Error('Main service must have an internal port specified'); + } + + if (form.openPort) { + service.addPort({ + containerPort: params.internalPort, + hostPort: '${APP_PORT}', + }); + } + + const traefikLabels = new TraefikLabelsBuilder({ + internalPort: params.internalPort, + appId: params.name, + exposedLocal: form.exposedLocal, + exposed: form.exposed, + }) + .addExposedLabels() + .addExposedLocalLabels(); + + service.setLabels(traefikLabels.build()); + } + + return service.build(); + }; +} diff --git a/packages/backend/src/modules/docker/helpers/colorize-logs.ts b/packages/backend/src/modules/docker/helpers/colorize-logs.ts new file mode 100644 index 0000000000..c19d744be0 --- /dev/null +++ b/packages/backend/src/modules/docker/helpers/colorize-logs.ts @@ -0,0 +1,14 @@ +import Convert from 'ansi-to-html'; + +const convert = new Convert(); + +export const colorizeLogs = async (lines: string[]) => + await Promise.all( + lines.map(async (line: string) => { + try { + return convert.toHtml(line); + } catch (e) { + return line; + } + }), + ); diff --git a/packages/backend/src/modules/env/env.module.ts b/packages/backend/src/modules/env/env.module.ts new file mode 100644 index 0000000000..db9803ffe2 --- /dev/null +++ b/packages/backend/src/modules/env/env.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { EnvUtils } from './env.utils'; + +@Module({ + imports: [], + providers: [EnvUtils], + exports: [EnvUtils], +}) +export class EnvModule {} diff --git a/packages/backend/src/modules/env/env.utils.ts b/packages/backend/src/modules/env/env.utils.ts new file mode 100644 index 0000000000..74c33d382f --- /dev/null +++ b/packages/backend/src/modules/env/env.utils.ts @@ -0,0 +1,102 @@ +import { Injectable } from '@nestjs/common'; +import crypto from 'node:crypto'; +import os from 'node:os'; +import webpush from 'web-push'; +import fs from 'node:fs'; +import path from 'node:path'; +import { DATA_DIR } from '@/common/constants'; + +type RandomFieldEncoding = 'hex' | 'base64'; + +@Injectable() +export class EnvUtils { + public generateVapidKeys = () => { + const vapidKeys = webpush.generateVAPIDKeys(); + return { + publicKey: vapidKeys.publicKey, + privateKey: vapidKeys.privateKey, + }; + }; + + private getSeed = (): string => { + const seedFilePath = path.join(DATA_DIR, 'state', 'seed'); + if (!fs.existsSync(seedFilePath)) { + throw new Error('Seed file not found'); + } + return fs.readFileSync(seedFilePath, 'utf-8'); + }; + + public getArchitecture = () => { + const arch = os.arch(); + + if (arch === 'arm64') return 'arm64'; + if (arch === 'x64') return 'amd64'; + + throw new Error(`Unsupported architecture: ${arch}`); + }; + + /** + * This function generates a random string of the provided length by using the SHA-256 hash algorithm. + * It takes the provided name and a seed value, concatenates them, and uses them as input for the hash algorithm. + * It then returns a substring of the resulting hash of the provided length. + * + * @param {string} name - A name used as input for the hash algorithm. + * @param {number} length - The desired length of the random string. + */ + public createRandomString = (name: string, length: number, encoding: RandomFieldEncoding = 'hex') => { + const seed = this.getSeed(); + const hash = crypto.createHash('sha256'); + + hash.update(name + seed.toString()); + + if (encoding === 'base64') { + // Generate the hash and slice the buffer to get the exact number of bytes + const randomBytes = hash.digest().slice(0, length); + return randomBytes.toString('base64'); + } + + return hash.digest('hex').substring(0, length); + }; + + /** + * Derives a new entropy value from the provided entropy and the seed + * @param {string} entropy - The entropy value to derive from + */ + public deriveEntropy = (entropy: string): string => { + const seed = this.getSeed(); + const hmac = crypto.createHmac('sha256', seed); + hmac.update(entropy); + return hmac.digest('hex'); + }; + + /** + * Convert a Map of environment variables to a valid string of environment variables + * that can be used in a .env file + * + * @param {Map} envMap - Map of environment variables + */ + public envMapToString = (envMap: Map) => { + const envArray = Array.from(envMap).map(([key, value]) => `${key}=${value}`); + return envArray.join('\n'); + }; + + /** + * Convert a string of environment variables to a Map + * + * @param {string} envString - String of environment variables + */ + public envStringToMap = (envString: string) => { + const envMap = new Map(); + const envArray = envString.split('\n'); + + for (const env of envArray) { + if (env.startsWith('#')) continue; + + const [key, ...rest] = env.split('='); + + if (key && rest.length) envMap.set(key, rest.join('=')); + } + + return envMap; + }; +} diff --git a/packages/backend/src/modules/i18n/i18n.controller.ts b/packages/backend/src/modules/i18n/i18n.controller.ts new file mode 100644 index 0000000000..f7b2bf73ce --- /dev/null +++ b/packages/backend/src/modules/i18n/i18n.controller.ts @@ -0,0 +1,13 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { I18nService } from './i18n.service'; + +@Controller('i18n') +export class I18nController { + constructor(private readonly i18nService: I18nService) {} + + @Get('/locales/:lng/:ns.json') + async getTranslation(@Param('lng') lng: string, @Param('ns') ns: string) { + const translations = await this.i18nService.getTranslation(lng, ns); + return translations || {}; + } +} diff --git a/packages/backend/src/modules/i18n/i18n.module.ts b/packages/backend/src/modules/i18n/i18n.module.ts new file mode 100644 index 0000000000..11ba947146 --- /dev/null +++ b/packages/backend/src/modules/i18n/i18n.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { I18nController } from './i18n.controller'; +import { I18nService } from './i18n.service'; + +@Module({ + imports: [], + controllers: [I18nController], + providers: [I18nService], +}) +export class I18nModule {} diff --git a/packages/backend/src/modules/i18n/i18n.service.ts b/packages/backend/src/modules/i18n/i18n.service.ts new file mode 100644 index 0000000000..8de5308d3d --- /dev/null +++ b/packages/backend/src/modules/i18n/i18n.service.ts @@ -0,0 +1,26 @@ +import path from 'node:path'; +import { Injectable } from '@nestjs/common'; +import i18n from 'i18next'; +import Backend from 'i18next-fs-backend'; +import type { FsBackendOptions } from 'i18next-fs-backend'; + +@Injectable() +export class I18nService { + constructor() { + i18n.use(Backend).init({ + initImmediate: false, + fallbackLng: 'en', + backend: { + loadPath: + process.env.NODE_ENV === 'production' + ? path.join(process.cwd(), 'assets', 'translations', '{{lng}}.json') + : path.join(process.cwd(), 'src', 'modules', 'i18n', 'translations', '{{lng}}.json'), + }, + }); + } + + async getTranslation(language: string, namespace: string) { + // Load translation for specific language and namespace + return i18n.getResourceBundle(language, namespace); + } +} diff --git a/packages/backend/src/modules/i18n/translations/en.json b/packages/backend/src/modules/i18n/translations/en.json new file mode 100644 index 0000000000..d83beb3270 --- /dev/null +++ b/packages/backend/src/modules/i18n/translations/en.json @@ -0,0 +1,344 @@ +{ + "APP_ACTION_CANCEL": "Cancel", + "APP_ACTION_INSTALL": "Install", + "APP_ACTION_LOADING": "Loading", + "APP_ACTION_OPEN": "Open", + "APP_ACTION_REMOVE": "Remove", + "APP_ACTION_SETTINGS": "Settings", + "APP_ACTION_START": "Start", + "APP_ACTION_STOP": "Stop", + "APP_ACTION_RESTART": "Restart", + "APP_ACTION_UPDATE": "Update", + "APP_CATEGORY_AI": "AI", + "APP_CATEGORY_AUTOMATION": "Automation", + "APP_CATEGORY_BOOKS": "Books", + "APP_CATEGORY_DATA": "Data", + "APP_CATEGORY_DEVELOPMENT": "Development", + "APP_CATEGORY_FEATURED": "Featured", + "APP_CATEGORY_FINANCE": "Finance", + "APP_CATEGORY_GAMING": "Gaming", + "APP_CATEGORY_MEDIA": "Media", + "APP_CATEGORY_MUSIC": "Music", + "APP_CATEGORY_NETWORK": "Network", + "APP_CATEGORY_PHOTOGRAPHY": "Photography", + "APP_CATEGORY_SECURITY": "Security", + "APP_CATEGORY_SOCIAL": "Social", + "APP_CATEGORY_UTILITIES": "Utilities", + "APP_DETAILS_AUTHOR": "Author", + "APP_DETAILS_BASE_INFO": "Base info", + "APP_DETAILS_CATEGORIES_TITLE": "Categories", + "APP_DETAILS_CHOOSE_OPEN_METHOD": "Choose open method", + "APP_DETAILS_DEPRECATED_ALERT_SUBTITLE": "A breaking change in this app prevents it from being updated automatically. You can still use this version and update it manually, but it is recommended to switch to a newer version and migrate your data. You can find an updated version in the app store under the same name.", + "APP_DETAILS_DEPRECATED_ALERT_TITLE": "This app is deprecated", + "APP_DETAILS_DESCRIPTION": "Description", + "APP_DETAILS_LINK": "Link", + "APP_DETAILS_PORT": "Port", + "APP_DETAILS_SOURCE_CODE": "Source code", + "APP_DETAILS_SUPPORTED_ARCH": "Supported architectures", + "APP_DETAILS_TITLE": "App details", + "APP_DETAILS_VERSION": "Version", + "APP_DETAILS_WEBSITE": "Website", + "APP_ERROR_APP_FAILED_TO_INSTALL": "Failed to install app {{id}}, see logs for more details", + "APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {{id}}, see logs for more details", + "APP_ERROR_APP_FAILED_TO_START": "Failed to start app {{id}}, see logs for more details", + "APP_ERROR_APP_FAILED_TO_STOP": "Failed to stop app {{id}}, see logs for more details", + "APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {{id}}, see logs for more details", + "APP_ERROR_APP_FAILED_TO_UNINSTALL": "Failed to uninstall app {{id}}, see logs for more details", + "APP_ERROR_APP_FAILED_TO_UPDATE": "Failed to update app {{id}}, see logs for more details", + "APP_ERROR_APP_FORCE_EXPOSED": "App {{id}} works only with exposed domain", + "APP_ERROR_APP_NOT_EXPOSABLE": "App {{id}} is not exposable", + "APP_ERROR_APP_NOT_FOUND": "App {{id}} not found", + "APP_ERROR_ARCHITECTURE_NOT_SUPPORTED": "Your architecture {{arch}} is not supported by app {{id}}", + "APP_ERROR_DOMAIN_ALREADY_IN_USE": "Domain {{domain}} is already in use by app {{id}}", + "APP_ERROR_DOMAIN_NOT_VALID": "Domain {{domain}} is not a valid domain", + "APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP": "Domain is required if app is exposed", + "APP_ERROR_INVALID_CONFIG": "App {{id}} has an invalid config.json file", + "APP_INSTALL_FORM_CHOOSE_OPTION": "Choose an option...", + "APP_INSTALL_FORM_DISPLAY_ON_GUEST_DASHBOARD": "Display on guest dashboard", + "APP_INSTALL_FORM_DOMAIN_NAME": "Domain name", + "APP_INSTALL_FORM_DOMAIN_NAME_HINT": "Make sure this exact domain contains an A record pointing to your IP.", + "APP_INSTALL_FORM_ERROR_BETWEEN_LENGTH": "{{label}} must be between {{min}} and {{max}} characters", + "APP_INSTALL_FORM_ERROR_FQDN": "{{label}} must be a valid domain", + "APP_INSTALL_FORM_ERROR_FQDNIP": "{{label}} must be a valid domain or IP address", + "APP_INSTALL_FORM_ERROR_INVALID_EMAIL": "{{label}} must be a valid email address", + "APP_INSTALL_FORM_ERROR_IP": "{{label}} must be a valid IP address", + "APP_INSTALL_FORM_ERROR_MAX_LENGTH": "{{label}} must be less than {{max}} characters", + "APP_INSTALL_FORM_ERROR_MIN_LENGTH": "{{label}} must be at least {{min}} characters", + "APP_INSTALL_FORM_ERROR_NUMBER": "{{label}} must be a number", + "APP_INSTALL_FORM_ERROR_REGEX": "{{label}} must match the pattern {{pattern}}", + "APP_INSTALL_FORM_ERROR_REQUIRED": "{{label}} is required", + "APP_INSTALL_FORM_ERROR_URL": "{{label}} must be a valid URL", + "APP_INSTALL_FORM_EXPOSE_APP": "Expose app on the internet", + "APP_INSTALL_FORM_OPEN_PORT": "Open port", + "APP_INSTALL_FORM_OPEN_PORT_HINT": "Open a port on the host? This app will be accessible at {{internalIp}}:{{port}}. (Easiest but less secure)", + "APP_INSTALL_FORM_EXPOSE_LOCAL": "Expose app on local network", + "APP_INSTALL_FORM_EXPOSE_LOCAL_HINT": "Expose the app on the local network? This app will be accessible at {{appId}}.{{domain}}. (Visit settings page to setup your local domain)", + "APP_INSTALL_FORM_RESET": "Reset app", + "APP_INSTALL_FORM_SUBMIT_INSTALL": "Install", + "APP_INSTALL_FORM_SUBMIT_UPDATE": "Update", + "APP_INSTALL_FORM_TITLE": "Install {{name}}", + "APP_INSTALL_FORM_GENERAL": "General", + "APP_INSTALL_FORM_REVERSE_PROXY": "Reverse proxy", + "APP_INSTALL_SUCCESS": "App {{id}} installed successfully", + "APP_LOGS_TAB_FOLLOW": "Follow logs", + "APP_LOGS_TAB_MAX_LINES": "Max lines:", + "APP_LOGS_TAB_TITLE": "Logs", + "APP_LOGS_TAB_WRAP_LINES": "Wrap lines", + "APP_NEW": "NEW", + "APP_RESET_FORM_SUBMIT": "Reset", + "APP_RESET_FORM_SUBTITLE": "All data for this app will be lost.", + "APP_RESET_FORM_TITLE": "Reset {{name}} ?", + "APP_RESET_FORM_WARNING": "Are you sure? This action cannot be undone.", + "APP_RESET_SUCCESS": "App {{id}} reset successfully", + "APP_START_SUCCESS": "App {{id}} started successfully", + "APP_BACKUP_TITLE": "Backup {{name}}", + "APP_BACKUP_SUBTITLE": "A tar archive will be created in the backups folder to store your app's data.", + "APP_BACKUP_SUBMIT": "Backup", + "APP_RESTORE_TITLE": "Restore {{name}} backup", + "APP_RESTORE_WARNING": "Do you really want to restore backup {{id}} made on {{date}}?", + "APP_RESTORE_SUBTITLE": "All the current data of the app will be erased and replaced with the data from the backup. It is recommended to backup your app before restoring.", + "APP_RESTORE_SUBMIT": "Restore", + "APP_BACKUPS_TAB_TITLE": "Backups", + "APP_SETTINGS_GENERAL_TITLE": "General", + "APP_SETTINGS_BACKUPS_TITLE": "Backups", + "APP_STATUS_INSTALLING": "Installing", + "APP_STATUS_MISSING": "Missing", + "APP_STATUS_RESETTING": "Resetting", + "APP_STATUS_RUNNING": "Running", + "APP_STATUS_STARTING": "Starting", + "APP_STATUS_STOPPED": "Stopped", + "APP_STATUS_STOPPING": "Stopping", + "APP_STATUS_RESTARTING": "Restarting", + "APP_STATUS_UNINSTALLING": "Uninstalling", + "APP_STATUS_UPDATING": "Updating", + "APP_STATUS_BACKING_UP": "Backing up", + "APP_STATUS_RESTORING": "Restoring", + "APP_STOP_FORM_SUBMIT": "Stop", + "APP_STOP_FORM_SUBTITLE": "All data will be retained", + "APP_STOP_FORM_TITLE": "Stop {{name}} ?", + "APP_STOP_SUCCESS": "App {{id}} stopped successfully", + "APP_RESTART_FORM_SUBMIT": "Restart", + "APP_RESTART_FORM_SUBTITLE": "All data will be retained", + "APP_RESTART_FORM_TITLE": "Restart {{name}} ?", + "APP_RESTART_SUCCESS": "App {{id}} restarted successfully", + "APP_STORE_CATEGORY_PLACEHOLDER": "Select a category", + "APP_STORE_NO_RESULTS": "No app found", + "APP_STORE_NO_RESULTS_SUBTITLE": "Try to refine your search", + "APP_STORE_SEARCH_PLACEHOLDER": "Search apps", + "APP_STORE_TITLE": "App Store", + "APP_UNINSTALL_FORM_SUBMIT": "Uninstall", + "APP_UNINSTALL_FORM_SUBTITLE": "All data for this app will be lost.", + "APP_UNINSTALL_FORM_TITLE": "Uninstall {{name}} ?", + "APP_UNINSTALL_FORM_WARNING": "Are you sure? This action cannot be undone.", + "APP_UNINSTALL_SUCCESS": "App {{id}} uninstalled successfully", + "APP_UPDATE_CONFIG_SUCCESS": "App config updated successfully. Restart the app to apply the changes", + "APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {{id}} update requires Tipi version {{minVersion}} or higher. Please update your instance.", + "APP_UPDATE_FORM_SUBMIT": "Update", + "APP_UPDATE_FORM_SUBTITLE_1": "Update app to latest verion :", + "APP_UPDATE_FORM_SUBTITLE_2": "Make sure you've read the release notes of the app and you've backed up your app data.", + "APP_UPDATE_FORM_TITLE": "Update {{name}} ?", + "APP_UPDATE_SETTINGS_FORM_TITLE": "Update {{name}} config", + "APP_UPDATE_SUCCESS": "App {{id}} updated successfully", + "APP_UPDATE_FORM_BACKUP": "Backup app before updating", + "APP_BACKUP_SUCCESS": "App {{id}} backed up successfully", + "APP_BACKUP_ERROR": "Failed to backup {{id}}, see logs for more details", + "APP_RESTORE_SUCCESS": "App {{id}} restored successfully", + "APP_RESTORE_ERROR": "Failed to restore {{id}}, see logs for more details", + "AUTH_ERROR_ADMIN_ALREADY_EXISTS": "There is already an admin user. Please login to create a new user from the admin panel.", + "AUTH_ERROR_ERROR_CREATING_USER": "Error creating user", + "AUTH_ERROR_INVALID_CREDENTIALS": "Invalid credentials", + "AUTH_ERROR_INVALID_PASSWORD": "Invalid password", + "AUTH_ERROR_INVALID_PASSWORD_LENGTH": "Password must be at least 8 characters long", + "AUTH_ERROR_INVALID_USERNAME": "Invalid username", + "AUTH_ERROR_MISSING_EMAIL_OR_PASSWORD": "Missing email or password", + "AUTH_ERROR_NO_CHANGE_PASSWORD_REQUEST": "No change password request found", + "AUTH_ERROR_OPERATOR_NOT_FOUND": "Operator user not found", + "AUTH_ERROR_TOTP_ALREADY_ENABLED": "2FA is already enabled for this user", + "AUTH_ERROR_TOTP_INVALID_CODE": "Invalid 2FA code", + "AUTH_ERROR_TOTP_NOT_ENABLED": "2FA is not enabled for this user", + "AUTH_ERROR_TOTP_SESSION_NOT_FOUND": "2FA session not found", + "AUTH_ERROR_USER_ALREADY_EXISTS": "User already exists", + "AUTH_ERROR_USER_NOT_FOUND": "User not found", + "AUTH_FORM_EMAIL": "Email address", + "AUTH_FORM_EMAIL_PLACEHOLDER": "you@example.com", + "AUTH_FORM_ERROR_EMAIL_EMAIL": "Email address is invalid", + "AUTH_FORM_ERROR_EMAIL_INVALID": "Email address is invalid", + "AUTH_FORM_ERROR_EMAIL_REQUIRED": "Email address is required", + "AUTH_FORM_ERROR_PASSWORD_CONFIRMATION_LENGTH": "Password confirmation must be at least 8 characters", + "AUTH_FORM_ERROR_PASSWORD_CONFIRMATION_MATCH": "Passwords do not match", + "AUTH_FORM_ERROR_PASSWORD_CONFIRMATION_REQUIRED": "Password confirmation is required", + "AUTH_FORM_ERROR_PASSWORD_LENGTH": "Password must be at least 8 characters", + "AUTH_FORM_ERROR_PASSWORD_REQUIRED": "Password is required", + "AUTH_FORM_FORGOT": "Forgot password?", + "AUTH_FORM_NEW_PASSWORD_CONFIRMATION_PLACEHOLDER": "Confirm your new password", + "AUTH_FORM_NEW_PASSWORD_PLACEHOLDER": "Your new password", + "AUTH_FORM_PASSWORD": "Password", + "AUTH_FORM_PASSWORD_CONFIRMATION": "Confirm password", + "AUTH_FORM_PASSWORD_CONFIRMATION_PLACEHOLDER": "Confirm your password", + "AUTH_FORM_PASSWORD_PLACEHOLDER": "Enter your password", + "AUTH_LOGIN_SUBMIT": "Login", + "AUTH_LOGIN_TITLE": "Login to your account", + "AUTH_REGISTER_SUBMIT": "Register", + "AUTH_REGISTER_TITLE": "Register your account", + "AUTH_RESET_PASSWORD_BACK_TO_LOGIN": "Back to login", + "AUTH_RESET_PASSWORD_CANCEL": "Cancel password change request", + "AUTH_RESET_PASSWORD_INSTRUCTIONS": "To get started, run this command on your server and then refresh this page. If you have previously done so, the password reset request may have expired. In that case please retry", + "AUTH_RESET_PASSWORD_SUBMIT": "Reset password", + "AUTH_RESET_PASSWORD_SUCCESS": "Your password has been reset. You can now login with your new password. And your email {{email}}", + "AUTH_RESET_PASSWORD_SUCCESS_TITLE": "Password reset", + "AUTH_RESET_PASSWORD_TITLE": "Reset your password", + "AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app", + "AUTH_TOTP_SUBMIT": "Confirm", + "AUTH_TOTP_TITLE": "Two-factor authentication", + "BACKUPS_LIST": "Backups list", + "BACKUPS_LIST_BACKUP_NOW": "Backup now", + "BACKUPS_LIST_ROW_TITLE_ID": "ID", + "BACKUPS_LIST_ROW_TITLE_DATE": "Date", + "BACKUPS_LIST_ROW_TITLE_ACTIONS": "Actions", + "BACKUPS_LIST_ROW_TITLE_SIZE": "Size", + "BACKUPS_LIST_DELETE_SUCCESS": "Backup deleted successfully", + "COMMON_CLOSE": "Close", + "DASHBOARD_CPU_SUBTITLE": "Uninstall apps to reduce load", + "DASHBOARD_CPU_TITLE": "CPU load", + "DASHBOARD_DISK_SPACE_SUBTITLE": "Used out of {{total}} GB", + "DASHBOARD_DISK_SPACE_TITLE": "Disk space", + "DASHBOARD_MEMORY_TITLE": "Memory used", + "DASHBOARD_TITLE": "Dashboard", + "DASHBOARD_IP_WARNING_TITLE": "Insecure configuration", + "DASHBOARD_IP_WARNING": "Warning, you might be at risk! it looks like you are accessing your instance through a public IP address. This makes your dashboard and all apps that you install vulnerable to attackers", + "DELETE_BACKUP_MODAL_TITLE": "Delete backup", + "DELETE_BACKUP_MODAL_WARNING": "Are you sure you want to delete backup {{id}} made on {{date}}?", + "DELETE_BACKUP_MODAL_SUBTITLE": "This action cannot be undone", + "DELETE_BACKUP_MODAL_SUBMIT": "Delete", + "GUEST_DASHBOARD": "Guest dashboard", + "GUEST_DASHBOARD_NO_APPS": "No apps to display", + "GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Ask your administrator to add apps to the guest dashboard or login to see your apps.", + "HEADER_APPS": "My Apps", + "HEADER_APP_STORE": "App Store", + "HEADER_DARK_MODE": "Dark Mode", + "HEADER_DASHBOARD": "Dashboard", + "HEADER_LIGHT_MODE": "Light Mode", + "HEADER_LOGIN": "Login", + "HEADER_LOGOUT": "Logout", + "HEADER_SETTINGS": "Settings", + "HEADER_SOURCE_CODE": "Source code", + "HEADER_SPONSOR": "Sponsor", + "HEADER_UPDATE_AVAILABLE": "Update available", + "INTERNAL_SERVER_ERROR": "Internal server error", + "LINKS_ADD_SUBMIT": "Submit", + "LINKS_ADD_SUBTITLE": "Add external link to the dashboard", + "LINKS_ADD_SUCCESS": "Link added successfully", + "LINKS_ADD_TITLE": "Add external link", + "LINKS_DELETE_CONTEXT_MENU": "Delete", + "LINKS_DELETE_SUBMIT": "Delete", + "LINKS_DELETE_SUBTITLE": "Are you sure you want to delete this external link?", + "LINKS_DELETE_SUCCESS": "Link deleted successfully", + "LINKS_DELETE_TITLE": "Delete external link", + "LINKS_EDIT_CONTEXT_MENU": "Edit", + "LINKS_EDIT_SUBMIT": "Save", + "LINKS_EDIT_SUCCESS": "Link edited successfully", + "LINKS_EDIT_TITLE": "Edit link", + "LINKS_FORM_ICON_PLACEHOLDER": "Link logo URL", + "LINKS_FORM_ICON_URL": "Icon URL", + "LINKS_FORM_LINK_TITLE": "Link title", + "LINKS_FORM_LINK_URL": "Link URL", + "LINKS_FROM_LINK_DESCRIPTION": "Link Description", + "MY_APPS_DEPRECATED": "This app is deprecated", + "MY_APPS_EMPTY_ACTION": "Go to app store", + "MY_APPS_EMPTY_SUBTITLE": "Install an app from the app store to get started", + "MY_APPS_EMPTY_TITLE": "No app installed", + "MY_APPS_TITLE": "My Apps", + "MY_APPS_UPDATE_ALL_FORM_SUBMIT": "Update all", + "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_1": "Do you want to update all your apps to the latest version?", + "MY_APPS_UPDATE_ALL_FORM_SUBTITLE_2": "This will update all your apps to the latest version. Make sure you've read the release notes of the apps and you've backed up your app data.", + "MY_APPS_UPDATE_ALL_FORM_TITLE": "Update all apps", + "MY_APPS_UPDATE_ALL_IN_PROGRESS": "Updating all apps", + "MY_APPS_UPDATE_AVAILABLE": "Update available", + "RUNTIPI": "Runtipi", + "SERVER_ERROR_INVALID_LOCALE": "Invalid locale", + "SERVER_ERROR_NOT_ALLOWED_IN_DEMO": "Not allowed in demo mode", + "SERVER_ERROR_NOT_ALLOWED_IN_DEV": "Not allowed in dev mode", + "SETTINGS_ACTIONS_ALREADY_LATEST": "Already up to date", + "SETTINGS_ACTIONS_CURRENT_VERSION": "Current version: {{version}}", + "SETTINGS_ACTIONS_MAINTENANCE_SUBTITLE": "Common actions to perform on your instance", + "SETTINGS_ACTIONS_MAINTENANCE_TITLE": "Maintenance", + "SETTINGS_ACTIONS_NEW_VERSION": "A new version ({{version}}) of Tipi is available", + "SETTINGS_ACTIONS_RESTART": "Restart", + "SETTINGS_ACTIONS_STAY_UP_TO_DATE": "Stay up to date with the latest version of Tipi", + "SETTINGS_ACTIONS_TAB_TITLE": "Actions", + "SETTINGS_ACTIONS_TITLE": "Actions", + "SETTINGS_ACTIONS_UPDATE_REPO_TITLE": "Update Repository", + "SETTINGS_ACTIONS_UPDATE_REPO_SUBTITLE": "Use this button to update your appstore", + "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_SUBTITLE": "This will reset your repository and pull the latest changes from GitHub", + "SETTINGS_ACTIONS_UPDATE_REPO_MODAL_BUTTON": "Update", + "SETTINGS_ACTIONS_UPDATE_REPO_SUCCESS": "Appstore repository updated successfully", + "SETTINGS_GENERAL_ALLOW_AUTO_THEMES": "Allow auto themes", + "SETTINGS_GENERAL_ALLOW_AUTO_THEMES_HINT": "Be surprised by themes that change automatically based on the time of the year.", + "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING": "Allow anonymous error monitoring", + "SETTINGS_GENERAL_ALLOW_ERROR_MONITORING_HINT": "Error monitoring is used to track errors and improve Tipi. Keep this option enabled to help us improve Tipi.", + "SETTINGS_GENERAL_APPS_REPO": "Apps repo URL", + "SETTINGS_GENERAL_APPS_REPO_HINT": "URL to the apps repository.", + "SETTINGS_GENERAL_DNS_IP": "DNS IP", + "SETTINGS_GENERAL_DOMAIN_NAME": "Domain name", + "SETTINGS_GENERAL_DOMAIN_NAME_HINT": "Make sure this exact domain contains an A record pointing to your IP.", + "SETTINGS_GENERAL_DOWNLOAD_CERTIFICATE": "Download certificate", + "SETTINGS_GENERAL_GUEST_DASHBOARD": "Enable guest dashboard", + "SETTINGS_GENERAL_GUEST_DASHBOARD_HINT": "This will allow non-authenticated users to see a limited dashboard and easily access the running apps on your instance.", + "SETTINGS_GENERAL_INTERNAL_IP": "Internal IP", + "SETTINGS_GENERAL_INTERNAL_IP_HINT": "IP address your server is listening on.", + "SETTINGS_GENERAL_INVALID_DOMAIN": "Invalid domain", + "SETTINGS_GENERAL_INVALID_IP": "Invalid IP address", + "SETTINGS_GENERAL_INVALID_URL": "Invalid URL", + "SETTINGS_GENERAL_LANGUAGE": "Language", + "SETTINGS_GENERAL_LANGUAGE_HELP_TRANSLATE": "Help translate Tipi", + "SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain", + "SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.", + "SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.", + "SETTINGS_GENERAL_STORAGE_PATH": "Storage path", + "SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.", + "SETTINGS_GENERAL_SUBMIT": "Update settings", + "SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.", + "SETTINGS_GENERAL_TAB_TITLE": "Settings", + "SETTINGS_GENERAL_TITLE": "General settings", + "SETTINGS_GENERAL_USER_SETTINGS": "User settings", + "SETTINGS_SECURITY_2FA_DISABLE_SUCCESS": "Two-factor authentication disabled", + "SETTINGS_SECURITY_2FA_ENABLE_SUCCESS": "Two-factor authentication enabled", + "SETTINGS_SECURITY_2FA_SUBTITLE": "Two-factor authentication (2FA) adds an additional layer of security to your account.", + "SETTINGS_SECURITY_2FA_SUBTITLE_2": "When enabled, you will be prompted to enter a code from your authenticator app when you log in.", + "SETTINGS_SECURITY_2FA_TITLE": "Two-factor authentication", + "SETTINGS_SECURITY_CHANGE_PASSWORD_SUBTITLE": "Changing your password will log you out of all devices.", + "SETTINGS_SECURITY_CHANGE_PASSWORD_TITLE": "Change password", + "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_INVALID_USERNAME": "Must be a valid email address", + "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_NEW_USERNAME": "New username", + "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_PASSWORD": "Password", + "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_PASSWORD_NEEDED_HINT": "Your password is required to change your username.", + "SETTINGS_SECURITY_CHANGE_USERNAME_FORM_SUBMIT": "Change username", + "SETTINGS_SECURITY_CHANGE_USERNAME_SUBTITLE": "Changing your username will log you out of all devices.", + "SETTINGS_SECURITY_CHANGE_USERNAME_SUCCESS": "Username changed successfully", + "SETTINGS_SECURITY_CHANGE_USERNAME_TITLE": "Change username", + "SETTINGS_SECURITY_DISABLE_2FA": "Disable two-factor authentication", + "SETTINGS_SECURITY_ENABLE_2FA": "Enable two-factor authentication", + "SETTINGS_SECURITY_ENTER_2FA_CODE": "Enter the 6-digit code from your authenticator app", + "SETTINGS_SECURITY_ENTER_KEY_MANUALLY": "Or enter this key manually.", + "SETTINGS_SECURITY_FORM_CHANGE_PASSWORD_SUBMIT": "Change password", + "SETTINGS_SECURITY_FORM_CONFIRM_PASSWORD": "Confirm new password", + "SETTINGS_SECURITY_FORM_CURRENT_PASSWORD": "Current password", + "SETTINGS_SECURITY_FORM_NEW_PASSWORD": "New password", + "SETTINGS_SECURITY_FORM_PASSWORD": "Password", + "SETTINGS_SECURITY_FORM_PASSWORD_LENGTH": "Password must be at least 8 characters", + "SETTINGS_SECURITY_FORM_PASSWORD_MATCH": "Passwords do not match", + "SETTINGS_SECURITY_PASSWORD_CHANGE_SUCCESS": "Password changed successfully", + "SETTINGS_SECURITY_PASSWORD_NEEDED": "Password needed", + "SETTINGS_SECURITY_PASSWORD_NEEDED_HINT": "Your password is required to change two-factor authentication settings.", + "SETTINGS_SECURITY_SCAN_QR_CODE": "Scan this QR code with your authenticator app.", + "SETTINGS_SECURITY_TAB_TITLE": "Security", + "SETTINGS_LOGS_TAB_TITLE": "Logs", + "SETTINGS_TITLE": "Settings", + "SYSTEM_ERROR_COULD_NOT_GET_LATEST_VERSION": "Could not get latest version", + "SYSTEM_ERROR_CURRENT_VERSION_IS_LATEST": "Current version is already up to date", + "SYSTEM_ERROR_DEMO_MODE_LIMIT": "Only 6 apps can be installed in the demo mode. Please uninstall an other app to install a new one.", + "SYSTEM_ERROR_MAJOR_VERSION_UPDATE": "The major version has changed. Please update manually (instructions in release notes)", + "SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN": "You must be logged in to perform this action", + "TIMEZONE_SELECTOR_LABEL": "Timezone", + "TIMEZONE_SELECTOR_PLACEHOLDER": "Select a timezone" +} diff --git a/packages/backend/src/modules/links/dto/links.dto.ts b/packages/backend/src/modules/links/dto/links.dto.ts new file mode 100644 index 0000000000..04e88a50ba --- /dev/null +++ b/packages/backend/src/modules/links/dto/links.dto.ts @@ -0,0 +1,41 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod'; + +export const linkSchema = z.object({ + id: z.number(), + title: z.string().min(1).max(20), + description: z.string().min(0).max(50).nullable(), + url: z.string().url(), + iconUrl: z.string().url().or(z.string().max(0)).nullable(), + userId: z.number(), +}); + +export class LinkBodyDto extends createZodDto( + linkSchema + .omit({ + id: true, + userId: true, + description: true, + iconUrl: true, + }) + .extend({ + description: z.string().min(0).max(50).optional(), + iconUrl: z.string().url().or(z.string().max(0)).optional(), + }), +) {} + +export class EditLinkBodyDto extends createZodDto( + linkSchema + .omit({ + id: true, + userId: true, + description: true, + iconUrl: true, + }) + .extend({ + description: z.string().min(0).max(50).optional(), + iconUrl: z.string().url().or(z.string().max(0)).optional(), + }), +) {} + +export class LinksDto extends createZodDto(z.object({ links: z.array(linkSchema) })) {} diff --git a/packages/backend/src/modules/links/links.controller.ts b/packages/backend/src/modules/links/links.controller.ts new file mode 100644 index 0000000000..af1f9fb62e --- /dev/null +++ b/packages/backend/src/modules/links/links.controller.ts @@ -0,0 +1,49 @@ +import { TranslatableError } from '@/common/error/translatable-error'; +import { Body, Controller, Delete, Get, Injectable, Param, Patch, Post, Req, UseGuards } from '@nestjs/common'; +import type { Request } from 'express'; +import { ZodSerializerDto } from 'nestjs-zod'; +import { AuthGuard } from '../auth/auth.guard'; +import { EditLinkBodyDto, LinkBodyDto, LinksDto } from './dto/links.dto'; +import { LinksService } from './links.service'; + +@Injectable() +@Controller('links') +@UseGuards(AuthGuard) +export class LinksController { + constructor(private readonly linksService: LinksService) {} + + @Get() + @ZodSerializerDto(LinksDto) + async getLinks(@Req() req: Request): Promise { + const links = await this.linksService.getLinks(req.user?.id); + + return { links }; + } + + @Post() + async createLink(@Body() body: LinkBodyDto, @Req() req: Request) { + if (!req.user) { + throw new TranslatableError('SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN'); + } + + return this.linksService.add(body, req.user.id); + } + + @Patch(':id') + async editLink(@Param('id') id: number, @Body() body: EditLinkBodyDto, @Req() req: Request) { + if (!req.user) { + throw new TranslatableError('SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN'); + } + + return this.linksService.edit(id, body, req.user.id); + } + + @Delete(':id') + async deleteLink(@Param('id') id: number, @Req() req: Request) { + if (!req.user) { + throw new TranslatableError('SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN'); + } + + return this.linksService.delete(id, req.user.id); + } +} diff --git a/packages/backend/src/modules/links/links.module.ts b/packages/backend/src/modules/links/links.module.ts new file mode 100644 index 0000000000..8a2ecfee04 --- /dev/null +++ b/packages/backend/src/modules/links/links.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { LinksController } from './links.controller'; +import { LinksRepository } from './links.repository'; +import { LinksService } from './links.service'; + +@Module({ + imports: [], + controllers: [LinksController], + providers: [LinksService, LinksRepository], +}) +export class LinksModule {} diff --git a/packages/backend/src/modules/links/links.repository.ts b/packages/backend/src/modules/links/links.repository.ts new file mode 100644 index 0000000000..f969cd8d87 --- /dev/null +++ b/packages/backend/src/modules/links/links.repository.ts @@ -0,0 +1,56 @@ +import { DatabaseService } from '@/core/database/database.service'; +import { linkTable } from '@/core/database/schema'; +import { Injectable } from '@nestjs/common'; +import { and, eq } from 'drizzle-orm'; +import type { EditLinkBodyDto, LinkBodyDto } from './dto/links.dto'; + +@Injectable() +export class LinksRepository { + constructor(private databaseService: DatabaseService) {} + + /** + * Adds a new link to the database. + * @param {LinkInfo} link - The link information to be added. + * @returns The newly added link. + */ + public async addLink(link: LinkBodyDto, userId: number) { + const { title, description, url, iconUrl } = link; + const newLinks = await this.databaseService.db.insert(linkTable).values({ title, description, url, iconUrl, userId }).returning(); + return newLinks[0]; + } + + /** + * Edits an existing link in the database. + * @param {LinkInfo} link - The updated link information. + * @returns The updated link. + * @throws Error if no id is provided. + */ + public async editLink(linkId: number, link: EditLinkBodyDto, userId: number) { + const { title, description, url, iconUrl } = link; + + const updatedLinks = await this.databaseService.db + .update(linkTable) + .set({ title, description, url, iconUrl, updatedAt: new Date() }) + .where(and(eq(linkTable.id, linkId), eq(linkTable.userId, userId))) + .returning(); + + return updatedLinks[0]; + } + + /** + * Deletes a link from the database. + * @param {number} linkId - The id of the link to be deleted. + */ + public async deleteLink(linkId: number, userId: number) { + await this.databaseService.db.delete(linkTable).where(and(eq(linkTable.id, linkId), eq(linkTable.userId, userId))); + } + + /** + * Retrieves all links for a given user from the database. + * @param {number} userId - The id of the user. + * @returns An array of links belonging to the user. + */ + public async getLinks(userId: number) { + return this.databaseService.db.select().from(linkTable).where(eq(linkTable.userId, userId)).orderBy(linkTable.id); + } +} diff --git a/packages/backend/src/modules/links/links.service.ts b/packages/backend/src/modules/links/links.service.ts new file mode 100644 index 0000000000..6bf6d7b540 --- /dev/null +++ b/packages/backend/src/modules/links/links.service.ts @@ -0,0 +1,35 @@ +import { TranslatableError } from '@/common/error/translatable-error'; +import { ConfigurationService } from '@/core/config/configuration.service'; +import { Injectable } from '@nestjs/common'; +import type { EditLinkBodyDto, LinkBodyDto } from './dto/links.dto'; +import { LinksRepository } from './links.repository'; + +@Injectable() +export class LinksService { + constructor( + private readonly config: ConfigurationService, + private readonly linksRepository: LinksRepository, + ) {} + + public add = async (link: LinkBodyDto, userId: number) => { + if (this.config.get('demoMode')) { + throw new TranslatableError('SERVER_ERROR_NOT_ALLOWED_IN_DEMO'); + } + + return this.linksRepository.addLink(link, userId); + }; + + public edit = async (linkId: number, link: EditLinkBodyDto, userId: number) => { + return this.linksRepository.editLink(linkId, link, userId); + }; + + public delete = async (linkId: number, userId: number) => { + return this.linksRepository.deleteLink(linkId, userId); + }; + + public async getLinks(userId: number | undefined) { + if (!userId) return []; + + return this.linksRepository.getLinks(userId); + } +} diff --git a/packages/backend/src/modules/queue/entities/app-events.ts b/packages/backend/src/modules/queue/entities/app-events.ts new file mode 100644 index 0000000000..b72a528c6e --- /dev/null +++ b/packages/backend/src/modules/queue/entities/app-events.ts @@ -0,0 +1,40 @@ +import { appFormSchema } from '@/modules/app-lifecycle/dto/app-lifecycle.dto'; +import { Injectable } from '@nestjs/common'; +import { z } from 'zod'; +import { Queue } from '../queue.entity'; + +const commonAppCommandSchema = z.object({ + command: z.union([ + z.literal('start'), + z.literal('stop'), + z.literal('install'), + z.literal('uninstall'), + z.literal('reset'), + z.literal('restart'), + z.literal('generate_env'), + z.literal('backup'), + ]), + appid: z.string(), + skipEnv: z.boolean().optional().default(false), + form: appFormSchema, +}); + +const restoreAppCommandSchema = z.object({ + command: z.literal('restore'), + appid: z.string(), + filename: z.string(), + form: appFormSchema, +}); + +export const appEventSchema = commonAppCommandSchema.or(restoreAppCommandSchema); + +export const appEventResultSchema = z.object({ + success: z.boolean(), + message: z.string(), +}); + +export type AppEventFormInput = z.input['form']; +export type AppEventForm = z.output['form']; + +@Injectable() +export class AppEventsQueue extends Queue {} diff --git a/packages/backend/src/modules/queue/entities/repo-events.ts b/packages/backend/src/modules/queue/entities/repo-events.ts new file mode 100644 index 0000000000..67920e7ded --- /dev/null +++ b/packages/backend/src/modules/queue/entities/repo-events.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { Queue } from '../queue.entity'; +import { z } from 'zod'; + +export const repoCommandSchema = z.object({ + command: z.union([z.literal('clone'), z.literal('update')]), + url: z.string().url(), +}); + +export const repoCommandResultSchema = z.object({ + success: z.boolean(), + message: z.string(), +}); + +@Injectable() +export class RepoEventsQueue extends Queue { } diff --git a/packages/backend/src/modules/queue/queue.entity.ts b/packages/backend/src/modules/queue/queue.entity.ts new file mode 100644 index 0000000000..d0b3c71817 --- /dev/null +++ b/packages/backend/src/modules/queue/queue.entity.ts @@ -0,0 +1,118 @@ +import cron from 'node-cron'; +import type { AsyncMessage, Publisher } from 'rabbitmq-client'; +import { type ZodSchema, z } from 'zod'; +import type { QueueFactory } from './queue.factory'; + +const FIVE_MINUTES = 5 * 60 * 1000; + +export class Queue> { + private queueNameResponse: string; + private responsePromises: { [id: string]: { resolve: (data: z.output) => void } }; + + constructor( + private queueFactory: QueueFactory, + private publisher: Publisher, + private queueName: string, + private workers: number, + private eventSchema: T, + private resultSchema: R, + ) { + this.queueNameResponse = `${this.queueName}-response`; + this.responsePromises = {}; + this.workers = workers; + } + + private generateEventId(command: string) { + return `${command}-${Math.random().toString(36).substring(7)}`; + } + + public publishRepeatable(data: z.input, cronPattern: string) { + if (!cron.validate(cronPattern)) { + throw new Error('Invalid cron pattern'); + } + + const eventData = this.eventSchema.safeParse(data); + + cron.schedule(cronPattern, () => { + this.publisher.send(this.queueName, { eventId: this.generateEventId(data.command), ...eventData.data }); + }); + } + + public onEvent(callback: (data: z.output & { eventId: string }) => void) { + this.queueFactory.createConsumer(this.queueName, (eventData) => { + if (Object.keys(this.responsePromises).length > this.workers) { + this.requeueWithBackoff(eventData); + return; + } + + const parsedData = this.eventSchema.and(z.object({ eventId: z.string() })).safeParse(eventData.body); + if (parsedData.success) { + callback(parsedData.data); + } + }); + + this.queueFactory.createConsumer(this.queueNameResponse, ({ body }) => { + const { resolve } = this.responsePromises[body.eventId] ?? {}; + + if (resolve) { + const response = this.resultSchema.safeParse(body.data); + if (response.success) { + resolve({ ...response.data }); + } else { + console.error('Invalid response data', response.error.flatten()); + resolve({ success: false, message: 'Invalid response data' }); + } + + delete this.responsePromises[body.eventId]; + } + }); + } + + private requeueWithBackoff(eventData: AsyncMessage) { + console.warn('Requeuing event due to backpressure'); + const backoffTime = 5000; + setTimeout(() => { + this.publisher.send(this.queueName, eventData.body); + }, backoffTime); + } + + async publishAsync(event: z.input, timeout = FIVE_MINUTES): Promise> { + const eventData = this.eventSchema.safeParse(event); + + if (!eventData.success) { + throw new Error('Invalid event data'); + } + + const eventId = await this.publish(eventData.data); + + return new Promise((resolve) => { + this.responsePromises[eventId] = { resolve }; + + setTimeout(() => { + if (this.responsePromises[eventId]) { + // @ts-ignore - TS doesn't know that the promise is still in the map + resolve({ success: false, message: 'Timeout' }); + delete this.responsePromises[eventId]; + } + }, timeout); + }); + } + + async publish(data: z.input) { + const eventData = this.eventSchema.safeParse(data); + + if (!eventData.success) { + throw new Error('Invalid event data'); + } + + const eventId = this.generateEventId(data.command); + + await this.publisher.send(this.queueName, { eventId, ...eventData.data }); + + return eventId; + } + + async sendEventResponse(eventId: string, data: z.input) { + await this.publisher.send(this.queueNameResponse, { eventId, data }); + } +} diff --git a/packages/backend/src/modules/queue/queue.factory.ts b/packages/backend/src/modules/queue/queue.factory.ts new file mode 100644 index 0000000000..af74fd7b7e --- /dev/null +++ b/packages/backend/src/modules/queue/queue.factory.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { type AsyncMessage, Connection } from 'rabbitmq-client'; +import { type ZodSchema, z } from 'zod'; +import { Queue } from './queue.entity'; + +@Injectable() +export class QueueFactory { + private rabbit: Connection; + + public constructor() { + this.initializeConnection(); + } + + private initializeConnection() { + this.rabbit = new Connection({ url: 'amqp://guest:guest@localhost:5672' }); + } + + public createQueue(params: { queueName: string; workers?: number; eventSchema: T; resultSchema?: R }) { + const { queueName, workers = 3, eventSchema, resultSchema = z.object({ success: z.boolean(), message: z.string() }) } = params; + + const publisher = this.rabbit.createPublisher({ + confirm: true, + maxAttempts: 3, + }); + + return new Queue(this, publisher, queueName, workers, eventSchema, resultSchema); + } + + public createConsumer(queueName: string, callback: (data: AsyncMessage) => void) { + return this.rabbit.createConsumer({ queue: queueName }, callback); + } +} diff --git a/packages/backend/src/modules/queue/queue.health.ts b/packages/backend/src/modules/queue/queue.health.ts new file mode 100644 index 0000000000..3349f6564d --- /dev/null +++ b/packages/backend/src/modules/queue/queue.health.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { HealthCheckError, HealthIndicator, type HealthIndicatorResult } from '@nestjs/terminus'; +import Connection from 'rabbitmq-client'; + +@Injectable() +export class QueueHealthIndicator extends HealthIndicator { + private connection = new Connection({ + url: 'amqp://guest:guest@localhost:5672', + connectionTimeout: 30000, + }); + + async isHealthy(key: string): Promise { + const isHealthy = this.connection.ready; + + const result = this.getStatus(key, isHealthy); + + if (isHealthy) { + return result; + } + throw new HealthCheckError('Queue healthcheck failed', result); + } +} diff --git a/packages/backend/src/modules/queue/queue.module.ts b/packages/backend/src/modules/queue/queue.module.ts new file mode 100644 index 0000000000..ba4607b7df --- /dev/null +++ b/packages/backend/src/modules/queue/queue.module.ts @@ -0,0 +1,37 @@ +import { Module } from '@nestjs/common'; +import { AppEventsQueue, appEventResultSchema, appEventSchema } from './entities/app-events'; +import { RepoEventsQueue, repoCommandResultSchema, repoCommandSchema } from './entities/repo-events'; +import { QueueFactory } from './queue.factory'; +import { QueueHealthIndicator } from './queue.health'; + +@Module({ + imports: [], + providers: [ + QueueHealthIndicator, + QueueFactory, + { + provide: AppEventsQueue, + useFactory: (queueFactory: QueueFactory) => + queueFactory.createQueue({ + queueName: 'app-events-queue', + workers: 1, + eventSchema: appEventSchema, + resultSchema: appEventResultSchema, + }), + inject: [QueueFactory], + }, + { + provide: RepoEventsQueue, + useFactory: (queueFactory: QueueFactory) => + queueFactory.createQueue({ + queueName: 'repo-queue', + workers: 3, + eventSchema: repoCommandSchema, + resultSchema: repoCommandResultSchema, + }), + inject: [QueueFactory], + }, + ], + exports: [AppEventsQueue, RepoEventsQueue, QueueHealthIndicator], +}) +export class QueueModule {} diff --git a/packages/backend/src/modules/repos/repos.module.ts b/packages/backend/src/modules/repos/repos.module.ts new file mode 100644 index 0000000000..dc60f1a3f6 --- /dev/null +++ b/packages/backend/src/modules/repos/repos.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ReposService } from './repos.service'; +import { QueueModule } from '../queue/queue.module'; + +@Module({ + imports: [QueueModule], + controllers: [], + providers: [ReposService], + exports: [ReposService], +}) +export class ReposModule { } diff --git a/packages/backend/src/modules/repos/repos.service.ts b/packages/backend/src/modules/repos/repos.service.ts new file mode 100644 index 0000000000..f90597ca2a --- /dev/null +++ b/packages/backend/src/modules/repos/repos.service.ts @@ -0,0 +1,163 @@ +import crypto from 'node:crypto'; +import path from 'node:path'; +import { execAsync } from '@/common/helpers/exec-helpers'; +import { ConfigurationService } from '@/core/config/configuration.service'; +import { FilesystemService } from '@/core/filesystem/filesystem.service'; +import { LoggerService } from '@/core/logger/logger.service'; +import { Injectable } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; +import { RepoEventsQueue } from '../queue/entities/repo-events'; + +@Injectable() +export class ReposService { + constructor( + private readonly logger: LoggerService, + private readonly configuration: ConfigurationService, + private readonly filesystem: FilesystemService, + private readonly repoQueue: RepoEventsQueue, + ) { + this.repoQueue.onEvent(({ command, url }) => { + switch (command) { + case 'clone': + this.cloneRepo(url); + break; + case 'update': + this.pullRepo(url); + break; + default: + this.logger.error(`Unknown command: ${command}`); + } + }); + } + + /** + * Given a repo url, return a hash of it to be used as a folder name + * + * @param {string} repoUrl + */ + public getRepoHash = (repoUrl: string) => { + const hash = crypto.createHash('sha256'); + hash.update(repoUrl); + return hash.digest('hex'); + }; + + /** + * Extracts the base URL and branch from a repository URL. + * @param repoUrl The repository URL. + * @returns An array containing the base URL and branch, or just the base URL if no branch is found. + */ + private getRepoBaseUrlAndBranch = (repoUrl: string) => { + const branchMatch = repoUrl.match(/^(.*)\/tree\/(.*)$/); + if (branchMatch) { + return [branchMatch[1], branchMatch[2]]; + } + + return [repoUrl, undefined]; + }; + + /** + * Error handler for repo operations + * @param {unknown} err + */ + private handleRepoError = (err: unknown) => { + Sentry.captureException(err); + + if (err instanceof Error) { + this.logger.error(`An error occurred: ${err.message}`); + return { success: false, message: err.message }; + } + + return { success: false, message: `An error occurred: ${String(err)}` }; + }; + + /** + * Given a repo url, clone it to the repos folder if it doesn't exist + * + * @param {string} url + */ + public cloneRepo = async (url: string) => { + try { + const { dataDir } = this.configuration.get('directories'); + + // We may have a potential branch computed in the hash (see getRepoBaseUrlAndBranch) + // so we do it here before splitting the url into repoUrl and branch + const repoHash = this.getRepoHash(url); + const repoPath = path.join(dataDir, 'repos', repoHash); + + if (await this.filesystem.pathExists(repoPath)) { + this.logger.info(`Repo ${url} already exists`); + return { success: true, message: '' }; + } + + const [repoUrl, branch] = this.getRepoBaseUrlAndBranch(url); + + let cloneCommand: string; + if (branch) { + this.logger.info(`Cloning repo ${repoUrl} on branch ${branch} to ${repoPath}`); + cloneCommand = `git clone -b ${branch} --depth 1 ${repoUrl} ${repoPath}`; + } else { + this.logger.info(`Cloning repo ${repoUrl} to ${repoPath}`); + cloneCommand = `git clone --depth 1 ${repoUrl} ${repoPath}`; + } + await execAsync(cloneCommand); + + // Chmod the repo folder to 777 + this.logger.info(`Executing: chmod -R 755 ${repoPath}`); + await execAsync(`chmod -R 755 ${repoPath}`); + + this.logger.info(`Cloned repo ${repoUrl} to ${repoPath}`); + return { success: true, message: '' }; + } catch (err) { + return this.handleRepoError(err); + } + }; + + /** + * Given a repo url, pull it to the repos folder if it exists + * + * @param {string} repoUrl + */ + public pullRepo = async (repoUrl: string) => { + try { + const { dataDir } = this.configuration.get('directories'); + + const repoHash = this.getRepoHash(repoUrl); + const repoPath = path.join(dataDir, 'repos', repoHash); + + if (!(await this.filesystem.pathExists(repoPath))) { + this.logger.info(`Repo ${repoUrl} does not exist`); + return { success: false, message: `Repo ${repoUrl} does not exist` }; + } + + this.logger.info(`Pulling repo ${repoUrl} to ${repoPath}`); + + this.logger.info(`Executing: git config --global --add safe.directory ${repoPath}`); + await execAsync(`git config --global --add safe.directory ${repoPath}`).then(({ stderr }) => { + if (stderr) { + this.logger.error(`stderr: ${stderr}`); + } + }); + + // git config pull.rebase false + this.logger.info(`Executing: git -C ${repoPath} config pull.rebase false`); + await execAsync(`git -C ${repoPath} config pull.rebase false`).then(({ stderr }) => { + if (stderr) { + this.logger.error(`stderr: ${stderr}`); + } + }); + + this.logger.info(`Executing: git -C ${repoPath} rev-parse --abbrev-ref HEAD`); + const currentBranch = await execAsync(`git -C ${repoPath} rev-parse --abbrev-ref HEAD`).then(({ stdout }) => { + return stdout.trim(); + }); + + this.logger.info(`Executing: git -C ${repoPath} fetch origin && git -C ${repoPath} reset --hard origin/${currentBranch}`); + await execAsync(`git -C ${repoPath} fetch origin && git -C ${repoPath} reset --hard origin/${currentBranch}`); + + this.logger.info(`Pulled repo ${repoUrl} to ${repoPath}`); + return { success: true, message: '' }; + } catch (err) { + return this.handleRepoError(err); + } + }; +} diff --git a/packages/backend/src/modules/system/dto/system.dto.ts b/packages/backend/src/modules/system/dto/system.dto.ts new file mode 100644 index 0000000000..052295ee7d --- /dev/null +++ b/packages/backend/src/modules/system/dto/system.dto.ts @@ -0,0 +1,14 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod'; + +// Load +export class LoadDto extends createZodDto( + z.object({ + diskUsed: z.number().nullish().default(0), + diskSize: z.number().nullish().default(0), + percentUsed: z.number().nullish().default(0), + cpuLoad: z.number().nullish().default(0), + memoryTotal: z.number().nullish().default(0), + percentUsedMemory: z.number().nullish().default(0), + }), +) {} diff --git a/packages/backend/src/modules/system/system.controller.ts b/packages/backend/src/modules/system/system.controller.ts new file mode 100644 index 0000000000..60a79ffa69 --- /dev/null +++ b/packages/backend/src/modules/system/system.controller.ts @@ -0,0 +1,31 @@ +import { Controller, Get, Res, UseGuards } from '@nestjs/common'; +import type { Response } from 'express'; +import { ZodSerializerDto } from 'nestjs-zod'; +import { AuthGuard } from '../auth/auth.guard'; +import { LoadDto } from './dto/system.dto'; +import { SystemService } from './system.service'; + +@UseGuards(AuthGuard) +@Controller('system') +export class SystemController { + constructor(private readonly systemService: SystemService) {} + + @Get('/load') + @ZodSerializerDto(LoadDto) + async systemLoad(): Promise { + const res = await this.systemService.getSystemLoad(); + return res; + } + + @Get('/certificate') + async downloadLocalCertificate(@Res() res: Response) { + const cert = await this.systemService.getLocalCertificate(); + + res.set({ + 'Content-Type': 'application/x-pem-file', + 'Content-Disposition': 'attachment; filename=cert.pem', + }); + + res.send(cert); + } +} diff --git a/packages/backend/src/modules/system/system.module.ts b/packages/backend/src/modules/system/system.module.ts new file mode 100644 index 0000000000..99bda2b6a6 --- /dev/null +++ b/packages/backend/src/modules/system/system.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { SystemController } from './system.controller'; +import { SystemService } from './system.service'; + +@Module({ + imports: [], + controllers: [SystemController], + providers: [SystemService], + exports: [], +}) +export class SystemModule {} diff --git a/packages/backend/src/modules/system/system.service.ts b/packages/backend/src/modules/system/system.service.ts new file mode 100644 index 0000000000..590ea02d80 --- /dev/null +++ b/packages/backend/src/modules/system/system.service.ts @@ -0,0 +1,54 @@ +import { ConfigurationService } from '@/core/config/configuration.service'; +import { FilesystemService } from '@/core/filesystem/filesystem.service'; +import { LoggerService } from '@/core/logger/logger.service'; +import { Injectable } from '@nestjs/common'; +import si from 'systeminformation'; + +@Injectable() +export class SystemService { + constructor( + private readonly logger: LoggerService, + private readonly config: ConfigurationService, + private readonly filesystem: FilesystemService, + ) {} + + public async getSystemLoad() { + const { currentLoad } = await si.currentLoad(); + + const memResult = { total: 0, used: 0, available: 0 }; + + try { + const memInfo = await this.filesystem.readTextFile('/host/proc/meminfo'); + + memResult.total = Number(memInfo?.toString().match(/MemTotal:\s+(\d+)/)?.[1] ?? 0) * 1024; + memResult.available = Number(memInfo?.toString().match(/MemAvailable:\s+(\d+)/)?.[1] ?? 0) * 1024; + memResult.used = memResult.total - memResult.available; + } catch (e) { + this.logger.error(`Unable to read /host/proc/meminfo: ${e}`); + } + + const [disk0] = await si.fsSize(); + + const disk = disk0 ?? { available: 0, size: 0 }; + const diskFree = Math.round(disk.available / 1024 / 1024 / 1024); + const diskSize = Math.round(disk.size / 1024 / 1024 / 1024); + const diskUsed = diskSize - diskFree; + const percentUsed = Math.round((diskUsed / diskSize) * 100); + + const memoryTotal = Math.round(Number(memResult.total) / 1024 / 1024 / 1024); + const memoryFree = Math.round(Number(memResult.available) / 1024 / 1024 / 1024); + const percentUsedMemory = Math.round(((memoryTotal - memoryFree) / memoryTotal) * 100); + + return { diskUsed, diskSize, percentUsed, cpuLoad: currentLoad, memoryTotal, percentUsedMemory }; + } + + public async getLocalCertificate() { + const { dataDir } = this.config.get('directories'); + const filePath = `${dataDir}/traefik/tls/cert.pem`; + + if (await this.filesystem.pathExists(filePath)) { + const file = await this.filesystem.readTextFile(filePath); + return file; + } + } +} diff --git a/packages/backend/src/modules/user/dto/user.dto.ts b/packages/backend/src/modules/user/dto/user.dto.ts new file mode 100644 index 0000000000..270fa36986 --- /dev/null +++ b/packages/backend/src/modules/user/dto/user.dto.ts @@ -0,0 +1,13 @@ +import { createZodDto } from 'nestjs-zod'; +import { z } from 'zod'; + +export const userSchema = z.object({ + id: z.number(), + username: z.string(), + totpEnabled: z.boolean(), + locale: z.string(), + operator: z.boolean(), + hasSeenWelcome: z.boolean(), +}); + +export class UserDto extends createZodDto(userSchema) {} diff --git a/packages/backend/src/modules/user/user.module.ts b/packages/backend/src/modules/user/user.module.ts new file mode 100644 index 0000000000..cca8ed4048 --- /dev/null +++ b/packages/backend/src/modules/user/user.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { UserRepository } from './user.repository'; + +@Module({ + imports: [], + controllers: [], + providers: [UserRepository], + exports: [UserRepository], +}) +export class UserModule {} diff --git a/packages/backend/src/modules/user/user.repository.ts b/packages/backend/src/modules/user/user.repository.ts new file mode 100644 index 0000000000..10857c0efd --- /dev/null +++ b/packages/backend/src/modules/user/user.repository.ts @@ -0,0 +1,79 @@ +import { DatabaseService } from '@/core/database/database.service'; +import { type NewUser, userTable } from '@/core/database/schema'; +import { Injectable } from '@nestjs/common'; +import { eq } from 'drizzle-orm/sql'; + +@Injectable() +export class UserRepository { + constructor(private databaseService: DatabaseService) {} + + /** + * Given a username, return the user associated to it + * + * @param {string} username - The username of the user to return + */ + public async getUserByUsername(username: string) { + return this.databaseService.db.query.userTable.findFirst({ where: eq(userTable.username, username.trim().toLowerCase()) }); + } + + /** + * Given a userId, return the user associated to it + * + * @param {number} id - The id of the user to return + */ + public async getUserById(id: number) { + return this.databaseService.db.query.userTable.findFirst({ where: eq(userTable.id, Number(id)) }); + } + + /** + * Given a userId, return the user associated to it with only the id, username, and totpEnabled fields + * + * @param {number} id - The id of the user to return + */ + public async getUserDtoById(id: number) { + return this.databaseService.db.query.userTable.findFirst({ + where: eq(userTable.id, Number(id)), + columns: { id: true, username: true, totpEnabled: true, locale: true, operator: true, hasSeenWelcome: true }, + }); + } + + /** + * Given a userId, update the user with the given data + * + * @param {number} id - The id of the user to update + * @param {Partial} data - The data to update the user with + */ + public async updateUser(id: number, data: Partial) { + const updatedUsers = await this.databaseService.db + .update(userTable) + .set(data) + .where(eq(userTable.id, Number(id))) + .returning(); + + return updatedUsers[0]; + } + + /** + * Returns all operators registered in the system + */ + public async getOperators() { + return this.databaseService.db.select().from(userTable).where(eq(userTable.operator, true)); + } + + /** + * Returns the first operator found in the system + */ + public async getFirstOperator() { + return this.databaseService.db.query.userTable.findFirst({ where: eq(userTable.operator, true) }); + } + + /** + * Given user data, creates a new user + * + * @param {NewUser} data - The data to create the user with + */ + public async createUser(data: NewUser) { + const newUsers = await this.databaseService.db.insert(userTable).values(data).returning(); + return newUsers[0]; + } +} diff --git a/packages/backend/src/schemas/queue-schemas.ts b/packages/backend/src/schemas/queue-schemas.ts new file mode 100644 index 0000000000..f6d7c47336 --- /dev/null +++ b/packages/backend/src/schemas/queue-schemas.ts @@ -0,0 +1,33 @@ +import { appFormSchema } from '@/modules/app-lifecycle/dto/app-lifecycle.dto'; +import { z } from 'zod'; + +export const EVENT_TYPES = { + SYSTEM: 'system', + REPO: 'repo', + APP: 'app', +} as const; + +export type EventType = (typeof EVENT_TYPES)[keyof typeof EVENT_TYPES]; + +const updateAppCommandSchema = z.object({ + command: z.literal('update'), + appid: z.string(), + form: appFormSchema, + performBackup: z.boolean(), +}); + +const restoreAppCommandSchema = z.object({ + command: z.literal('restore'), + appid: z.string(), + filename: z.string(), +}); + +const systemCommandSchema = z.object({ + type: z.literal(EVENT_TYPES.SYSTEM), + command: z.literal('system_info'), +}); + +export const eventResultSchema = z.object({ + success: z.boolean(), + stdout: z.string(), +}); diff --git a/packages/backend/src/swagger.json b/packages/backend/src/swagger.json new file mode 100644 index 0000000000..66333789e1 --- /dev/null +++ b/packages/backend/src/swagger.json @@ -0,0 +1,2739 @@ +{ + "openapi": "3.0.0", + "paths": { + "/api/user-context": { + "get": { + "operationId": "userContext", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserContextDto" + } + } + } + } + } + } + }, + "/api/app-context": { + "get": { + "operationId": "appContext", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppContextDto" + } + } + } + } + } + } + }, + "/api/user-settings": { + "patch": { + "operationId": "updateUserSettings", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartialUserSettingsDto" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/acknowledge-welcome": { + "patch": { + "operationId": "acknowledgeWelcome", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AcknowledgeWelcomeBody" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/debug-sentry": { + "get": { + "operationId": "getError", + "parameters": [], + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/system/load": { + "get": { + "operationId": "systemLoad", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoadDto" + } + } + } + } + } + } + }, + "/api/system/certificate": { + "get": { + "operationId": "downloadLocalCertificate", + "parameters": [], + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/i18n/locales/{lng}/{ns}.json": { + "get": { + "operationId": "getTranslation", + "parameters": [ + { + "name": "lng", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "ns", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/auth/login": { + "post": { + "operationId": "login", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginBody" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginDto" + } + } + } + } + } + } + }, + "/api/auth/verify-totp": { + "post": { + "operationId": "verifyTotp", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VerifyTotpBody" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginDto" + } + } + } + } + } + } + }, + "/api/auth/register": { + "post": { + "operationId": "register", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterBody" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterDto" + } + } + } + } + } + } + }, + "/api/auth/logout": { + "post": { + "operationId": "logout", + "parameters": [], + "responses": { + "201": { + "description": "" + } + } + } + }, + "/api/auth/username": { + "patch": { + "operationId": "changeUsername", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeUsernameBody" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/auth/password": { + "patch": { + "operationId": "changePassword", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangePasswordBody" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/auth/totp/get-uri": { + "patch": { + "operationId": "getTotpUri", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTotpUriBody" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTotpUriDto" + } + } + } + } + } + } + }, + "/api/auth/totp/setup": { + "patch": { + "operationId": "setupTotp", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetupTotpBody" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/auth/totp/disable": { + "patch": { + "operationId": "disableTotp", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DisableTotpBody" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/auth/reset-password": { + "post": { + "operationId": "resetPassword", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResetPasswordBody" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResetPasswordDto" + } + } + } + } + } + }, + "delete": { + "operationId": "cancelResetPassword", + "parameters": [], + "responses": { + "200": { + "description": "" + } + } + }, + "get": { + "operationId": "checkResetPasswordRequest", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckResetPasswordRequestDto" + } + } + } + } + } + } + }, + "/api/apps/installed": { + "get": { + "operationId": "getInstalledApps", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MyAppsDto" + } + } + } + } + } + } + }, + "/api/apps/guest": { + "get": { + "operationId": "getGuestApps", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuestAppsDto" + } + } + } + } + } + } + }, + "/api/apps/search": { + "get": { + "operationId": "searchApps", + "parameters": [ + { + "name": "category", + "required": false, + "in": "query", + "schema": { + "enum": [ + "network", + "media", + "development", + "automation", + "social", + "utilities", + "photography", + "security", + "featured", + "books", + "data", + "music", + "finance", + "gaming", + "ai" + ], + "type": "string" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "search", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchAppsDto" + } + } + } + } + } + } + }, + "/api/apps/{id}": { + "get": { + "operationId": "getAppDetails", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppDetailsDto" + } + } + } + } + } + } + }, + "/api/apps/{id}/image": { + "get": { + "operationId": "getImage", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/app-lifecycle/{id}/install": { + "post": { + "operationId": "installApp", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppFormBody" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + } + } + }, + "/api/app-lifecycle/{id}/start": { + "post": { + "operationId": "startApp", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "" + } + } + } + }, + "/api/app-lifecycle/{id}/stop": { + "post": { + "operationId": "stopApp", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "" + } + } + } + }, + "/api/app-lifecycle/{id}/restart": { + "post": { + "operationId": "restartApp", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "" + } + } + } + }, + "/api/app-lifecycle/{id}/uninstall": { + "delete": { + "operationId": "uninstallApp", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/app-lifecycle/{id}/reset": { + "post": { + "operationId": "resetApp", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "" + } + } + } + }, + "/api/backups/{appid}/backup": { + "post": { + "operationId": "backupApp", + "parameters": [ + { + "name": "appid", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "" + } + } + } + }, + "/api/backups/{appid}/restore": { + "post": { + "operationId": "restoreAppBackup", + "parameters": [ + { + "name": "appid", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestoreAppBackupDto" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + } + } + }, + "/api/backups/{id}": { + "get": { + "operationId": "getAppBackups", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAppBackupsDto" + } + } + } + } + } + } + }, + "/api/backups/{appid}": { + "delete": { + "operationId": "deleteAppBackup", + "parameters": [ + { + "name": "appid", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteAppBackupBodyDto" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/links": { + "get": { + "operationId": "getLinks", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinksDto" + } + } + } + } + } + }, + "post": { + "operationId": "createLink", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkBodyDto" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + } + } + }, + "/api/links/{id}": { + "patch": { + "operationId": "editLink", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditLinkBodyDto" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + } + }, + "delete": { + "operationId": "deleteLink", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, + "/api/health": { + "get": { + "operationId": "check", + "parameters": [], + "responses": { + "200": { + "description": "The Health Check is successful", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok" + }, + "info": { + "type": "object", + "example": { + "database": { + "status": "up" + } + }, + "additionalProperties": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + }, + "additionalProperties": true + }, + "nullable": true + }, + "error": { + "type": "object", + "example": {}, + "additionalProperties": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + }, + "additionalProperties": true + }, + "nullable": true + }, + "details": { + "type": "object", + "example": { + "database": { + "status": "up" + } + }, + "additionalProperties": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + }, + "additionalProperties": true + } + } + } + } + } + } + }, + "503": { + "description": "The Health Check is not successful", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "error" + }, + "info": { + "type": "object", + "example": { + "database": { + "status": "up" + } + }, + "additionalProperties": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + }, + "additionalProperties": true + }, + "nullable": true + }, + "error": { + "type": "object", + "example": { + "redis": { + "status": "down", + "message": "Could not connect" + } + }, + "additionalProperties": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + }, + "additionalProperties": true + }, + "nullable": true + }, + "details": { + "type": "object", + "example": { + "database": { + "status": "up" + }, + "redis": { + "status": "down", + "message": "Could not connect" + } + }, + "additionalProperties": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + }, + "additionalProperties": true + } + } + } + } + } + } + } + } + } + } + }, + "info": { + "title": "Runtipi API", + "description": "API specs for Runtipi", + "version": "1.0", + "contact": {} + }, + "tags": [], + "servers": [], + "components": { + "schemas": { + "UserContextDto": { + "type": "object", + "properties": { + "isLoggedIn": { + "description": "Indicates if the user is logged in", + "type": "boolean" + }, + "isConfigured": { + "description": "Indicates if the app is already configured", + "type": "boolean" + }, + "isGuestDashboardEnabled": { + "description": "Indicates if the guest dashboard is enabled", + "type": "boolean" + } + }, + "required": [ + "isLoggedIn", + "isConfigured", + "isGuestDashboardEnabled" + ] + }, + "AppContextDto": { + "type": "object", + "properties": { + "version": { + "type": "object", + "properties": { + "current": { + "type": "string" + }, + "latest": { + "type": "string" + }, + "body": { + "type": "string" + } + }, + "required": [ + "current", + "latest", + "body" + ] + }, + "userSettings": { + "type": "object", + "properties": { + "dnsIp": { + "type": "string" + }, + "internalIp": { + "type": "string" + }, + "postgresPort": { + "type": "number" + }, + "appsRepoUrl": { + "type": "string", + "format": "uri" + }, + "domain": { + "type": "string" + }, + "appDataPath": { + "type": "string" + }, + "localDomain": { + "type": "string" + }, + "demoMode": { + "type": "boolean" + }, + "guestDashboard": { + "type": "boolean" + }, + "allowAutoThemes": { + "type": "boolean" + }, + "allowErrorMonitoring": { + "type": "boolean" + }, + "persistTraefikConfig": { + "type": "boolean" + }, + "port": { + "type": "number" + }, + "sslPort": { + "type": "number" + }, + "listenIp": { + "type": "string" + }, + "timeZone": { + "type": "string" + } + }, + "required": [ + "dnsIp", + "internalIp", + "postgresPort", + "appsRepoUrl", + "domain", + "appDataPath", + "localDomain", + "demoMode", + "guestDashboard", + "allowAutoThemes", + "allowErrorMonitoring", + "persistTraefikConfig", + "port", + "sslPort", + "listenIp", + "timeZone" + ] + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "username": { + "type": "string" + }, + "totpEnabled": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "operator": { + "type": "boolean" + }, + "hasSeenWelcome": { + "type": "boolean" + } + }, + "required": [ + "id", + "username", + "totpEnabled", + "locale", + "operator", + "hasSeenWelcome" + ] + }, + "apps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "short_desc": { + "type": "string" + }, + "categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "network", + "media", + "development", + "automation", + "social", + "utilities", + "photography", + "security", + "featured", + "books", + "data", + "music", + "finance", + "gaming", + "ai" + ] + }, + "default": [] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "created_at": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": false, + "default": 0 + }, + "supported_architectures": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "arm64", + "amd64" + ] + } + }, + "available": { + "type": "boolean" + } + }, + "required": [ + "id", + "name", + "short_desc", + "available" + ] + } + } + }, + "required": [ + "version", + "userSettings", + "user", + "apps" + ] + }, + "PartialUserSettingsDto": { + "type": "object", + "properties": { + "dnsIp": { + "type": "string" + }, + "internalIp": { + "type": "string" + }, + "postgresPort": { + "type": "number" + }, + "appsRepoUrl": { + "type": "string", + "format": "uri" + }, + "domain": { + "type": "string" + }, + "appDataPath": { + "type": "string" + }, + "localDomain": { + "type": "string" + }, + "demoMode": { + "type": "boolean" + }, + "guestDashboard": { + "type": "boolean" + }, + "allowAutoThemes": { + "type": "boolean" + }, + "allowErrorMonitoring": { + "type": "boolean" + }, + "persistTraefikConfig": { + "type": "boolean" + }, + "port": { + "type": "number" + }, + "sslPort": { + "type": "number" + }, + "listenIp": { + "type": "string" + }, + "timeZone": { + "type": "string" + } + } + }, + "AcknowledgeWelcomeBody": { + "type": "object", + "properties": { + "allowErrorMonitoring": { + "type": "boolean" + } + }, + "required": [ + "allowErrorMonitoring" + ] + }, + "LoadDto": { + "type": "object", + "properties": { + "diskUsed": { + "type": "number", + "nullable": true, + "default": 0 + }, + "diskSize": { + "type": "number", + "nullable": true, + "default": 0 + }, + "percentUsed": { + "type": "number", + "nullable": true, + "default": 0 + }, + "cpuLoad": { + "type": "number", + "nullable": true, + "default": 0 + }, + "memoryTotal": { + "type": "number", + "nullable": true, + "default": 0 + }, + "percentUsedMemory": { + "type": "number", + "nullable": true, + "default": 0 + } + } + }, + "LoginBody": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "LoginDto": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "totpSessionId": { + "type": "string" + } + }, + "required": [ + "success" + ] + }, + "VerifyTotpBody": { + "type": "object", + "properties": { + "totpCode": { + "type": "string" + }, + "totpSessionId": { + "type": "string" + } + }, + "required": [ + "totpCode", + "totpSessionId" + ] + }, + "RegisterBody": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + }, + "RegisterDto": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ] + }, + "ChangeUsernameBody": { + "type": "object", + "properties": { + "newUsername": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "newUsername", + "password" + ] + }, + "ChangePasswordBody": { + "type": "object", + "properties": { + "currentPassword": { + "type": "string" + }, + "newPassword": { + "type": "string" + } + }, + "required": [ + "currentPassword", + "newPassword" + ] + }, + "GetTotpUriBody": { + "type": "object", + "properties": { + "password": { + "type": "string" + } + }, + "required": [ + "password" + ] + }, + "GetTotpUriDto": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "key", + "uri" + ] + }, + "SetupTotpBody": { + "type": "object", + "properties": { + "code": { + "type": "string" + } + }, + "required": [ + "code" + ] + }, + "DisableTotpBody": { + "type": "object", + "properties": { + "password": { + "type": "string" + } + }, + "required": [ + "password" + ] + }, + "ResetPasswordBody": { + "type": "object", + "properties": { + "newPassword": { + "type": "string" + } + }, + "required": [ + "newPassword" + ] + }, + "ResetPasswordDto": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "email": { + "type": "string" + } + }, + "required": [ + "success", + "email" + ] + }, + "CheckResetPasswordRequestDto": { + "type": "object", + "properties": { + "isRequestPending": { + "type": "boolean" + } + }, + "required": [ + "isRequestPending" + ] + }, + "MyAppsDto": { + "type": "object", + "properties": { + "installed": { + "type": "array", + "items": { + "type": "object", + "properties": { + "app": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "running", + "stopped", + "starting", + "stopping", + "updating", + "missing", + "installing", + "uninstalling", + "resetting", + "restarting", + "backing_up", + "restoring" + ] + }, + "lastOpened": { + "type": "string", + "nullable": true + }, + "numOpened": { + "type": "number", + "default": 0 + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "number" + }, + "exposed": { + "type": "boolean" + }, + "openPort": { + "type": "boolean" + }, + "exposedLocal": { + "type": "boolean" + }, + "domain": { + "type": "string", + "nullable": true + }, + "isVisibleOnGuestDashboard": { + "type": "boolean" + } + }, + "required": [ + "id", + "status", + "lastOpened", + "version", + "exposed", + "openPort", + "exposedLocal", + "domain", + "isVisibleOnGuestDashboard" + ] + }, + "info": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "short_desc": { + "type": "string" + }, + "categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "network", + "media", + "development", + "automation", + "social", + "utilities", + "photography", + "security", + "featured", + "books", + "data", + "music", + "finance", + "gaming", + "ai" + ] + }, + "default": [] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "created_at": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": false, + "default": 0 + }, + "supported_architectures": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "arm64", + "amd64" + ] + } + }, + "available": { + "type": "boolean" + } + }, + "required": [ + "id", + "name", + "short_desc", + "available" + ] + }, + "updateInfo": { + "type": "object", + "properties": { + "latestVersion": { + "type": "number" + }, + "minTipiVersion": { + "type": "string" + }, + "latestDockerVersion": { + "type": "string" + } + }, + "required": [ + "latestVersion" + ] + } + }, + "required": [ + "app", + "info", + "updateInfo" + ] + } + } + }, + "required": [ + "installed" + ] + }, + "GuestAppsDto": { + "type": "object", + "properties": { + "installed": { + "type": "array", + "items": { + "type": "object", + "properties": { + "app": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "running", + "stopped", + "starting", + "stopping", + "updating", + "missing", + "installing", + "uninstalling", + "resetting", + "restarting", + "backing_up", + "restoring" + ] + }, + "lastOpened": { + "type": "string", + "nullable": true + }, + "numOpened": { + "type": "number", + "default": 0 + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "number" + }, + "exposed": { + "type": "boolean" + }, + "openPort": { + "type": "boolean" + }, + "exposedLocal": { + "type": "boolean" + }, + "domain": { + "type": "string", + "nullable": true + }, + "isVisibleOnGuestDashboard": { + "type": "boolean" + } + }, + "required": [ + "id", + "status", + "lastOpened", + "version", + "exposed", + "openPort", + "exposedLocal", + "domain", + "isVisibleOnGuestDashboard" + ] + }, + "info": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "available": { + "type": "boolean" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "port": { + "type": "number", + "minimum": 1, + "exclusiveMinimum": false, + "maximum": 65535, + "exclusiveMaximum": false + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "default": "" + }, + "version": { + "type": "string", + "default": "latest" + }, + "tipi_version": { + "type": "number" + }, + "short_desc": { + "type": "string" + }, + "author": { + "type": "string" + }, + "source": { + "type": "string" + }, + "website": { + "type": "string" + }, + "force_expose": { + "type": "boolean", + "default": false + }, + "generate_vapid_keys": { + "type": "boolean", + "default": false + }, + "categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "network", + "media", + "development", + "automation", + "social", + "utilities", + "photography", + "security", + "featured", + "books", + "data", + "music", + "finance", + "gaming", + "ai" + ] + }, + "default": [] + }, + "url_suffix": { + "type": "string" + }, + "form_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "text", + "password", + "email", + "number", + "fqdn", + "ip", + "fqdnip", + "url", + "random", + "boolean" + ] + }, + "label": { + "type": "string" + }, + "placeholder": { + "type": "string" + }, + "max": { + "type": "number" + }, + "min": { + "type": "number" + }, + "hint": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "label", + "value" + ] + } + }, + "required": { + "type": "boolean", + "default": false + }, + "default": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "regex": { + "type": "string" + }, + "pattern_error": { + "type": "string" + }, + "env_variable": { + "type": "string" + }, + "encoding": { + "type": "string", + "enum": [ + "hex", + "base64" + ] + } + }, + "required": [ + "type", + "label", + "env_variable" + ] + }, + "default": [] + }, + "https": { + "type": "boolean", + "default": false + }, + "exposable": { + "type": "boolean", + "default": false + }, + "no_gui": { + "type": "boolean", + "default": false + }, + "supported_architectures": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "arm64", + "amd64" + ] + } + }, + "uid": { + "type": "number" + }, + "gid": { + "type": "number" + }, + "dynamic_config": { + "type": "boolean", + "default": false + }, + "min_tipi_version": { + "type": "string" + }, + "created_at": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": false, + "default": 0 + }, + "updated_at": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": false, + "default": 0 + } + }, + "required": [ + "id", + "available", + "port", + "name", + "tipi_version", + "short_desc", + "author", + "source" + ] + }, + "updateInfo": { + "type": "object", + "properties": { + "latestVersion": { + "type": "number" + }, + "minTipiVersion": { + "type": "string" + }, + "latestDockerVersion": { + "type": "string" + } + }, + "required": [ + "latestVersion" + ] + } + }, + "required": [ + "app", + "info", + "updateInfo" + ] + } + } + }, + "required": [ + "installed" + ] + }, + "SearchAppsDto": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "short_desc": { + "type": "string" + }, + "categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "network", + "media", + "development", + "automation", + "social", + "utilities", + "photography", + "security", + "featured", + "books", + "data", + "music", + "finance", + "gaming", + "ai" + ] + }, + "default": [] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "created_at": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": false, + "default": 0 + }, + "supported_architectures": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "arm64", + "amd64" + ] + } + }, + "available": { + "type": "boolean" + } + }, + "required": [ + "id", + "name", + "short_desc", + "available" + ] + } + }, + "nextCursor": { + "type": "string" + }, + "total": { + "type": "number" + } + }, + "required": [ + "data", + "total" + ] + }, + "AppDetailsDto": { + "type": "object", + "properties": { + "info": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "available": { + "type": "boolean" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "port": { + "type": "number", + "minimum": 1, + "exclusiveMinimum": false, + "maximum": 65535, + "exclusiveMaximum": false + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "default": "" + }, + "version": { + "type": "string", + "default": "latest" + }, + "tipi_version": { + "type": "number" + }, + "short_desc": { + "type": "string" + }, + "author": { + "type": "string" + }, + "source": { + "type": "string" + }, + "website": { + "type": "string" + }, + "force_expose": { + "type": "boolean", + "default": false + }, + "generate_vapid_keys": { + "type": "boolean", + "default": false + }, + "categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "network", + "media", + "development", + "automation", + "social", + "utilities", + "photography", + "security", + "featured", + "books", + "data", + "music", + "finance", + "gaming", + "ai" + ] + }, + "default": [] + }, + "url_suffix": { + "type": "string" + }, + "form_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "text", + "password", + "email", + "number", + "fqdn", + "ip", + "fqdnip", + "url", + "random", + "boolean" + ] + }, + "label": { + "type": "string" + }, + "placeholder": { + "type": "string" + }, + "max": { + "type": "number" + }, + "min": { + "type": "number" + }, + "hint": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "label", + "value" + ] + } + }, + "required": { + "type": "boolean", + "default": false + }, + "default": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "regex": { + "type": "string" + }, + "pattern_error": { + "type": "string" + }, + "env_variable": { + "type": "string" + }, + "encoding": { + "type": "string", + "enum": [ + "hex", + "base64" + ] + } + }, + "required": [ + "type", + "label", + "env_variable" + ] + }, + "default": [] + }, + "https": { + "type": "boolean", + "default": false + }, + "exposable": { + "type": "boolean", + "default": false + }, + "no_gui": { + "type": "boolean", + "default": false + }, + "supported_architectures": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "arm64", + "amd64" + ] + } + }, + "uid": { + "type": "number" + }, + "gid": { + "type": "number" + }, + "dynamic_config": { + "type": "boolean", + "default": false + }, + "min_tipi_version": { + "type": "string" + }, + "created_at": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": false, + "default": 0 + }, + "updated_at": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": false, + "default": 0 + } + }, + "required": [ + "id", + "available", + "port", + "name", + "tipi_version", + "short_desc", + "author", + "source" + ] + }, + "app": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "running", + "stopped", + "starting", + "stopping", + "updating", + "missing", + "installing", + "uninstalling", + "resetting", + "restarting", + "backing_up", + "restoring" + ] + }, + "lastOpened": { + "type": "string", + "nullable": true + }, + "numOpened": { + "type": "number", + "default": 0 + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "number" + }, + "exposed": { + "type": "boolean" + }, + "openPort": { + "type": "boolean" + }, + "exposedLocal": { + "type": "boolean" + }, + "domain": { + "type": "string", + "nullable": true + }, + "isVisibleOnGuestDashboard": { + "type": "boolean" + } + }, + "required": [ + "id", + "status", + "lastOpened", + "version", + "exposed", + "openPort", + "exposedLocal", + "domain", + "isVisibleOnGuestDashboard" + ] + }, + "updateInfo": { + "type": "object", + "properties": { + "latestVersion": { + "type": "number" + }, + "minTipiVersion": { + "type": "string" + }, + "latestDockerVersion": { + "type": "string" + } + }, + "required": [ + "latestVersion" + ] + } + }, + "required": [ + "info", + "app", + "updateInfo" + ] + }, + "AppFormBody": { + "type": "object", + "properties": { + "exposed": { + "type": "boolean" + }, + "exposedLocal": { + "type": "boolean" + }, + "openPort": { + "type": "boolean", + "default": true + }, + "domain": { + "type": "string" + }, + "isVisibleOnGuestDashboard": { + "type": "boolean" + } + } + }, + "RestoreAppBackupDto": { + "type": "object", + "properties": { + "filename": { + "type": "string" + } + }, + "required": [ + "filename" + ] + }, + "GetAppBackupsDto": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "size": { + "type": "number" + }, + "date": { + "type": "number" + } + }, + "required": [ + "id", + "size", + "date" + ] + } + }, + "total": { + "type": "number" + }, + "currentPage": { + "type": "number" + }, + "lastPage": { + "type": "number" + } + }, + "required": [ + "data", + "total", + "currentPage", + "lastPage" + ] + }, + "DeleteAppBackupBodyDto": { + "type": "object", + "properties": { + "filename": { + "type": "string" + } + }, + "required": [ + "filename" + ] + }, + "LinksDto": { + "type": "object", + "properties": { + "links": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 20 + }, + "description": { + "type": "string", + "minLength": 0, + "maxLength": 50, + "nullable": true + }, + "url": { + "type": "string", + "format": "uri" + }, + "iconUrl": { + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "string", + "maxLength": 0 + } + ], + "nullable": true + }, + "userId": { + "type": "number" + } + }, + "required": [ + "id", + "title", + "description", + "url", + "iconUrl", + "userId" + ] + } + } + }, + "required": [ + "links" + ] + }, + "LinkBodyDto": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 20 + }, + "url": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string", + "minLength": 0, + "maxLength": 50 + }, + "iconUrl": { + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "string", + "maxLength": 0 + } + ] + } + }, + "required": [ + "title", + "url" + ] + }, + "EditLinkBodyDto": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 20 + }, + "url": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string", + "minLength": 0, + "maxLength": 50 + }, + "iconUrl": { + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "string", + "maxLength": 0 + } + ] + } + }, + "required": [ + "title", + "url" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/backend/src/types/express/index.d.ts b/packages/backend/src/types/express/index.d.ts new file mode 100644 index 0000000000..b6974e3089 --- /dev/null +++ b/packages/backend/src/types/express/index.d.ts @@ -0,0 +1,9 @@ +import { UserDto } from '@/modules/user/user.repository'; + +declare global { + namespace Express { + interface Request { + user?: UserDto; + } + } +} diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json new file mode 100644 index 0000000000..618df4d7f7 --- /dev/null +++ b/packages/backend/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + /* Base Options: */ + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": true, + /* Strictness */ + "strict": true, + "strictNullChecks": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "module": "preserve", + "outDir": "./dist", + "noEmit": false, + "lib": [ + "es2022" + ], + "typeRoots": [ + "./src/@types", + "./node_modules/@types" + ], + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "baseUrl": ".", + "paths": { + "@/*": [ + "./src/*" + ] + } + } +} diff --git a/packages/frontend/.gitignore b/packages/frontend/.gitignore new file mode 100644 index 0000000000..cb622fa984 --- /dev/null +++ b/packages/frontend/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +public/js/* +!public/js/.gitkeep diff --git a/packages/frontend/README.md b/packages/frontend/README.md new file mode 100644 index 0000000000..74872fd4af --- /dev/null +++ b/packages/frontend/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/packages/frontend/index.html b/packages/frontend/index.html new file mode 100644 index 0000000000..b9a1017c53 --- /dev/null +++ b/packages/frontend/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + Runtipi + + + +
+ + + + diff --git a/packages/frontend/package.json b/packages/frontend/package.json new file mode 100644 index 0000000000..c1f330b528 --- /dev/null +++ b/packages/frontend/package.json @@ -0,0 +1,85 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview", + "tsc": "tsc --noEmit", + "postinstall": "./scripts/postinstall.sh" + }, + "dependencies": { + "@hey-api/client-fetch": "^0.4.0", + "@hookform/resolvers": "^3.9.0", + "@radix-ui/react-context-menu": "^2.2.1", + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dropdown-menu": "^2.1.1", + "@radix-ui/react-scroll-area": "^1.1.0", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.0", + "@tabler/core": "1.0.0-beta21", + "@tabler/icons-react": "^3.17.0", + "@tanstack/react-query": "^5.56.2", + "@tanstack/react-query-devtools": "^5.58.0", + "@uidotdev/usehooks": "^2.4.1", + "backend": "workspace:*", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "consola": "^3.2.3", + "dompurify": "^3.1.7", + "geist": "^1.3.1", + "i18next": "^23.15.1", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^2.6.1", + "immer": "^10.1.1", + "js-cookie": "^3.0.5", + "qrcode.react": "^4.1.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-error-boundary": "^4.0.13", + "react-hook-form": "^7.53.0", + "react-hot-toast": "^2.4.1", + "react-i18next": "^15.0.2", + "react-markdown": "^9.0.1", + "react-router-dom": "^6.26.2", + "react-timezone-select": "^3.2.8", + "react-tooltip": "^5.28.0", + "rehype-raw": "^7.0.0", + "remark-breaks": "^4.0.0", + "remark-gfm": "^4.0.0", + "semver": "^7.6.3", + "socket.io-client": "^4.8.0", + "validator": "^13.12.0", + "zod": "^3.23.8", + "zustand": "5.0.0-rc.2" + }, + "devDependencies": { + "@eslint/js": "^9.9.0", + "@faker-js/faker": "^9.0.3", + "@hey-api/openapi-ts": "^0.53.7", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.1", + "@testing-library/user-event": "^14.5.2", + "@types/dompurify": "^3.0.5", + "@types/js-cookie": "^3.0.6", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/semver": "^7.5.8", + "@types/validator": "^13.12.2", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^9.9.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.1", + "vite": "^5.4.1", + "vitest": "^2.1.1" + } +} diff --git a/packages/frontend/public/app-not-found.jpg b/packages/frontend/public/app-not-found.jpg new file mode 100644 index 0000000000000000000000000000000000000000..59c1a6ccc7213cad9a7ddeec0091d7aeb544d71b GIT binary patch literal 9582 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 literal 0 HcmV?d00001 diff --git a/packages/frontend/public/empty.svg b/packages/frontend/public/empty.svg new file mode 100644 index 0000000000..612b259296 --- /dev/null +++ b/packages/frontend/public/empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/frontend/public/error.png b/packages/frontend/public/error.png new file mode 100644 index 0000000000000000000000000000000000000000..f0020369998cf2e4e5de9afde0ee2a63dca4f618 GIT binary patch literal 33481 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�cO}XmTg{>wF zj_a@%MbUa|b#+eshHxXZ#d;^~yEk?u8b?Fy(Hj-$vpE80Fx4YQ^;d5&iHCEa03`La zI#u8E69lC?O3x`HGSUG}ySU0E|5L#m7-ut942l^7UYmWu;#bU%-SonhT`qS&Xb`nCqh&%L%Y9V#Bfylepa0hUeh^*!Q9 z=mBo?OertZ!8JNkA8I;P;Ej-MGM5yW(&WRGQ^E`8t_f zPRz}HX@i@dka0#U_qDZm|9N&re9F?WDv)zGh_PtO12iH@Ek!r6IAcgUGcQBgtw{g2 zSTQ&YC0gmdZEc^}XyqH13LBg&5Z?qQF28r!KiL&=US3tmGo#UZNEVcP6+;}{+GdU& zo`wKB>6kKK`shUiArL0V08^y%2o%jwAQwm)MvDpqB{zS)WZNIAjXawGF`<4ZE|Kgt zWLz%ceydr?app`eEGzO(9DnhPA$&Kwcys45N24H zdbR+9IE7T8n85?Ja>OuvGr<7wg1-60i$ zn&@+qsyS%6g@Ok2v))uDw5_duha5Q%Ue4Mt!E-g%l93Bq22l@y_UvFR3zwxLqJKX8 zZiaBeiSe`T^qm9*-H>ai;Xf6u|5od3w$&9cX-|ncEY!cBt$XDHgtLz>H3v|ZXrKfz zl+N}>8ON1{Ge;m!hZ`bgzDKG(KJiN)k~}g#>APkrC93a4@T25ZKy^wZ_`d=<3iVQs($Qib)GGC5Z{zwKBRkIC&p%4jz%gU5<34j>0EGYjPN5njT zogeY+{(W`-)nfqBMI*It$_2vkx%{YDkr7q~Q3bsq#fy;RSVsS@R``vy@o0~fpRENtmIVmX+u>f~%8fce2wnm&0UcPxLd20C99k6IOLkM&@ z9VH~F)*+y=FHn7!;%TLipC`I#@k}muvG9_r$fr-a5LyTv&lklG0m4V1yxqGzdEooY z((m0N6dpJBZp$sqhCVBewmBk#;y@zcdsqaS;dEnoFp_g3z$P5WC8P>37BeACqS0(; zHmG}gN()V~>pxxds&B;%x3?+mwHg`SQH05XUQ@!!buQtD?C%>tLY~rhSR{K_Hy;F4 z;d97G5<%Z-TnEodk^{8pO8;P8EQ~gMEYo`z-z%IA(QaO5UT-T zK?zv~;3hB=4?;pX6q|a;$v*IHh(hMY9$Y%^)l^&<>E|lXbqSt(=2@tw_6*8@ zg;fs9do_G|LKnpA243^@Fayv=Y{T=eVi0SD!eQBBV*&H4+ur`P*T6spDr9}s+sG*7y1&aSHHCI5(5HE5Pw1>R>LotYV*n|m2@xa>UD5vNJ;GHM2 z^mv=Tmk{Z>0APc7+WBy`6@-xR(i2!cNW59Wk6+t9o>zpR61cEIQMeyl(l+wyN>K%+ zd+q=pi>Z)0PnrG+zjx-6-cl(SAt{!#h6$1N#tT@>{fmn$2&A>~cI@C^u9Uj<`k}vh z-*%UiIM784Lf7J9l3NW?$}CQ~cz=chi1}7)4Si5YiatXwgG9upK%J|E9^RL8WD1zP zua3U(--l6Y--_on9P^Jjth6OQwMo>P|->) zTU9>FHgn*6_$(mdA?!m&)7p@u4Tk@l;0#iW5}y14K*VcM>pc$*^^!cQ>G(Q0CkPFJ z$Q4=$8w3@|x+u-qm%A}8pjCq?M=CUtX!;jk{j_sfBa`I3`Zal?=qzT7dGmhT^>mh=g1$k74UO-_vreJ_VK&Aa#W1$T(@E}n)Mx!>| z5TmG@E+}8dk8)goixRdOB09IPO!!&6x)XZaV+Ezei&=inKv@(9q7ZHySfP%{KbJPt zRUt0I3M_ZBhIJQFwgsnL{Ua~~Y8bwVH8sg~{Dp^iphiPQ+2F}kJG3K1%IBg?$lU};8>qY?EUFF>9#sL6bxGej;sbq@Z4jz}B+FUla3hMev&l%@Wm znfuAL=m2z$T0{th3^x0#0@9UP`pymN9>&!0+nnb3EqP#K9|K0jg$EOFPN7+P$$ufrcSBDZ z%A-q1F|gCjEalxZ!Mj-R{=^_Xb%9-6CKEJPlPXm7+72f&UN7L;%MC literal 0 HcmV?d00001 diff --git a/packages/frontend/public/icons/apple-touch-icon.png b/packages/frontend/public/icons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..981bf257fce218eed430daebf08a26767901a95f GIT binary patch literal 6728 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 edqyCiEdD>?S7CdGH+ggb0000Px@`$B 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 P00000NkvXXu0mjf`S3ER literal 0 HcmV?d00001 diff --git a/packages/frontend/public/icons/favicon.ico b/packages/frontend/public/icons/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..58a77e9313754fcb73632e3e5118d7b2471313ba GIT binary patch literal 15086 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 BJ4gTk literal 0 HcmV?d00001 diff --git a/packages/frontend/public/icons/favicon.svg b/packages/frontend/public/icons/favicon.svg new file mode 100644 index 0000000000..3ce294a3e4 --- /dev/null +++ b/packages/frontend/public/icons/favicon.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/frontend/public/icons/site.webmanifest b/packages/frontend/public/icons/site.webmanifest new file mode 100644 index 0000000000..f435806c0c --- /dev/null +++ b/packages/frontend/public/icons/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "MyWebSite", + "short_name": "MySite", + "icons": [ + { + "src": "/icons/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/icons/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/packages/frontend/public/icons/web-app-manifest-192x192.png b/packages/frontend/public/icons/web-app-manifest-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..f7caea8d9ee6b6558772a41db161baad3d2311ff GIT binary patch literal 6289 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 Hu0mjfq!x&t literal 0 HcmV?d00001 diff --git a/packages/frontend/public/icons/web-app-manifest-512x512.png b/packages/frontend/public/icons/web-app-manifest-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..3f41195444774541983e6b62571bd27e3d51b63f GIT binary patch literal 20132 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 { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + const headers = Object.fromEntries(requestClone.headers.entries()) + + // Remove internal MSW request header so the passthrough request + // complies with any potential CORS preflight checks on the server. + // Some servers forbid unknown request headers. + delete headers['x-msw-intention'] + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/packages/frontend/public/placeholder.png b/packages/frontend/public/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..5b4dbec602b2909b0c912d48c6f818074535b19a GIT binary patch literal 136 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�G4hG#n}A1k{2j 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ⅆcmY*g0+DYbR~=d`l%FMm@wH7d{Y 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 NhR58g*;D+s{U2&Mai#zO literal 0 HcmV?d00001 diff --git a/packages/frontend/public/vite.svg b/packages/frontend/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/packages/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/frontend/scripts/postinstall.sh b/packages/frontend/scripts/postinstall.sh new file mode 100755 index 0000000000..c3c850fe94 --- /dev/null +++ b/packages/frontend/scripts/postinstall.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cp ./node_modules/@tabler/core/dist/js/tabler.min.js ./public/js/tabler.min.js diff --git a/packages/frontend/src/App.css b/packages/frontend/src/App.css new file mode 100644 index 0000000000..8de7af009b --- /dev/null +++ b/packages/frontend/src/App.css @@ -0,0 +1,17 @@ +@import '@tabler/core/dist/css/tabler.min.css'; + + +#root { + overflow-y: scroll; + height: 100%; +} + +main { + height: 100%; +} + +.tooltip { + max-width: 300px; + text-align: center; +} + diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx new file mode 100644 index 0000000000..b980ae2279 --- /dev/null +++ b/packages/frontend/src/App.tsx @@ -0,0 +1,16 @@ +import { Navigate } from 'react-router-dom'; +import { useUserContext } from './context/user-context'; + +export const App = () => { + const { isLoggedIn, isConfigured, isGuestDashboardEnabled } = useUserContext(); + + if (isLoggedIn || isGuestDashboardEnabled) { + return ; + } + + if (isConfigured) { + return ; + } + + return ; +}; diff --git a/packages/frontend/src/api-client/@tanstack/react-query.gen.ts b/packages/frontend/src/api-client/@tanstack/react-query.gen.ts new file mode 100644 index 0000000000..e53c00d6d7 --- /dev/null +++ b/packages/frontend/src/api-client/@tanstack/react-query.gen.ts @@ -0,0 +1,983 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options } from '@hey-api/client-fetch'; +import { queryOptions, type UseMutationOptions, infiniteQueryOptions, type InfiniteData } from '@tanstack/react-query'; +import { + client, + userContext, + appContext, + updateUserSettings, + acknowledgeWelcome, + systemLoad, + getTranslation, + login, + verifyTotp, + register, + logout, + changeUsername, + changePassword, + getTotpUri, + setupTotp, + disableTotp, + resetPassword, + cancelResetPassword, + checkResetPasswordRequest, + getInstalledApps, + getGuestApps, + searchApps, + getAppDetails, + getImage, + installApp, + startApp, + stopApp, + restartApp, + uninstallApp, + resetApp, + backupApp, + restoreAppBackup, + getAppBackups, + deleteAppBackup, + getLinks, + createLink, + editLink, + deleteLink, +} from '../services.gen'; +import type { + UpdateUserSettingsData, + UpdateUserSettingsError, + UpdateUserSettingsResponse, + AcknowledgeWelcomeData, + AcknowledgeWelcomeError, + AcknowledgeWelcomeResponse, + GetTranslationData, + LoginData, + LoginError, + LoginResponse, + VerifyTotpData, + VerifyTotpError, + VerifyTotpResponse, + RegisterData, + RegisterError, + RegisterResponse, + LogoutError, + LogoutResponse, + ChangeUsernameData, + ChangeUsernameError, + ChangeUsernameResponse, + ChangePasswordData, + ChangePasswordError, + ChangePasswordResponse, + GetTotpUriData, + GetTotpUriError, + GetTotpUriResponse, + SetupTotpData, + SetupTotpError, + SetupTotpResponse, + DisableTotpData, + DisableTotpError, + DisableTotpResponse, + ResetPasswordData, + ResetPasswordError, + ResetPasswordResponse, + CancelResetPasswordError, + CancelResetPasswordResponse, + SearchAppsData, + SearchAppsError, + SearchAppsResponse, + GetAppDetailsData, + GetImageData, + InstallAppData, + InstallAppError, + InstallAppResponse, + StartAppData, + StartAppError, + StartAppResponse, + StopAppData, + StopAppError, + StopAppResponse, + RestartAppData, + RestartAppError, + RestartAppResponse, + UninstallAppData, + UninstallAppError, + UninstallAppResponse, + ResetAppData, + ResetAppError, + ResetAppResponse, + BackupAppData, + BackupAppError, + BackupAppResponse, + RestoreAppBackupData, + RestoreAppBackupError, + RestoreAppBackupResponse, + GetAppBackupsData, + GetAppBackupsError, + GetAppBackupsResponse, + DeleteAppBackupData, + DeleteAppBackupError, + DeleteAppBackupResponse, + CreateLinkData, + CreateLinkError, + CreateLinkResponse, + EditLinkData, + EditLinkError, + EditLinkResponse, + DeleteLinkData, + DeleteLinkError, + DeleteLinkResponse, +} from '../types.gen'; + +type QueryKey = [ + Pick & { + _id: string; + _infinite?: boolean; + }, +]; + +const createQueryKey = (id: string, options?: TOptions, infinite?: boolean): QueryKey[0] => { + const params: QueryKey[0] = { _id: id, baseUrl: (options?.client ?? client).getConfig().baseUrl } as QueryKey[0]; + if (infinite) { + params._infinite = infinite; + } + if (options?.body) { + params.body = options.body; + } + if (options?.headers) { + params.headers = options.headers; + } + if (options?.path) { + params.path = options.path; + } + if (options?.query) { + params.query = options.query; + } + return params; +}; + +export const userContextQueryKey = (options?: Options) => [createQueryKey('userContext', options)]; + +export const userContextOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await userContext({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: userContextQueryKey(options), + }); +}; + +export const appContextQueryKey = (options?: Options) => [createQueryKey('appContext', options)]; + +export const appContextOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await appContext({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: appContextQueryKey(options), + }); +}; + +export const updateUserSettingsMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await updateUserSettings({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const acknowledgeWelcomeMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await acknowledgeWelcome({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const systemLoadQueryKey = (options?: Options) => [createQueryKey('systemLoad', options)]; + +export const systemLoadOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await systemLoad({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: systemLoadQueryKey(options), + }); +}; + +export const getTranslationQueryKey = (options: Options) => [createQueryKey('getTranslation', options)]; + +export const getTranslationOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await getTranslation({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: getTranslationQueryKey(options), + }); +}; + +export const loginQueryKey = (options: Options) => [createQueryKey('login', options)]; + +export const loginOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await login({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: loginQueryKey(options), + }); +}; + +export const loginMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await login({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const verifyTotpQueryKey = (options: Options) => [createQueryKey('verifyTotp', options)]; + +export const verifyTotpOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await verifyTotp({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: verifyTotpQueryKey(options), + }); +}; + +export const verifyTotpMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await verifyTotp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const registerQueryKey = (options: Options) => [createQueryKey('register', options)]; + +export const registerOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await register({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: registerQueryKey(options), + }); +}; + +export const registerMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await register({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const logoutQueryKey = (options?: Options) => [createQueryKey('logout', options)]; + +export const logoutOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await logout({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: logoutQueryKey(options), + }); +}; + +export const logoutMutation = () => { + const mutationOptions: UseMutationOptions = { + mutationFn: async (options) => { + const { data } = await logout({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const changeUsernameMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await changeUsername({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const changePasswordMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await changePassword({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const getTotpUriMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await getTotpUri({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const setupTotpMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await setupTotp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const disableTotpMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await disableTotp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const resetPasswordQueryKey = (options: Options) => [createQueryKey('resetPassword', options)]; + +export const resetPasswordOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await resetPassword({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: resetPasswordQueryKey(options), + }); +}; + +export const resetPasswordMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await resetPassword({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const cancelResetPasswordMutation = () => { + const mutationOptions: UseMutationOptions = { + mutationFn: async (options) => { + const { data } = await cancelResetPassword({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const checkResetPasswordRequestQueryKey = (options?: Options) => [createQueryKey('checkResetPasswordRequest', options)]; + +export const checkResetPasswordRequestOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await checkResetPasswordRequest({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: checkResetPasswordRequestQueryKey(options), + }); +}; + +export const getInstalledAppsQueryKey = (options?: Options) => [createQueryKey('getInstalledApps', options)]; + +export const getInstalledAppsOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await getInstalledApps({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: getInstalledAppsQueryKey(options), + }); +}; + +export const getGuestAppsQueryKey = (options?: Options) => [createQueryKey('getGuestApps', options)]; + +export const getGuestAppsOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await getGuestApps({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: getGuestAppsQueryKey(options), + }); +}; + +export const searchAppsQueryKey = (options?: Options) => [createQueryKey('searchApps', options)]; + +export const searchAppsOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await searchApps({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: searchAppsQueryKey(options), + }); +}; + +const createInfiniteParams = [0], 'body' | 'headers' | 'path' | 'query'>>(queryKey: QueryKey, page: K) => { + const params = queryKey[0]; + if (page.body) { + params.body = { + ...(queryKey[0].body as any), + ...(page.body as any), + }; + } + if (page.headers) { + params.headers = { + ...queryKey[0].headers, + ...page.headers, + }; + } + if (page.path) { + params.path = { + ...queryKey[0].path, + ...page.path, + }; + } + if (page.query) { + params.query = { + ...queryKey[0].query, + ...page.query, + }; + } + return params as unknown as typeof page; +}; + +export const searchAppsInfiniteQueryKey = (options?: Options): QueryKey> => [ + createQueryKey('searchApps', options, true), +]; + +export const searchAppsInfiniteOptions = (options?: Options) => { + return infiniteQueryOptions< + SearchAppsResponse, + SearchAppsError, + InfiniteData, + QueryKey>, + string | Pick>[0], 'body' | 'headers' | 'path' | 'query'> + >( + // @ts-ignore + { + queryFn: async ({ pageParam, queryKey }) => { + // @ts-ignore + const page: Pick>[0], 'body' | 'headers' | 'path' | 'query'> = + typeof pageParam === 'object' + ? pageParam + : { + query: { + cursor: pageParam, + }, + }; + const params = createInfiniteParams(queryKey, page); + const { data } = await searchApps({ + ...options, + ...params, + throwOnError: true, + }); + return data; + }, + queryKey: searchAppsInfiniteQueryKey(options), + }, + ); +}; + +export const getAppDetailsQueryKey = (options: Options) => [createQueryKey('getAppDetails', options)]; + +export const getAppDetailsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await getAppDetails({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: getAppDetailsQueryKey(options), + }); +}; + +export const getImageQueryKey = (options: Options) => [createQueryKey('getImage', options)]; + +export const getImageOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await getImage({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: getImageQueryKey(options), + }); +}; + +export const installAppQueryKey = (options: Options) => [createQueryKey('installApp', options)]; + +export const installAppOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await installApp({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: installAppQueryKey(options), + }); +}; + +export const installAppMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await installApp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const startAppQueryKey = (options: Options) => [createQueryKey('startApp', options)]; + +export const startAppOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await startApp({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: startAppQueryKey(options), + }); +}; + +export const startAppMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await startApp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const stopAppQueryKey = (options: Options) => [createQueryKey('stopApp', options)]; + +export const stopAppOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await stopApp({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: stopAppQueryKey(options), + }); +}; + +export const stopAppMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await stopApp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const restartAppQueryKey = (options: Options) => [createQueryKey('restartApp', options)]; + +export const restartAppOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await restartApp({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: restartAppQueryKey(options), + }); +}; + +export const restartAppMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await restartApp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const uninstallAppMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await uninstallApp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const resetAppQueryKey = (options: Options) => [createQueryKey('resetApp', options)]; + +export const resetAppOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await resetApp({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: resetAppQueryKey(options), + }); +}; + +export const resetAppMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await resetApp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const backupAppQueryKey = (options: Options) => [createQueryKey('backupApp', options)]; + +export const backupAppOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await backupApp({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: backupAppQueryKey(options), + }); +}; + +export const backupAppMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await backupApp({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const restoreAppBackupQueryKey = (options: Options) => [createQueryKey('restoreAppBackup', options)]; + +export const restoreAppBackupOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await restoreAppBackup({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: restoreAppBackupQueryKey(options), + }); +}; + +export const restoreAppBackupMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await restoreAppBackup({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const getAppBackupsQueryKey = (options: Options) => [createQueryKey('getAppBackups', options)]; + +export const getAppBackupsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await getAppBackups({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: getAppBackupsQueryKey(options), + }); +}; + +export const getAppBackupsInfiniteQueryKey = (options: Options): QueryKey> => [ + createQueryKey('getAppBackups', options, true), +]; + +export const getAppBackupsInfiniteOptions = (options: Options) => { + return infiniteQueryOptions< + GetAppBackupsResponse, + GetAppBackupsError, + InfiniteData, + QueryKey>, + number | Pick>[0], 'body' | 'headers' | 'path' | 'query'> + >( + // @ts-ignore + { + queryFn: async ({ pageParam, queryKey }) => { + // @ts-ignore + const page: Pick>[0], 'body' | 'headers' | 'path' | 'query'> = + typeof pageParam === 'object' + ? pageParam + : { + query: { + page: pageParam, + }, + }; + const params = createInfiniteParams(queryKey, page); + const { data } = await getAppBackups({ + ...options, + ...params, + throwOnError: true, + }); + return data; + }, + queryKey: getAppBackupsInfiniteQueryKey(options), + }, + ); +}; + +export const deleteAppBackupMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await deleteAppBackup({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const getLinksQueryKey = (options?: Options) => [createQueryKey('getLinks', options)]; + +export const getLinksOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await getLinks({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: getLinksQueryKey(options), + }); +}; + +export const createLinkQueryKey = (options: Options) => [createQueryKey('createLink', options)]; + +export const createLinkOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey }) => { + const { data } = await createLink({ + ...options, + ...queryKey[0], + throwOnError: true, + }); + return data; + }, + queryKey: createLinkQueryKey(options), + }); +}; + +export const createLinkMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await createLink({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const editLinkMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await editLink({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const deleteLinkMutation = () => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (options) => { + const { data } = await deleteLink({ + ...options, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; diff --git a/packages/frontend/src/api-client/index.ts b/packages/frontend/src/api-client/index.ts new file mode 100644 index 0000000000..002afc3338 --- /dev/null +++ b/packages/frontend/src/api-client/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './schemas.gen'; +export * from './services.gen'; +export * from './types.gen'; diff --git a/packages/frontend/src/api-client/schemas.gen.ts b/packages/frontend/src/api-client/schemas.gen.ts new file mode 100644 index 0000000000..282234b19b --- /dev/null +++ b/packages/frontend/src/api-client/schemas.gen.ts @@ -0,0 +1,1456 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export const UserContextDtoSchema = { + type: 'object', + properties: { + isLoggedIn: { + description: 'Indicates if the user is logged in', + type: 'boolean', + }, + isConfigured: { + description: 'Indicates if the app is already configured', + type: 'boolean', + }, + isGuestDashboardEnabled: { + description: 'Indicates if the guest dashboard is enabled', + type: 'boolean', + }, + }, + required: ['isLoggedIn', 'isConfigured', 'isGuestDashboardEnabled'], +} as const; + +export const AppContextDtoSchema = { + type: 'object', + properties: { + version: { + type: 'object', + properties: { + current: { + type: 'string', + }, + latest: { + type: 'string', + }, + body: { + type: 'string', + }, + }, + required: ['current', 'latest', 'body'], + }, + userSettings: { + type: 'object', + properties: { + dnsIp: { + type: 'string', + }, + internalIp: { + type: 'string', + }, + postgresPort: { + type: 'number', + }, + appsRepoUrl: { + type: 'string', + format: 'uri', + }, + domain: { + type: 'string', + }, + appDataPath: { + type: 'string', + }, + localDomain: { + type: 'string', + }, + demoMode: { + type: 'boolean', + }, + guestDashboard: { + type: 'boolean', + }, + allowAutoThemes: { + type: 'boolean', + }, + allowErrorMonitoring: { + type: 'boolean', + }, + persistTraefikConfig: { + type: 'boolean', + }, + port: { + type: 'number', + }, + sslPort: { + type: 'number', + }, + listenIp: { + type: 'string', + }, + timeZone: { + type: 'string', + }, + }, + required: [ + 'dnsIp', + 'internalIp', + 'postgresPort', + 'appsRepoUrl', + 'domain', + 'appDataPath', + 'localDomain', + 'demoMode', + 'guestDashboard', + 'allowAutoThemes', + 'allowErrorMonitoring', + 'persistTraefikConfig', + 'port', + 'sslPort', + 'listenIp', + 'timeZone', + ], + }, + user: { + type: 'object', + properties: { + id: { + type: 'number', + }, + username: { + type: 'string', + }, + totpEnabled: { + type: 'boolean', + }, + locale: { + type: 'string', + }, + operator: { + type: 'boolean', + }, + hasSeenWelcome: { + type: 'boolean', + }, + }, + required: ['id', 'username', 'totpEnabled', 'locale', 'operator', 'hasSeenWelcome'], + }, + apps: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + }, + name: { + type: 'string', + }, + short_desc: { + type: 'string', + }, + categories: { + type: 'array', + items: { + type: 'string', + enum: [ + 'network', + 'media', + 'development', + 'automation', + 'social', + 'utilities', + 'photography', + 'security', + 'featured', + 'books', + 'data', + 'music', + 'finance', + 'gaming', + 'ai', + ], + }, + default: [], + }, + deprecated: { + type: 'boolean', + default: false, + }, + created_at: { + type: 'integer', + minimum: 0, + exclusiveMinimum: false, + default: 0, + }, + supported_architectures: { + type: 'array', + items: { + type: 'string', + enum: ['arm64', 'amd64'], + }, + }, + available: { + type: 'boolean', + }, + }, + required: ['id', 'name', 'short_desc', 'available'], + }, + }, + }, + required: ['version', 'userSettings', 'user', 'apps'], +} as const; + +export const PartialUserSettingsDtoSchema = { + type: 'object', + properties: { + dnsIp: { + type: 'string', + }, + internalIp: { + type: 'string', + }, + postgresPort: { + type: 'number', + }, + appsRepoUrl: { + type: 'string', + format: 'uri', + }, + domain: { + type: 'string', + }, + appDataPath: { + type: 'string', + }, + localDomain: { + type: 'string', + }, + demoMode: { + type: 'boolean', + }, + guestDashboard: { + type: 'boolean', + }, + allowAutoThemes: { + type: 'boolean', + }, + allowErrorMonitoring: { + type: 'boolean', + }, + persistTraefikConfig: { + type: 'boolean', + }, + port: { + type: 'number', + }, + sslPort: { + type: 'number', + }, + listenIp: { + type: 'string', + }, + timeZone: { + type: 'string', + }, + }, +} as const; + +export const AcknowledgeWelcomeBodySchema = { + type: 'object', + properties: { + allowErrorMonitoring: { + type: 'boolean', + }, + }, + required: ['allowErrorMonitoring'], +} as const; + +export const LoadDtoSchema = { + type: 'object', + properties: { + diskUsed: { + type: 'number', + nullable: true, + default: 0, + }, + diskSize: { + type: 'number', + nullable: true, + default: 0, + }, + percentUsed: { + type: 'number', + nullable: true, + default: 0, + }, + cpuLoad: { + type: 'number', + nullable: true, + default: 0, + }, + memoryTotal: { + type: 'number', + nullable: true, + default: 0, + }, + percentUsedMemory: { + type: 'number', + nullable: true, + default: 0, + }, + }, +} as const; + +export const LoginBodySchema = { + type: 'object', + properties: { + username: { + type: 'string', + }, + password: { + type: 'string', + }, + }, + required: ['username', 'password'], +} as const; + +export const LoginDtoSchema = { + type: 'object', + properties: { + success: { + type: 'boolean', + }, + totpSessionId: { + type: 'string', + }, + }, + required: ['success'], +} as const; + +export const VerifyTotpBodySchema = { + type: 'object', + properties: { + totpCode: { + type: 'string', + }, + totpSessionId: { + type: 'string', + }, + }, + required: ['totpCode', 'totpSessionId'], +} as const; + +export const RegisterBodySchema = { + type: 'object', + properties: { + username: { + type: 'string', + }, + password: { + type: 'string', + }, + }, + required: ['username', 'password'], +} as const; + +export const RegisterDtoSchema = { + type: 'object', + properties: { + success: { + type: 'boolean', + }, + }, + required: ['success'], +} as const; + +export const ChangeUsernameBodySchema = { + type: 'object', + properties: { + newUsername: { + type: 'string', + }, + password: { + type: 'string', + }, + }, + required: ['newUsername', 'password'], +} as const; + +export const ChangePasswordBodySchema = { + type: 'object', + properties: { + currentPassword: { + type: 'string', + }, + newPassword: { + type: 'string', + }, + }, + required: ['currentPassword', 'newPassword'], +} as const; + +export const GetTotpUriBodySchema = { + type: 'object', + properties: { + password: { + type: 'string', + }, + }, + required: ['password'], +} as const; + +export const GetTotpUriDtoSchema = { + type: 'object', + properties: { + key: { + type: 'string', + }, + uri: { + type: 'string', + }, + }, + required: ['key', 'uri'], +} as const; + +export const SetupTotpBodySchema = { + type: 'object', + properties: { + code: { + type: 'string', + }, + }, + required: ['code'], +} as const; + +export const DisableTotpBodySchema = { + type: 'object', + properties: { + password: { + type: 'string', + }, + }, + required: ['password'], +} as const; + +export const ResetPasswordBodySchema = { + type: 'object', + properties: { + newPassword: { + type: 'string', + }, + }, + required: ['newPassword'], +} as const; + +export const ResetPasswordDtoSchema = { + type: 'object', + properties: { + success: { + type: 'boolean', + }, + email: { + type: 'string', + }, + }, + required: ['success', 'email'], +} as const; + +export const CheckResetPasswordRequestDtoSchema = { + type: 'object', + properties: { + isRequestPending: { + type: 'boolean', + }, + }, + required: ['isRequestPending'], +} as const; + +export const MyAppsDtoSchema = { + type: 'object', + properties: { + installed: { + type: 'array', + items: { + type: 'object', + properties: { + app: { + type: 'object', + properties: { + id: { + type: 'string', + }, + status: { + type: 'string', + enum: [ + 'running', + 'stopped', + 'starting', + 'stopping', + 'updating', + 'missing', + 'installing', + 'uninstalling', + 'resetting', + 'restarting', + 'backing_up', + 'restoring', + ], + }, + lastOpened: { + type: 'string', + nullable: true, + }, + numOpened: { + type: 'number', + default: 0, + }, + createdAt: { + type: 'string', + }, + updatedAt: { + type: 'string', + }, + version: { + type: 'number', + }, + exposed: { + type: 'boolean', + }, + openPort: { + type: 'boolean', + }, + exposedLocal: { + type: 'boolean', + }, + domain: { + type: 'string', + nullable: true, + }, + isVisibleOnGuestDashboard: { + type: 'boolean', + }, + }, + required: ['id', 'status', 'lastOpened', 'version', 'exposed', 'openPort', 'exposedLocal', 'domain', 'isVisibleOnGuestDashboard'], + }, + info: { + type: 'object', + properties: { + id: { + type: 'string', + }, + name: { + type: 'string', + }, + short_desc: { + type: 'string', + }, + categories: { + type: 'array', + items: { + type: 'string', + enum: [ + 'network', + 'media', + 'development', + 'automation', + 'social', + 'utilities', + 'photography', + 'security', + 'featured', + 'books', + 'data', + 'music', + 'finance', + 'gaming', + 'ai', + ], + }, + default: [], + }, + deprecated: { + type: 'boolean', + default: false, + }, + created_at: { + type: 'integer', + minimum: 0, + exclusiveMinimum: false, + default: 0, + }, + supported_architectures: { + type: 'array', + items: { + type: 'string', + enum: ['arm64', 'amd64'], + }, + }, + available: { + type: 'boolean', + }, + }, + required: ['id', 'name', 'short_desc', 'available'], + }, + updateInfo: { + type: 'object', + properties: { + latestVersion: { + type: 'number', + }, + minTipiVersion: { + type: 'string', + }, + latestDockerVersion: { + type: 'string', + }, + }, + required: ['latestVersion'], + }, + }, + required: ['app', 'info', 'updateInfo'], + }, + }, + }, + required: ['installed'], +} as const; + +export const GuestAppsDtoSchema = { + type: 'object', + properties: { + installed: { + type: 'array', + items: { + type: 'object', + properties: { + app: { + type: 'object', + properties: { + id: { + type: 'string', + }, + status: { + type: 'string', + enum: [ + 'running', + 'stopped', + 'starting', + 'stopping', + 'updating', + 'missing', + 'installing', + 'uninstalling', + 'resetting', + 'restarting', + 'backing_up', + 'restoring', + ], + }, + lastOpened: { + type: 'string', + nullable: true, + }, + numOpened: { + type: 'number', + default: 0, + }, + createdAt: { + type: 'string', + }, + updatedAt: { + type: 'string', + }, + version: { + type: 'number', + }, + exposed: { + type: 'boolean', + }, + openPort: { + type: 'boolean', + }, + exposedLocal: { + type: 'boolean', + }, + domain: { + type: 'string', + nullable: true, + }, + isVisibleOnGuestDashboard: { + type: 'boolean', + }, + }, + required: ['id', 'status', 'lastOpened', 'version', 'exposed', 'openPort', 'exposedLocal', 'domain', 'isVisibleOnGuestDashboard'], + }, + info: { + type: 'object', + properties: { + id: { + type: 'string', + }, + available: { + type: 'boolean', + }, + deprecated: { + type: 'boolean', + default: false, + }, + port: { + type: 'number', + minimum: 1, + exclusiveMinimum: false, + maximum: 65535, + exclusiveMaximum: false, + }, + name: { + type: 'string', + }, + description: { + type: 'string', + default: '', + }, + version: { + type: 'string', + default: 'latest', + }, + tipi_version: { + type: 'number', + }, + short_desc: { + type: 'string', + }, + author: { + type: 'string', + }, + source: { + type: 'string', + }, + website: { + type: 'string', + }, + force_expose: { + type: 'boolean', + default: false, + }, + generate_vapid_keys: { + type: 'boolean', + default: false, + }, + categories: { + type: 'array', + items: { + type: 'string', + enum: [ + 'network', + 'media', + 'development', + 'automation', + 'social', + 'utilities', + 'photography', + 'security', + 'featured', + 'books', + 'data', + 'music', + 'finance', + 'gaming', + 'ai', + ], + }, + default: [], + }, + url_suffix: { + type: 'string', + }, + form_fields: { + type: 'array', + items: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['text', 'password', 'email', 'number', 'fqdn', 'ip', 'fqdnip', 'url', 'random', 'boolean'], + }, + label: { + type: 'string', + }, + placeholder: { + type: 'string', + }, + max: { + type: 'number', + }, + min: { + type: 'number', + }, + hint: { + type: 'string', + }, + options: { + type: 'array', + items: { + type: 'object', + properties: { + label: { + type: 'string', + }, + value: { + type: 'string', + }, + }, + required: ['label', 'value'], + }, + }, + required: { + type: 'boolean', + default: false, + }, + default: { + oneOf: [ + { + type: 'boolean', + }, + { + type: 'string', + }, + { + type: 'number', + }, + ], + }, + regex: { + type: 'string', + }, + pattern_error: { + type: 'string', + }, + env_variable: { + type: 'string', + }, + encoding: { + type: 'string', + enum: ['hex', 'base64'], + }, + }, + required: ['type', 'label', 'env_variable'], + }, + default: [], + }, + https: { + type: 'boolean', + default: false, + }, + exposable: { + type: 'boolean', + default: false, + }, + no_gui: { + type: 'boolean', + default: false, + }, + supported_architectures: { + type: 'array', + items: { + type: 'string', + enum: ['arm64', 'amd64'], + }, + }, + uid: { + type: 'number', + }, + gid: { + type: 'number', + }, + dynamic_config: { + type: 'boolean', + default: false, + }, + min_tipi_version: { + type: 'string', + }, + created_at: { + type: 'integer', + minimum: 0, + exclusiveMinimum: false, + default: 0, + }, + updated_at: { + type: 'integer', + minimum: 0, + exclusiveMinimum: false, + default: 0, + }, + }, + required: ['id', 'available', 'port', 'name', 'tipi_version', 'short_desc', 'author', 'source'], + }, + updateInfo: { + type: 'object', + properties: { + latestVersion: { + type: 'number', + }, + minTipiVersion: { + type: 'string', + }, + latestDockerVersion: { + type: 'string', + }, + }, + required: ['latestVersion'], + }, + }, + required: ['app', 'info', 'updateInfo'], + }, + }, + }, + required: ['installed'], +} as const; + +export const SearchAppsDtoSchema = { + type: 'object', + properties: { + data: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + }, + name: { + type: 'string', + }, + short_desc: { + type: 'string', + }, + categories: { + type: 'array', + items: { + type: 'string', + enum: [ + 'network', + 'media', + 'development', + 'automation', + 'social', + 'utilities', + 'photography', + 'security', + 'featured', + 'books', + 'data', + 'music', + 'finance', + 'gaming', + 'ai', + ], + }, + default: [], + }, + deprecated: { + type: 'boolean', + default: false, + }, + created_at: { + type: 'integer', + minimum: 0, + exclusiveMinimum: false, + default: 0, + }, + supported_architectures: { + type: 'array', + items: { + type: 'string', + enum: ['arm64', 'amd64'], + }, + }, + available: { + type: 'boolean', + }, + }, + required: ['id', 'name', 'short_desc', 'available'], + }, + }, + nextCursor: { + type: 'string', + }, + total: { + type: 'number', + }, + }, + required: ['data', 'total'], +} as const; + +export const AppDetailsDtoSchema = { + type: 'object', + properties: { + info: { + type: 'object', + properties: { + id: { + type: 'string', + }, + available: { + type: 'boolean', + }, + deprecated: { + type: 'boolean', + default: false, + }, + port: { + type: 'number', + minimum: 1, + exclusiveMinimum: false, + maximum: 65535, + exclusiveMaximum: false, + }, + name: { + type: 'string', + }, + description: { + type: 'string', + default: '', + }, + version: { + type: 'string', + default: 'latest', + }, + tipi_version: { + type: 'number', + }, + short_desc: { + type: 'string', + }, + author: { + type: 'string', + }, + source: { + type: 'string', + }, + website: { + type: 'string', + }, + force_expose: { + type: 'boolean', + default: false, + }, + generate_vapid_keys: { + type: 'boolean', + default: false, + }, + categories: { + type: 'array', + items: { + type: 'string', + enum: [ + 'network', + 'media', + 'development', + 'automation', + 'social', + 'utilities', + 'photography', + 'security', + 'featured', + 'books', + 'data', + 'music', + 'finance', + 'gaming', + 'ai', + ], + }, + default: [], + }, + url_suffix: { + type: 'string', + }, + form_fields: { + type: 'array', + items: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['text', 'password', 'email', 'number', 'fqdn', 'ip', 'fqdnip', 'url', 'random', 'boolean'], + }, + label: { + type: 'string', + }, + placeholder: { + type: 'string', + }, + max: { + type: 'number', + }, + min: { + type: 'number', + }, + hint: { + type: 'string', + }, + options: { + type: 'array', + items: { + type: 'object', + properties: { + label: { + type: 'string', + }, + value: { + type: 'string', + }, + }, + required: ['label', 'value'], + }, + }, + required: { + type: 'boolean', + default: false, + }, + default: { + oneOf: [ + { + type: 'boolean', + }, + { + type: 'string', + }, + { + type: 'number', + }, + ], + }, + regex: { + type: 'string', + }, + pattern_error: { + type: 'string', + }, + env_variable: { + type: 'string', + }, + encoding: { + type: 'string', + enum: ['hex', 'base64'], + }, + }, + required: ['type', 'label', 'env_variable'], + }, + default: [], + }, + https: { + type: 'boolean', + default: false, + }, + exposable: { + type: 'boolean', + default: false, + }, + no_gui: { + type: 'boolean', + default: false, + }, + supported_architectures: { + type: 'array', + items: { + type: 'string', + enum: ['arm64', 'amd64'], + }, + }, + uid: { + type: 'number', + }, + gid: { + type: 'number', + }, + dynamic_config: { + type: 'boolean', + default: false, + }, + min_tipi_version: { + type: 'string', + }, + created_at: { + type: 'integer', + minimum: 0, + exclusiveMinimum: false, + default: 0, + }, + updated_at: { + type: 'integer', + minimum: 0, + exclusiveMinimum: false, + default: 0, + }, + }, + required: ['id', 'available', 'port', 'name', 'tipi_version', 'short_desc', 'author', 'source'], + }, + app: { + type: 'object', + properties: { + id: { + type: 'string', + }, + status: { + type: 'string', + enum: [ + 'running', + 'stopped', + 'starting', + 'stopping', + 'updating', + 'missing', + 'installing', + 'uninstalling', + 'resetting', + 'restarting', + 'backing_up', + 'restoring', + ], + }, + lastOpened: { + type: 'string', + nullable: true, + }, + numOpened: { + type: 'number', + default: 0, + }, + createdAt: { + type: 'string', + }, + updatedAt: { + type: 'string', + }, + version: { + type: 'number', + }, + exposed: { + type: 'boolean', + }, + openPort: { + type: 'boolean', + }, + exposedLocal: { + type: 'boolean', + }, + domain: { + type: 'string', + nullable: true, + }, + isVisibleOnGuestDashboard: { + type: 'boolean', + }, + }, + required: ['id', 'status', 'lastOpened', 'version', 'exposed', 'openPort', 'exposedLocal', 'domain', 'isVisibleOnGuestDashboard'], + }, + updateInfo: { + type: 'object', + properties: { + latestVersion: { + type: 'number', + }, + minTipiVersion: { + type: 'string', + }, + latestDockerVersion: { + type: 'string', + }, + }, + required: ['latestVersion'], + }, + }, + required: ['info', 'app', 'updateInfo'], +} as const; + +export const AppFormBodySchema = { + type: 'object', + properties: { + exposed: { + type: 'boolean', + }, + exposedLocal: { + type: 'boolean', + }, + openPort: { + type: 'boolean', + default: true, + }, + domain: { + type: 'string', + }, + isVisibleOnGuestDashboard: { + type: 'boolean', + }, + }, +} as const; + +export const RestoreAppBackupDtoSchema = { + type: 'object', + properties: { + filename: { + type: 'string', + }, + }, + required: ['filename'], +} as const; + +export const GetAppBackupsDtoSchema = { + type: 'object', + properties: { + data: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + }, + size: { + type: 'number', + }, + date: { + type: 'number', + }, + }, + required: ['id', 'size', 'date'], + }, + }, + total: { + type: 'number', + }, + currentPage: { + type: 'number', + }, + lastPage: { + type: 'number', + }, + }, + required: ['data', 'total', 'currentPage', 'lastPage'], +} as const; + +export const DeleteAppBackupBodyDtoSchema = { + type: 'object', + properties: { + filename: { + type: 'string', + }, + }, + required: ['filename'], +} as const; + +export const LinksDtoSchema = { + type: 'object', + properties: { + links: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number', + }, + title: { + type: 'string', + minLength: 1, + maxLength: 20, + }, + description: { + type: 'string', + minLength: 0, + maxLength: 50, + nullable: true, + }, + url: { + type: 'string', + format: 'uri', + }, + iconUrl: { + oneOf: [ + { + type: 'string', + format: 'uri', + }, + { + type: 'string', + maxLength: 0, + }, + ], + nullable: true, + }, + userId: { + type: 'number', + }, + }, + required: ['id', 'title', 'description', 'url', 'iconUrl', 'userId'], + }, + }, + }, + required: ['links'], +} as const; + +export const LinkBodyDtoSchema = { + type: 'object', + properties: { + title: { + type: 'string', + minLength: 1, + maxLength: 20, + }, + url: { + type: 'string', + format: 'uri', + }, + description: { + type: 'string', + minLength: 0, + maxLength: 50, + }, + iconUrl: { + oneOf: [ + { + type: 'string', + format: 'uri', + }, + { + type: 'string', + maxLength: 0, + }, + ], + }, + }, + required: ['title', 'url'], +} as const; + +export const EditLinkBodyDtoSchema = { + type: 'object', + properties: { + title: { + type: 'string', + minLength: 1, + maxLength: 20, + }, + url: { + type: 'string', + format: 'uri', + }, + description: { + type: 'string', + minLength: 0, + maxLength: 50, + }, + iconUrl: { + oneOf: [ + { + type: 'string', + format: 'uri', + }, + { + type: 'string', + maxLength: 0, + }, + ], + }, + }, + required: ['title', 'url'], +} as const; diff --git a/packages/frontend/src/api-client/services.gen.ts b/packages/frontend/src/api-client/services.gen.ts new file mode 100644 index 0000000000..556bc31813 --- /dev/null +++ b/packages/frontend/src/api-client/services.gen.ts @@ -0,0 +1,368 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; +import type { + UserContextError, + UserContextResponse, + AppContextError, + AppContextResponse, + UpdateUserSettingsData, + UpdateUserSettingsError, + UpdateUserSettingsResponse, + AcknowledgeWelcomeData, + AcknowledgeWelcomeError, + AcknowledgeWelcomeResponse, + SystemLoadError, + SystemLoadResponse, + GetTranslationData, + GetTranslationError, + GetTranslationResponse, + LoginData, + LoginError, + LoginResponse, + VerifyTotpData, + VerifyTotpError, + VerifyTotpResponse, + RegisterData, + RegisterError, + RegisterResponse, + LogoutError, + LogoutResponse, + ChangeUsernameData, + ChangeUsernameError, + ChangeUsernameResponse, + ChangePasswordData, + ChangePasswordError, + ChangePasswordResponse, + GetTotpUriData, + GetTotpUriError, + GetTotpUriResponse, + SetupTotpData, + SetupTotpError, + SetupTotpResponse, + DisableTotpData, + DisableTotpError, + DisableTotpResponse, + ResetPasswordData, + ResetPasswordError, + ResetPasswordResponse, + CancelResetPasswordError, + CancelResetPasswordResponse, + CheckResetPasswordRequestError, + CheckResetPasswordRequestResponse, + GetInstalledAppsError, + GetInstalledAppsResponse, + GetGuestAppsError, + GetGuestAppsResponse, + SearchAppsData, + SearchAppsError, + SearchAppsResponse, + GetAppDetailsData, + GetAppDetailsError, + GetAppDetailsResponse, + GetImageData, + GetImageError, + GetImageResponse, + InstallAppData, + InstallAppError, + InstallAppResponse, + StartAppData, + StartAppError, + StartAppResponse, + StopAppData, + StopAppError, + StopAppResponse, + RestartAppData, + RestartAppError, + RestartAppResponse, + UninstallAppData, + UninstallAppError, + UninstallAppResponse, + ResetAppData, + ResetAppError, + ResetAppResponse, + BackupAppData, + BackupAppError, + BackupAppResponse, + RestoreAppBackupData, + RestoreAppBackupError, + RestoreAppBackupResponse, + GetAppBackupsData, + GetAppBackupsError, + GetAppBackupsResponse, + DeleteAppBackupData, + DeleteAppBackupError, + DeleteAppBackupResponse, + GetLinksError, + GetLinksResponse, + CreateLinkData, + CreateLinkError, + CreateLinkResponse, + EditLinkData, + EditLinkError, + EditLinkResponse, + DeleteLinkData, + DeleteLinkError, + DeleteLinkResponse, +} from './types.gen'; + +export const client = createClient(createConfig()); + +export const userContext = (options?: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/user-context', + }); +}; + +export const appContext = (options?: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/app-context', + }); +}; + +export const updateUserSettings = (options: Options) => { + return (options?.client ?? client).patch({ + ...options, + url: '/api/user-settings', + }); +}; + +export const acknowledgeWelcome = (options: Options) => { + return (options?.client ?? client).patch({ + ...options, + url: '/api/acknowledge-welcome', + }); +}; + +export const systemLoad = (options?: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/system/load', + }); +}; + +export const getTranslation = (options: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/i18n/locales/{lng}/{ns}.json', + }); +}; + +export const login = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/auth/login', + }); +}; + +export const verifyTotp = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/auth/verify-totp', + }); +}; + +export const register = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/auth/register', + }); +}; + +export const logout = (options?: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/auth/logout', + }); +}; + +export const changeUsername = (options: Options) => { + return (options?.client ?? client).patch({ + ...options, + url: '/api/auth/username', + }); +}; + +export const changePassword = (options: Options) => { + return (options?.client ?? client).patch({ + ...options, + url: '/api/auth/password', + }); +}; + +export const getTotpUri = (options: Options) => { + return (options?.client ?? client).patch({ + ...options, + url: '/api/auth/totp/get-uri', + }); +}; + +export const setupTotp = (options: Options) => { + return (options?.client ?? client).patch({ + ...options, + url: '/api/auth/totp/setup', + }); +}; + +export const disableTotp = (options: Options) => { + return (options?.client ?? client).patch({ + ...options, + url: '/api/auth/totp/disable', + }); +}; + +export const resetPassword = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/auth/reset-password', + }); +}; + +export const cancelResetPassword = (options?: Options) => { + return (options?.client ?? client).delete({ + ...options, + url: '/api/auth/reset-password', + }); +}; + +export const checkResetPasswordRequest = (options?: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/auth/reset-password', + }); +}; + +export const getInstalledApps = (options?: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/apps/installed', + }); +}; + +export const getGuestApps = (options?: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/apps/guest', + }); +}; + +export const searchApps = (options?: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/apps/search', + }); +}; + +export const getAppDetails = (options: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/apps/{id}', + }); +}; + +export const getImage = (options: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/apps/{id}/image', + }); +}; + +export const installApp = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/app-lifecycle/{id}/install', + }); +}; + +export const startApp = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/app-lifecycle/{id}/start', + }); +}; + +export const stopApp = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/app-lifecycle/{id}/stop', + }); +}; + +export const restartApp = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/app-lifecycle/{id}/restart', + }); +}; + +export const uninstallApp = (options: Options) => { + return (options?.client ?? client).delete({ + ...options, + url: '/api/app-lifecycle/{id}/uninstall', + }); +}; + +export const resetApp = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/app-lifecycle/{id}/reset', + }); +}; + +export const backupApp = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/backups/{appid}/backup', + }); +}; + +export const restoreAppBackup = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/backups/{appid}/restore', + }); +}; + +export const getAppBackups = (options: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/backups/{id}', + }); +}; + +export const deleteAppBackup = (options: Options) => { + return (options?.client ?? client).delete({ + ...options, + url: '/api/backups/{appid}', + }); +}; + +export const getLinks = (options?: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/api/links', + }); +}; + +export const createLink = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + url: '/api/links', + }); +}; + +export const editLink = (options: Options) => { + return (options?.client ?? client).patch({ + ...options, + url: '/api/links/{id}', + }); +}; + +export const deleteLink = (options: Options) => { + return (options?.client ?? client).delete({ + ...options, + url: '/api/links/{id}', + }); +}; diff --git a/packages/frontend/src/api-client/types.gen.ts b/packages/frontend/src/api-client/types.gen.ts new file mode 100644 index 0000000000..51ac0e59f0 --- /dev/null +++ b/packages/frontend/src/api-client/types.gen.ts @@ -0,0 +1,846 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type UserContextDto = { + /** + * Indicates if the user is logged in + */ + isLoggedIn: boolean; + /** + * Indicates if the app is already configured + */ + isConfigured: boolean; + /** + * Indicates if the guest dashboard is enabled + */ + isGuestDashboardEnabled: boolean; +}; + +export type AppContextDto = { + version: { + current: string; + latest: string; + body: string; + }; + userSettings: { + dnsIp: string; + internalIp: string; + postgresPort: number; + appsRepoUrl: string; + domain: string; + appDataPath: string; + localDomain: string; + demoMode: boolean; + guestDashboard: boolean; + allowAutoThemes: boolean; + allowErrorMonitoring: boolean; + persistTraefikConfig: boolean; + port: number; + sslPort: number; + listenIp: string; + timeZone: string; + }; + user: { + id: number; + username: string; + totpEnabled: boolean; + locale: string; + operator: boolean; + hasSeenWelcome: boolean; + }; + apps: Array<{ + id: string; + name: string; + short_desc: string; + categories?: Array< + | 'network' + | 'media' + | 'development' + | 'automation' + | 'social' + | 'utilities' + | 'photography' + | 'security' + | 'featured' + | 'books' + | 'data' + | 'music' + | 'finance' + | 'gaming' + | 'ai' + >; + deprecated?: boolean; + created_at?: number; + supported_architectures?: Array<'arm64' | 'amd64'>; + available: boolean; + }>; +}; + +export type PartialUserSettingsDto = { + dnsIp?: string; + internalIp?: string; + postgresPort?: number; + appsRepoUrl?: string; + domain?: string; + appDataPath?: string; + localDomain?: string; + demoMode?: boolean; + guestDashboard?: boolean; + allowAutoThemes?: boolean; + allowErrorMonitoring?: boolean; + persistTraefikConfig?: boolean; + port?: number; + sslPort?: number; + listenIp?: string; + timeZone?: string; +}; + +export type AcknowledgeWelcomeBody = { + allowErrorMonitoring: boolean; +}; + +export type LoadDto = { + diskUsed?: number | null; + diskSize?: number | null; + percentUsed?: number | null; + cpuLoad?: number | null; + memoryTotal?: number | null; + percentUsedMemory?: number | null; +}; + +export type LoginBody = { + username: string; + password: string; +}; + +export type LoginDto = { + success: boolean; + totpSessionId?: string; +}; + +export type VerifyTotpBody = { + totpCode: string; + totpSessionId: string; +}; + +export type RegisterBody = { + username: string; + password: string; +}; + +export type RegisterDto = { + success: boolean; +}; + +export type ChangeUsernameBody = { + newUsername: string; + password: string; +}; + +export type ChangePasswordBody = { + currentPassword: string; + newPassword: string; +}; + +export type GetTotpUriBody = { + password: string; +}; + +export type GetTotpUriDto = { + key: string; + uri: string; +}; + +export type SetupTotpBody = { + code: string; +}; + +export type DisableTotpBody = { + password: string; +}; + +export type ResetPasswordBody = { + newPassword: string; +}; + +export type ResetPasswordDto = { + success: boolean; + email: string; +}; + +export type CheckResetPasswordRequestDto = { + isRequestPending: boolean; +}; + +export type MyAppsDto = { + installed: Array<{ + app: { + id: string; + status: + | 'running' + | 'stopped' + | 'starting' + | 'stopping' + | 'updating' + | 'missing' + | 'installing' + | 'uninstalling' + | 'resetting' + | 'restarting' + | 'backing_up' + | 'restoring'; + lastOpened: string | null; + numOpened?: number; + createdAt?: string; + updatedAt?: string; + version: number; + exposed: boolean; + openPort: boolean; + exposedLocal: boolean; + domain: string | null; + isVisibleOnGuestDashboard: boolean; + }; + info: { + id: string; + name: string; + short_desc: string; + categories?: Array< + | 'network' + | 'media' + | 'development' + | 'automation' + | 'social' + | 'utilities' + | 'photography' + | 'security' + | 'featured' + | 'books' + | 'data' + | 'music' + | 'finance' + | 'gaming' + | 'ai' + >; + deprecated?: boolean; + created_at?: number; + supported_architectures?: Array<'arm64' | 'amd64'>; + available: boolean; + }; + updateInfo: { + latestVersion: number; + minTipiVersion?: string; + latestDockerVersion?: string; + }; + }>; +}; + +export type GuestAppsDto = { + installed: Array<{ + app: { + id: string; + status: + | 'running' + | 'stopped' + | 'starting' + | 'stopping' + | 'updating' + | 'missing' + | 'installing' + | 'uninstalling' + | 'resetting' + | 'restarting' + | 'backing_up' + | 'restoring'; + lastOpened: string | null; + numOpened?: number; + createdAt?: string; + updatedAt?: string; + version: number; + exposed: boolean; + openPort: boolean; + exposedLocal: boolean; + domain: string | null; + isVisibleOnGuestDashboard: boolean; + }; + info: { + id: string; + available: boolean; + deprecated?: boolean; + port: number; + name: string; + description?: string; + version?: string; + tipi_version: number; + short_desc: string; + author: string; + source: string; + website?: string; + force_expose?: boolean; + generate_vapid_keys?: boolean; + categories?: Array< + | 'network' + | 'media' + | 'development' + | 'automation' + | 'social' + | 'utilities' + | 'photography' + | 'security' + | 'featured' + | 'books' + | 'data' + | 'music' + | 'finance' + | 'gaming' + | 'ai' + >; + url_suffix?: string; + form_fields?: Array<{ + type: 'text' | 'password' | 'email' | 'number' | 'fqdn' | 'ip' | 'fqdnip' | 'url' | 'random' | 'boolean'; + label: string; + placeholder?: string; + max?: number; + min?: number; + hint?: string; + options?: Array<{ + label: string; + value: string; + }>; + required?: boolean; + default?: boolean | string | number; + regex?: string; + pattern_error?: string; + env_variable: string; + encoding?: 'hex' | 'base64'; + }>; + https?: boolean; + exposable?: boolean; + no_gui?: boolean; + supported_architectures?: Array<'arm64' | 'amd64'>; + uid?: number; + gid?: number; + dynamic_config?: boolean; + min_tipi_version?: string; + created_at?: number; + updated_at?: number; + }; + updateInfo: { + latestVersion: number; + minTipiVersion?: string; + latestDockerVersion?: string; + }; + }>; +}; + +export type SearchAppsDto = { + data: Array<{ + id: string; + name: string; + short_desc: string; + categories?: Array< + | 'network' + | 'media' + | 'development' + | 'automation' + | 'social' + | 'utilities' + | 'photography' + | 'security' + | 'featured' + | 'books' + | 'data' + | 'music' + | 'finance' + | 'gaming' + | 'ai' + >; + deprecated?: boolean; + created_at?: number; + supported_architectures?: Array<'arm64' | 'amd64'>; + available: boolean; + }>; + nextCursor?: string; + total: number; +}; + +export type AppDetailsDto = { + info: { + id: string; + available: boolean; + deprecated?: boolean; + port: number; + name: string; + description?: string; + version?: string; + tipi_version: number; + short_desc: string; + author: string; + source: string; + website?: string; + force_expose?: boolean; + generate_vapid_keys?: boolean; + categories?: Array< + | 'network' + | 'media' + | 'development' + | 'automation' + | 'social' + | 'utilities' + | 'photography' + | 'security' + | 'featured' + | 'books' + | 'data' + | 'music' + | 'finance' + | 'gaming' + | 'ai' + >; + url_suffix?: string; + form_fields?: Array<{ + type: 'text' | 'password' | 'email' | 'number' | 'fqdn' | 'ip' | 'fqdnip' | 'url' | 'random' | 'boolean'; + label: string; + placeholder?: string; + max?: number; + min?: number; + hint?: string; + options?: Array<{ + label: string; + value: string; + }>; + required?: boolean; + default?: boolean | string | number; + regex?: string; + pattern_error?: string; + env_variable: string; + encoding?: 'hex' | 'base64'; + }>; + https?: boolean; + exposable?: boolean; + no_gui?: boolean; + supported_architectures?: Array<'arm64' | 'amd64'>; + uid?: number; + gid?: number; + dynamic_config?: boolean; + min_tipi_version?: string; + created_at?: number; + updated_at?: number; + }; + app: { + id: string; + status: + | 'running' + | 'stopped' + | 'starting' + | 'stopping' + | 'updating' + | 'missing' + | 'installing' + | 'uninstalling' + | 'resetting' + | 'restarting' + | 'backing_up' + | 'restoring'; + lastOpened: string | null; + numOpened?: number; + createdAt?: string; + updatedAt?: string; + version: number; + exposed: boolean; + openPort: boolean; + exposedLocal: boolean; + domain: string | null; + isVisibleOnGuestDashboard: boolean; + }; + updateInfo: { + latestVersion: number; + minTipiVersion?: string; + latestDockerVersion?: string; + }; +}; + +export type status = + | 'running' + | 'stopped' + | 'starting' + | 'stopping' + | 'updating' + | 'missing' + | 'installing' + | 'uninstalling' + | 'resetting' + | 'restarting' + | 'backing_up' + | 'restoring'; + +export type AppFormBody = { + exposed?: boolean; + exposedLocal?: boolean; + openPort?: boolean; + domain?: string; + isVisibleOnGuestDashboard?: boolean; +}; + +export type RestoreAppBackupDto = { + filename: string; +}; + +export type GetAppBackupsDto = { + data: Array<{ + id: string; + size: number; + date: number; + }>; + total: number; + currentPage: number; + lastPage: number; +}; + +export type DeleteAppBackupBodyDto = { + filename: string; +}; + +export type LinksDto = { + links: Array<{ + id: number; + title: string; + description: string | null; + url: string; + iconUrl: string | null; + userId: number; + }>; +}; + +export type LinkBodyDto = { + title: string; + url: string; + description?: string; + iconUrl?: string; +}; + +export type EditLinkBodyDto = { + title: string; + url: string; + description?: string; + iconUrl?: string; +}; + +export type UserContextResponse = UserContextDto; + +export type UserContextError = unknown; + +export type AppContextResponse = AppContextDto; + +export type AppContextError = unknown; + +export type UpdateUserSettingsData = { + body: PartialUserSettingsDto; +}; + +export type UpdateUserSettingsResponse = unknown; + +export type UpdateUserSettingsError = unknown; + +export type AcknowledgeWelcomeData = { + body: AcknowledgeWelcomeBody; +}; + +export type AcknowledgeWelcomeResponse = unknown; + +export type AcknowledgeWelcomeError = unknown; + +export type SystemLoadResponse = LoadDto; + +export type SystemLoadError = unknown; + +export type GetTranslationData = { + path: { + lng: string; + ns: string; + }; +}; + +export type GetTranslationResponse = { + [key: string]: unknown; +}; + +export type GetTranslationError = unknown; + +export type LoginData = { + body: LoginBody; +}; + +export type LoginResponse = LoginDto; + +export type LoginError = unknown; + +export type VerifyTotpData = { + body: VerifyTotpBody; +}; + +export type VerifyTotpResponse = LoginDto; + +export type VerifyTotpError = unknown; + +export type RegisterData = { + body: RegisterBody; +}; + +export type RegisterResponse = RegisterDto; + +export type RegisterError = unknown; + +export type LogoutResponse = unknown; + +export type LogoutError = unknown; + +export type ChangeUsernameData = { + body: ChangeUsernameBody; +}; + +export type ChangeUsernameResponse = unknown; + +export type ChangeUsernameError = unknown; + +export type ChangePasswordData = { + body: ChangePasswordBody; +}; + +export type ChangePasswordResponse = unknown; + +export type ChangePasswordError = unknown; + +export type GetTotpUriData = { + body: GetTotpUriBody; +}; + +export type GetTotpUriResponse = GetTotpUriDto; + +export type GetTotpUriError = unknown; + +export type SetupTotpData = { + body: SetupTotpBody; +}; + +export type SetupTotpResponse = unknown; + +export type SetupTotpError = unknown; + +export type DisableTotpData = { + body: DisableTotpBody; +}; + +export type DisableTotpResponse = unknown; + +export type DisableTotpError = unknown; + +export type ResetPasswordData = { + body: ResetPasswordBody; +}; + +export type ResetPasswordResponse = ResetPasswordDto; + +export type ResetPasswordError = unknown; + +export type CancelResetPasswordResponse = unknown; + +export type CancelResetPasswordError = unknown; + +export type CheckResetPasswordRequestResponse = CheckResetPasswordRequestDto; + +export type CheckResetPasswordRequestError = unknown; + +export type GetInstalledAppsResponse = MyAppsDto; + +export type GetInstalledAppsError = unknown; + +export type GetGuestAppsResponse = GuestAppsDto; + +export type GetGuestAppsError = unknown; + +export type SearchAppsData = { + query?: { + category?: + | 'network' + | 'media' + | 'development' + | 'automation' + | 'social' + | 'utilities' + | 'photography' + | 'security' + | 'featured' + | 'books' + | 'data' + | 'music' + | 'finance' + | 'gaming' + | 'ai'; + cursor?: string; + pageSize?: number; + search?: string; + }; +}; + +export type SearchAppsResponse = SearchAppsDto; + +export type SearchAppsError = unknown; + +export type GetAppDetailsData = { + path: { + id: string; + }; +}; + +export type GetAppDetailsResponse = AppDetailsDto; + +export type GetAppDetailsError = unknown; + +export type GetImageData = { + path: { + id: string; + }; +}; + +export type GetImageResponse = unknown; + +export type GetImageError = unknown; + +export type InstallAppData = { + body: AppFormBody; + path: { + id: string; + }; +}; + +export type InstallAppResponse = unknown; + +export type InstallAppError = unknown; + +export type StartAppData = { + path: { + id: string; + }; +}; + +export type StartAppResponse = unknown; + +export type StartAppError = unknown; + +export type StopAppData = { + path: { + id: string; + }; +}; + +export type StopAppResponse = unknown; + +export type StopAppError = unknown; + +export type RestartAppData = { + path: { + id: string; + }; +}; + +export type RestartAppResponse = unknown; + +export type RestartAppError = unknown; + +export type UninstallAppData = { + path: { + id: string; + }; +}; + +export type UninstallAppResponse = unknown; + +export type UninstallAppError = unknown; + +export type ResetAppData = { + path: { + id: string; + }; +}; + +export type ResetAppResponse = unknown; + +export type ResetAppError = unknown; + +export type BackupAppData = { + path: { + appid: string; + }; +}; + +export type BackupAppResponse = unknown; + +export type BackupAppError = unknown; + +export type RestoreAppBackupData = { + body: RestoreAppBackupDto; + path: { + appid: string; + }; +}; + +export type RestoreAppBackupResponse = unknown; + +export type RestoreAppBackupError = unknown; + +export type GetAppBackupsData = { + path: { + id: string; + }; + query?: { + page?: number; + pageSize?: number; + }; +}; + +export type GetAppBackupsResponse = GetAppBackupsDto; + +export type GetAppBackupsError = unknown; + +export type DeleteAppBackupData = { + body: DeleteAppBackupBodyDto; + path: { + appid: string; + }; +}; + +export type DeleteAppBackupResponse = unknown; + +export type DeleteAppBackupError = unknown; + +export type GetLinksResponse = LinksDto; + +export type GetLinksError = unknown; + +export type CreateLinkData = { + body: LinkBodyDto; +}; + +export type CreateLinkResponse = unknown; + +export type CreateLinkError = unknown; + +export type EditLinkData = { + body: EditLinkBodyDto; + path: { + id: number; + }; +}; + +export type EditLinkResponse = unknown; + +export type EditLinkError = unknown; + +export type DeleteLinkData = { + path: { + id: number; + }; +}; + +export type DeleteLinkResponse = unknown; + +export type DeleteLinkError = unknown; diff --git a/packages/frontend/src/assets/react.svg b/packages/frontend/src/assets/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/packages/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/frontend/src/components/app-logo/app-logo.css b/packages/frontend/src/components/app-logo/app-logo.css new file mode 100644 index 0000000000..b1f4318905 --- /dev/null +++ b/packages/frontend/src/components/app-logo/app-logo.css @@ -0,0 +1,3 @@ +.drop-shadow { + filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06)); +} diff --git a/packages/frontend/src/components/app-logo/app-logo.tsx b/packages/frontend/src/components/app-logo/app-logo.tsx new file mode 100644 index 0000000000..3bd629a95d --- /dev/null +++ b/packages/frontend/src/components/app-logo/app-logo.tsx @@ -0,0 +1,25 @@ +import clsx from 'clsx'; +import type React from 'react'; +import './app-logo.css'; + +export const AppLogo: React.FC<{ id?: string; url?: string; size?: number; className?: string; alt?: string; placeholder?: boolean }> = ({ + id, + url, + size = 80, + className = '', + alt = '', +}) => { + const logoUrl = id ? `/api/apps/${id}/image` : '/app-not-found.jpg'; + + return ( +
+ {/* biome-ignore lint/a11y/noSvgWithoutTitle: Svg has no alt attibute */} + + + + + + +
+ ); +}; diff --git a/packages/frontend/src/components/app-store-layout-actions/app-store-layout-actions.css b/packages/frontend/src/components/app-store-layout-actions/app-store-layout-actions.css new file mode 100644 index 0000000000..907901a179 --- /dev/null +++ b/packages/frontend/src/components/app-store-layout-actions/app-store-layout-actions.css @@ -0,0 +1,5 @@ +.search-input { + @media screen and (min-width: 768px) { + max-width: 300px; + } +} diff --git a/packages/frontend/src/components/app-store-layout-actions/app-store-layout-actions.tsx b/packages/frontend/src/components/app-store-layout-actions/app-store-layout-actions.tsx new file mode 100644 index 0000000000..35400df205 --- /dev/null +++ b/packages/frontend/src/components/app-store-layout-actions/app-store-layout-actions.tsx @@ -0,0 +1,31 @@ +import { Input } from '@/components/ui/Input'; +import clsx from 'clsx'; +import type React from 'react'; +import { useState } from 'react'; +import { useAppStoreState } from '@/stores/app-store'; +import { useTranslation } from 'react-i18next'; +import './app-store-layout-actions.css'; +import { CategorySelector } from '../category-selector/category-selector'; + +export const AppStoreLayoutActions = () => { + const { setCategory, category, search: initialSearch, setSearch } = useAppStoreState(); + const [search, setLocalSearch] = useState(initialSearch); + const { t } = useTranslation(); + + const onSearch = (e: React.ChangeEvent) => { + setLocalSearch(e.target.value); + setSearch(e.target.value); + }; + + return ( +
+ + +
+ ); +}; diff --git a/packages/frontend/src/components/category-selector/category-selector.tsx b/packages/frontend/src/components/category-selector/category-selector.tsx new file mode 100644 index 0000000000..506b4c9673 --- /dev/null +++ b/packages/frontend/src/components/category-selector/category-selector.tsx @@ -0,0 +1,45 @@ +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/Select'; +import { iconForCategory } from '@/modules/app/helpers/table-helpers'; +import type { AppCategory } from '@/types/app.types'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +interface Props { + onSelect: (value?: AppCategory) => void; + className?: string; + initialValue?: AppCategory; +} + +export const CategorySelector = ({ onSelect, className, initialValue }: Props) => { + const { t } = useTranslation(); + const options = iconForCategory.map((category) => ({ + value: category.id, + label: t(`APP_CATEGORY_${category.id.toUpperCase()}`), + icon: category.icon, + })); + + const [value, setValue] = useState(initialValue); + + const handleChange = (option: AppCategory) => { + setValue(option); + onSelect(option); + }; + + return ( + + ); +}; diff --git a/packages/frontend/src/components/date-format/date-format.tsx b/packages/frontend/src/components/date-format/date-format.tsx new file mode 100644 index 0000000000..f9a6bd5f1f --- /dev/null +++ b/packages/frontend/src/components/date-format/date-format.tsx @@ -0,0 +1,35 @@ +import { useAppContext } from '@/context/app-context'; +import Cookies from 'js-cookie'; + +type IProps = { + date: Date | string; +}; + +export const useDateFormat = () => { + const { userSettings } = useAppContext(); + const { timeZone } = userSettings; + + const locale = Cookies.get('tipi-locale') || 'en-US'; + + const formatDate = (date?: Date | string) => { + if (!date) return 'Invalid date'; + + const parsedDate = new Date(date); + if (Number.isNaN(parsedDate.getTime())) return 'Invalid date'; + + return new Date(date).toLocaleString(locale, { timeZone }); + }; + + return formatDate; +}; + +export const DateFormat = ({ date }: IProps) => { + const { userSettings } = useAppContext(); + const { timeZone } = userSettings; + + const locale = Cookies.get('tipi-locale') || 'en-US'; + + const formattedDate = new Date(date).toLocaleString(locale, { timeZone }); + + return <>{formattedDate}; +}; diff --git a/packages/frontend/src/components/empty-page/empty-page.css b/packages/frontend/src/components/empty-page/empty-page.css new file mode 100644 index 0000000000..c05f521c26 --- /dev/null +++ b/packages/frontend/src/components/empty-page/empty-page.css @@ -0,0 +1,4 @@ +.empty-image { + height: 80px; + width: 80px; +} diff --git a/packages/frontend/src/components/empty-page/empty-page.tsx b/packages/frontend/src/components/empty-page/empty-page.tsx new file mode 100644 index 0000000000..bd3f8b5ef0 --- /dev/null +++ b/packages/frontend/src/components/empty-page/empty-page.tsx @@ -0,0 +1,42 @@ +import { Button } from '@/components/ui/Button'; +import type React from 'react'; +import './empty-page.css'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; + +interface IProps { + title: string; + subtitle?: string; + actionLabel?: string; + redirectPath?: string; +} + +export const EmptyPage: React.FC = ({ title, subtitle, redirectPath, actionLabel }) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ Empty box +

{t(title)}

+ {subtitle &&

{t(subtitle)}

} +
+ {redirectPath && actionLabel && ( + + )} +
+
+ ); +}; diff --git a/packages/frontend/src/components/error/error-page.tsx b/packages/frontend/src/components/error/error-page.tsx new file mode 100644 index 0000000000..91f4e5ffcf --- /dev/null +++ b/packages/frontend/src/components/error/error-page.tsx @@ -0,0 +1,37 @@ +import { IconReload } from '@tabler/icons-react'; +import { useLocation } from 'react-router-dom'; +import { Button } from '../ui/Button'; + +type ErrorPageProps = { + onReset: () => void; + error: Error; +}; + +export const ErrorPage = ({ error, onReset }: ErrorPageProps) => { + const location = useLocation(); + + return ( +
+
+
+

Oops... An error occurred!

+

+ Try refreshing the page or click the button below to try again. If the problem persists, open an issue on GitHub with the error message + below. +

+
+ +
+
+            {error.message}
+            
+ Location: {location.pathname} +
+
+
+
+ ); +}; diff --git a/packages/frontend/src/components/file-size/file-size.tsx b/packages/frontend/src/components/file-size/file-size.tsx new file mode 100644 index 0000000000..6b43081c48 --- /dev/null +++ b/packages/frontend/src/components/file-size/file-size.tsx @@ -0,0 +1,21 @@ +const formatBytes = (bytes: number, decimals = 2) => { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`; +}; + +type IProps = { + size: number; +}; + +export const FileSize = ({ size }: IProps) => { + const fileSize = formatBytes(size); + + return <>{fileSize}; +}; diff --git a/packages/frontend/src/components/header/guest-header.tsx b/packages/frontend/src/components/header/guest-header.tsx new file mode 100644 index 0000000000..985f34df9c --- /dev/null +++ b/packages/frontend/src/components/header/guest-header.tsx @@ -0,0 +1,91 @@ +import { getLogo } from '@/lib/theme/theme'; +import { useUIStore } from '@/stores/ui-store'; +import { IconBrandGithub, IconHeart, IconLogin, IconMoon, IconSun } from '@tabler/icons-react'; +import { useTranslation } from 'react-i18next'; +import { Link, useNavigate } from 'react-router-dom'; +import { Tooltip } from 'react-tooltip'; + +export const GuestHeader = () => { + const { setDarkMode } = useUIStore(); + + const { t } = useTranslation(); + + const navigate = useNavigate(); + + return ( +
+ ); +}; diff --git a/packages/frontend/src/components/header/header.tsx b/packages/frontend/src/components/header/header.tsx new file mode 100644 index 0000000000..9a6a109b45 --- /dev/null +++ b/packages/frontend/src/components/header/header.tsx @@ -0,0 +1,105 @@ +import { logoutMutation } from '@/api-client/@tanstack/react-query.gen'; +import { getLogo } from '@/lib/theme/theme'; +import { useUIStore } from '@/stores/ui-store'; +import { IconBrandGithub, IconHeart, IconLogin, IconLogout, IconMoon, IconSun } from '@tabler/icons-react'; +import { useMutation } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { Tooltip } from 'react-tooltip'; +import { NavBar } from '../navbar/navbar'; + +type HeaderProps = { + isUpdateAvailable: boolean; + isLoggedIn: boolean; + allowAutoThemes: boolean; +}; + +export const Header = (props: HeaderProps) => { + const { setDarkMode } = useUIStore(); + + const { isUpdateAvailable, allowAutoThemes, isLoggedIn } = props; + + const { t } = useTranslation(); + + const logout = useMutation({ + ...logoutMutation(), + }); + + return ( +
+
+ + +

+ Runtipi logo + Runtipi +

+ +
+ +
+ + {t('HEADER_DARK_MODE')} + + + + {t('HEADER_LIGHT_MODE')} + + + + {isLoggedIn ? t('HEADER_LOGOUT') : t('HEADER_LOGIN')} + + +
+
+ +
+
+ ); +}; diff --git a/packages/frontend/src/components/language-selector/language-selector.tsx b/packages/frontend/src/components/language-selector/language-selector.tsx new file mode 100644 index 0000000000..8dc900bcd3 --- /dev/null +++ b/packages/frontend/src/components/language-selector/language-selector.tsx @@ -0,0 +1,52 @@ +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/Select'; +import { LOCALE_OPTIONS, type Locale, getLocaleFromString } from '@/lib/i18n/locales'; +import { IconExternalLink } from '@tabler/icons-react'; +import Cookies from 'js-cookie'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +type IProps = { + showLabel?: boolean; + locale: Locale; +}; + +const LanguageSelectorLabel = () => { + const { t } = useTranslation(); + + return ( + + {t('SETTINGS_GENERAL_LANGUAGE')}  + + {t('SETTINGS_GENERAL_LANGUAGE_HELP_TRANSLATE')} + + + + ); +}; + +export const LanguageSelector = (props: IProps) => { + const { locale: initialLocale } = props; + const [locale, setLocale] = React.useState(initialLocale); + const { showLabel = false } = props; + + const onChange = (newLocale: Locale) => { + const locale = getLocaleFromString(newLocale); + setLocale(newLocale); + Cookies.set('tipi-locale', locale); + }; + + return ( + + ); +}; diff --git a/packages/frontend/src/components/layouts/auth/layout.tsx b/packages/frontend/src/components/layouts/auth/layout.tsx new file mode 100644 index 0000000000..d5ec79eeaa --- /dev/null +++ b/packages/frontend/src/components/layouts/auth/layout.tsx @@ -0,0 +1,27 @@ +import { getLogo } from '@/lib/theme/theme'; +import type { PropsWithChildren } from 'react'; + +export const AuthLayout = ({ children }: PropsWithChildren) => { + return ( +
+
+
+
+ Tipi logo +
+
+
{children}
+
+
+
+ ); +}; diff --git a/packages/frontend/src/components/layouts/dashboard/layout-actions.tsx b/packages/frontend/src/components/layouts/dashboard/layout-actions.tsx new file mode 100644 index 0000000000..76665bbf92 --- /dev/null +++ b/packages/frontend/src/components/layouts/dashboard/layout-actions.tsx @@ -0,0 +1,21 @@ +import { AppStoreLayoutActions } from '@/components/app-store-layout-actions/app-store-layout-actions'; +import { useUIStore } from '@/stores/ui-store'; + +type Props = { + availableUpdates: number; +}; + +export const LayoutActions = (props: Props) => { + // const { availableUpdates } = props; + const { activeRoute } = useUIStore(); + + if (activeRoute === 'app-store') { + return ; + } + + // if (activeRoute === '/apps' && availableUpdates >= 2) { + // return ; + // } + + return null; +}; diff --git a/packages/frontend/src/components/layouts/dashboard/layout.css b/packages/frontend/src/components/layouts/dashboard/layout.css new file mode 100644 index 0000000000..1baf418f56 --- /dev/null +++ b/packages/frontend/src/components/layouts/dashboard/layout.css @@ -0,0 +1,5 @@ + +.title { + min-height: 50px; + min-width: 200px; +} diff --git a/packages/frontend/src/components/layouts/dashboard/layout.tsx b/packages/frontend/src/components/layouts/dashboard/layout.tsx new file mode 100644 index 0000000000..855a412ceb --- /dev/null +++ b/packages/frontend/src/components/layouts/dashboard/layout.tsx @@ -0,0 +1,67 @@ +import { Header } from '@/components/header/header'; +import clsx from 'clsx'; +import type { PropsWithChildren } from 'react'; +import semver from 'semver'; +import './layout.css'; +import { PageTitle } from '@/components/page-title/page-title'; +import { Welcome } from '@/components/welcome/welcome'; +import { useAppContext } from '@/context/app-context'; +import { useUserContext } from '@/context/user-context'; +import { LayoutActions } from './layout-actions'; + +export const DashboardLayoutSuspense = ({ children }: PropsWithChildren) => { + return ( +
+
+
+
+ +
+
+
+
{children}
+
+
+
+
+ ); +}; + +export const DashboardLayout = ({ children }: PropsWithChildren) => { + const { userSettings, user, apps, version } = useAppContext(); + // const availableUpdates = apps.filter((app) => Number(app.version) < Number(app.latestVersion) && app.status !== 'updating').length; + + const { isLoggedIn } = useUserContext(); + + const isLatest = semver.valid(version.current) && semver.valid(version.latest) && semver.gte(version.current, version.latest); + + if (!user.hasSeenWelcome) { + return ; + } + + return ( +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ {children} + {/* {isInstanceInsecure() && } */} +
+
+
+
+ ); +}; diff --git a/packages/frontend/src/components/logs-terminal/logs-terminal.css b/packages/frontend/src/components/logs-terminal/logs-terminal.css new file mode 100644 index 0000000000..56179a49e6 --- /dev/null +++ b/packages/frontend/src/components/logs-terminal/logs-terminal.css @@ -0,0 +1,9 @@ +.log-terminal { + height: 300px; + overflow-y: scroll !important; + scrollbar-color: rgb(94, 107, 133) rgb(29, 39, 59); +} + +.wrap-lines { + text-wrap: wrap; +} diff --git a/packages/frontend/src/components/logs-terminal/logs-terminal.tsx b/packages/frontend/src/components/logs-terminal/logs-terminal.tsx new file mode 100644 index 0000000000..de0244cb5f --- /dev/null +++ b/packages/frontend/src/components/logs-terminal/logs-terminal.tsx @@ -0,0 +1,75 @@ +import { useLocalStorage } from '@uidotdev/usehooks'; +import clsx from 'clsx'; +import DOMPurify from 'dompurify'; +import { useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import './logs-terminal.css'; + +type Props = { + logs: { id: number; text: string }[]; + maxLines: number; + onMaxLinesChange: (lines: number) => void; +}; + +export const LogsTerminal = (props: Props) => { + const { t } = useTranslation(); + + const { logs, onMaxLinesChange, maxLines } = props; + const [follow, setFollow] = useLocalStorage('logs-follow', true); + const [wrapLines, setWrapLines] = useLocalStorage('logs-wraplines', false); + const ref = useRef(null); + + const lastLogId = logs.length > 0 ? logs.at(-1)?.id : null; + + // biome-ignore lint/correctness/useExhaustiveDependencies: necessary to update the scroll when a new log is added + useEffect(() => { + if (ref.current && follow) { + ref.current.scrollTop = ref.current.scrollHeight; + } + }, [lastLogId, follow]); + + const updateMaxLines = (lines: number) => { + const linesToKeep = Math.max(1, lines); + onMaxLinesChange(linesToKeep); + }; + + return ( +
+
+
+ +
+
+ +
+
+
+ {t('APP_LOGS_TAB_MAX_LINES')} + updateMaxLines(Number.parseInt(e.target.value, 10))} + /> +
+
+
+
 DOMPurify.sanitize(log.text)).join('
') }} + /> +
+ ); +}; diff --git a/packages/frontend/src/components/markdown/markdown.tsx b/packages/frontend/src/components/markdown/markdown.tsx new file mode 100644 index 0000000000..62d5ddcee9 --- /dev/null +++ b/packages/frontend/src/components/markdown/markdown.tsx @@ -0,0 +1,12 @@ +import clsx from 'clsx'; +import type React from 'react'; +import ReactMarkdown from 'react-markdown'; +import rehypeRaw from 'rehype-raw'; +import remarkBreaks from 'remark-breaks'; +import remarkGfm from 'remark-gfm'; + +export const Markdown: React.FC<{ content: string; className: string }> = ({ content, className }) => ( + + {content} + +); diff --git a/packages/frontend/src/components/navbar/navbar.tsx b/packages/frontend/src/components/navbar/navbar.tsx new file mode 100644 index 0000000000..8d70337f45 --- /dev/null +++ b/packages/frontend/src/components/navbar/navbar.tsx @@ -0,0 +1,45 @@ +import { useUIStore } from '@/stores/ui-store'; +import { IconApps, IconBrandAppstore, IconHome, IconSettings } from '@tabler/icons-react'; +import clsx from 'clsx'; +import type React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; + +interface IProps { + isUpdateAvailable?: boolean; +} + +export const NavBar: React.FC = ({ isUpdateAvailable }) => { + const { t } = useTranslation(); + const { activeRoute } = useUIStore(); + + const renderItem = (title: string, name: string, IconComponent: typeof IconApps) => { + const isActive = activeRoute?.split('/')[0] === name; + const itemClass = clsx('nav-item', { active: isActive, 'border-primary': isActive, 'border-bottom-wide': isActive }); + + return ( +
  • + + + + + {title} + +
  • + ); + }; + + return ( + + ); +}; diff --git a/packages/frontend/src/components/page-title/page-title.tsx b/packages/frontend/src/components/page-title/page-title.tsx new file mode 100644 index 0000000000..c99aac2b3f --- /dev/null +++ b/packages/frontend/src/components/page-title/page-title.tsx @@ -0,0 +1,41 @@ +import { useUIStore } from '@/stores/ui-store'; +import type { AppInfoSimple } from '@/types/app.types'; +import clsx from 'clsx'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; + +type Props = { + apps: AppInfoSimple[]; +}; + +export const PageTitle = ({ apps }: Props) => { + const { t } = useTranslation(); + const { activeRoute } = useUIStore(); + + const path = window.location.pathname; + // biome-ignore lint/correctness/useExhaustiveDependencies: Explicitly ignore this rule to re-render the component when the activeRoute changes + const pathArray = useMemo(() => path?.substring(1).split('/') || [], [activeRoute]); + + const renderBreadcrumbs = () => { + return ( +
      + {pathArray.map((breadcrumb, index) => ( +
    1. + {breadcrumb} +
    2. + ))} +
    + ); + }; + + const appTitle = apps.find((app) => app.id === pathArray[1])?.name; + const title = appTitle ?? t(`HEADER_${pathArray[pathArray.length - 1]?.toUpperCase().replace('-', '_')}`); + + return ( + <> +
    {renderBreadcrumbs()}
    +

    {title}

    + + ); +}; diff --git a/packages/frontend/src/components/providers/i18n/i18n-provider.tsx b/packages/frontend/src/components/providers/i18n/i18n-provider.tsx new file mode 100644 index 0000000000..ebe90b57af --- /dev/null +++ b/packages/frontend/src/components/providers/i18n/i18n-provider.tsx @@ -0,0 +1,23 @@ +import type { PropsWithChildren } from 'react'; +import i18n from 'i18next'; +import HttpBackend from 'i18next-http-backend'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import { initReactI18next, I18nextProvider } from 'react-i18next'; + +i18n + .use(HttpBackend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + backend: { + loadPath: '/api/i18n/locales/{{lng}}/{{ns}}.json', + }, + fallbackLng: 'en', + interpolation: { + escapeValue: false, + }, + }); + +export const I18nProvider = ({ children }: PropsWithChildren) => { + return {children}; +}; diff --git a/packages/frontend/src/components/providers/location-listener/location-listener.tsx b/packages/frontend/src/components/providers/location-listener/location-listener.tsx new file mode 100644 index 0000000000..5744b22930 --- /dev/null +++ b/packages/frontend/src/components/providers/location-listener/location-listener.tsx @@ -0,0 +1,15 @@ +import { useUIStore } from '@/stores/ui-store'; +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +export const LocationListener = () => { + const { setActiveRoute } = useUIStore(); + const location = useLocation(); + + useEffect(() => { + // This will run whenever the URL changes + setActiveRoute(location.pathname.substring(1)); + }, [location, setActiveRoute]); + + return null; +}; diff --git a/packages/frontend/src/components/providers/providers.tsx b/packages/frontend/src/components/providers/providers.tsx new file mode 100644 index 0000000000..2a4ccb8c29 --- /dev/null +++ b/packages/frontend/src/components/providers/providers.tsx @@ -0,0 +1,58 @@ +import { UserContextProvider } from '@/context/user-context'; +import { MutationCache, QueryClient, QueryClientProvider, QueryErrorResetBoundary } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { type PropsWithChildren, Suspense } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; +import { ErrorPage } from '../error/error-page'; +import { I18nProvider } from './i18n/i18n-provider'; +import { SocketProvider } from './socket/socket-provider'; +import { ThemeProvider } from './theme/theme-provider'; + +const queryClient = new QueryClient({ + mutationCache: new MutationCache({ + onSuccess: () => { + queryClient.invalidateQueries(); + }, + }), + defaultOptions: { + queries: { retry: false, refetchOnWindowFocus: false }, + }, +}); + +const PageSuspense = ({ children }: PropsWithChildren) => { + return ( +
    +
    {children}
    +
    + ); +}; + +export const Providers = ({ children }: PropsWithChildren) => { + return ( + + + {({ reset }) => ( + ( + + + + )} + onReset={reset} + > + }> + + + + {children} + + + + + + )} + + + + ); +}; diff --git a/packages/frontend/src/components/providers/socket/socket-provider.tsx b/packages/frontend/src/components/providers/socket/socket-provider.tsx new file mode 100644 index 0000000000..00ec8f27c5 --- /dev/null +++ b/packages/frontend/src/components/providers/socket/socket-provider.tsx @@ -0,0 +1,80 @@ +import { useSocket } from '@/lib/hooks/use-socket'; +import { useQueryClient } from '@tanstack/react-query'; +import type { PropsWithChildren } from 'react'; +import toast from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; + +export const SocketProvider = ({ children }: PropsWithChildren) => { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + + useSocket({ + onEvent: (event, data) => { + queryClient.invalidateQueries(); + + switch (event) { + case 'status_change': + break; + case 'install_success': + toast.success(t('APP_INSTALL_SUCCESS', { id: data.appId })); + break; + case 'install_error': + toast.error(t('APP_ERROR_APP_FAILED_TO_INSTALL', { id: data.appId })); + break; + case 'start_success': + toast.success(t('APP_START_SUCCESS', { id: data.appId })); + break; + case 'start_error': + toast.error(t('APP_ERROR_APP_FAILED_TO_START', { id: data.appId })); + break; + case 'stop_success': + toast.success(t('APP_STOP_SUCCESS', { id: data.appId })); + break; + case 'stop_error': + toast.error(t('APP_ERROR_APP_FAILED_TO_STOP', { id: data.appId })); + break; + case 'uninstall_success': + toast.success(t('APP_UNINSTALL_SUCCESS', { id: data.appId })); + break; + case 'uninstall_error': + toast.error(t('APP_ERROR_APP_FAILED_TO_UNINSTALL', { id: data.appId })); + break; + case 'update_success': + toast.success(t('APP_UPDATE_SUCCESS', { id: data.appId })); + break; + case 'update_error': + toast.error(t('APP_ERROR_APP_FAILED_TO_UPDATE', { id: data.appId })); + break; + case 'reset_success': + toast.success(t('APP_RESET_SUCCESS', { id: data.appId })); + break; + case 'reset_error': + toast.error(t('APP_ERROR_APP_FAILED_TO_RESET', { id: data.appId })); + break; + case 'restart_success': + toast.success(t('APP_RESTART_SUCCESS', { id: data.appId })); + break; + case 'restart_error': + toast.error(t('APP_ERROR_APP_FAILED_TO_RESTART', { id: data.appId })); + break; + case 'backup_success': + toast.success(t('APP_BACKUP_SUCCESS', { id: data.appId })); + break; + case 'backup_error': + toast.error(t('APP_BACKUP_ERROR', { id: data.appId })); + break; + case 'restore_success': + toast.success(t('APP_RESTORE_SUCCESS', { id: data.appId })); + break; + case 'restore_error': + toast.error(t('APP_RESTORE_ERROR', { id: data.appId })); + break; + default: + break; + } + }, + selector: { type: 'app' }, + }); + + return children; +}; diff --git a/packages/frontend/src/components/providers/theme/theme-provider.tsx b/packages/frontend/src/components/providers/theme/theme-provider.tsx new file mode 100644 index 0000000000..28b3673b95 --- /dev/null +++ b/packages/frontend/src/components/providers/theme/theme-provider.tsx @@ -0,0 +1,33 @@ +import { useUIStore } from '@/stores/ui-store'; +import Cookies from 'js-cookie'; +import type React from 'react'; +import { useEffect } from 'react'; + +type Props = { + children: React.ReactNode; + initialTheme?: string; +}; + +export const ThemeProvider = (props: Props) => { + const { children, initialTheme } = props; + + const { theme, setDarkMode } = useUIStore(); + + useEffect(() => { + if (theme) { + Cookies.set('theme', theme || initialTheme || 'light', { path: '/', expires: 365 }); + document.body.dataset.bsTheme = theme; + } else if (!Cookies.get('theme')) { + // Detect system theme + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + setDarkMode(systemTheme === 'dark'); + Cookies.set('theme', systemTheme, { path: '/', expires: 365 }); + document.body.dataset.bsTheme = systemTheme; + } + + const cookieTheme = Cookies.get('theme'); + setDarkMode(cookieTheme === 'dark'); + }, [initialTheme, setDarkMode, theme]); + + return children; +}; diff --git a/packages/frontend/src/components/routes/authenticated-route.tsx b/packages/frontend/src/components/routes/authenticated-route.tsx new file mode 100644 index 0000000000..d59fa20e4d --- /dev/null +++ b/packages/frontend/src/components/routes/authenticated-route.tsx @@ -0,0 +1,49 @@ +import { AppContextProvider } from '@/context/app-context'; +import { useUserContext } from '@/context/user-context'; +import { GuestDashboard } from '@/modules/dashboard/pages/guest-dashboard'; +import { QueryErrorResetBoundary } from '@tanstack/react-query'; +import { Suspense } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; +import { Navigate, Outlet } from 'react-router-dom'; +import { ErrorPage } from '../error/error-page'; +import { DashboardLayout, DashboardLayoutSuspense } from '../layouts/dashboard/layout'; +import { RouteWrapper } from './route-wrapper'; + +export const AuthenticatedRoute = () => { + const { isLoggedIn, isGuestDashboardEnabled } = useUserContext(); + + if (!isLoggedIn && !isGuestDashboardEnabled) { + return ; + } + + if (isGuestDashboardEnabled && !isLoggedIn) { + return ; + } + + return ( + + + {({ reset }) => ( + ( + + + + )} + onReset={reset} + > + }> + + }> + + + + + + + + )} + + + ); +}; diff --git a/packages/frontend/src/components/routes/route-error.tsx b/packages/frontend/src/components/routes/route-error.tsx new file mode 100644 index 0000000000..3f205838b3 --- /dev/null +++ b/packages/frontend/src/components/routes/route-error.tsx @@ -0,0 +1,21 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { useNavigate, useRouteError } from 'react-router-dom'; +import { ErrorPage } from '../error/error-page'; + +export const RouteError = () => { + const error = useRouteError(); + const queryClient = useQueryClient(); + const navigate = useNavigate(); + + let message = 'Unknown error occurred'; + if (error instanceof Error) { + message = error.message; + } + + const onRetry = () => { + queryClient.resetQueries(); + navigate('/'); + }; + + return ; +}; diff --git a/packages/frontend/src/components/routes/route-wrapper.tsx b/packages/frontend/src/components/routes/route-wrapper.tsx new file mode 100644 index 0000000000..cd126d5445 --- /dev/null +++ b/packages/frontend/src/components/routes/route-wrapper.tsx @@ -0,0 +1,11 @@ +import type { PropsWithChildren } from 'react'; +import { LocationListener } from '../providers/location-listener/location-listener'; + +export const RouteWrapper = ({ children }: PropsWithChildren) => { + return ( + <> + {children} + + + ); +}; diff --git a/packages/frontend/src/components/routes/unauthenticated-route.tsx b/packages/frontend/src/components/routes/unauthenticated-route.tsx new file mode 100644 index 0000000000..1be0cf57a5 --- /dev/null +++ b/packages/frontend/src/components/routes/unauthenticated-route.tsx @@ -0,0 +1,16 @@ +import { Suspense } from 'react'; +import { AuthLayout } from '../layouts/auth/layout'; +import { Outlet } from 'react-router-dom'; +import { RouteWrapper } from './route-wrapper'; + +export const UnauthenticatedRoute = () => { + return ( + + }> + + + + + + ); +}; diff --git a/packages/frontend/src/components/timezone-selector/timezone-selector.tsx b/packages/frontend/src/components/timezone-selector/timezone-selector.tsx new file mode 100644 index 0000000000..a54ca36284 --- /dev/null +++ b/packages/frontend/src/components/timezone-selector/timezone-selector.tsx @@ -0,0 +1,48 @@ +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/Select'; +import { useTranslation } from 'react-i18next'; +import { allTimezones, useTimezoneSelect } from 'react-timezone-select'; + +type IProps = { + timeZone?: string; + onChange: (timeZone: string) => void; +}; + +export const TimeZoneSuspense = () => { + const { t } = useTranslation(); + + return ( + + ); +}; + +export const TimeZoneSelector = (props: IProps) => { + const { onChange, timeZone } = props; + const { options, parseTimezone } = useTimezoneSelect({ labelStyle: 'abbrev', timezones: allTimezones }); + const { t } = useTranslation(); + + const onTimezoneChange = (e: string) => { + if (!e) return; + onChange(e); + }; + + const zone = parseTimezone(timeZone || 'Etc/GMT').value || 'Etc/GMT'; + + return ( + + ); +}; diff --git a/packages/frontend/src/components/ui/Button/Button.module.css b/packages/frontend/src/components/ui/Button/Button.module.css new file mode 100644 index 0000000000..b92cd5bc6a --- /dev/null +++ b/packages/frontend/src/components/ui/Button/Button.module.css @@ -0,0 +1,3 @@ +.button { + max-height: 40px; +} diff --git a/packages/frontend/src/components/ui/Button/Button.test.tsx b/packages/frontend/src/components/ui/Button/Button.test.tsx new file mode 100644 index 0000000000..7223e30247 --- /dev/null +++ b/packages/frontend/src/components/ui/Button/Button.test.tsx @@ -0,0 +1,54 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { Button } from './Button'; + +describe('Button component', () => { + it('should render without crashing', () => { + render(); + }); + + it('should render children correctly', () => { + render(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('should apply className prop correctly', () => { + // arrange + render(); + const button = screen.getByRole('button'); + + // assert + expect(button).toHaveClass('test-class'); + }); + + it('should disable button when disabled prop is true', () => { + // arrange + render(); + const button = screen.getByRole('button'); + + // assert + expect(button).toBeDisabled(); + }); + + it('should set type correctly', () => { + // arrange + render(); + const button = screen.getByRole('button'); + + // assert + expect(button).toHaveAttribute('type', 'submit'); + }); + + it('should call onClick callback when clicked', () => { + // arrange + const onClick = vi.fn(); + render(); + const button = screen.getByRole('button'); + + // act + fireEvent.click(button); + + // assert + expect(onClick).toHaveBeenCalled(); + }); +}); diff --git a/packages/frontend/src/components/ui/Button/Button.tsx b/packages/frontend/src/components/ui/Button/Button.tsx new file mode 100644 index 0000000000..3f04f553e5 --- /dev/null +++ b/packages/frontend/src/components/ui/Button/Button.tsx @@ -0,0 +1,91 @@ +import { Slot } from '@radix-ui/react-slot'; +import { type VariantProps, cva } from 'class-variance-authority'; +import clsx from 'clsx'; +import * as React from 'react'; + +const buttonVariants = cva('btn', { + variants: { + variant: { + default: '', + ghost: 'btn-ghost-primary', + outline: 'btn-outline-primary', + }, + intent: { + default: '', + primary: 'btn-primary', + secondary: 'btn-secondary', + success: 'btn-success', + warning: 'btn-warning', + danger: 'btn-danger', + info: 'btn-info', + dark: 'btn-dark', + light: 'btn-light', + }, + size: { + default: '', + sm: 'btn-sm', + lg: 'btn-lg', + icon: 'btn-icon', + }, + }, + compoundVariants: [ + { + intent: 'danger', + variant: 'outline', + className: 'btn-outline-danger', + }, + { + intent: 'success', + variant: 'outline', + className: 'btn-outline-success', + }, + { + intent: ['default', 'primary'], + variant: 'outline', + className: 'btn-outline-primary', + }, + { + intent: 'danger', + variant: 'ghost', + className: 'btn-ghost-danger', + }, + { + intent: 'success', + variant: 'ghost', + className: 'btn-ghost-success', + }, + { + intent: ['default', 'primary'], + variant: 'ghost', + className: 'btn-ghost-primary', + }, + ], + defaultVariants: { + intent: 'default', + variant: 'default', + size: 'default', + }, +}); + +export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { + asChild?: boolean; + loading?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, intent, asChild = false, disabled, loading, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + }, +); +Button.displayName = 'Button'; + +export { Button, buttonVariants }; diff --git a/packages/frontend/src/components/ui/Button/index.ts b/packages/frontend/src/components/ui/Button/index.ts new file mode 100644 index 0000000000..438d8d11c4 --- /dev/null +++ b/packages/frontend/src/components/ui/Button/index.ts @@ -0,0 +1,4 @@ +import { Button, buttonVariants, type ButtonProps } from './Button'; + +export { Button, buttonVariants }; +export type { ButtonProps }; diff --git a/packages/frontend/src/components/ui/ContextMenu/ContextMenu.tsx b/packages/frontend/src/components/ui/ContextMenu/ContextMenu.tsx new file mode 100644 index 0000000000..3551a28520 --- /dev/null +++ b/packages/frontend/src/components/ui/ContextMenu/ContextMenu.tsx @@ -0,0 +1,124 @@ +'use client'; + +import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'; +import { IconCheck, IconChevronRight, IconCircle } from '@tabler/icons-react'; +import clsx from 'clsx'; +import * as React from 'react'; + +const ContextMenu = ContextMenuPrimitive.Root; + +const ContextMenuTrigger = ContextMenuPrimitive.Trigger; + +const ContextMenuGroup = ContextMenuPrimitive.Group; + +const ContextMenuPortal = ContextMenuPrimitive.Portal; + +const ContextMenuSub = ContextMenuPrimitive.Sub; + +const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup; + +const ContextMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName; + +const ContextMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName; + +const ContextMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName; + +const ContextMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName; + +const ContextMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName; + +const ContextMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName; + +const ContextMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ); +ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName; + +const ContextMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName; + +const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return ; +}; +ContextMenuShortcut.displayName = 'ContextMenuShortcut'; + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuCheckboxItem, + ContextMenuRadioItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuGroup, + ContextMenuPortal, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuRadioGroup, +}; diff --git a/packages/frontend/src/components/ui/DataGrid/DataGrid.test.tsx b/packages/frontend/src/components/ui/DataGrid/DataGrid.test.tsx new file mode 100644 index 0000000000..d75d63c800 --- /dev/null +++ b/packages/frontend/src/components/ui/DataGrid/DataGrid.test.tsx @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; +import { DataGrid } from './DataGrid'; +import { DataGridItem } from './DataGridItem'; +import { render, screen } from '@/tests/test-utils'; + +describe('DataGrid', () => { + it('should renders its children', () => { + // arrange + render(Test child); + + // assert + expect(screen.getByText('Test child')).toBeInTheDocument(); + }); +}); + +describe('DataGridItem', () => { + it('renders its children', () => { + // arrange + render(Test child); + + // assert + expect(screen.getByText('Test child')).toBeInTheDocument(); + }); + + it('renders the correct title', () => { + // arrange + render(Hello); + + // assert + expect(screen.getByText('Test Title')).toBeInTheDocument(); + }); +}); diff --git a/packages/frontend/src/components/ui/DataGrid/DataGrid.tsx b/packages/frontend/src/components/ui/DataGrid/DataGrid.tsx new file mode 100644 index 0000000000..56d5949c12 --- /dev/null +++ b/packages/frontend/src/components/ui/DataGrid/DataGrid.tsx @@ -0,0 +1,13 @@ +import type React from 'react'; + +interface IProps { + children: React.ReactNode; +} + +export const DataGrid: React.FC = ({ children }) => ( +
    +
    +
    {children}
    +
    +
    +); diff --git a/packages/frontend/src/components/ui/DataGrid/DataGridItem.tsx b/packages/frontend/src/components/ui/DataGrid/DataGridItem.tsx new file mode 100644 index 0000000000..0aa6606632 --- /dev/null +++ b/packages/frontend/src/components/ui/DataGrid/DataGridItem.tsx @@ -0,0 +1,13 @@ +import type React from 'react'; + +interface IProps { + title: string; + children: React.ReactNode; +} + +export const DataGridItem: React.FC = ({ children, title }) => ( +
    +
    {title}
    +
    {children}
    +
    +); diff --git a/packages/frontend/src/components/ui/DataGrid/index.ts b/packages/frontend/src/components/ui/DataGrid/index.ts new file mode 100644 index 0000000000..ce78b682a8 --- /dev/null +++ b/packages/frontend/src/components/ui/DataGrid/index.ts @@ -0,0 +1,2 @@ +export { DataGrid } from './DataGrid'; +export { DataGridItem } from './DataGridItem'; diff --git a/packages/frontend/src/components/ui/Dialog/Dialog.css b/packages/frontend/src/components/ui/Dialog/Dialog.css new file mode 100644 index 0000000000..90e41767c5 --- /dev/null +++ b/packages/frontend/src/components/ui/Dialog/Dialog.css @@ -0,0 +1,36 @@ +@keyframes zoomIn { + 0% { + transform: scale(0.8); + } + 50% { + transform: scale(1.05); + } + 100% { + transform: scale(1); + } +} + +@keyframes dimmedBackground { + from { + background-color: rgba(0, 0, 0, 0); + } + to { + background-color: rgba(0, 0, 0, 0.5); + } +} + +.dimmed-background { + animation-name: dimmedBackground; + animation-duration: 0.2s; + animation-iteration-count: 1; + animation-timing-function: ease-in-out; + animation-fill-mode: forwards; +} + +.zoom-in { + animation-name: zoomIn; + animation-duration: 0.25s; + animation-iteration-count: 1; + animation-timing-function: ease-out; + animation-fill-mode: forwards; +} diff --git a/packages/frontend/src/components/ui/Dialog/Dialog.tsx b/packages/frontend/src/components/ui/Dialog/Dialog.tsx new file mode 100644 index 0000000000..7ff12abe09 --- /dev/null +++ b/packages/frontend/src/components/ui/Dialog/Dialog.tsx @@ -0,0 +1,80 @@ +'use client'; + +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import clsx from 'clsx'; +import * as React from 'react'; +import './Dialog.css'; + +type Sizes = 'sm' | 'md' | 'lg' | 'xl'; +type ModalType = 'default' | 'primary' | 'success' | 'info' | 'warning' | 'danger'; + +type ModalProps = { + size?: Sizes; + type?: ModalType; +}; + +const Dialog = DialogPrimitive.Root; +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = ({ children, ...props }: DialogPrimitive.DialogPortalProps & ModalProps) => ( + +
    +
    +
    +
    + {children} +
    +
    +
    + +); +DialogPortal.displayName = DialogPrimitive.Portal.displayName; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & ModalProps +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
    +); +DialogHeader.displayName = 'DialogHeader'; + +const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
    +); +DialogFooter.displayName = 'DialogFooter'; + +const DialogTitle = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => , +); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +
    {props.children}
    +
    +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription }; diff --git a/packages/frontend/src/components/ui/Dialog/index.ts b/packages/frontend/src/components/ui/Dialog/index.ts new file mode 100644 index 0000000000..b5ef2c9d9c --- /dev/null +++ b/packages/frontend/src/components/ui/Dialog/index.ts @@ -0,0 +1 @@ +export { Dialog, DialogTitle, DialogFooter, DialogHeader, DialogContent, DialogTrigger, DialogDescription } from './Dialog'; diff --git a/packages/frontend/src/components/ui/DropdownMenu/DropdownMenu.tsx b/packages/frontend/src/components/ui/DropdownMenu/DropdownMenu.tsx new file mode 100644 index 0000000000..fdd0fe16dd --- /dev/null +++ b/packages/frontend/src/components/ui/DropdownMenu/DropdownMenu.tsx @@ -0,0 +1,68 @@ +'use client'; + +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; +import { IconChevronRight } from '@tabler/icons-react'; +import clsx from 'clsx'; +import * as React from 'react'; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +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) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuGroup }; diff --git a/packages/frontend/src/components/ui/DropdownMenu/index.ts b/packages/frontend/src/components/ui/DropdownMenu/index.ts new file mode 100644 index 0000000000..dcf2b54f96 --- /dev/null +++ b/packages/frontend/src/components/ui/DropdownMenu/index.ts @@ -0,0 +1 @@ +export { DropdownMenu, DropdownMenuItem, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuContent, DropdownMenuTrigger } from './DropdownMenu'; diff --git a/packages/frontend/src/components/ui/Input/Input.test.tsx b/packages/frontend/src/components/ui/Input/Input.test.tsx new file mode 100644 index 0000000000..33b6759501 --- /dev/null +++ b/packages/frontend/src/components/ui/Input/Input.test.tsx @@ -0,0 +1,149 @@ +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; +import { Input } from './Input'; +import { fireEvent, render, screen, waitFor } from '@/tests/test-utils'; + +describe('Input', () => { + it('should render without errors', () => { + // arrange + const { container } = render(); + + // assert + expect(container).toBeTruthy(); + }); + + it('should render the label if provided', () => { + // arrange + render(); + const input = screen.getByLabelText('Test Label'); + + // assert + expect(input).toBeInTheDocument(); + }); + + it('should render the placeholder if provided', () => { + // arrange + render(); + + // assert + const input = screen.getByPlaceholderText('Test Placeholder'); + expect(input).toBeInTheDocument(); + }); + + it('should render the error message if provided', () => { + // arrange + render(); + + // assert + const error = screen.getByText('Test Error'); + expect(error).toBeInTheDocument(); + }); + + it('should call onChange when the input value is changed', async () => { + // arrange + const onChange = vi.fn(); + render(); + const input = screen.getByLabelText('Test Label'); + + // act + fireEvent.change(input, { target: { value: 'changed' } }); + + // assert + await waitFor(() => expect(onChange).toHaveBeenCalledTimes(1)); + }); + + it('should call onBlur when the input is blurred', async () => { + // arrange + const onBlur = vi.fn(); + render(); + const input = screen.getByLabelText('Test Label'); + + // act + fireEvent.blur(input); + + // assert + await waitFor(() => expect(onBlur).toHaveBeenCalledTimes(1)); + }); + + it('should set the input type if provided', () => { + // arrange + render(); + const input = screen.getByLabelText('Test Label') as HTMLInputElement; + + // assert + expect(input.type).toBe('password'); + }); + + it('should set the input value if provided', () => { + // arrange + render(); + const input = screen.getByLabelText('Test Label') as HTMLInputElement; + + // assert + expect(input.value).toBe('Test Value'); + }); + + it('should apply the isInvalid prop to the input element', () => { + // arrange + render(); + const input = screen.getByLabelText('Test Label'); + + // assert + expect(input).toHaveClass('is-invalid', 'is-invalid-lite'); + }); + + it('should apply the disabled prop to the input element', () => { + // arrange + render(); + + // assert + const input = screen.getByLabelText('Test Label'); + expect(input).toBeDisabled(); + }); + + it('should set the input name attribute if provided', () => { + // arrange + render(); + const input = screen.getByLabelText('Test Label'); + + // assert + expect(input).toHaveAttribute('name', 'test-input'); + }); + + it('should set the input id attribute if provided', () => { + // arrange + render(); + const input = screen.getByLabelText('Test Label'); + + // assert + expect(input).toHaveAttribute('id', 'test-input'); + }); + + it('should set the input ref if provided', () => { + // arrange + const ref = React.createRef(); + render(); + const input = screen.getByLabelText('Test Label'); + + // assert + expect(input).toEqual(ref.current); + }); + + it('should set the input type attribute to "text" if not provided or if an invalid value is provided', () => { + // arrange + render(); + const input = screen.getByLabelText('Test Label') as HTMLInputElement; + + // assert + expect(input.type).toBe('text'); + }); + + it('should set the input placeholder attribute if provided', () => { + // arrange + render(); + const input = screen.getByLabelText('Test Label'); + + // assert + expect(input).toHaveAttribute('placeholder', 'Test Placeholder'); + }); +}); diff --git a/packages/frontend/src/components/ui/Input/Input.tsx b/packages/frontend/src/components/ui/Input/Input.tsx new file mode 100644 index 0000000000..b794539e75 --- /dev/null +++ b/packages/frontend/src/components/ui/Input/Input.tsx @@ -0,0 +1,48 @@ +import clsx from 'clsx'; +import React from 'react'; + +interface IProps { + placeholder?: string; + error?: string; + label?: string | React.ReactNode; + className?: string; + isInvalid?: boolean; + type?: HTMLInputElement['type']; + onChange?: (e: React.ChangeEvent) => void; + name?: string; + onBlur?: (e: React.FocusEvent) => void; + disabled?: boolean; + value?: string; + readOnly?: boolean; + maxLength?: number; +} + +export const Input = React.forwardRef( + ({ onChange, onBlur, name, label, placeholder, error, type = 'text', className, value, isInvalid, disabled, readOnly, maxLength }, ref) => ( +
    + {label && ( + + )} + + {error &&
    {error}
    } +
    + ), +); diff --git a/packages/frontend/src/components/ui/Input/index.ts b/packages/frontend/src/components/ui/Input/index.ts new file mode 100644 index 0000000000..6322cf3241 --- /dev/null +++ b/packages/frontend/src/components/ui/Input/index.ts @@ -0,0 +1 @@ +export { Input } from './Input'; diff --git a/packages/frontend/src/components/ui/OtpInput/OtpInput.css b/packages/frontend/src/components/ui/OtpInput/OtpInput.css new file mode 100644 index 0000000000..94af58a706 --- /dev/null +++ b/packages/frontend/src/components/ui/OtpInput/OtpInput.css @@ -0,0 +1,16 @@ +.otp-group { + display: flex; + width: 100%; + max-width: 360px; + column-gap: 10px; +} + +.otp-input { + width: 100%; + border: 1px solid #ccc; + border-radius: 5px; + text-align: center; + font-size: 32px; + font-weight: bold; + line-height: 1; +} diff --git a/packages/frontend/src/components/ui/OtpInput/OtpInput.test.tsx b/packages/frontend/src/components/ui/OtpInput/OtpInput.test.tsx new file mode 100644 index 0000000000..c911d1013c --- /dev/null +++ b/packages/frontend/src/components/ui/OtpInput/OtpInput.test.tsx @@ -0,0 +1,262 @@ +import { faker } from '@faker-js/faker'; +import { describe, expect, it, vi } from 'vitest'; +import { OtpInput } from './OtpInput'; +import { fireEvent, render, screen } from '@/tests/test-utils'; + +describe('', () => { + it('should accept value & valueLength props', () => { + // arrange + const value = faker.number.int({ min: 0, max: 999999 }).toString(); + const valueArray = value.split(''); + const valueLength = value.length; + render( {}} />); + + const inputEls = screen.queryAllByRole('textbox'); + + // assert + expect(inputEls).toHaveLength(valueLength); + inputEls.forEach((inputEl, idx) => { + expect(inputEl).toHaveValue(valueArray[idx]); + }); + }); + + it('should allow typing of digits', () => { + // arrange + const valueLength = faker.number.int({ min: 2, max: 6 }); // random number from 2-6 (minimum 2 so it can focus on the next input) + const onChange = vi.fn(); + render(); + + const inputEls = screen.queryAllByRole('textbox'); + + // assert + expect(inputEls).toHaveLength(valueLength); + inputEls.forEach((inputEl, idx) => { + const digit = faker.number.int({ min: 0, max: 9 }).toString(); // random number from 0-9, typing of digits is 1 by 1 + + // trigger a change event + fireEvent.change(inputEl, { + target: { value: digit }, // pass it as the target.value in the event data + }); + + // custom matcher to check that "onChange" function was called with the same digit + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith(digit); + + const inputFocused = inputEls[idx + 1] || inputEl; + expect(inputFocused).toHaveFocus(); + onChange.mockReset(); // resets the call times for the next iteration of the loop + }); + }); + + it('should NOT allow typing of non-digits', () => { + // arrange + const valueLength = faker.number.int({ min: 2, max: 6 }); + const onChange = vi.fn(); + render(); + + const inputEls = screen.queryAllByRole('textbox'); + + // assert + expect(inputEls).toHaveLength(valueLength); + + for (const inputEl of inputEls) { + const nonDigit = faker.number.float(1); + + fireEvent.change(inputEl, { + target: { value: nonDigit }, + }); + + expect(onChange).not.toBeCalled(); + + onChange.mockReset(); + } + }); + + it('should allow deleting of digits (focus on previous element)', () => { + const value = faker.number.int({ min: 10, max: 999999 }).toString(); // minimum 2-digit so it can focus on the previous input + const valueLength = value.length; + const lastIdx = valueLength - 1; + const onChange = vi.fn(); + + render(); + + const inputEls = screen.queryAllByRole('textbox'); + + expect(inputEls).toHaveLength(valueLength); + + for (let idx = lastIdx; idx > -1; idx -= 1) { + // loop backwards to simulate the focus on the previous input + const inputEl = inputEls[idx] as HTMLInputElement; + const target = { value: '' }; + + // trigger both change and keydown event + fireEvent.change(inputEl, { target }); + fireEvent.keyDown(inputEl, { + target, + key: 'Backspace', + }); + + const valueArray = value.split(''); + + valueArray[idx] = ' '; // the deleted digit is expected to be replaced with a space in the string + + const expectedValue = valueArray.join(''); + + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith(expectedValue); + + // custom matcher to check that the focus is on the previous input + // OR + // focus is on the current input if previous input doesn't exist + const inputFocused = inputEls[idx - 1] || inputEl; + + expect(inputFocused).toHaveFocus(); + + onChange.mockReset(); + } + }); + + it('should allow deleting of digits (do NOT focus on previous element)', () => { + const value = faker.number.int({ min: 10, max: 999999 }).toString(); + const valueArray = value.split(''); + const valueLength = value.length; + const lastIdx = valueLength - 1; + const onChange = vi.fn(); + + render(); + + const inputEls = screen.queryAllByRole('textbox'); + + expect(inputEls).toHaveLength(valueLength); + + for (let idx = lastIdx; idx > 0; idx -= 1) { + // idx > 0, because there's no previous input in index 0 + const inputEl = inputEls[idx] as HTMLInputElement; + + fireEvent.keyDown(inputEl, { + key: 'Backspace', + target: { value: valueArray[idx] }, + }); + + const prevInputEl = inputEls[idx - 1]; + + expect(prevInputEl).not.toHaveFocus(); + + onChange.mockReset(); + } + }); + + it('should NOT allow deleting of digits in the middle', () => { + const value = faker.number.int({ min: 100000, max: 999999 }).toString(); + const valueLength = value.length; + const onChange = vi.fn(); + + render(); + + const inputEls = screen.queryAllByRole('textbox'); + const thirdInputEl = inputEls[2] as HTMLInputElement; + const target = { value: '' }; + + fireEvent.change(thirdInputEl, { target: { value: '' } }); + fireEvent.keyDown(thirdInputEl, { + target, + key: 'Backspace', + }); + + expect(onChange).not.toBeCalled(); + }); + + it('should allow pasting of digits (same length as valueLength)', () => { + const value = faker.number.int({ min: 10, max: 999999 }).toString(); // minimum 2-digit so it is considered as a paste event + const valueLength = value.length; + const onChange = vi.fn(); + + render(); + + const inputEls = screen.queryAllByRole('textbox'); + + // get a random input element from the input elements to paste the digits on + const randomIdx = faker.number.int({ min: 0, max: valueLength - 1 }); + const randomInputEl = inputEls[randomIdx] as HTMLInputElement; + + fireEvent.change(randomInputEl, { target: { value } }); + + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith(value); + + expect(randomInputEl).not.toHaveFocus(); + }); + + it('should NOT allow pasting of digits (less than valueLength)', () => { + const value = faker.number.int({ min: 10, max: 99999 }).toString(); // random 2-5 digit code (less than "valueLength") + const valueLength = faker.number.int({ min: 6, max: 10 }); // random number from 6-10 + const onChange = vi.fn(); + + render(); + + const inputEls = screen.queryAllByRole('textbox'); + const randomIdx = faker.number.int({ min: 0, max: valueLength - 1 }); + const randomInputEl = inputEls[randomIdx] as HTMLInputElement; + + fireEvent.change(randomInputEl, { target: { value } }); + + expect(onChange).not.toBeCalled(); + }); + + it('should focus to next element on right/down key', () => { + render(); + + const inputEls = screen.queryAllByRole('textbox'); + const firstInputEl = inputEls[0] as HTMLInputElement; + + fireEvent.keyDown(firstInputEl, { + key: 'ArrowRight', + }); + + expect(inputEls[1]).toHaveFocus(); + + const secondInputEl = inputEls[1] as HTMLInputElement; + + fireEvent.keyDown(secondInputEl, { + key: 'ArrowDown', + }); + + expect(inputEls[2]).toHaveFocus(); + }); + + it('should focus to next element on left/up key', () => { + render(); + + const inputEls = screen.queryAllByRole('textbox'); + const lastInputEl = inputEls[2] as HTMLInputElement; + + fireEvent.keyDown(lastInputEl, { + key: 'ArrowLeft', + }); + + expect(inputEls[1]).toHaveFocus(); + + const secondInputEl = inputEls[1] as HTMLInputElement; + + fireEvent.keyDown(secondInputEl, { + key: 'ArrowUp', + }); + + expect(inputEls[0]).toHaveFocus(); + }); + + it('should only focus to input if previous input has value', () => { + const valueLength = 6; + + render(); + + const inputEls = screen.queryAllByRole('textbox'); + const lastInputEl = inputEls[valueLength - 1] as HTMLInputElement; + + lastInputEl.focus(); + + const firstInputEl = inputEls[0]; + + expect(firstInputEl).toHaveFocus(); + }); +}); diff --git a/packages/frontend/src/components/ui/OtpInput/OtpInput.tsx b/packages/frontend/src/components/ui/OtpInput/OtpInput.tsx new file mode 100644 index 0000000000..755a13fa51 --- /dev/null +++ b/packages/frontend/src/components/ui/OtpInput/OtpInput.tsx @@ -0,0 +1,158 @@ +import clsx from 'clsx'; +import type React from 'react'; +import { useMemo } from 'react'; +import './OtpInput.css'; + +type Props = { + value: string; + valueLength: number; + onChange: (value: string) => void; + className?: string; +}; + +const RE_DIGIT = /^\d+$/; + +export const OtpInput = ({ value, valueLength, onChange, className }: Props) => { + const valueItems = useMemo(() => { + const valueArray = value.split(''); + const items: string[] = []; + + for (let i = 0; i < valueLength; i += 1) { + const char = valueArray[i]; + + if (char && RE_DIGIT.test(char)) { + items.push(char); + } else { + items.push(''); + } + } + + return items; + }, [value, valueLength]); + + const focusToNextInput = (target: HTMLElement) => { + const nextElementSibling = target.nextElementSibling as HTMLInputElement | null; + + if (nextElementSibling) { + nextElementSibling.focus(); + } + }; + const focusToPrevInput = (target: HTMLElement) => { + const previousElementSibling = target.previousElementSibling as HTMLInputElement | null; + + if (previousElementSibling) { + previousElementSibling.focus(); + } + }; + + const inputOnChange = (e: React.ChangeEvent, idx: number) => { + const { target } = e; + let targetValue = target.value.trim(); + const isTargetValueDigit = RE_DIGIT.test(targetValue); + + if (!isTargetValueDigit && targetValue !== '') { + return; + } + + const nextInputEl = target.nextElementSibling as HTMLInputElement | null; + + // only delete digit if next input element has no value + if (!isTargetValueDigit && nextInputEl && nextInputEl.value !== '') { + return; + } + + targetValue = isTargetValueDigit ? targetValue : ' '; + + const targetValueLength = targetValue.length; + + if (targetValueLength === 1) { + const newValue = value.substring(0, idx) + targetValue + value.substring(idx + 1); + + onChange(newValue); + + if (!isTargetValueDigit) { + return; + } + + focusToNextInput(target); + + const nextElementSibling = target.nextElementSibling as HTMLInputElement | null; + + if (nextElementSibling) { + nextElementSibling.focus(); + } + } else if (targetValueLength === valueLength) { + onChange(targetValue); + + target.blur(); + } + }; + + const inputOnFocus = (e: React.FocusEvent) => { + const { target } = e; + + const prevInputEl = target.previousElementSibling as HTMLInputElement | null; + + if (prevInputEl && prevInputEl.value === '') { + return prevInputEl.focus(); + } + + target.setSelectionRange(0, target.value.length); + + return null; + }; + + const inputOnKeyDown = (e: React.KeyboardEvent) => { + const { key } = e; + const target = e.target as HTMLInputElement; + + if (key === 'ArrowRight' || key === 'ArrowDown') { + e.preventDefault(); + return focusToNextInput(target); + } + + if (key === 'ArrowLeft' || key === 'ArrowUp') { + e.preventDefault(); + return focusToPrevInput(target); + } + + const targetValue = target.value; + target.setSelectionRange(0, targetValue.length); + + if (e.key !== 'Backspace' || target.value !== '') { + return null; + } + + focusToPrevInput(target); + + const previousElementSibling = target.previousElementSibling as HTMLInputElement | null; + + if (previousElementSibling) { + previousElementSibling.focus(); + } + + return null; + }; + + return ( +
    + {valueItems.map((digit, idx) => ( + inputOnChange(e, idx)} + onKeyDown={inputOnKeyDown} + onFocus={inputOnFocus} + // biome-ignore lint/suspicious/noArrayIndexKey: The index is used as a key because the array is static + key={idx} + type="text" + inputMode="numeric" + autoComplete="one-time-code" + pattern="\d{1}" + maxLength={valueLength} + className={clsx('form-control otp-input', className)} + value={digit} + /> + ))} +
    + ); +}; diff --git a/packages/frontend/src/components/ui/OtpInput/index.ts b/packages/frontend/src/components/ui/OtpInput/index.ts new file mode 100644 index 0000000000..f035748977 --- /dev/null +++ b/packages/frontend/src/components/ui/OtpInput/index.ts @@ -0,0 +1 @@ +export { OtpInput } from './OtpInput'; diff --git a/packages/frontend/src/components/ui/Pagination/Pagination.css b/packages/frontend/src/components/ui/Pagination/Pagination.css new file mode 100644 index 0000000000..3575712d66 --- /dev/null +++ b/packages/frontend/src/components/ui/Pagination/Pagination.css @@ -0,0 +1,9 @@ +.pagination-link { + width: 26px; + max-width: 26px; + min-width: 26px; +} + +.page-button { + border: 0px; +} diff --git a/packages/frontend/src/components/ui/Pagination/Pagination.tsx b/packages/frontend/src/components/ui/Pagination/Pagination.tsx new file mode 100644 index 0000000000..43f4d6998f --- /dev/null +++ b/packages/frontend/src/components/ui/Pagination/Pagination.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; + +import { IconChevronLeft, IconChevronRight, IconDots } from '@tabler/icons-react'; +import clsx from 'clsx'; +import { Button, type ButtonProps } from '../Button/Button'; +import './Pagination.css'; + +const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => ( +
    +
    + + +

    + Runtipi logo + Runtipi +

    + +
    + +
    + + {t('HEADER_DARK_MODE')} + + + + {t('HEADER_LIGHT_MODE')} + + + + {t('HEADER_LOGIN')} + + +
    +
    +
    +