release/3.3.0 (#1424)

* refactor(app-store): improve apps payload to include only necessary fields

* chore: downgrade to pnpm v9

* chore(deps): bump the minor-patch group across 1 directory with 7 updates

Bumps the minor-patch group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [next](https://github.com/vercel/next.js) | `14.2.2` | `14.2.3` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `18.2.0` | `18.3.1` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `18.2.79` | `18.3.1` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `18.2.0` | `18.3.1` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `18.2.25` | `18.3.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `20.12.7` | `20.12.8` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `14.2.2` | `14.2.3` |



Updates `next` from 14.2.2 to 14.2.3
- [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.2...v14.2.3)

Updates `react` from 18.2.0 to 18.3.1
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v18.3.1/packages/react)

Updates `@types/react` from 18.2.79 to 18.3.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-dom` from 18.2.0 to 18.3.1
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v18.3.1/packages/react-dom)

Updates `@types/react-dom` from 18.2.25 to 18.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@types/node` from 20.12.7 to 20.12.8
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/react` from 18.2.79 to 18.3.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 18.2.25 to 18.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `eslint-config-next` from 14.2.2 to 14.2.3
- [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/commits/v14.2.3/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: react
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/react-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"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat: add tipi minimum version requirement when installing and updating

* feat(dashboard): add warning if internal ip is public

fix(dashboard): fix eslint error

* New Crowdin updates (#1387)

* New translations en.json (French)

* New translations en.json (Turkish)

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (English)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Russian)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (French)

* New translations en.json (Turkish)

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (English)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Russian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (French)

* New translations en.json (Turkish)

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (English)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* chore(deps-dev): bump @testing-library/react from 14.3.1 to 15.0.6

Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 14.3.1 to 15.0.6.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v14.3.1...v15.0.6)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps-dev): bump @testing-library/dom from 9.3.4 to 10.1.0

Bumps [@testing-library/dom](https://github.com/testing-library/dom-testing-library) from 9.3.4 to 10.1.0.
- [Release notes](https://github.com/testing-library/dom-testing-library/releases)
- [Changelog](https://github.com/testing-library/dom-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/dom-testing-library/compare/v9.3.4...v10.1.0)

---
updated-dependencies:
- dependency-name: "@testing-library/dom"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* New Crowdin updates (#1396)

* New translations en.json (French)

* New translations en.json (Turkish)

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (English)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Russian)

* chore(deps): bump the minor-patch group across 1 directory with 24 updates (#1403)

Bumps the minor-patch group with 24 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@sentry/integrations](https://github.com/getsentry/sentry-javascript) | `7.113.0` | `7.114.0` |
| [@sentry/nextjs](https://github.com/getsentry/sentry-javascript) | `7.113.0` | `7.114.0` |
| [next-intl](https://github.com/amannn/next-intl) | `3.12.1` | `3.13.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.51.3` | `7.51.4` |
| [sass](https://github.com/sass/dart-sass) | `1.76.0` | `1.77.0` |
| [semver](https://github.com/npm/node-semver) | `7.6.0` | `7.6.2` |
| [validator](https://github.com/validatorjs/validator.js) | `13.11.0` | `13.12.0` |
| [zod](https://github.com/colinhacks/zod) | `3.23.6` | `3.23.8` |
| [@playwright/test](https://github.com/microsoft/playwright) | `1.43.1` | `1.44.0` |
| [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) | `6.4.3` | `6.4.5` |
| [@testing-library/react](https://github.com/testing-library/react-testing-library) | `15.0.6` | `15.0.7` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `20.12.8` | `20.12.11` |
| [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg) | `8.11.5` | `8.11.6` |
| [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `1.5.3` | `1.6.0` |
| [dotenv-cli](https://github.com/entropitor/dotenv-cli) | `7.4.1` | `7.4.2` |
| [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) | `28.4.0` | `28.5.0` |
| [knip](https://github.com/webpro/knip/tree/HEAD/packages/knip) | `5.12.0` | `5.13.0` |
| [msw](https://github.com/mswjs/msw) | `2.2.14` | `2.3.0` |
| [tsx](https://github.com/privatenumber/tsx) | `4.8.2` | `4.9.3` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `1.5.3` | `1.6.0` |
| [@sentry/types](https://github.com/getsentry/sentry-javascript) | `7.113.0` | `7.114.0` |
| [@sentry/node](https://github.com/getsentry/sentry-javascript) | `7.113.0` | `7.114.0` |
| [hono](https://github.com/honojs/hono) | `4.2.9` | `4.3.4` |
| [systeminformation](https://github.com/sebhildebrandt/systeminformation) | `5.22.7` | `5.22.8` |



Updates `@sentry/integrations` from 7.113.0 to 7.114.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.114.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.113.0...7.114.0)

Updates `@sentry/nextjs` from 7.113.0 to 7.114.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.114.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.113.0...7.114.0)

Updates `next-intl` from 3.12.1 to 3.13.0
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v3.12.1...v3.13.0)

Updates `react-hook-form` from 7.51.3 to 7.51.4
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.51.3...v7.51.4)

Updates `sass` from 1.76.0 to 1.77.0
- [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.76.0...1.77.0)

Updates `semver` from 7.6.0 to 7.6.2
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.6.0...v7.6.2)

Updates `validator` from 13.11.0 to 13.12.0
- [Release notes](https://github.com/validatorjs/validator.js/releases)
- [Changelog](https://github.com/validatorjs/validator.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/validatorjs/validator.js/compare/13.11.0...13.12.0)

Updates `zod` from 3.23.6 to 3.23.8
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Changelog](https://github.com/colinhacks/zod/blob/master/CHANGELOG.md)
- [Commits](https://github.com/colinhacks/zod/compare/v3.23.6...v3.23.8)

Updates `@playwright/test` from 1.43.1 to 1.44.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.43.1...v1.44.0)

Updates `@testing-library/jest-dom` from 6.4.3 to 6.4.5
- [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.4.3...v6.4.5)

Updates `@testing-library/react` from 15.0.6 to 15.0.7
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v15.0.6...v15.0.7)

Updates `@types/node` from 20.12.8 to 20.12.11
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/pg` from 8.11.5 to 8.11.6
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)

Updates `@vitest/coverage-v8` from 1.5.3 to 1.6.0
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.6.0/packages/coverage-v8)

Updates `dotenv-cli` from 7.4.1 to 7.4.2
- [Release notes](https://github.com/entropitor/dotenv-cli/releases)
- [Commits](https://github.com/entropitor/dotenv-cli/compare/v7.4.1...v7.4.2)

Updates `eslint-plugin-jest` from 28.4.0 to 28.5.0
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v28.4.0...v28.5.0)

Updates `knip` from 5.12.0 to 5.13.0
- [Release notes](https://github.com/webpro/knip/releases)
- [Changelog](https://github.com/webpro/knip/blob/main/packages/knip/.release-it.json)
- [Commits](https://github.com/webpro/knip/commits/5.13.0/packages/knip)

Updates `msw` from 2.2.14 to 2.3.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.2.14...v2.3.0)

Updates `tsx` from 4.8.2 to 4.9.3
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.8.2...v4.9.3)

Updates `vitest` from 1.5.3 to 1.6.0
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.6.0/packages/vitest)

Updates `@sentry/types` from 7.113.0 to 7.114.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.114.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.113.0...7.114.0)

Updates `@sentry/node` from 7.113.0 to 7.114.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.114.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.113.0...7.114.0)

Updates `hono` from 4.2.9 to 4.3.4
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.2.9...v4.3.4)

Updates `systeminformation` from 5.22.7 to 5.22.8
- [Changelog](https://github.com/sebhildebrandt/systeminformation/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sebhildebrandt/systeminformation/compare/v5.22.7...v5.22.8)

---
updated-dependencies:
- dependency-name: "@sentry/integrations"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@sentry/nextjs"
  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: react-hook-form
  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: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: validator
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: zod
  dependency-type: direct:production
  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: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@testing-library/react"
  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: "@types/pg"
  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-minor
  dependency-group: minor-patch
- dependency-name: dotenv-cli
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: eslint-plugin-jest
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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: tsx
  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-minor
  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-minor
  dependency-group: minor-patch
- dependency-name: systeminformation
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Add password reset request expiry check and automatic session invalidation

* chore(deps): bump the minor-patch group across 1 directory with 7 updates (#1405)

* Update auth.service.ts

Move request expiry time to a constant so it can be changed easier

* chore: bump sentry dependencies

* chore(deps): bump the minor-patch group across 1 directory with 7 updates (#1405)

* chore: bump sentry dependencies

* chore(auth.service): small async improvements

* fix: missing sentry/next in worker

* New Crowdin updates (#1400)

* New translations en.json (Spanish)

* New translations en.json (Spanish)

* New translations en.json (French)

* chore(deps): bump pnpm/action-setup from 3.0.0 to 4.0.0

Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v3.0.0...v4.0.0)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(app-actions): missing open buttons with legacy app setup

* test: enable app folder in tests

* test: migrate to vitest

* feat/section-app-install-form (#1414)

* done

* fix: wrong conditions

---------

Co-authored-by: Nicolas Meienberger <github@thisprops.com>

* refactor(docker-compose.json): fallback to classic docker compose in case of failing to parse file

* chore(app.executors): add more logging

* chore(watcher): pull repo only in non-dev mode

* chore: bump version to 3.3.0

* test(e2e): add cookie for insecure banner

* fix(insecure-instance): include loopback ip in secure ones

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Stavros <steveiliop56@gmail.com>
Co-authored-by: hex-developer <77530549+hex-developer@users.noreply.github.com>
This commit is contained in:
Nicolas Meienberger 2024-05-25 20:38:38 +02:00 committed by GitHub
parent eed3742bab
commit 0c718ee1fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
104 changed files with 9147 additions and 9700 deletions

View File

@ -44,7 +44,7 @@ module.exports = {
'**/*.factory.{ts,tsx}',
'**/mocks/**',
'**/__mocks__/**',
'tests/**',
'**/tests/**',
'**/*.d.ts',
'**/*.workspace.ts',
'**/*.setup.{ts,js}',

View File

@ -45,7 +45,7 @@ jobs:
with:
node-version: 20
- uses: pnpm/action-setup@v3.0.0
- uses: pnpm/action-setup@v4.0.0
name: Install pnpm
id: pnpm-install
with:
@ -95,7 +95,7 @@ jobs:
with:
node-version: 20
- uses: pnpm/action-setup@v3.0.0
- uses: pnpm/action-setup@v4.0.0
name: Install pnpm
id: pnpm-install
with:

View File

@ -112,7 +112,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3.0.0
- uses: pnpm/action-setup@v4.0.0
name: Install pnpm
id: pnpm-install
with:

View File

@ -1,35 +0,0 @@
import { fs, vol } from 'memfs';
const copyFolderRecursiveSync = (src: string, dest: string) => {
const exists = vol.existsSync(src);
const stats = vol.statSync(src);
const isDirectory = exists && stats.isDirectory();
if (isDirectory) {
vol.mkdirSync(dest, { recursive: true });
vol.readdirSync(src).forEach((childItemName) => {
copyFolderRecursiveSync(`${src}/${childItemName}`, `${dest}/${childItemName}`);
});
} else {
vol.copyFileSync(src, dest);
}
};
export default {
...fs,
copySync: (src: string, dest: string) => {
copyFolderRecursiveSync(src, dest);
},
__resetAllMocks: () => {
vol.reset();
},
__applyMockFiles: (newMockFiles: Record<string, string>) => {
// Create folder tree
vol.fromJSON(newMockFiles, 'utf8');
},
__createMockFiles: (newMockFiles: Record<string, string>) => {
vol.reset();
// Create folder tree
vol.fromJSON(newMockFiles, 'utf8');
},
__printVol: () => console.log(vol.toTree()),
};

View File

@ -1,29 +0,0 @@
const values = new Map();
const expirations = new Map();
export const createClient = jest.fn(() => {
return {
isOpen: true,
connect: jest.fn(),
set: (key: string, value: string, exp: number) => {
values.set(key, value);
expirations.set(key, exp);
},
get: (key: string) => values.get(key),
quit: jest.fn(),
del: (key: string) => values.delete(key),
ttl: (key: string) => expirations.get(key),
on: jest.fn(),
keys: (key: string) => {
const keyprefix = key.substring(0, key.length - 1);
const keys = [];
// eslint-disable-next-line no-restricted-syntax
for (const [k] of values) {
if (k.startsWith(keyprefix)) {
keys.push(k);
}
}
return keys;
},
};
});

View File

@ -3,7 +3,7 @@ services:
container_name: runtipi-reverse-proxy
depends_on:
- runtipi
image: traefik:v2.11
image: traefik:v3.0
restart: unless-stopped
ports:
- 80:80

View File

@ -4,7 +4,7 @@ services:
depends_on:
runtipi:
condition: service_healthy
image: traefik:v2.11
image: traefik:v3.0
restart: unless-stopped
ports:
- 80:80

View File

@ -19,8 +19,8 @@ test('user can login and is redirected to the dashboard', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
test('user can logout', async ({ page }) => {
await loginUser(page);
test('user can logout', async ({ page, context }) => {
await loginUser(page, context);
await page.getByTestId('logout-button').click();
await expect(page.getByText('Login to your account')).toBeVisible();

View File

@ -2,9 +2,9 @@ import { test, expect } from '@playwright/test';
import { loginUser } from './fixtures/fixtures';
import { clearDatabase } from './helpers/db';
test.beforeEach(async ({ page, isMobile }) => {
test.beforeEach(async ({ page, context, isMobile }) => {
await clearDatabase();
await loginUser(page);
await loginUser(page, context);
if (isMobile) {
// TODO: Fix mobile accessibility for the dropdown menu

View File

@ -5,10 +5,10 @@ import { clearDatabase } from './helpers/db';
import { testUser } from './helpers/constants';
import { setSettings } from './helpers/settings';
test.beforeEach(async ({ page }) => {
test.beforeEach(async ({ page, context }) => {
await setSettings({});
await clearDatabase();
await loginUser(page);
await loginUser(page, context);
await page.goto('/settings');
});

View File

@ -10,8 +10,8 @@ test.beforeEach(async () => {
await setSettings({});
});
test('user can activate the guest dashboard and see it when logged out', async ({ page }) => {
await loginUser(page);
test('user can activate the guest dashboard and see it when logged out', async ({ page, context }) => {
await loginUser(page, context);
await page.goto('/settings');
await page.getByRole('tab', { name: 'Settings' }).click();
@ -34,17 +34,15 @@ test('logged out users can see the apps on the guest dashboard', async ({ browse
status: 'running',
openPort: true,
});
await db
.insert(appTable)
.values({
config: {},
openPort: true,
isVisibleOnGuestDashboard: false,
id: 'actual-budget',
exposed: false,
exposedLocal: false,
status: 'running',
});
await db.insert(appTable).values({
config: {},
openPort: true,
isVisibleOnGuestDashboard: false,
id: 'actual-budget',
exposed: false,
exposedLocal: false,
status: 'running',
});
const context = await browser.newContext();
const page = await context.newPage();
@ -62,8 +60,8 @@ test('logged out users can see the apps on the guest dashboard', async ({ browse
await context.close();
});
test('user can deactivate the guest dashboard and not see it when logged out', async ({ page }) => {
await loginUser(page);
test('user can deactivate the guest dashboard and not see it when logged out', async ({ page, context }) => {
await loginUser(page, context);
await page.goto('/settings');
await page.getByRole('tab', { name: 'Settings' }).click();

View File

@ -1,5 +1,5 @@
import * as argon2 from 'argon2';
import { expect, Page } from '@playwright/test';
import { BrowserContext, expect, Page } from '@playwright/test';
import { userTable } from '@/server/db/schema';
import { db } from '../helpers/db';
import { testUser } from '../helpers/constants';
@ -10,10 +10,12 @@ export const createTestUser = async () => {
await db.insert(userTable).values({ password, username: testUser.email, operator: true });
};
export const loginUser = async (page: Page) => {
export const loginUser = async (page: Page, context: BrowserContext) => {
// Create user in database
await createTestUser();
await context.addCookies([{ name: 'hide-insecure-instance', value: 'true', path: '/', domain: process.env.SERVER_IP }]);
// Login flow
await page.goto('/login');

View File

@ -18,9 +18,10 @@ export const setSettings = async (settings: z.infer<typeof settingsSchema>) => {
export const setPassowrdChangeRequest = async () => {
if (process.env.REMOTE === 'true') {
await execRemoteCommand('touch ./runtipi/state/password-change-request');
// Write date in ms to file
await execRemoteCommand('touch ./runtipi/state/password-change-request && echo $(date +%s) >> ./runtipi/state/password-change-request');
} else {
await promises.writeFile('./state/password-change-request', '');
await promises.writeFile('./state/password-change-request', `${new Date().getTime() / 1000}`);
}
};

View File

@ -8,7 +8,11 @@ const createJestConfig = nextJest({
const customClientConfig = {
testEnvironment: 'jest-environment-jsdom',
setupFilesAfterEnv: ['<rootDir>/tests/client/jest.setup.tsx'],
testMatch: ['<rootDir>/src/client/**/*.{spec,test}.{ts,tsx}', '!<rootDir>/src/server/**/*.{spec,test}.{ts,tsx}'],
testMatch: [
'<rootDir>/src/client/**/*.{spec,test}.{ts,tsx}',
'<rootDir>/src/app/**/*.{spec,test}.{ts,tsx}',
'!<rootDir>/src/server/**/*.{spec,test}.{ts,tsx}',
],
};
const customServerConfig = {

View File

@ -1,11 +1,12 @@
{
"name": "runtipi",
"version": "3.2.0",
"version": "3.3.0",
"description": "A homeserver for everyone",
"scripts": {
"clean-containers": "docker rm -f $(docker ps -a -q)",
"knip": "knip",
"test": "dotenv -e .env.test -- jest --colors",
"test": "dotenv -e .env.test -- vitest run --coverage",
"test:ui": "dotenv -e .env.test -- vitest --ui",
"test:e2e": "NODE_ENV=test dotenv -e .env -e .env.e2e -- playwright test",
"test:e2e:ui": "NODE_ENV=test dotenv -e .env -e .env.e2e -- playwright test --ui",
"test:client": "jest --colors --selectProjects client --",
@ -44,8 +45,7 @@
"@radix-ui/react-tabs": "^1.0.4",
"@runtipi/postgres-migrations": "^5.3.0",
"@runtipi/shared": "workspace:^",
"@sentry/integrations": "^7.111.0",
"@sentry/nextjs": "^7.111.0",
"@sentry/nextjs": "^8.0.0",
"@tabler/core": "1.0.0-beta20",
"@tabler/icons-react": "^3.2.0",
"argon2": "^0.40.1",
@ -55,19 +55,20 @@
"drizzle-orm": "^0.30.9",
"fs-extra": "^11.2.0",
"geist": "^1.3.0",
"ipaddr.js": "^2.2.0",
"jsonwebtoken": "^9.0.2",
"let-it-go": "^1.0.0",
"lodash.merge": "^4.6.2",
"minisearch": "^6.3.0",
"next": "14.2.2",
"next": "14.2.3",
"next-client-cookies": "^1.1.1",
"next-intl": "^3.11.3",
"next-intl": "^3.13.0",
"next-safe-action": "^6.2.0",
"pg": "^8.11.5",
"qrcode.react": "^3.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.51.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hook-form": "^7.51.4",
"react-hot-toast": "^2.4.1",
"react-markdown": "^9.0.1",
"react-query": "^3.39.3",
@ -77,72 +78,76 @@
"rehype-raw": "^7.0.0",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.0",
"sass": "^1.75.0",
"semver": "^7.6.0",
"sass": "^1.77.1",
"semver": "^7.6.2",
"sharp": "0.33.3",
"socket.io-client": "^4.7.5",
"uuid": "^9.0.1",
"validator": "^13.11.0",
"validator": "^13.12.0",
"winston": "^3.13.0",
"zod": "^3.23.0",
"zod": "^3.23.8",
"zustand": "^4.5.2"
},
"devDependencies": {
"@babel/core": "^7.24.4",
"@babel/core": "^7.24.5",
"@faker-js/faker": "^8.4.1",
"@playwright/test": "^1.43.1",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.2",
"@playwright/test": "^1.44.0",
"@testing-library/dom": "^10.1.0",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
"@total-typescript/shoehorn": "^0.1.2",
"@total-typescript/ts-reset": "^0.5.1",
"@types/eslint": "^8.56.10",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.12",
"@types/jsonwebtoken": "^9.0.6",
"@types/lodash.merge": "^4.6.9",
"@types/node": "20.12.7",
"@types/pg": "^8.11.5",
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25",
"@types/node": "20.12.11",
"@types/pg": "^8.11.6",
"@types/react": "18.3.2",
"@types/react-dom": "18.3.0",
"@types/semver": "^7.5.8",
"@types/ssh2": "^1.15.0",
"@types/uuid": "^9.0.8",
"@types/validator": "^13.11.9",
"@types/validator": "^13.11.10",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^1.5.0",
"@vitest/ui": "^1.6.0",
"dotenv-cli": "^7.4.1",
"eslint": "8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-next": "14.2.2",
"eslint-config-next": "14.2.3",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-drizzle": "^0.2.3",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^28.2.0",
"eslint-plugin-jest": "^28.5.0",
"eslint-plugin-jest-dom": "^5.4.0",
"eslint-plugin-jsonc": "^2.15.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-testing-library": "^6.2.2",
"eslint-plugin-vitest": "^0.5.4",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^24.0.0",
"knip": "^5.9.4",
"memfs": "^4.8.2",
"msw": "^2.2.14",
"msw": "^2.3.0",
"next-router-mock": "^0.9.13",
"prettier": "^3.2.5",
"ssh2": "^1.15.0",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"tsx": "^4.7.2",
"tsx": "^4.10.2",
"typescript": "5.4.5",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.5.0",
"vitest": "^1.6.0",
"wait-for-expect": "^3.0.2"
},
"msw": {

View File

@ -32,12 +32,12 @@
"license": "ISC",
"dependencies": {
"lodash.clonedeep": "^4.5.0",
"validator": "^13.11.0",
"validator": "^13.12.0",
"winston": "^3.13.0",
"zod": "^3.23.0"
"zod": "^3.23.8"
},
"devDependencies": {
"@sentry/types": "^7.111.0",
"@sentry/types": "^8.0.0",
"@types/lodash.clonedeep": "^4.5.9"
}
}

View File

@ -79,6 +79,7 @@ export const appInfoSchema = z.object({
uid: z.number().optional(),
gid: z.number().optional(),
dynamic_config: z.boolean().optional().default(false),
min_tipi_version: z.string().optional(),
});
// Derived types

View File

@ -20,31 +20,31 @@
"@sentry/esbuild-plugin": "^2.16.1",
"@total-typescript/shoehorn": "^0.1.2",
"@types/web-push": "^3.6.3",
"dotenv-cli": "^7.4.1",
"dotenv-cli": "^7.4.2",
"esbuild": "^0.19.4",
"knip": "^5.9.4",
"knip": "^5.15.1",
"memfs": "^4.8.2",
"nodemon": "^3.1.0",
"tsx": "^4.7.2",
"tsx": "^4.10.2",
"typescript": "^5.4.5",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.5.0"
"vitest": "^1.6.0"
},
"dependencies": {
"@hono/node-server": "^1.11.0",
"@runtipi/postgres-migrations": "^5.3.0",
"@runtipi/shared": "workspace:^",
"@sentry/integrations": "^7.111.0",
"@sentry/node": "^7.111.0",
"@sentry/integrations": "^7.114.0",
"@sentry/node": "^8.0.0",
"bullmq": "^5.7.4",
"dotenv": "^16.4.5",
"hono": "^4.2.6",
"hono": "^4.3.6",
"ioredis": "^5.4.1",
"pg": "^8.11.5",
"socket.io": "^4.7.5",
"systeminformation": "^5.22.7",
"systeminformation": "^5.22.9",
"web-push": "^3.6.7",
"yaml": "^2.4.1",
"zod": "^3.23.0"
"zod": "^3.23.8"
}
}

View File

@ -30,10 +30,10 @@ const setupSentry = (release?: string) => {
tags: { version: release },
},
integrations: [
new Sentry.Integrations.LocalVariables({
Sentry.localVariablesIntegration({
captureAllExceptions: true,
}),
extraErrorDataIntegration(),
extraErrorDataIntegration,
],
});
};
@ -49,8 +49,14 @@ const main = async () => {
logger.info('Copying system files...');
await copySystemFiles(envMap);
if (envMap.get('ALLOW_ERROR_MONITORING') === 'true' && process.env.NODE_ENV === 'production' && process.env.LOCAL !== 'true') {
logger.info(`Anonymous error monitoring is enabled, to disable it add "allowErrorMonitoring": false to your settings.json file. Version: ${process.env.TIPI_VERSION}`);
if (
envMap.get('ALLOW_ERROR_MONITORING') === 'true' &&
process.env.NODE_ENV === 'production' &&
process.env.LOCAL !== 'true'
) {
logger.info(
`Anonymous error monitoring is enabled, to disable it add "allowErrorMonitoring": false to your settings.json file. Version: ${process.env.TIPI_VERSION}`,
);
setupSentry(process.env.TIPI_VERSION);
}
@ -69,21 +75,41 @@ const main = async () => {
if (!clone.success) {
logger.error(`Failed to clone repo ${envMap.get('APPS_REPO_URL') as string}`);
}
const pull = await repoExecutors.pullRepo(envMap.get('APPS_REPO_URL') as string);
if (!pull.success) {
logger.error(`Failed to pull repo ${envMap.get('APPS_REPO_URL') as string}`);
if (process.env.NODE_ENV !== 'development') {
const pull = await repoExecutors.pullRepo(envMap.get('APPS_REPO_URL') as string);
if (!pull.success) {
logger.error(`Failed to pull repo ${envMap.get('APPS_REPO_URL') as string}`);
}
}
logger.info('Starting queue...');
const queue = new Queue('events', { connection: { host: envMap.get('REDIS_HOST'), port: 6379, password: envMap.get('REDIS_PASSWORD') } });
const repeatQueue = new Queue('repeat', { connection: { host: envMap.get('REDIS_HOST'), port: 6379, password: envMap.get('REDIS_PASSWORD') } });
const queue = new Queue('events', {
connection: {
host: envMap.get('REDIS_HOST'),
port: 6379,
password: envMap.get('REDIS_PASSWORD'),
},
});
const repeatQueue = new Queue('repeat', {
connection: {
host: envMap.get('REDIS_HOST'),
port: 6379,
password: envMap.get('REDIS_PASSWORD'),
},
});
logger.info('Obliterating queue...');
await queue.drain(true);
await repeatQueue.drain(true);
// Scheduled jobs
if (process.env.NODE_ENV === 'production') {
logger.info('Adding scheduled jobs to queue...');
await repeatQueue.add(`${Math.random().toString()}_repo_update`, { type: 'repo', command: 'update', url: envMap.get('APPS_REPO_URL') } as SystemEvent, { repeat: { pattern: '*/30 * * * *' } });
await repeatQueue.add(
`${Math.random().toString()}_repo_update`,
{ type: 'repo', command: 'update', url: envMap.get('APPS_REPO_URL') } as SystemEvent,
{ repeat: { pattern: '*/30 * * * *' } },
);
}
logger.info('Closing queue...');
@ -101,7 +127,11 @@ const main = async () => {
// Set status to running
logger.info('Setting status to running...');
const cache = new Redis({ host: envMap.get('REDIS_HOST'), port: 6379, password: envMap.get('REDIS_PASSWORD') });
const cache = new Redis({
host: envMap.get('REDIS_HOST'),
port: 6379,
password: envMap.get('REDIS_PASSWORD'),
});
await cache.set('status', 'RUNNING');
await cache.quit();

View File

@ -21,7 +21,11 @@ export class AppExecutors {
this.logger = logger;
}
private handleAppError = (err: unknown, appId: string, event: Extract<SocketEvent, { type: 'app' }>['event']) => {
private handleAppError = (
err: unknown,
appId: string,
event: Extract<SocketEvent, { type: 'app' }>['event'],
) => {
Sentry.captureException(err, {
tags: { appId, event },
});
@ -66,20 +70,32 @@ export class AppExecutors {
await fs.promises.cp(repoPath, appDirPath, { recursive: true });
}
// Check if app has a compose.json file
if (await pathExists(path.join(repoPath, 'compose.json'))) {
// Generate docker-compose.yml file
const rawComposeConfig = await fs.promises.readFile(path.join(repoPath, 'compose.json'), 'utf-8');
const jsonComposeConfig = JSON.parse(rawComposeConfig);
// Check if app has a docker-compose.json file
if (await pathExists(path.join(repoPath, 'docker-compose.json'))) {
try {
// Generate docker-compose.yml file
const rawComposeConfig = await fs.promises.readFile(
path.join(repoPath, 'docker-compose.json'),
'utf-8',
);
const jsonComposeConfig = JSON.parse(rawComposeConfig);
const composeFile = getDockerCompose(jsonComposeConfig.services, form);
const composeFile = getDockerCompose(jsonComposeConfig.services, form);
await fs.promises.writeFile(dockerFilePath, composeFile);
await fs.promises.writeFile(dockerFilePath, 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 execAsync(`chmod -Rf a+rwx ${path.join(appDataDirPath)}`).catch(() => {
await execAsync(`chmod -Rf a+rwx ${path.join(appDataDirPath)}`).catch((e) => {
this.logger.error(`Error setting permissions for app ${appId}`);
Sentry.captureException(e);
});
};
@ -106,7 +122,9 @@ export class AppExecutors {
SocketManager.emit({ type: 'app', event: 'status_change', data: { appId } });
if (process.getuid && process.getgid) {
this.logger.info(`Installing app ${appId} as User ID: ${process.getuid()}, Group ID: ${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.`);
}
@ -116,7 +134,9 @@ export class AppExecutors {
const { appDirPath, repoPath, appDataDirPath } = this.getAppPaths(appId);
// Check if app exists in repo
const apps = await fs.promises.readdir(path.join(DATA_DIR, 'repos', sanitizePath(appsRepoId), 'apps'));
const apps = await fs.promises.readdir(
path.join(DATA_DIR, 'repos', sanitizePath(appsRepoId), 'apps'),
);
if (!apps.includes(appId)) {
this.logger.error(`App ${appId} not found in repo ${appsRepoId}`);
@ -291,7 +311,9 @@ export class AppExecutors {
await compose(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`);
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;
}
@ -333,7 +355,9 @@ export class AppExecutors {
await compose(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.`);
this.logger.warn(
`Could not reset app ${appId}. Most likely there have been made changes to the compose file.`,
);
} else {
throw err;
}
@ -384,7 +408,9 @@ export class AppExecutors {
await compose(appId, 'up --detach --force-recreate --remove-orphans');
await compose(appId, 'down --rmi all --remove-orphans');
} catch (err) {
logger.warn(`App ${appId} has likely a broken docker-compose.yml file. Continuing with update...`);
logger.warn(
`App ${appId} has likely a broken docker-compose.yml file. Continuing with update...`,
);
}
this.logger.info(`Deleting folder ${appDirPath}`);
@ -424,7 +450,9 @@ export class AppExecutors {
}
// Update all apps with status different than running or stopped to stopped
await client?.query(`UPDATE app SET status = 'stopped' WHERE status != 'stopped' AND status != 'running' AND status != 'missing'`);
await client?.query(
`UPDATE app SET status = 'stopped' WHERE status != 'stopped' AND status != 'running' AND status != 'missing'`,
);
// Start all apps
for (const row of rows) {

View File

@ -4,7 +4,16 @@ import { AppExecutors, RepoExecutors } from '@/services';
import { logger } from '@/lib/logger';
import { getEnv } from '@/lib/environment';
const { installApp, resetApp, startApp, stopApp, restartApp, uninstallApp, updateApp, regenerateAppEnv } = new AppExecutors();
const {
installApp,
resetApp,
startApp,
stopApp,
restartApp,
uninstallApp,
updateApp,
regenerateAppEnv,
} = new AppExecutors();
const { cloneRepo, pullRepo } = new RepoExecutors();
const runCommand = async (jobData: unknown) => {
@ -56,7 +65,7 @@ const runCommand = async (jobData: unknown) => {
({ success, message } = await cloneRepo(data.url));
}
if (data.command === 'update') {
if (data.command === 'update' && process.env.NODE_ENV !== 'development') {
({ success, message } = await pullRepo(data.url));
}
}
@ -78,7 +87,17 @@ export const startWorker = async () => {
return { success, stdout: message };
},
{ connection: { host: getEnv().redisHost, port: 6379, password: getEnv().redisPassword, connectTimeout: 60000 }, removeOnComplete: { count: 200 }, removeOnFail: { count: 500 }, concurrency: 3 },
{
connection: {
host: getEnv().redisHost,
port: 6379,
password: getEnv().redisPassword,
connectTimeout: 60000,
},
removeOnComplete: { count: 200 },
removeOnFail: { count: 500 },
concurrency: 3,
},
);
const worker = new Worker(
@ -89,7 +108,17 @@ export const startWorker = async () => {
return { success, stdout: message };
},
{ connection: { host: getEnv().redisHost, port: 6379, password: getEnv().redisPassword, connectTimeout: 60000 }, removeOnComplete: { count: 200 }, removeOnFail: { count: 500 }, concurrency: 1 },
{
connection: {
host: getEnv().redisHost,
port: 6379,
password: getEnv().redisPassword,
connectTimeout: 60000,
},
removeOnComplete: { count: 200 },
removeOnFail: { count: 500 },
concurrency: 1,
},
);
worker.on('ready', () => {

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
* - Please do NOT serve this file on production.
*/
const PACKAGE_VERSION = '2.2.14'
const PACKAGE_VERSION = '2.3.0'
const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()

View File

@ -1,6 +1,5 @@
import * as Sentry from '@sentry/nextjs';
import { settingsSchema, cleanseErrorData } from '@runtipi/shared';
import { extraErrorDataIntegration } from '@sentry/integrations';
const getClientConfig = () => {
if (typeof window === 'undefined') {
@ -26,7 +25,7 @@ if (allowErrorMonitoring && process.env.NODE_ENV === 'production' && process.env
environment: process.env.NODE_ENV,
dsn: 'https://7a73d72f886948478b55621e7b92c3c7@o4504242900238336.ingest.sentry.io/4504826587971584',
beforeSend: cleanseErrorData,
integrations: [extraErrorDataIntegration()],
integrations: [Sentry.extraErrorDataIntegration()],
initialScope: {
tags: { version: process.env.TIPI_VERSION },
},

View File

@ -6,7 +6,6 @@
import * as Sentry from '@sentry/nextjs';
import { TipiConfig } from '@/server/core/TipiConfig';
import { cleanseErrorData } from '@runtipi/shared';
import { extraErrorDataIntegration } from '@sentry/integrations';
const { version, allowErrorMonitoring, NODE_ENV } = TipiConfig.getConfig();
@ -16,7 +15,7 @@ if (allowErrorMonitoring && NODE_ENV === 'production' && process.env.LOCAL !== '
environment: NODE_ENV,
dsn: 'https://7a73d72f886948478b55621e7b92c3c7@o4504242900238336.ingest.sentry.io/4504826587971584',
beforeSend: cleanseErrorData,
integrations: [extraErrorDataIntegration()],
integrations: [Sentry.extraErrorDataIntegration()],
initialScope: {
tags: { version },
},

View File

@ -5,7 +5,6 @@
import * as Sentry from '@sentry/nextjs';
import { TipiConfig } from '@/server/core/TipiConfig';
import { cleanseErrorData } from '@runtipi/shared';
import { extraErrorDataIntegration } from '@sentry/integrations';
const { version, allowErrorMonitoring, NODE_ENV } = TipiConfig.getConfig();
@ -15,7 +14,7 @@ if (allowErrorMonitoring && NODE_ENV === 'production' && process.env.LOCAL !== '
environment: NODE_ENV,
dsn: 'https://7a73d72f886948478b55621e7b92c3c7@o4504242900238336.ingest.sentry.io/4504826587971584',
beforeSend: cleanseErrorData,
integrations: [extraErrorDataIntegration()],
integrations: [Sentry.extraErrorDataIntegration()],
initialScope: {
tags: { version },
},

View File

@ -4,13 +4,15 @@ import { getTranslatorFromCookie } from '@/lib/get-translator';
import { ResetPasswordContainer } from './components/ResetPasswordContainer';
export default async function ResetPasswordPage() {
const isRequested = AuthServiceClass.checkPasswordChangeRequest();
const translator = await getTranslatorFromCookie();
const authService = new AuthServiceClass();
const isRequested = await authService.checkPasswordChangeRequest();
if (isRequested) {
return <ResetPasswordContainer />;
}
const translator = await getTranslatorFromCookie();
return (
<>
<h2 className="h2 text-center mb-3">{translator('AUTH_RESET_PASSWORD_TITLE')}</h2>

View File

@ -1,5 +1,6 @@
import React from 'react';
import { AppInfo } from '@runtipi/shared';
import { vi, afterEach, describe, it, expect } from 'vitest';
import { AppActions } from './AppActions';
import { cleanup, fireEvent, render, screen, waitFor, userEvent } from '../../../../../../../tests/test-utils';
@ -19,8 +20,8 @@ describe('Test: AppActions', () => {
it('should call the callbacks when buttons are clicked', () => {
// arrange
const onStart = jest.fn();
const onRemove = jest.fn();
const onStart = vi.fn();
const onRemove = vi.fn();
// @ts-expect-error - we don't need to pass all props for this test
render(<AppActions status="stopped" app={app} onStart={onStart} onUninstall={onRemove} />);
@ -130,7 +131,7 @@ describe('Test: AppActions', () => {
exposed: true,
domain: 'myapp.example.com',
};
const openFn = jest.fn();
const openFn = vi.fn();
// @ts-expect-error - we don't need to pass all props for this test
render(<AppActions onOpen={openFn} status="running" app={appWithDomain} />);
@ -149,11 +150,11 @@ describe('Test: AppActions', () => {
});
});
it('should render local_domain open button', async () => {
it('should render local_domain open button when exposed locally', async () => {
// arrange
const openFn = jest.fn();
const openFn = vi.fn();
// @ts-expect-error - we don't need to pass all props for this test
render(<AppActions localDomain="tipi.lan" onOpen={openFn} status="running" app={app} />);
render(<AppActions localDomain="tipi.lan" onOpen={openFn} status="running" app={{ ...app, exposedLocal: true }} />);
// act
const openButton = screen.getByRole('button', { name: 'Open' });
@ -170,11 +171,11 @@ describe('Test: AppActions', () => {
});
});
it('should render local open button', async () => {
it('should render local open button when port is open', async () => {
// arrange
const openFn = jest.fn();
const openFn = vi.fn();
// @ts-expect-error - we don't need to pass all props for this test
render(<AppActions localUrl="http://localhost:3000" onOpen={openFn} status="running" app={app} />);
render(<AppActions localUrl="http://localhost:3000" onOpen={openFn} status="running" app={{ ...app, openPort: true }} />);
// act
const openButton = screen.getByRole('button', { name: 'Open' });

View File

@ -118,11 +118,13 @@ export const AppActions: React.FC<IProps> = ({
{app.domain}
</DropdownMenuItem>
)}
<DropdownMenuItem onClick={() => onOpen('local_domain')}>
<IconLock className="text-muted me-2" size={16} />
{app.id}.{localDomain}
</DropdownMenuItem>
{!app.info.force_expose && (
{(app.exposedLocal || !info.dynamic_config) && (
<DropdownMenuItem onClick={() => onOpen('local_domain')}>
<IconLock className="text-muted me-2" size={16} />
{app.id}.{localDomain}
</DropdownMenuItem>
)}
{(app.openPort || !info.dynamic_config) && (
<DropdownMenuItem onClick={() => onOpen('local')}>
<IconLockOff className="text-muted me-2" size={16} />
{hostname}:{app.info.port}

View File

@ -2,12 +2,22 @@ import React from 'react';
import { faker } from '@faker-js/faker';
import { fromPartial } from '@total-typescript/shoehorn';
import { FormField } from '@runtipi/shared';
import { vi, it, beforeEach, describe, expect } from 'vitest';
import { fireEvent, render, screen, waitFor } from '../../../../../../../tests/test-utils';
import { InstallForm } from './InstallForm';
let useClientSettingsMock = { guestDashboard: false };
vi.mock('../../../../../hooks/use-client-settings.ts', () => ({
useClientSettings: () => useClientSettingsMock,
}));
beforeEach(() => {
useClientSettingsMock = { guestDashboard: false };
});
describe('Test: InstallForm', () => {
it('should render the form', () => {
render(<InstallForm formFields={[]} onSubmit={jest.fn} info={fromPartial({})} />);
render(<InstallForm formFields={[]} onSubmit={vi.fn} info={fromPartial({})} />);
expect(screen.getByText('Install')).toBeInTheDocument();
});
@ -21,7 +31,7 @@ describe('Test: InstallForm', () => {
{ env_variable: 'test5', label: 'test5', type: 'number', required: false },
];
render(<InstallForm info={fromPartial({})} formFields={formFields} onSubmit={jest.fn} />);
render(<InstallForm info={fromPartial({})} formFields={formFields} onSubmit={vi.fn} />);
expect(screen.getByLabelText('test')).toBeInTheDocument();
expect(screen.getByLabelText('test2')).toBeInTheDocument();
@ -33,7 +43,7 @@ describe('Test: InstallForm', () => {
it('should call submit function with correct values', async () => {
const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: 'text', required: false }];
const onSubmit = jest.fn();
const onSubmit = vi.fn();
render(<InstallForm info={fromPartial({})} formFields={formFields} onSubmit={onSubmit} />);
@ -62,7 +72,7 @@ describe('Test: InstallForm', () => {
},
];
const onSubmit = jest.fn();
const onSubmit = vi.fn();
render(<InstallForm info={fromPartial({})} formFields={formFields} onSubmit={onSubmit} />);
@ -92,7 +102,7 @@ describe('Test: InstallForm', () => {
{ env_variable: 'test-boolean', label: 'test-boolean', type: 'boolean', required: true },
];
const onSubmit = jest.fn();
const onSubmit = vi.fn();
render(
<InstallForm
@ -111,17 +121,17 @@ describe('Test: InstallForm', () => {
it('should render expose switch when app is exposable', () => {
const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: 'text', required: true }];
const onSubmit = jest.fn();
const onSubmit = vi.fn();
render(<InstallForm formFields={formFields} onSubmit={onSubmit} info={fromPartial({ exposable: true })} />);
expect(screen.getByLabelText('Expose app')).toBeInTheDocument();
expect(screen.getByLabelText('Expose app on the internet')).toBeInTheDocument();
});
it('should render the domain form and disable the expose switch when info has force_expose set to true', () => {
const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: 'text', required: true }];
const onSubmit = jest.fn();
const onSubmit = vi.fn();
render(<InstallForm formFields={formFields} onSubmit={onSubmit} info={fromPartial({ force_expose: true, exposable: true })} />);
@ -129,4 +139,48 @@ describe('Test: InstallForm', () => {
expect(screen.getByRole('switch')).toBeChecked();
expect(screen.getByRole('textbox', { name: 'domain' })).toBeInTheDocument();
});
it('should disable the open port switch when info has force_expose set to true', () => {
const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: 'text', required: true }];
const onSubmit = vi.fn();
render(<InstallForm formFields={formFields} onSubmit={onSubmit} info={fromPartial({ force_expose: true, dynamic_config: true })} />);
expect(screen.getByRole('switch', { name: 'openPort' })).toBeDisabled();
expect(screen.getByRole('switch', { name: 'openPort' })).not.toBeChecked();
});
it('should show display on guest dashboard switch when guest dashboard setting is true', async () => {
// Arrange
useClientSettingsMock = { guestDashboard: true };
const onSubmit = vi.fn();
// Act
render(<InstallForm formFields={[]} onSubmit={onSubmit} info={fromPartial({})} />);
fireEvent.click(screen.getByRole('switch', { name: 'isVisibleOnGuestDashboard' }));
screen.getByText('Install').click();
// Assert
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({ isVisibleOnGuestDashboard: true });
});
});
it('should not show display on guest dashboard switch when guest dashboard setting is false', async () => {
// Arrange
useClientSettingsMock = { guestDashboard: false };
const onSubmit = vi.fn();
// Act
render(<InstallForm formFields={[]} onSubmit={onSubmit} info={fromPartial({})} />);
screen.getByText('Install').click();
// Assert
expect(screen.queryByRole('switch', { name: 'isVisibleOnGuestDashboard' })).not.toBeInTheDocument();
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({});
});
});
});

View File

@ -114,7 +114,8 @@ export const InstallForm: React.FC<IProps> = ({ formFields, info, onSubmit, init
<Controller
control={control}
name="openPort"
defaultValue
defaultValue={!info.force_expose}
disabled={info.force_expose}
render={({ field: { onChange, value, ref, ...props } }) => (
<Switch
{...props}
@ -176,7 +177,7 @@ export const InstallForm: React.FC<IProps> = ({ formFields, info, onSubmit, init
return (
<form className="flex flex-col" onSubmit={handleSubmit(validate)}>
{info.dynamic_config && renderDynamicConfigForm()}
{(guestDashboard || formFields.filter(typeFilter).length !== 0) && <h3>{t('APP_INSTALL_FORM_GENERAL')}</h3>}
{formFields.filter(typeFilter).map(renderField)}
{guestDashboard && (
<Controller
@ -186,7 +187,6 @@ export const InstallForm: React.FC<IProps> = ({ formFields, info, onSubmit, init
render={({ field: { onChange, value, ref, ...props } }) => (
<Switch
className="mb-3"
disabled={info.force_expose}
ref={ref}
checked={value}
onCheckedChange={onChange}
@ -196,6 +196,8 @@ export const InstallForm: React.FC<IProps> = ({ formFields, info, onSubmit, init
)}
/>
)}
{(info.exposable || info.dynamic_config) && <h3>{t('APP_INSTALL_FORM_REVERSE_PROXY')}</h3>}
{info.dynamic_config && renderDynamicConfigForm()}
{info.exposable && renderExposeForm()}
<Button loading={loading} type="submit" className="btn-success">
{initialValues ? t('APP_INSTALL_FORM_SUBMIT_UPDATE') : t('APP_INSTALL_FORM_SUBMIT_INSTALL')}

View File

@ -1,5 +1,6 @@
import React from 'react';
import { AppInfo } from '@runtipi/shared';
import { vi, describe, it, expect } from 'vitest';
import { InstallModal } from './InstallModal';
import { fireEvent, render, screen, waitFor } from '../../../../../../../tests/test-utils';
@ -14,29 +15,29 @@ describe('InstallModal', () => {
} as unknown as AppInfo;
it('renders with the correct title', () => {
render(<InstallModal info={app} isOpen onClose={jest.fn()} onSubmit={jest.fn()} />);
render(<InstallModal info={app} isOpen onClose={vi.fn()} onSubmit={vi.fn()} />);
expect(screen.getByText(`Install ${app.name}`)).toBeInTheDocument();
});
it('renders the InstallForm with the correct props', () => {
render(<InstallModal info={app} isOpen onClose={jest.fn()} onSubmit={jest.fn()} />);
render(<InstallModal info={app} isOpen onClose={vi.fn()} onSubmit={vi.fn()} />);
expect(screen.getByRole('textbox', { name: app.form_fields[0]?.env_variable })).toBeInTheDocument();
expect(screen.getByRole('textbox', { name: app.form_fields[1]?.env_variable })).toBeInTheDocument();
});
it('calls onClose when the close button is clicked', () => {
const onClose = jest.fn();
render(<InstallModal info={app} isOpen onClose={onClose} onSubmit={jest.fn()} />);
const onClose = vi.fn();
render(<InstallModal info={app} isOpen onClose={onClose} onSubmit={vi.fn()} />);
fireEvent.click(screen.getByTestId('modal-close-button'));
expect(onClose).toHaveBeenCalled();
});
it('calls onSubmit with the correct values when the form is submitted', async () => {
const onSubmit = jest.fn();
render(<InstallModal info={app} isOpen onClose={jest.fn()} onSubmit={onSubmit} />);
const onSubmit = vi.fn();
render(<InstallModal info={app} isOpen onClose={vi.fn()} onSubmit={onSubmit} />);
const hostnameInput = screen.getByRole('textbox', { name: app.form_fields[0]?.env_variable });
const passwordInput = screen.getByRole('textbox', { name: app.form_fields[1]?.env_variable });

View File

@ -1,4 +1,5 @@
import React from 'react';
import { vi, expect, describe, it } from 'vitest';
import { fireEvent, render, screen } from '../../../../../../../tests/test-utils';
import { UpdateModal } from './UpdateModal';
@ -8,7 +9,7 @@ describe('UpdateModal', () => {
it('renders with the correct title and version number', () => {
// arrange
render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={jest.fn()} onConfirm={jest.fn()} />);
render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={vi.fn()} onConfirm={vi.fn()} />);
// assert
expect(screen.getByText(`Update ${app.name} ?`)).toBeInTheDocument();
@ -17,7 +18,7 @@ describe('UpdateModal', () => {
it('should not render when isOpen is false', () => {
// arrange
render(<UpdateModal info={app} newVersion={newVersion} isOpen={false} onClose={jest.fn()} onConfirm={jest.fn()} />);
render(<UpdateModal info={app} newVersion={newVersion} isOpen={false} onClose={vi.fn()} onConfirm={vi.fn()} />);
const modal = screen.queryByTestId('modal');
// assert
@ -26,8 +27,8 @@ describe('UpdateModal', () => {
it('calls onClose when the close button is clicked', () => {
// arrange
const onClose = jest.fn();
render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={onClose} onConfirm={jest.fn()} />);
const onClose = vi.fn();
render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={onClose} onConfirm={vi.fn()} />);
// act
const closeButton = screen.getByTestId('modal-close-button');
@ -37,8 +38,8 @@ describe('UpdateModal', () => {
it('calls onConfirm when the update button is clicked', () => {
// arrange
const onConfirm = jest.fn();
render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={jest.fn()} onConfirm={onConfirm} />);
const onConfirm = vi.fn();
render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={vi.fn()} onConfirm={onConfirm} />);
// act
const updateButton = screen.getByText('Update');

View File

@ -1,4 +1,5 @@
import { FormField } from '@runtipi/shared';
import { describe, it, expect } from 'vitest';
import { validateAppConfig, validateField } from './validators';
describe('Test: validateField', () => {

View File

@ -1,11 +1,12 @@
import React from 'react';
import { vi, describe, it, expect } from 'vitest';
import { CategorySelector } from './CategorySelector';
import { fireEvent, render, screen } from '../../../../../../tests/test-utils';
describe('Test: CategorySelector', () => {
it('should render without crashing', () => {
// arrange
const onSelect = jest.fn();
const onSelect = vi.fn();
const className = 'test-class';
// act
@ -17,24 +18,25 @@ describe('Test: CategorySelector', () => {
it('should call onSelect when an option is selected', () => {
// arrange
const onSelect = jest.fn();
const onSelect = vi.fn();
const className = 'test-class';
render(<CategorySelector onSelect={onSelect} className={className} />);
const combobox = screen.getByRole('combobox');
// act
fireEvent.input(combobox, { target: { value: 'automation' } });
const listItem = screen.getByText('Automation');
fireEvent.click(listItem);
fireEvent.focus(combobox);
fireEvent.keyDown(combobox, { key: 'ArrowDown' });
const listItem = screen.getByText('Network');
fireEvent.keyDown(listItem, { key: 'Enter' });
// assert
expect(onSelect).toHaveBeenCalledWith('automation');
expect(onSelect).toHaveBeenCalledWith('network');
});
it('should set the initial value when provided', () => {
// arrange
const onSelect = jest.fn();
const onSelect = vi.fn();
const className = 'test-class';
render(<CategorySelector onSelect={onSelect} className={className} initialValue="automation" />);

View File

@ -1,4 +1,5 @@
import { limitText } from '@/lib/helpers/text-helpers';
import { describe, it, expect } from 'vitest';
import { createAppConfig } from '../../../../../server/tests/apps.factory';
import { sortTable } from '../table.helpers';
import { AppTableData } from '../table.types';

View File

@ -0,0 +1,35 @@
'use client';
import React from 'react';
import { OffCanvas } from 'src/app/components/OffCanvas/OffCanvas';
import { Button } from '@/components/ui/Button';
import { useTranslations } from 'next-intl';
import { useDisclosure } from '@/client/hooks/useDisclosure';
import { useCookies } from 'next-client-cookies';
import { IconAlertTriangle } from '@tabler/icons-react';
export const AtRiskBanner = ({ isInsecure }: { isInsecure: boolean }) => {
const { isOpen, close } = useDisclosure(isInsecure);
const t = useTranslations();
const cookies = useCookies();
const onClose = () => {
cookies.set('hide-insecure-instance', 'true', { path: '/', expires: 7 });
close();
};
return (
<OffCanvas position="bottom" visible={isOpen}>
<div className="d-flex gap-4 align-items-center">
<IconAlertTriangle color="orange" size={40} />
<div>
<h4 className="alert-title">{t('DASHBOARD_IP_WARNING_TITLE')}</h4>
<div className="text-secondary">{t('DASHBOARD_IP_WARNING')}</div>
</div>
<Button className="flex-1" onClick={onClose}>
{t('COMMON_CLOSE')}
</Button>
</div>
</OffCanvas>
);
};

View File

@ -6,11 +6,13 @@ import semver from 'semver';
import clsx from 'clsx';
import { appService } from '@/server/services/apps/apps.service';
import { TipiConfig } from '@/server/core/TipiConfig';
import { isInstanceInsecure } from '@/server/utils/network';
import { Header } from './components/Header';
import { PageTitle } from './components/PageTitle';
import styles from './layout.module.scss';
import { LayoutActions } from './components/LayoutActions/LayoutActions';
import { Welcome } from './components/Welcome/Welcome';
import { AtRiskBanner } from './components/AtRiskBanner/AtRiskBanner';
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
const user = await getUserFromCookie();
@ -50,7 +52,10 @@ export default async function DashboardLayout({ children }: { children: React.Re
</div>
</div>
<div className="page-body">
<div className="container-xl">{children}</div>
<div className="container-xl">
{children}
{isInstanceInsecure() && <AtRiskBanner isInsecure />}
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,6 @@
import React from 'react';
import { faker } from '@faker-js/faker';
import { describe, it, expect } from 'vitest';
import { render, screen, waitFor, fireEvent } from '../../../../../../tests/test-utils';
import { ChangePasswordForm } from './ChangePasswordForm';

View File

@ -1,4 +1,5 @@
import React from 'react';
import { describe, it } from 'vitest';
import { render } from '../../../../../../tests/test-utils';
import { SecurityContainer } from './SecurityContainer';

View File

@ -1,11 +1,12 @@
import React from 'react';
import { faker } from '@faker-js/faker';
import { vi, describe, it, expect } from 'vitest';
import { SettingsForm } from './SettingsForm';
import { fireEvent, render, screen, waitFor } from '../../../../../../tests/test-utils';
describe('Test: SettingsForm', () => {
it('should render without error', () => {
render(<SettingsForm onSubmit={jest.fn()} />);
render(<SettingsForm onSubmit={vi.fn()} />);
expect(screen.getByText('General settings')).toBeInTheDocument();
});
@ -19,7 +20,7 @@ describe('Test: SettingsForm', () => {
appsRepoUrl: faker.internet.url(),
appDataPath: faker.system.directoryPath(),
};
render(<SettingsForm onSubmit={jest.fn()} initalValues={initialValues} />);
render(<SettingsForm onSubmit={vi.fn()} initalValues={initialValues} />);
// assert
await waitFor(() => {
@ -41,7 +42,7 @@ describe('Test: SettingsForm', () => {
appDataPath: 'invalid path',
localDomain: 'invalid local domain',
};
render(<SettingsForm onSubmit={jest.fn()} submitErrors={submitErrors} />);
render(<SettingsForm onSubmit={vi.fn()} submitErrors={submitErrors} />);
// assert
await waitFor(() => {
@ -56,8 +57,8 @@ describe('Test: SettingsForm', () => {
it('should correctly validate the form', async () => {
// arrange
render(<SettingsForm onSubmit={jest.fn()} />);
const submitButton = screen.getByRole('button', { name: 'Save' });
render(<SettingsForm onSubmit={vi.fn()} />);
const submitButton = screen.getByRole('button', { name: 'Update settings' });
const dnsIpInput = screen.getByRole('textbox', { name: 'dnsIp' });
const domainInput = screen.getByRole('textbox', { name: 'domain' });
const internalIpInput = screen.getByRole('textbox', { name: 'internalIp' });
@ -82,9 +83,9 @@ describe('Test: SettingsForm', () => {
it('should call onSubmit when the form is submitted', async () => {
// arrange
const onSubmit = jest.fn();
const onSubmit = vi.fn();
render(<SettingsForm onSubmit={onSubmit} />);
const submitButton = screen.getByRole('button', { name: 'Save' });
const submitButton = screen.getByRole('button', { name: 'Update settings' });
// act
fireEvent.click(submitButton);
@ -97,8 +98,8 @@ describe('Test: SettingsForm', () => {
it('should download the certificate when the download button is clicked', async () => {
// arrange
const spy = jest.spyOn(window, 'open').mockImplementation();
render(<SettingsForm onSubmit={jest.fn} />);
const spy = vi.spyOn(window, 'open').mockImplementation(() => null);
render(<SettingsForm onSubmit={vi.fn} />);
const downloadButton = screen.getByRole('button', { name: 'Download certificate' });
// act

View File

@ -0,0 +1,23 @@
'use client';
import React, { PropsWithChildren } from 'react';
import clsx from 'clsx';
type Props = {
position: 'bottom' | 'top';
visible: boolean;
};
export const OffCanvas = (props: PropsWithChildren<Props>) => {
const { position, children, visible } = props;
return (
<>
<div className={clsx(`offcanvas offcanvas-${position} h-auto`, { show: visible })}>
<div className="offcanvas-body">
<div className="container">{children}</div>
</div>
</div>
<div className={clsx('pe-none offcanvas-backdrop fade', { show: visible })} />
</>
);
};

View File

@ -1,5 +1,6 @@
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import { vi, expect, describe, it } from 'vitest';
import { Button } from './Button';
describe('Button component', () => {
@ -59,7 +60,7 @@ describe('Button component', () => {
it('should call onClick callback when clicked', () => {
// arrange
const onClick = jest.fn();
const onClick = vi.fn();
render(<Button onClick={onClick}>Click me</Button>);
const button = screen.getByRole('button');

View File

@ -1,4 +1,5 @@
import React from 'react';
import { describe, it, expect } from 'vitest';
import { render, screen } from '../../../../../tests/test-utils';
import { DataGrid } from './DataGrid';
import { DataGridItem } from './DataGridItem';

View File

@ -1,4 +1,5 @@
import React from 'react';
import { vi, describe, it, expect } from 'vitest';
import { fireEvent, screen, render } from '../../../../../tests/test-utils';
import { EmptyPage } from './EmptyPage';
@ -14,7 +15,7 @@ describe('<EmptyPage />', () => {
it('should render the action button and trigger the onAction callback', () => {
// arrange
const onAction = jest.fn();
const onAction = vi.fn();
render(<EmptyPage title="Title" onAction={onAction} actionLabel="Action" />);
// act

View File

@ -1,4 +1,5 @@
import React from 'react';
import { vi, it, describe, expect } from 'vitest';
import { fireEvent, render, screen } from '../../../../../tests/test-utils';
import { ErrorPage } from './ErrorPage';
@ -11,7 +12,7 @@ describe('ErrorPage', () => {
});
it('should render the retry button when onRetry is provided', () => {
const onRetry = jest.fn();
const onRetry = vi.fn();
render(<ErrorPage onRetry={onRetry} />);
expect(screen.getByTestId('error-page-action')).toBeInTheDocument();
@ -24,7 +25,7 @@ describe('ErrorPage', () => {
});
it('should call the onRetry callback when the retry button is clicked', () => {
const onRetry = jest.fn();
const onRetry = vi.fn();
render(<ErrorPage onRetry={onRetry} />);
fireEvent.click(screen.getByTestId('error-page-action'));

View File

@ -1,4 +1,5 @@
import React from 'react';
import { vi, describe, it, expect } from 'vitest';
import { Input } from './Input';
import { fireEvent, render, waitFor, screen } from '../../../../../tests/test-utils';
@ -40,7 +41,7 @@ describe('Input', () => {
it('should call onChange when the input value is changed', async () => {
// arrange
const onChange = jest.fn();
const onChange = vi.fn();
render(<Input name="test-input" label="Test Label" onChange={onChange} />);
const input = screen.getByLabelText('Test Label');
@ -53,7 +54,7 @@ describe('Input', () => {
it('should call onBlur when the input is blurred', async () => {
// arrange
const onBlur = jest.fn();
const onBlur = vi.fn();
render(<Input name="test-input" label="Test Label" onBlur={onBlur} />);
const input = screen.getByLabelText('Test Label');
@ -75,7 +76,7 @@ describe('Input', () => {
it('should set the input value if provided', () => {
// arrange
render(<Input name="test-input" label="Test Label" value="Test Value" onChange={jest.fn} />);
render(<Input name="test-input" label="Test Label" value="Test Value" onChange={vi.fn} />);
const input = screen.getByLabelText('Test Label') as HTMLInputElement;
// assert

View File

@ -1,5 +1,6 @@
import React from 'react';
import { faker } from '@faker-js/faker';
import { describe, it, expect, vi } from 'vitest';
import { OtpInput } from './OtpInput';
import { fireEvent, render, screen } from '../../../../../tests/test-utils';
@ -23,7 +24,7 @@ describe('<OtpInput />', () => {
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 = jest.fn();
const onChange = vi.fn();
render(<OtpInput valueLength={valueLength} onChange={onChange} value="" />);
const inputEls = screen.queryAllByRole('textbox');
@ -51,7 +52,7 @@ describe('<OtpInput />', () => {
it('should NOT allow typing of non-digits', () => {
// arrange
const valueLength = faker.number.int({ min: 2, max: 6 });
const onChange = jest.fn();
const onChange = vi.fn();
render(<OtpInput valueLength={valueLength} onChange={onChange} value="" />);
const inputEls = screen.queryAllByRole('textbox');
@ -76,7 +77,7 @@ describe('<OtpInput />', () => {
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 = jest.fn();
const onChange = vi.fn();
render(<OtpInput value={value} valueLength={valueLength} onChange={onChange} />);
@ -121,7 +122,7 @@ describe('<OtpInput />', () => {
const valueArray = value.split('');
const valueLength = value.length;
const lastIdx = valueLength - 1;
const onChange = jest.fn();
const onChange = vi.fn();
render(<OtpInput value={value} valueLength={valueLength} onChange={onChange} />);
@ -149,7 +150,7 @@ describe('<OtpInput />', () => {
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 = jest.fn();
const onChange = vi.fn();
render(<OtpInput value={value} valueLength={valueLength} onChange={onChange} />);
@ -169,7 +170,7 @@ describe('<OtpInput />', () => {
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 = jest.fn();
const onChange = vi.fn();
render(<OtpInput valueLength={valueLength} onChange={onChange} value="" />);
@ -190,7 +191,7 @@ describe('<OtpInput />', () => {
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 = jest.fn();
const onChange = vi.fn();
render(<OtpInput valueLength={valueLength} onChange={onChange} value="" />);
@ -204,7 +205,7 @@ describe('<OtpInput />', () => {
});
it('should focus to next element on right/down key', () => {
render(<OtpInput valueLength={3} onChange={jest.fn} value="1234" />);
render(<OtpInput valueLength={3} onChange={vi.fn} value="1234" />);
const inputEls = screen.queryAllByRole('textbox');
const firstInputEl = inputEls[0] as HTMLInputElement;
@ -225,7 +226,7 @@ describe('<OtpInput />', () => {
});
it('should focus to next element on left/up key', () => {
render(<OtpInput valueLength={3} onChange={jest.fn} value="1234" />);
render(<OtpInput valueLength={3} onChange={vi.fn} value="1234" />);
const inputEls = screen.queryAllByRole('textbox');
const lastInputEl = inputEls[2] as HTMLInputElement;
@ -248,7 +249,7 @@ describe('<OtpInput />', () => {
it('should only focus to input if previous input has value', () => {
const valueLength = 6;
render(<OtpInput valueLength={valueLength} onChange={jest.fn} value="" />);
render(<OtpInput valueLength={valueLength} onChange={vi.fn} value="" />);
const inputEls = screen.queryAllByRole('textbox');
const lastInputEl = inputEls[valueLength - 1] as HTMLInputElement;

View File

@ -1,5 +1,6 @@
import React from 'react';
import { vi, describe, it, expect } from 'vitest';
import { Switch } from './Switch';
import { fireEvent, render, screen } from '../../../../../tests/test-utils';
@ -25,7 +26,7 @@ describe('Switch', () => {
it('renders the checked state', () => {
// arrange
render(<Switch checked onChange={jest.fn} />);
render(<Switch checked onChange={vi.fn} />);
const checkbox = screen.getByRole('switch');
// assert
@ -34,7 +35,7 @@ describe('Switch', () => {
it('triggers onChange event when clicked', () => {
// arrange
const onChange = jest.fn();
const onChange = vi.fn();
render(<Switch onCheckedChange={onChange} />);
const checkbox = screen.getByRole('switch');
@ -47,7 +48,7 @@ describe('Switch', () => {
it('triggers onBlur event when blurred', () => {
// arrange
const onBlur = jest.fn();
const onBlur = vi.fn();
render(<Switch onBlur={onBlur} />);
const checkbox = screen.getByRole('switch');
@ -60,7 +61,7 @@ describe('Switch', () => {
it('should change the checked state when clicked', () => {
// arrange
render(<Switch onChange={jest.fn} />);
render(<Switch onChange={vi.fn} />);
const checkbox = screen.getByRole('switch');
// act

View File

@ -1,5 +1,6 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { describe, test, expect } from 'vitest';
import { act } from 'react';
import { useDisclosure } from '../useDisclosure';
describe('useDisclosure', () => {

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Configuració",
"APP_ACTION_START": "Inicia",
"APP_ACTION_STOP": "Atura",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Actualitza",
"APP_CATEGORY_AI": "IA",
"APP_CATEGORY_AUTOMATION": "Automatització",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Ha fallat l'inici de l'aplicació {id}, consulta els logs pels detalls",
"APP_ERROR_APP_FAILED_TO_START": "Ha fallat l'inici de l'aplicació {id}, consulta els logs pels detalls",
"APP_ERROR_APP_FAILED_TO_STOP": "Ha fallat l'aturada de l'aplicació {id}, consulta els logs pels detalls",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Ha fallat la desinstal·lació de l'aplicació {id}, consulta els logs pels detalls",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Ha fallat l'actualització de l'aplicació {id}, consulta els logs pels detalls",
"APP_ERROR_APP_FORCE_EXPOSED": "L'aplicació {id} només funciona amb un domini exposat",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} ha de coincidir amb el patró {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} és obligatori",
"APP_INSTALL_FORM_ERROR_URL": "{label} ha de ser una URL vàlida",
"APP_INSTALL_FORM_EXPOSE_APP": "Exposa l'aplicació",
"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": "Instal·la",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Actualitza",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Iniciant",
"APP_STATUS_STOPPED": "Aturat",
"APP_STATUS_STOPPING": "Aturant",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Desinstal·lant",
"APP_STATUS_UPDATING": "Actualitzant",
"APP_STOP_FORM_SUBMIT": "Atura",
"APP_STOP_FORM_SUBTITLE": "Totes les dades es conservaran",
"APP_STOP_FORM_TITLE": "Atura {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 s'ha trobat cap aplicació",
"APP_STORE_NO_RESULTS_SUBTITLE": "Intenta afinar la teva cerca",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "N'esteu segur? Aquesta acció no es pot desfer.",
"APP_UNINSTALL_SUCCESS": "App {id} uninstalled successfully",
"APP_UPDATE_CONFIG_SUCCESS": "La configuració de l'aplicació s'ha actualitzat correctament. Reinicia l'aplicació per aplicar els canvis",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Actualitza",
"APP_UPDATE_FORM_SUBTITLE_1": "Actualitza l'aplicació a l'última versió:",
"APP_UPDATE_FORM_SUBTITLE_2": "Make sure you've read the release notes of the app and you've backed up your app data.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Introduïu el codi de la vostra aplicació d'autenticació",
"AUTH_TOTP_SUBMIT": "Confirma",
"AUTH_TOTP_TITLE": "Autenticació de doble factor",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Desinstal·la aplicacions per reduir la càrrega",
"DASHBOARD_CPU_TITLE": "Càrrega de CPU",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Utilitzats del total de {total} GB",
"DASHBOARD_DISK_SPACE_TITLE": "Espai de disc",
"DASHBOARD_MEMORY_TITLE": "Memoria en ús",
"DASHBOARD_TITLE": "Panell de control",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Einstellungen",
"APP_ACTION_START": "Start",
"APP_ACTION_STOP": "Anhalten",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Aktualisieren",
"APP_CATEGORY_AI": "KI",
"APP_CATEGORY_AUTOMATION": "Automatisierung",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_START": "Starten der App {id} fehlgeschlagen. Siehe die Logs für weitere Informationen",
"APP_ERROR_APP_FAILED_TO_STOP": "Stoppen der App {id} fehlgeschlagen. Siehe die Logs für weitere Informationen",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Deinstallieren der App {id} fehlgeschlagen. Siehe die Logs für weitere Informationen",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Aktualisieren der App {id} fehlgeschlagen. Siehe die Logs für weitere Informationen",
"APP_ERROR_APP_FORCE_EXPOSED": "App {id} funktioniert nur mit veröffentlichter Domain",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} stimmt nicht mit dem Format {pattern} überein",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} ist erforderlich",
"APP_INSTALL_FORM_ERROR_URL": "{label} muss eine gültige URL sein",
"APP_INSTALL_FORM_EXPOSE_APP": "App veröffentlichen",
"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": "Installieren",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Aktualisieren",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Startet",
"APP_STATUS_STOPPED": "Angehalten",
"APP_STATUS_STOPPING": "Stoppen",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Deinstallieren",
"APP_STATUS_UPDATING": "Aktualisieren",
"APP_STOP_FORM_SUBMIT": "Anhalten",
"APP_STOP_FORM_SUBTITLE": "Alle Daten werden aufbewahrt",
"APP_STOP_FORM_TITLE": "{name} anhalten?",
"APP_STOP_SUCCESS": "App {id} erfolgreich angehalten",
"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": "Kategorie wählen",
"APP_STORE_NO_RESULTS": "Keine App gefunden",
"APP_STORE_NO_RESULTS_SUBTITLE": "Versuche, deine Suche zu verbessern",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Sind Sie sicher? Dieser Schritt kann nicht rückgängig gemacht werden.",
"APP_UNINSTALL_SUCCESS": "App {id} erfolgreich deinstalliert",
"APP_UPDATE_CONFIG_SUCCESS": "App-Konfiguration erfolgreich aktualisiert. Starte die App neu, um die Änderungen zu übernehmen",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Aktualisieren",
"APP_UPDATE_FORM_SUBTITLE_1": "App auf die neueste Version aktualisieren:",
"APP_UPDATE_FORM_SUBTITLE_2": "Dies wird Ihre benutzerdefinierte Konfiguration zurücksetzen (z.B. Änderungen in docker-compose.yml).",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Code aus der Authenticator-App eingeben",
"AUTH_TOTP_SUBMIT": "Bestätigen",
"AUTH_TOTP_TITLE": "Zwei-Faktor Authentifizierung",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Apps deinstallieren um Last zu reduzieren",
"DASHBOARD_CPU_TITLE": "CPU Last",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Wird von {total} GB verwendet",
"DASHBOARD_DISK_SPACE_TITLE": "Speicherplatz",
"DASHBOARD_MEMORY_TITLE": "Benutzter Speicher",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Ρυθμίσεις",
"APP_ACTION_START": "Εκκίνηση",
"APP_ACTION_STOP": "Παύση",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Ενημέρωση",
"APP_CATEGORY_AI": "Τεχνητή Νοημοσύνη",
"APP_CATEGORY_AUTOMATION": "Αυτοματισμός",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_START": "Αποτυχία εκκίνησης της εφαρμογής {id}, δείτε τα αρχεία καταγραφής για περισσότερες λεπτομέρειες",
"APP_ERROR_APP_FAILED_TO_STOP": "Αποτυχία διακοπής της εφαρμογής {id}, δείτε τα αρχεία καταγραφής για περισσότερες λεπτομέρειες",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Αποτυχία απεγκατάστασης εφαρμογής {id}, δείτε τα αρχεία καταγραφής για περισσότερες λεπτομέρειες",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Αποτυχία ενημέρωσης της εφαρμογής {id}, δείτε το αρχείο καταγραφής για περισσότερες λεπτομέρειες",
"APP_ERROR_APP_FORCE_EXPOSED": "Η εφαρμογή {id} λειτουργεί μόνο με εκτεθειμένο τομέα",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "Το {label} πρέπει να ταιριάζει με το μοτίβο {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "Το πεδίο {label} είναι απαραίτητο",
"APP_INSTALL_FORM_ERROR_URL": "Το πεδίο {label} πρέπει να είναι μια έγκυρη διεύθυνση URL",
"APP_INSTALL_FORM_EXPOSE_APP": "Έκθεση εφαρμογής",
"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": "Εγκατάσταση",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Ενημέρωση",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Εκκίνηση",
"APP_STATUS_STOPPED": "Σταματημένο",
"APP_STATUS_STOPPING": "Παύση",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Απεγκατάσταση",
"APP_STATUS_UPDATING": "Ενημέρωση",
"APP_STOP_FORM_SUBMIT": "Παύση",
"APP_STOP_FORM_SUBTITLE": "Όλα τα δεδομένα θα διατηρηθούν",
"APP_STOP_FORM_TITLE": "Παύση {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": "Διαλέξτε μία κατηγορία",
"APP_STORE_NO_RESULTS": "Δε βρέθηκαν εφαρμογές",
"APP_STORE_NO_RESULTS_SUBTITLE": "Προσπαθήστε να βελτιώσετε την αναζήτησή σας",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Είστε βέβαιοι; Αυτό δεν μπορεί να αναιρεθεί.",
"APP_UNINSTALL_SUCCESS": "App {id} uninstalled successfully",
"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_FORM_SUBMIT": "Ενημέρωση",
"APP_UPDATE_FORM_SUBTITLE_1": "Ενημέρωση στην την πιο πρόσφατη έκδοση:",
"APP_UPDATE_FORM_SUBTITLE_2": "Αυτό θα επαναφέρει τις προσαρμοσμένες ρυθμίσεις σας (π.χ. αλλαγές στο docker-compose.yml).",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Πληκτρολογήστε τον κωδικό από την εφαρμογή ταυτοποίησης",
"AUTH_TOTP_SUBMIT": "Επιβεβαίωση",
"AUTH_TOTP_TITLE": "Επαλήθευση δύο βημάτων",
"COMMON_CLOSE": "Close",
"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": "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",
"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.",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -76,7 +76,10 @@
"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_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} ?",
@ -112,6 +115,7 @@
"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.",
@ -156,7 +160,7 @@
"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": "Run this command on your server and then refresh this page",
"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",
@ -164,12 +168,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Configuración",
"APP_ACTION_START": "Comenzar",
"APP_ACTION_STOP": "Detener",
"APP_ACTION_RESTART": "Reiniciar",
"APP_ACTION_UPDATE": "Actualizar",
"APP_CATEGORY_AI": "IA",
"APP_CATEGORY_AUTOMATION": "Automatización",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Error al reiniciar la aplicación {id}, consulta los registros para obtener 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",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "No se pudo desinstalar la aplicación {id}, consulta los registros para obtener más detalles",
"APP_ERROR_APP_FAILED_TO_UPDATE": "No se pudo actualizar la aplicación {id}, consulta los registros para obtener más detalles",
"APP_ERROR_APP_FORCE_EXPOSED": "La aplicación {id} solo funciona con un dominio expuesto",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} debe coincidir con el patrón {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} es obligatorio",
"APP_INSTALL_FORM_ERROR_URL": "{label} tiene que ser una URL válida",
"APP_INSTALL_FORM_EXPOSE_APP": "Exponer aplicación",
"APP_INSTALL_FORM_EXPOSE_APP": "Expose app on the internet",
"APP_INSTALL_FORM_OPEN_PORT": "Puerto abierto",
"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": "Restablecer aplicación",
"APP_INSTALL_FORM_SUBMIT_INSTALL": "Instalar",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Actualizar",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Iniciando",
"APP_STATUS_STOPPED": "Detenida",
"APP_STATUS_STOPPING": "Deteniendo",
"APP_STATUS_RESTARTING": "Reiniciando",
"APP_STATUS_UNINSTALLING": "Desinstalando",
"APP_STATUS_UPDATING": "Actualizando",
"APP_STOP_FORM_SUBMIT": "Detener",
"APP_STOP_FORM_SUBTITLE": "Todos los datos serán conservados",
"APP_STOP_FORM_TITLE": "¿Detener {name}?",
"APP_STOP_SUCCESS": "La aplicación {id} se detuvo correctamente",
"APP_RESTART_FORM_SUBMIT": "Reiniciar",
"APP_RESTART_FORM_SUBTITLE": "Todos los datos serán conservados",
"APP_RESTART_FORM_TITLE": "Reiniciar {name} ?",
"APP_RESTART_SUCCESS": "La aplicación {id} se reinició correctamente",
"APP_STORE_CATEGORY_PLACEHOLDER": "Selecciona una categoría",
"APP_STORE_NO_RESULTS": "No se encontraron aplicaciones",
"APP_STORE_NO_RESULTS_SUBTITLE": "Intenta refinar tu búsqueda",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "¿Estás seguro? Esta acción no se puede deshacer.",
"APP_UNINSTALL_SUCCESS": "La aplicación {id} se desinstaló correctamente",
"APP_UPDATE_CONFIG_SUCCESS": "La configuración de la aplicación se ha actualizado correctamente. Reinicie la aplicación para aplicar los cambios",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "La actualización de la aplicación {id} requiere que la versión de Tipi sea {minVersion} o superior. Por favor actualiza tu instancia.",
"APP_UPDATE_FORM_SUBMIT": "Actualizar",
"APP_UPDATE_FORM_SUBTITLE_1": "Actualizar la aplicación a la última versión:",
"APP_UPDATE_FORM_SUBTITLE_2": "Esto restablecerá su configuración personalizada (por ejemplo, los cambios en docker-compose.yml).",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Ingresa el código de tu aplicación de autenticación",
"AUTH_TOTP_SUBMIT": "Confirmar",
"AUTH_TOTP_TITLE": "Autenticación de dos factores",
"COMMON_CLOSE": "Cerrar",
"DASHBOARD_CPU_SUBTITLE": "Desinstala aplicaciones para reducir la carga",
"DASHBOARD_CPU_TITLE": "Carga de CPU",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Usado de {total} GB",
"DASHBOARD_DISK_SPACE_TITLE": "Espacio en disco",
"DASHBOARD_MEMORY_TITLE": "Memoria utilizada",
"DASHBOARD_TITLE": "Tablero",
"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",
"GUEST_DASHBOARD": "Panel de invitados",
"GUEST_DASHBOARD_NO_APPS": "No hay aplicaciones para mostrar",
"GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Pídele a tu administrador que agregue aplicaciones al panel de invitados o inicia sesión para ver tus aplicaciones.",
@ -173,7 +188,7 @@
"HEADER_SOURCE_CODE": "Código fuente",
"HEADER_SPONSOR": "Patrocinador",
"HEADER_UPDATE_AVAILABLE": "Actualización disponible",
"INTERNAL_SERVER_ERROR": "Internal server error",
"INTERNAL_SERVER_ERROR": "Error interno del servidor",
"LINKS_ADD_SUBMIT": "Enviar",
"LINKS_ADD_SUBTITLE": "Agrega un enlace externo al tablero",
"LINKS_ADD_SUCCESS": "Enlace agregado correctamente",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Paramètres",
"APP_ACTION_START": "Démarrer",
"APP_ACTION_STOP": "Arrêter",
"APP_ACTION_RESTART": "Redémarrer",
"APP_ACTION_UPDATE": "Mettre à jour",
"APP_CATEGORY_AI": "IA",
"APP_CATEGORY_AUTOMATION": "Automatisation",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Impossible de réinitialiser l'application {id}, voir les logs pour plus de détails",
"APP_ERROR_APP_FAILED_TO_START": "Impossible de démarrer l'application {id}, voir les logs pour plus de détails",
"APP_ERROR_APP_FAILED_TO_STOP": "Impossible d'arrêter l'application {id}, voir les logs pour plus de détails",
"APP_ERROR_APP_FAILED_TO_RESTART": "Échec de redémarrage l'application {id}, consultez les journaux pour plus de détails",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Impossible de désinstaller l'application {id}, voir les logs pour plus de détails",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Impossible de mettre à jour l'application {id}, voir les logs pour plus de détails",
"APP_ERROR_APP_FORCE_EXPOSED": "L'application {id} ne fonctionne qu'avec un domaine exposé",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} doit correspondre à la règle {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} est obligatoire",
"APP_INSTALL_FORM_ERROR_URL": "{label} doit être une URL valide",
"APP_INSTALL_FORM_EXPOSE_APP": "Exposer l'application",
"APP_INSTALL_FORM_EXPOSE_APP": "Exposer l'application sur internet",
"APP_INSTALL_FORM_OPEN_PORT": "Port ouvert",
"APP_INSTALL_FORM_OPEN_PORT_HINT": "Ouvrir un port sur l'hôte ? Cette application sera accessible sur {internalIp}:{port}. (Plus facile mais moins sûr)",
"APP_INSTALL_FORM_EXPOSE_LOCAL": "Exposer l'application sur le réseau local",
"APP_INSTALL_FORM_EXPOSE_LOCAL_HINT": "Exposer l'application sur le réseau local ? Cette application sera accessible à {appId}.{domain}. (Visitez la page des paramètres pour configurer votre domaine local)",
"APP_INSTALL_FORM_RESET": "Réinitialiser lapplication",
"APP_INSTALL_FORM_SUBMIT_INSTALL": "Installer",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Mettre à jour",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Démarrage en cours",
"APP_STATUS_STOPPED": "Arrêté",
"APP_STATUS_STOPPING": "Arrêt en cours",
"APP_STATUS_RESTARTING": "Redémarrage",
"APP_STATUS_UNINSTALLING": "Désinstallation en cours",
"APP_STATUS_UPDATING": "Mise à jour en cours",
"APP_STOP_FORM_SUBMIT": "Arrêter",
"APP_STOP_FORM_SUBTITLE": "Toutes les données seront conservées",
"APP_STOP_FORM_TITLE": "Arrêter {name}?",
"APP_STOP_SUCCESS": "Application {id} arrêtée avec succès",
"APP_RESTART_FORM_SUBMIT": "Redémarrer",
"APP_RESTART_FORM_SUBTITLE": "Toutes les données seront conservées",
"APP_RESTART_FORM_TITLE": "Redémarrer {name} ?",
"APP_RESTART_SUCCESS": "Application {id} redémarrée avec succès",
"APP_STORE_CATEGORY_PLACEHOLDER": "Sélectionnez une catégorie",
"APP_STORE_NO_RESULTS": "Aucune application trouvée",
"APP_STORE_NO_RESULTS_SUBTITLE": "Essayez d'affiner votre recherche",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Êtes-vous sûr ? Cette action ne peut pas être annulée.",
"APP_UNINSTALL_SUCCESS": "Application {id} désinstallée avec succès",
"APP_UPDATE_CONFIG_SUCCESS": "Configuration de l'application mise à jour avec succès. Redémarrez l'application pour appliquer les modifications",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "La mise à jour de l'application {id} nécessite une version de Tipi {minVersion} ou supérieure. Veuillez mettre à jour votre instance.",
"APP_UPDATE_FORM_SUBMIT": "Mettre à jour",
"APP_UPDATE_FORM_SUBTITLE_1": "Mettre à jour l'application vers la dernière version :",
"APP_UPDATE_FORM_SUBTITLE_2": "Assurez-vous que vous avez lu les notes de version de l'application et que vous avez sauvegardé vos données d'application.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Entrez le code de votre application d'authentification",
"AUTH_TOTP_SUBMIT": "Confirmer",
"AUTH_TOTP_TITLE": "Authentification à deux facteurs",
"COMMON_CLOSE": "Fermer",
"DASHBOARD_CPU_SUBTITLE": "Désinstallez des applications pour réduire la charge",
"DASHBOARD_CPU_TITLE": "Utilisation du CPU",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Utilisé sur {total} Go",
"DASHBOARD_DISK_SPACE_TITLE": "Espace disque",
"DASHBOARD_MEMORY_TITLE": "Mémoire utilisée",
"DASHBOARD_TITLE": "Tableau de bord",
"DASHBOARD_IP_WARNING_TITLE": "Configuration non sécurisée",
"DASHBOARD_IP_WARNING": "Attention, vous pourriez courir un risque ! Il semble que vous accédez à votre instance via une adresse IP publique. Cela rend votre tableau de bord et toutes les applications que vous installez vulnérables aux attaquants",
"GUEST_DASHBOARD": "Tableau de bord invité",
"GUEST_DASHBOARD_NO_APPS": "Aucune app à afficher",
"GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Demandez à votre administrateur d'ajouter des applications au tableau de bord invité ou connectez-vous pour voir vos applications.",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Beállítások",
"APP_ACTION_START": "Indít",
"APP_ACTION_STOP": "Megállít",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Frissítés",
"APP_CATEGORY_AI": "AI",
"APP_CATEGORY_AUTOMATION": "Automatizálás",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Nem sikerült alaphelyzetbe állítani a(z) {id} alkalmazást. További részletekért tekintse meg a naplókat",
"APP_ERROR_APP_FAILED_TO_START": "Nem sikerült elindítani az {id} alkalmazást. További részletekért tekintse meg a naplókat",
"APP_ERROR_APP_FAILED_TO_STOP": "Nem sikerült leállítani az {id} alkalmazást. További részletekért tekintse meg a naplókat",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Nem sikerült eltávolítani az {id} alkalmazást. További részletekért tekintse meg a naplókat",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Nem sikerült frissíteni az {id} alkalmazást. További részletekért tekintse meg a naplókat",
"APP_ERROR_APP_FORCE_EXPOSED": "Az {id} alkalmazás csak nyilvános domainnel működik",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} meg kell egyeznie a mintával {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} megadása kötelező",
"APP_INSTALL_FORM_ERROR_URL": "{label} érvényes URL címnek kell lennie",
"APP_INSTALL_FORM_EXPOSE_APP": "Tegye ki az alkalmazást",
"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": "Alkalmazás alaphelyzetre állítása",
"APP_INSTALL_FORM_SUBMIT_INSTALL": "Telepítés",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Frissítés",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Indítás",
"APP_STATUS_STOPPED": "Megállítva",
"APP_STATUS_STOPPING": "Leállítás",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Eltávolítás",
"APP_STATUS_UPDATING": "Frissítés",
"APP_STOP_FORM_SUBMIT": "Megállít",
"APP_STOP_FORM_SUBTITLE": "Minden adat megmarad",
"APP_STOP_FORM_TITLE": "Megállít {name} ?",
"APP_STOP_SUCCESS": "{id} Alkalmazás sikeresen leállítva",
"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": "Kategória választása",
"APP_STORE_NO_RESULTS": "Nem található alkalmazás",
"APP_STORE_NO_RESULTS_SUBTITLE": "Próbálja meg finomítani a keresést",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Biztos benne? Ezt a műveletet nem lehet visszavonni.",
"APP_UNINSTALL_SUCCESS": "{id} Alkalmazás sikeresen eltávolítva",
"APP_UPDATE_CONFIG_SUCCESS": "Az alkalmazás konfigurációja sikeresen frissült. Indítsa újra az alkalmazást a módosítások alkalmazásához",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Frissítés",
"APP_UPDATE_FORM_SUBTITLE_1": "Frissítse az alkalmazást a legújabb verzióra:",
"APP_UPDATE_FORM_SUBTITLE_2": "Ez visszaállítja az egyéni konfigurációt (pl. változások a docker-compose.yml-ben)",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Írja be a kódot a hitelesítő alkalmazásból",
"AUTH_TOTP_SUBMIT": "Megerősít",
"AUTH_TOTP_TITLE": "Kétlépcsős hitelesítés",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Távolítson el alkalmazásokat a terhelés csökkentése érdekében",
"DASHBOARD_CPU_TITLE": "CPU kihasználtság",
"DASHBOARD_DISK_SPACE_SUBTITLE": "A {total} GB-ból használható",
"DASHBOARD_DISK_SPACE_TITLE": "Lemezterület",
"DASHBOARD_MEMORY_TITLE": "Memória használat",
"DASHBOARD_TITLE": "Irányítópult",
"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",
"GUEST_DASHBOARD": "Vendég műszerfal",
"GUEST_DASHBOARD_NO_APPS": "Nincs megjeleníthető alkalmazás",
"GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Kérje meg a rendszergazdát, hogy adjon hozzá alkalmazásokat a vendég műszerfalhoz, vagy jelentkezzen be az alkalmazások megtekintéséhez.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Impostazioni",
"APP_ACTION_START": "Avvia",
"APP_ACTION_STOP": "Arresta",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Aggiorna",
"APP_CATEGORY_AI": "AI",
"APP_CATEGORY_AUTOMATION": "Automazione",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_START": "Non possibile avviare l'app {id}, guarda i log per maggiori dettagli",
"APP_ERROR_APP_FAILED_TO_STOP": "Non possibile arrestare l'app {id}, guarda i log per maggiori dettagli",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Non possibile disinstallare l'app {id}, guarda i log per maggiori dettagli",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Non possibile aggiornare l'app {id}, guarda i log per maggiori dettagli",
"APP_ERROR_APP_FORCE_EXPOSED": "L'app {id} può funzionare solo con un dominio esposto",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} deve rispettare il pattern {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} è obbligatorio",
"APP_INSTALL_FORM_ERROR_URL": "{label} deve essere un URL valido",
"APP_INSTALL_FORM_EXPOSE_APP": "Esponi app",
"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": "Installa",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Aggiorna",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "In avvio",
"APP_STATUS_STOPPED": "Arrestato",
"APP_STATUS_STOPPING": "In arresto",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "In disinstallazione",
"APP_STATUS_UPDATING": "In aggiornamento",
"APP_STOP_FORM_SUBMIT": "Arresta",
"APP_STOP_FORM_SUBTITLE": "Tutti i dati verranno mantenuti",
"APP_STOP_FORM_TITLE": "Arrestare {name} ?",
"APP_STOP_SUCCESS": "App {id} arrestata con successo",
"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": "Seleziona una categoria",
"APP_STORE_NO_RESULTS": "Nessuna app trovata",
"APP_STORE_NO_RESULTS_SUBTITLE": "Prova a ottimizare la tua ricerca",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Sei sicuro? Questa azione è irreversibile.",
"APP_UNINSTALL_SUCCESS": "App {id} disinstallata con successo",
"APP_UPDATE_CONFIG_SUCCESS": "App configurata con successo. Riavvia l'app per applicare le modifiche",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Aggiorna",
"APP_UPDATE_FORM_SUBTITLE_1": "Aggiorna app all'ultima versione :",
"APP_UPDATE_FORM_SUBTITLE_2": "Questo resetterà la tua configurazione personalizzata (es. le modifiche in docker-compose.yml).",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Inserisci il codice fornito dalla tua app di autenticazione",
"AUTH_TOTP_SUBMIT": "Verifica",
"AUTH_TOTP_TITLE": "Autenticazione a due fattori",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Disinstalla alcune app per ridurre il carico sulla CPU",
"DASHBOARD_CPU_TITLE": "Carico CPU",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Utilizzati su un totale di {total} GB",
"DASHBOARD_DISK_SPACE_TITLE": "Spazio su disco",
"DASHBOARD_MEMORY_TITLE": "Memoria usata",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "設定",
"APP_ACTION_START": "開始",
"APP_ACTION_STOP": "停止",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "更新",
"APP_CATEGORY_AI": "AI",
"APP_CATEGORY_AUTOMATION": "自動化",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_START": "アプリ {id} の起動に失敗しました。詳細はログを参照してください。",
"APP_ERROR_APP_FAILED_TO_STOP": "アプリ {id} の停止に失敗しました。詳細はログを参照してください。",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "アプリ {id} のアンインストールに失敗しました。詳細はログを参照してください。",
"APP_ERROR_APP_FAILED_TO_UPDATE": "アプリ {id} の更新に失敗しました。詳細はログを参照してください。",
"APP_ERROR_APP_FORCE_EXPOSED": "アプリ {id} は、公開されたドメインでのみ動作します",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} はパターン {pattern} に一致する必要があります",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} が必要です",
"APP_INSTALL_FORM_ERROR_URL": "{label} は有効な URL でなければなりません",
"APP_INSTALL_FORM_EXPOSE_APP": "アプリを公開する",
"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": "インストール",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "更新",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "開始中",
"APP_STATUS_STOPPED": "停止中",
"APP_STATUS_STOPPING": "停止中",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "アンインストール中",
"APP_STATUS_UPDATING": "アップデート中",
"APP_STOP_FORM_SUBMIT": "停止",
"APP_STOP_FORM_SUBTITLE": "すべてのデータが保持されます",
"APP_STOP_FORM_TITLE": "{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": "カテゴリーを選択する",
"APP_STORE_NO_RESULTS": "アプリが見つかりません",
"APP_STORE_NO_RESULTS_SUBTITLE": "検索条件を絞り込んでください",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "本当に実行しますか?この作業は元に戻せません。",
"APP_UNINSTALL_SUCCESS": "App {id} uninstalled successfully",
"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_FORM_SUBMIT": "更新",
"APP_UPDATE_FORM_SUBTITLE_1": "アプリを最新のバージョンに更新:",
"APP_UPDATE_FORM_SUBTITLE_2": "カスタム構成をリセットします (例: docker-compose.yml の変更)",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "認証アプリからコードを入力してください",
"AUTH_TOTP_SUBMIT": "確認",
"AUTH_TOTP_TITLE": "2 段階認証",
"COMMON_CLOSE": "Close",
"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": "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",
"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.",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Instellingen",
"APP_ACTION_START": "Start",
"APP_ACTION_STOP": "Stop",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Bijwerken",
"APP_CATEGORY_AI": "AI",
"APP_CATEGORY_AUTOMATION": "Automatisering",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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": "Kies een categorie",
"APP_STORE_NO_RESULTS": "Geen app gevonden",
"APP_STORE_NO_RESULTS_SUBTITLE": "Probeer uw zoekopdracht te verfijnen",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Weet u het zeker? Deze actie kan niet ongedaan worden gemaakt.",
"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": "Bijwerken",
"APP_UPDATE_FORM_SUBTITLE_1": "Bijwerken naar de laatste versie:",
"APP_UPDATE_FORM_SUBTITLE_2": "Make sure you've read the release notes of the app and you've backed up your app data.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Ustawienia",
"APP_ACTION_START": "Start",
"APP_ACTION_STOP": "Zatrzymaj",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Aktualizuj",
"APP_CATEGORY_AI": "SI",
"APP_CATEGORY_AUTOMATION": "Automatyzacja",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_START": "Nie udało się uruchomić aplikacji {id}, zobacz logi po więcej informacji",
"APP_ERROR_APP_FAILED_TO_STOP": "Nie udało się zatrzymać aplikacji {id}, zobacz logi po więcej informacji",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Nie udało się odinstalować aplikacji {id}, zobacz logi po więcej informacji",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Nie udało się zaktualizować aplikacji {id}, zobacz logi po więcej informacji",
"APP_ERROR_APP_FORCE_EXPOSED": "Aplikacja {id} działa tylko z udostępnioną domeną",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} musi być zgodne ze wzorem {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} jest wymagane",
"APP_INSTALL_FORM_ERROR_URL": "{label} musi być prawidłowym adresem URL",
"APP_INSTALL_FORM_EXPOSE_APP": "Udostępnij aplikację",
"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": "Zainstaluj",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Aktualizuj",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Rozpoczynanie",
"APP_STATUS_STOPPED": "Zatrzymane",
"APP_STATUS_STOPPING": "Zatrzymywanie",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Odinstalowywanie",
"APP_STATUS_UPDATING": "Aktualizowanie",
"APP_STOP_FORM_SUBMIT": "Zatrzymaj",
"APP_STOP_FORM_SUBTITLE": "Wszystkie dane zostaną zachowane",
"APP_STOP_FORM_TITLE": "Zatrzymać {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": "Wybierz kategorię",
"APP_STORE_NO_RESULTS": "No app found",
"APP_STORE_NO_RESULTS_SUBTITLE": "Try to refine your search",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Jesteś pewien? Tej akcji nie można cofnąć.",
"APP_UNINSTALL_SUCCESS": "App {id} uninstalled successfully",
"APP_UPDATE_CONFIG_SUCCESS": "Konfiguracja aplikacji została zaktualizowana. Uruchom aplikację ponownie, aby zastosować zmiany",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Aktualizuj",
"APP_UPDATE_FORM_SUBTITLE_1": "Zaktualizuj aplikację do najnowszej wersji:",
"APP_UPDATE_FORM_SUBTITLE_2": "Spowoduje to zresetowanie twojej konfiguracji (np. zmiany w docker-compose.yml)",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Wprowadź kod z aplikacji uwierzytelniającej",
"AUTH_TOTP_SUBMIT": "Potwierdź",
"AUTH_TOTP_TITLE": "Weryfikacja dwuetapowa",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Odinstaluj aplikacje aby zmniejszyć obciążenie",
"DASHBOARD_CPU_TITLE": "Obciążenie CPU",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Użyto z {total} GB",
"DASHBOARD_DISK_SPACE_TITLE": "Miejsce na dysku",
"DASHBOARD_MEMORY_TITLE": "Użyta pamięć",
"DASHBOARD_TITLE": "Panel nawigacyjny",
"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",
"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.",

View File

@ -7,13 +7,14 @@
"APP_ACTION_SETTINGS": "Configurações",
"APP_ACTION_START": "Iniciar",
"APP_ACTION_STOP": "Parar",
"APP_ACTION_RESTART": "Reiniciar",
"APP_ACTION_UPDATE": "Atualizar",
"APP_CATEGORY_AI": "IA",
"APP_CATEGORY_AUTOMATION": "Automação",
"APP_CATEGORY_BOOKS": "Livros",
"APP_CATEGORY_DATA": "Dados",
"APP_CATEGORY_DEVELOPMENT": "Desenvolvimento",
"APP_CATEGORY_FEATURED": "Destaque",
"APP_CATEGORY_FEATURED": "Em Destaque",
"APP_CATEGORY_FINANCE": "Finanças",
"APP_CATEGORY_GAMING": "Jogos",
"APP_CATEGORY_MEDIA": "Mídia",
@ -26,9 +27,9 @@
"APP_DETAILS_AUTHOR": "Autor",
"APP_DETAILS_BASE_INFO": "Informações",
"APP_DETAILS_CATEGORIES_TITLE": "Categorias",
"APP_DETAILS_CHOOSE_OPEN_METHOD": "Choose open method",
"APP_DETAILS_CHOOSE_OPEN_METHOD": "Escolha o método de abertura",
"APP_DETAILS_DEPRECATED_ALERT_SUBTITLE": "Uma alteração significativa neste aplicativo impede que ele seja atualizado automaticamente. Você ainda pode usar esta versão e atualizá-la manualmente, mas é recomendado mudar para uma versão mais recente e migrar seus dados. Você pode encontrar uma versão atualizada na App Store com o mesmo nome.",
"APP_DETAILS_DEPRECATED_ALERT_TITLE": "Este aplicativo está obsoleto",
"APP_DETAILS_DEPRECATED_ALERT_TITLE": "Este aplicativo foi descontinuado pelo desenvolvedor ",
"APP_DETAILS_DESCRIPTION": "Descrição",
"APP_DETAILS_LINK": "Link",
"APP_DETAILS_PORT": "Porta",
@ -38,34 +39,39 @@
"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": "Falha ao redefinir o aplicativo {id}. Consulte os logs para obter mais detalhes",
"APP_ERROR_APP_FAILED_TO_RESET": "Falha ao redefinir os dados do aplicativo {id}. ",
"APP_ERROR_APP_FAILED_TO_START": "Falha ao iniciar o aplicativo {id}. Consulte os logs para obter mais detalhes",
"APP_ERROR_APP_FAILED_TO_STOP": "Falha ao parar o aplicativo {id}. Consulte os logs para mais detalhes",
"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 mais detalhes",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Falha ao atualizar do aplicativo {id}. Consulte os logs para mais detalhes",
"APP_ERROR_APP_FORCE_EXPOSED": "O App {id} funciona apenas com domínio exposto",
"APP_ERROR_APP_FORCE_EXPOSED": "O Aplicativo {id} funciona apenas com um domínio público",
"APP_ERROR_APP_NOT_EXPOSABLE": "O aplicativo {id} não pode ser exposto",
"APP_ERROR_APP_NOT_FOUND": "Aplicativo {id} não encontrado",
"APP_ERROR_DOMAIN_ALREADY_IN_USE": "O domínio {domain} já está em uso pelo aplicativo {id}",
"APP_ERROR_DOMAIN_NOT_VALID": "O domínio {domain} não é um domínio válido",
"APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP": "Domínio necessário se o app estiver exposto",
"APP_ERROR_INVALID_CONFIG": "App {id} possui um arquivo config.json inválido",
"APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP": "O Domínio é necessário se o aplicativo estiver exposto",
"APP_ERROR_INVALID_CONFIG": "O arquivo de configuração do aplicativo {id} contém erros de sintaxe ",
"APP_INSTALL_FORM_CHOOSE_OPTION": "Selecione uma opção...",
"APP_INSTALL_FORM_DISPLAY_ON_GUEST_DASHBOARD": "Exibir no painel do convidado",
"APP_INSTALL_FORM_DOMAIN_NAME": "Nome de domínio",
"APP_INSTALL_FORM_DOMAIN_NAME": "Nome do domínio",
"APP_INSTALL_FORM_DOMAIN_NAME_HINT": "Certifique-se de que este domínio exato contenha um registro A apontando para o seu 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",
"APP_INSTALL_FORM_ERROR_BETWEEN_LENGTH": "{label} Deve ter entre {min} e {max} caracteres",
"APP_INSTALL_FORM_ERROR_FQDN": "{label} Deve ser um domínio válido",
"APP_INSTALL_FORM_ERROR_FQDNIP": "{label} Deve ser um domínio ou endereço IP válido",
"APP_INSTALL_FORM_ERROR_INVALID_EMAIL": "Deve ser um endereço de e-mail válido",
"APP_INSTALL_FORM_ERROR_IP": "O endereço IP não é válido",
"APP_INSTALL_FORM_ERROR_MAX_LENGTH": "{label} Deve ter entre {min} e {max} caracteres",
"APP_INSTALL_FORM_ERROR_MIN_LENGTH": "{label} deve ter pelo menos {min} caracteres",
"APP_INSTALL_FORM_ERROR_NUMBER": "{label} deve ser um número",
"APP_INSTALL_FORM_ERROR_REGEX": "{label} deve corresponder ao padrão {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} É obrigatório",
"APP_INSTALL_FORM_ERROR_URL": "{label} Deve ser um endereço válido",
"APP_INSTALL_FORM_EXPOSE_APP": "Tornar aplicação pública",
"APP_INSTALL_FORM_OPEN_PORT": "Abrir porta",
"APP_INSTALL_FORM_OPEN_PORT_HINT": "Abrir uma porta no host? Este aplicativo será acessível em {internalIp}:{port}. (Mais fácil, mas menos seguro)",
"APP_INSTALL_FORM_EXPOSE_LOCAL": "Expor o aplicativo na rede local",
"APP_INSTALL_FORM_EXPOSE_LOCAL_HINT": "Expor o aplicativo na rede local? Este aplicativo estará acessível em {appId}.{domain}. (Visite a página de configurações para configurar seu domínio local)",
"APP_INSTALL_FORM_RESET": "Redefinir o aplicativo",
"APP_INSTALL_FORM_SUBMIT_INSTALL": "Instalar",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Atualizar",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Iniciando",
"APP_STATUS_STOPPED": "Parado",
"APP_STATUS_STOPPING": "Parando",
"APP_STATUS_RESTARTING": "Reiniciando",
"APP_STATUS_UNINSTALLING": "Desinstalando",
"APP_STATUS_UPDATING": "Atualizando",
"APP_STOP_FORM_SUBMIT": "Parar",
"APP_STOP_FORM_SUBTITLE": "Todos os dados serão removidos",
"APP_STOP_FORM_TITLE": "Parar {name}?",
"APP_STOP_SUCCESS": "O aplicativo {id} foi interrompido com sucesso",
"APP_RESTART_FORM_SUBMIT": "Reiniciar",
"APP_RESTART_FORM_SUBTITLE": "Todos os dados serão apagados",
"APP_RESTART_FORM_TITLE": "Reiniciar {name} ?",
"APP_RESTART_SUCCESS": "O aplicativo {id} foi reiniciado com sucesso",
"APP_STORE_CATEGORY_PLACEHOLDER": "Selecione uma categoria",
"APP_STORE_NO_RESULTS": "Nenhum aplicativo encontrado",
"APP_STORE_NO_RESULTS_SUBTITLE": "Tente aperfeiçoar a sua pesquisa",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Tem certeza? Esta ação não pode ser desfeita.",
"APP_UNINSTALL_SUCCESS": "App {id} desinstalado com sucesso",
"APP_UPDATE_CONFIG_SUCCESS": "Configuração do aplicativo atualizada com sucesso. Reinicie o aplicativo para aplicar as alterações",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Atualizar",
"APP_UPDATE_FORM_SUBTITLE_1": "Atualize o aplicativo para a última versão :",
"APP_UPDATE_FORM_SUBTITLE_2": "Certifique-se de ter lido as notas de lançamento do app e de ter feito backup dos dados do seu aplicativo.",
@ -145,43 +157,46 @@
"AUTH_REGISTER_TITLE": "Crie sua conta",
"AUTH_RESET_PASSWORD_BACK_TO_LOGIN": "Voltar para o login",
"AUTH_RESET_PASSWORD_CANCEL": "Cancelar solicitação de alteração de senha",
"AUTH_RESET_PASSWORD_INSTRUCTIONS": "Run this command on your server and then refresh this page",
"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",
"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",
"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 succesfully",
"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 succesfully",
"AUTH_RESET_PASSWORD_INSTRUCTIONS": "Execute este comando em seu servidor e depois atualize a página",
"AUTH_RESET_PASSWORD_SUBMIT": "Redefinir senha",
"AUTH_RESET_PASSWORD_SUCCESS": "Sua senha foi atualizada com sucesso, Agora você pode utilizar sua nova senha e seu e-mail {email}",
"AUTH_RESET_PASSWORD_SUCCESS_TITLE": "Senha atualizada",
"AUTH_RESET_PASSWORD_TITLE": "Redefinir minha senha",
"AUTH_TOTP_INSTRUCTIONS": "Insira o código de autenticação",
"AUTH_TOTP_SUBMIT": "Aceitar",
"AUTH_TOTP_TITLE": "Autenticação de 3 fatores",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Desinstale aplicativos para reduzir a carga",
"DASHBOARD_CPU_TITLE": "Utilização do Processador",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Espaço disponível {total} GB",
"DASHBOARD_DISK_SPACE_TITLE": "Espaço total em disco",
"DASHBOARD_MEMORY_TITLE": "Memória utilizada",
"DASHBOARD_TITLE": "Painel de controle",
"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",
"GUEST_DASHBOARD": "Painel de controle para visitantes",
"GUEST_DASHBOARD_NO_APPS": "Nenhum aplicativo instalado",
"GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Peça ao seu administrador para adicionar aplicativos ao painel de visitantes ou fazer login para ver seus aplicativos.",
"HEADER_APPS": "Meus aplicativos",
"HEADER_APP_STORE": "Loja de aplicativos",
"HEADER_DARK_MODE": "Tema escuro",
"HEADER_DASHBOARD": "Painel de controle",
"HEADER_LIGHT_MODE": "Tema claro",
"HEADER_LOGIN": "Entrar",
"HEADER_LOGOUT": "Finalizar sessão",
"HEADER_SETTINGS": "Configurações",
"HEADER_SOURCE_CODE": "Código-fonte",
"HEADER_SPONSOR": "Patrocinador",
"HEADER_UPDATE_AVAILABLE": "Atualização disponível",
"INTERNAL_SERVER_ERROR": "Error interno do servidor",
"LINKS_ADD_SUBMIT": "Enviar",
"LINKS_ADD_SUBTITLE": "Adicionar um endereço personalizado ao painel de controle",
"LINKS_ADD_SUCCESS": "Endereço adicionado com sucesso",
"LINKS_ADD_TITLE": "Adicionar endereço personalizado",
"LINKS_DELETE_CONTEXT_MENU": "Excluir",
"LINKS_DELETE_SUBMIT": "Excluir",
"LINKS_DELETE_SUBTITLE": "Você realmente quer remover esse endereço personalizado?",
"LINKS_DELETE_SUCCESS": "Endereço removido com sucesso",
"LINKS_DELETE_TITLE": "Excluir link externo",
"LINKS_EDIT_CONTEXT_MENU": "Editar",
"LINKS_EDIT_SUBMIT": "Salvar",
@ -200,7 +215,7 @@
"MY_APPS_UPDATE_ALL_FORM_SUBMIT": "Atualizar tudo",
"MY_APPS_UPDATE_ALL_FORM_SUBTITLE_1": "Você deseja atualizar todos os seus aplicativos para a versão mais recente?",
"MY_APPS_UPDATE_ALL_FORM_SUBTITLE_2": "Isso atualizará todos os seus aplicativos para a versão mais recente. Certifique-se de ter lido as notas de lançamento dos aplicativos e ter feito backup dos dados do seu aplicativo.",
"MY_APPS_UPDATE_ALL_FORM_TITLE": "Atualize todos os apps",
"MY_APPS_UPDATE_ALL_FORM_TITLE": "Atualizar todos os aplicativos",
"MY_APPS_UPDATE_ALL_IN_PROGRESS": "Atualizando todos os aplicativos",
"MY_APPS_UPDATE_AVAILABLE": "Atualização Disponível",
"RUNTIPI": "Runtipi",
@ -220,66 +235,66 @@
"SETTINGS_GENERAL_ALLOW_AUTO_THEMES_HINT": "Seja surpreendido com temas alterados automaticamente, com base na época do ano.",
"SETTINGS_GENERAL_ALLOW_ERROR_MONITORING": "Permitir monitoramento de erro anônimo",
"SETTINGS_GENERAL_ALLOW_ERROR_MONITORING_HINT": "O monitoramento de erros é usado para rastrear erros e melhorar o Tipi. Mantenha esta opção ativada para nos ajudar a melhorar o 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_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"
"SETTINGS_GENERAL_APPS_REPO": "URL do repositório de aplicativos",
"SETTINGS_GENERAL_APPS_REPO_HINT": "URL para o repositório de aplicativos.",
"SETTINGS_GENERAL_DNS_IP": "DNS personalizado",
"SETTINGS_GENERAL_DOMAIN_NAME": "Nome do domínio",
"SETTINGS_GENERAL_DOMAIN_NAME_HINT": "Certifique-se de que este domínio exato contenha um registro A apontando para o seu IP.",
"SETTINGS_GENERAL_DOWNLOAD_CERTIFICATE": "Baixar certificados",
"SETTINGS_GENERAL_GUEST_DASHBOARD": "Ativar painel de controle para visitantes",
"SETTINGS_GENERAL_GUEST_DASHBOARD_HINT": "Isso permitirá que usuários não autenticados vejam um painel limitado e acessem facilmente os aplicativos em execução na sua instância.",
"SETTINGS_GENERAL_INTERNAL_IP": "Endereço de IP local",
"SETTINGS_GENERAL_INTERNAL_IP_HINT": "Endereço IP em que seu servidor está executando.",
"SETTINGS_GENERAL_INVALID_DOMAIN": "Domínio inválido",
"SETTINGS_GENERAL_INVALID_IP": "Endereço IP inválido",
"SETTINGS_GENERAL_INVALID_URL": "URL inválida",
"SETTINGS_GENERAL_LANGUAGE": "Idiomas",
"SETTINGS_GENERAL_LANGUAGE_HELP_TRANSLATE": "Ajude a traduzir o Tipi",
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Domínio local",
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Nome de domínio usado para acessar aplicativos em sua rede local. Seus aplicativos estarão acessíveis em app-name.local-domain.",
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Configurações atualizadas. Reinicie sua instância para aplicar as novas configurações.",
"SETTINGS_GENERAL_STORAGE_PATH": "Diretório do Armazenamento",
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Caminho para o diretório de armazenamento. Certifique-se de que esse caminho exista.",
"SETTINGS_GENERAL_SUBMIT": "Salvar configurações",
"SETTINGS_GENERAL_SUBTITLE": "Isto irá atualizar o seu arquivo settings.json. Certifique-se de saber o que está fazendo antes de atualizar esses valores.",
"SETTINGS_GENERAL_TAB_TITLE": "Configurações",
"SETTINGS_GENERAL_TITLE": "Todas as configurações",
"SETTINGS_GENERAL_USER_SETTINGS": "Configurações do usuário",
"SETTINGS_SECURITY_2FA_DISABLE_SUCCESS": "Autenticação de 2 fatores, desativada",
"SETTINGS_SECURITY_2FA_ENABLE_SUCCESS": "Autenticação de 2 fatores, ativada",
"SETTINGS_SECURITY_2FA_SUBTITLE": "A autenticação de dois fatores (2FA) adiciona uma camada adicional de segurança à sua conta.",
"SETTINGS_SECURITY_2FA_SUBTITLE_2": "Quando ativado, você será solicitado a inserir um código do seu aplicativo autenticador ao fazer login.",
"SETTINGS_SECURITY_2FA_TITLE": "Autenticação de duas etapas",
"SETTINGS_SECURITY_CHANGE_PASSWORD_SUBTITLE": "Alterar sua senha irá desconectá-lo de todos os dispositivos.",
"SETTINGS_SECURITY_CHANGE_PASSWORD_TITLE": "Alterar senha",
"SETTINGS_SECURITY_CHANGE_USERNAME_FORM_INVALID_USERNAME": "Insira um email válido",
"SETTINGS_SECURITY_CHANGE_USERNAME_FORM_NEW_USERNAME": "Novo nome de usuário",
"SETTINGS_SECURITY_CHANGE_USERNAME_FORM_PASSWORD": "Senha",
"SETTINGS_SECURITY_CHANGE_USERNAME_FORM_PASSWORD_NEEDED_HINT": "Sua senha é necessária para alterar seu nome de usuário.",
"SETTINGS_SECURITY_CHANGE_USERNAME_FORM_SUBMIT": "Alterar nome de usuário",
"SETTINGS_SECURITY_CHANGE_USERNAME_SUBTITLE": "Alterar seu nome de usuário irá desconectá-lo de todos os dispositivos.",
"SETTINGS_SECURITY_CHANGE_USERNAME_SUCCESS": "Nome de usuário alterado com sucesso",
"SETTINGS_SECURITY_CHANGE_USERNAME_TITLE": "Atualizar nome de usuário",
"SETTINGS_SECURITY_DISABLE_2FA": "Desativar autenticação de dois fatores",
"SETTINGS_SECURITY_ENABLE_2FA": "Ativar autenticação de dois fatores",
"SETTINGS_SECURITY_ENTER_2FA_CODE": "Insira o código de autenticação de 6 dígitos do seu aplicativo de autenticação",
"SETTINGS_SECURITY_ENTER_KEY_MANUALLY": "Ou digite esta chave manualmente.",
"SETTINGS_SECURITY_FORM_CHANGE_PASSWORD_SUBMIT": "Alterar senha",
"SETTINGS_SECURITY_FORM_CONFIRM_PASSWORD": "Confirmar nova senha",
"SETTINGS_SECURITY_FORM_CURRENT_PASSWORD": "Senha atual",
"SETTINGS_SECURITY_FORM_NEW_PASSWORD": "Nova senha",
"SETTINGS_SECURITY_FORM_PASSWORD": "Senha",
"SETTINGS_SECURITY_FORM_PASSWORD_LENGTH": "A senha deve ter pelo menos 8 caracteres",
"SETTINGS_SECURITY_FORM_PASSWORD_MATCH": "As senhas não coincidem",
"SETTINGS_SECURITY_PASSWORD_CHANGE_SUCCESS": "Senha atualizada com sucesso",
"SETTINGS_SECURITY_PASSWORD_NEEDED": "E necessário uma senha",
"SETTINGS_SECURITY_PASSWORD_NEEDED_HINT": "Sua senha é necessária para alterar as configurações de autenticação em duas etapas.",
"SETTINGS_SECURITY_SCAN_QR_CODE": "Escaneie este código de barras com\nseu aplicativo autenticador.",
"SETTINGS_SECURITY_TAB_TITLE": "Segurança",
"SETTINGS_TITLE": "Configurações",
"SYSTEM_ERROR_COULD_NOT_GET_LATEST_VERSION": "Error ao baixar atualização",
"SYSTEM_ERROR_CURRENT_VERSION_IS_LATEST": "A versão atual já está atualizada",
"SYSTEM_ERROR_DEMO_MODE_LIMIT": "Apenas 6 aplicativos podem ser instalados no modo de demonstração. Por favor, desinstale outro aplicativo para instalar um novo.",
"SYSTEM_ERROR_MAJOR_VERSION_UPDATE": "A versão principal foi alterada. Por favor, atualize manualmente (instruções nas notas de lançamento)",
"SYSTEM_ERROR_YOU_MUST_BE_LOGGED_IN": "Error, você precisa está logado em sua conta"
}

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Configurações",
"APP_ACTION_START": "Começar",
"APP_ACTION_STOP": "Interromper",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Atualizar",
"APP_CATEGORY_AI": "AI",
"APP_CATEGORY_AUTOMATION": "Automação",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"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_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",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} deve corresponder ao padrão {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} é obrigatório",
"APP_INSTALL_FORM_ERROR_URL": "{label} deve ser um domínio válido",
"APP_INSTALL_FORM_EXPOSE_APP": "Expor aplicativo",
"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": "Instalar",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Atualizar",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "A iniciar",
"APP_STATUS_STOPPED": "Parado",
"APP_STATUS_STOPPING": "A parar",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Desinstalando",
"APP_STATUS_UPDATING": "Atualizando",
"APP_STOP_FORM_SUBMIT": "Interromper",
"APP_STOP_FORM_SUBTITLE": "Todos os dados serão retidos",
"APP_STOP_FORM_TITLE": "Interromper {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": "Selecione uma categoria",
"APP_STORE_NO_RESULTS": "Nenhum aplicativo encontrado",
"APP_STORE_NO_RESULTS_SUBTITLE": "Tente aperfeiçoar a sua pesquisa",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Tem a certeza? Esta ação não pode ser revertida.",
"APP_UNINSTALL_SUCCESS": "App {id} uninstalled successfully",
"APP_UPDATE_CONFIG_SUCCESS": "Configuração do aplicativo atualizada com sucesso. Reinicie o aplicativo para aplicar as mudanças",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Atualizar",
"APP_UPDATE_FORM_SUBTITLE_1": "Atualizar o aplicativo para a última versão :",
"APP_UPDATE_FORM_SUBTITLE_2": "Make sure you've read the release notes of the app and you've backed up your app data.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Introduza o código da sua aplicação de autenticação",
"AUTH_TOTP_SUBMIT": "Confirmar",
"AUTH_TOTP_TITLE": "Autenticação de dois factores",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Desinstalar aplicativos para reduzir a carga",
"DASHBOARD_CPU_TITLE": "Carga da CPU",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Usado de {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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Setări",
"APP_ACTION_START": "Pornire",
"APP_ACTION_STOP": "Oprire",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Actualizare",
"APP_CATEGORY_AI": "IA",
"APP_CATEGORY_AUTOMATION": "Automatizare",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_START": "Nu s-a putut porni aplicația {id}, vezi jurnalele pentru mai multe detalii",
"APP_ERROR_APP_FAILED_TO_STOP": "Nu s-a reușit oprirea aplicației {id}, vezi jurnalele pentru mai multe detalii",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Dezinstalarea aplicației {id} a eșuat, a se vedea jurnalele pentru mai multe detalii",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Nu s-a reușit actualizarea aplicației {id}, vezi jurnalele pentru mai multe detalii",
"APP_ERROR_APP_FORCE_EXPOSED": "Aplicația {id} funcționează numai cu domeniul expus",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} trebuie să corespundă modelului {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} este necesar",
"APP_INSTALL_FORM_ERROR_URL": "{label} trebuie să fie un URL valid",
"APP_INSTALL_FORM_EXPOSE_APP": "Expune aplicația",
"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": "Instalare",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Actualizare",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Pornire",
"APP_STATUS_STOPPED": "Oprit",
"APP_STATUS_STOPPING": "Se oprește",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Se dezinstalează",
"APP_STATUS_UPDATING": "Actualizare",
"APP_STOP_FORM_SUBMIT": "Oprire",
"APP_STOP_FORM_SUBTITLE": "Toate datele vor fi păstrate",
"APP_STOP_FORM_TITLE": "Opriți {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": "Selectează o categorie",
"APP_STORE_NO_RESULTS": "Nicio aplicație găsită",
"APP_STORE_NO_RESULTS_SUBTITLE": "Încercați să perfecționați căutarea",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Sunteți sigur? Această acțiune nu poate fi anulată.",
"APP_UNINSTALL_SUCCESS": "App {id} uninstalled successfully",
"APP_UPDATE_CONFIG_SUCCESS": "Configurarea aplicației a fost actualizată. Reporniți aplicația pentru a aplica modificările",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Actualizare",
"APP_UPDATE_FORM_SUBTITLE_1": "Actualizează aplicația la cea mai recentă versiune:",
"APP_UPDATE_FORM_SUBTITLE_2": "Aceasta va reseta configuraţia personalizată (de ex. modificări în docker-compose.yml)",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Introdu codul din aplicația de autentificare",
"AUTH_TOTP_SUBMIT": "Confirma",
"AUTH_TOTP_TITLE": "Autentificare doi factori",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Dezinstalează aplicații pentru a reduce încărcarea",
"DASHBOARD_CPU_TITLE": "Încărcătura procesorului",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Utilizat din {total} GB",
"DASHBOARD_DISK_SPACE_TITLE": "Spatiu pe disc",
"DASHBOARD_MEMORY_TITLE": "Memorie utilizată",
"DASHBOARD_TITLE": "Tablou de bord",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Настройки",
"APP_ACTION_START": "Запустить",
"APP_ACTION_STOP": "Остановить",
"APP_ACTION_RESTART": "Перезапустить",
"APP_ACTION_UPDATE": "Обновить",
"APP_CATEGORY_AI": "ИИ",
"APP_CATEGORY_AUTOMATION": "Автоматизация",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Не удалось сбросить приложение {id}. См. подробности в логах",
"APP_ERROR_APP_FAILED_TO_START": "Не удалось запустить приложение {id}. См. подробности в логах",
"APP_ERROR_APP_FAILED_TO_STOP": "Не удалось остановить приложение {id}. См. подробности в логах",
"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} возможен только по доменному имени",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "Поле {label} должно совпадать с шаблоном {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "Требуется {label}",
"APP_INSTALL_FORM_ERROR_URL": "Поле {label} должно быть корректным URL",
"APP_INSTALL_FORM_EXPOSE_APP": "Открыть доступ к приложению по доменному имени",
"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": "Обновить",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Запускается",
"APP_STATUS_STOPPED": "Остановлено",
"APP_STATUS_STOPPING": "Останавливается",
"APP_STATUS_RESTARTING": "Перезапуск",
"APP_STATUS_UNINSTALLING": "Удаляется",
"APP_STATUS_UPDATING": "Обновляется",
"APP_STOP_FORM_SUBMIT": "Остановить",
"APP_STOP_FORM_SUBTITLE": "Все данные будут сохранены",
"APP_STOP_FORM_TITLE": "Остановить {name}?",
"APP_STOP_SUCCESS": "Приложение {id} успешно остановлено",
"APP_RESTART_FORM_SUBMIT": "Перезапустить",
"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": "Попробуйте уточнить поиск",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Вы уверены? Это действие нельзя отменить.",
"APP_UNINSTALL_SUCCESS": "Приложение {id} успешно удалено",
"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)",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Введите код из приложения для аутентификации",
"AUTH_TOTP_SUBMIT": "Подтвердить",
"AUTH_TOTP_TITLE": "Двухфакторная аутентификация",
"COMMON_CLOSE": "Закрыть",
"DASHBOARD_CPU_SUBTITLE": "Удалите приложения для уменьшения нагрузки",
"DASHBOARD_CPU_TITLE": "Загрузка ЦП",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Используется из {total} ГБ",
"DASHBOARD_DISK_SPACE_TITLE": "Место на диске",
"DASHBOARD_MEMORY_TITLE": "Использовано памяти",
"DASHBOARD_TITLE": "Обзор",
"DASHBOARD_IP_WARNING_TITLE": "Небезопасная конфигурация",
"DASHBOARD_IP_WARNING": "Внимание! Похоже, что вы получаете доступ к своему экземпляру через публичный IP-адрес. Это делает вашу панель и все установленные вами приложения уязвимыми для атакующих",
"GUEST_DASHBOARD": "Гостевая панель",
"GUEST_DASHBOARD_NO_APPS": "Нет приложений",
"GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Попросите администратора добавить приложения на гостевую панель или войдите, чтобы увидеть ваши приложения.",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Inställningar",
"APP_ACTION_START": "Starta",
"APP_ACTION_STOP": "Stoppa",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Uppdatera",
"APP_CATEGORY_AI": "AI",
"APP_CATEGORY_AUTOMATION": "Automatisering",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Det gick inte att återställa appen {id}, se loggar för mer information",
"APP_ERROR_APP_FAILED_TO_START": "Det gick inte att starta app {id}, se loggar för mer information",
"APP_ERROR_APP_FAILED_TO_STOP": "Det gick inte att stoppa app {id}, se loggar för mer information",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Det gick inte att avinstallera app {id}, se loggar för mer information",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Det gick inte att uppdatera app {id}, se loggar för mer information",
"APP_ERROR_APP_FORCE_EXPOSED": "Appen {id} fungerar endast med exponerad domän",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} måste matcha mönstret {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} krävs",
"APP_INSTALL_FORM_ERROR_URL": "{label} måste vara en giltig URL",
"APP_INSTALL_FORM_EXPOSE_APP": "Exponera app",
"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": "Återställ app",
"APP_INSTALL_FORM_SUBMIT_INSTALL": "Installera",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Uppdatera",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Startar",
"APP_STATUS_STOPPED": "Stoppad",
"APP_STATUS_STOPPING": "Stoppar",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Avinstallerar",
"APP_STATUS_UPDATING": "Uppdaterar",
"APP_STOP_FORM_SUBMIT": "Stoppa",
"APP_STOP_FORM_SUBTITLE": "All data sparas",
"APP_STOP_FORM_TITLE": "Stoppa {name}?",
"APP_STOP_SUCCESS": "Appen {id} stoppades",
"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": "Välj en kategori",
"APP_STORE_NO_RESULTS": "Ingen app hittades",
"APP_STORE_NO_RESULTS_SUBTITLE": "Försök att förfina din sökning",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Är du säker? Den här åtgärden kan inte ångras.",
"APP_UNINSTALL_SUCCESS": "Appen {id} avinstallerades",
"APP_UPDATE_CONFIG_SUCCESS": "Appkonfiguration har uppdaterats. Starta om appen för att tillämpa ändringarna",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Uppdatera",
"APP_UPDATE_FORM_SUBTITLE_1": "Uppdatera appen till senaste version :",
"APP_UPDATE_FORM_SUBTITLE_2": "Detta kommer att återställa din anpassade konfiguration (t.ex. ändringar i docker-compose.yml)",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Ange koden från din autentiseringsapp",
"AUTH_TOTP_SUBMIT": "Bekräfta",
"AUTH_TOTP_TITLE": "Tvåfaktorsautentisering",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Avinstallera appar för att minska belastning",
"DASHBOARD_CPU_TITLE": "CPU Belastning",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Används av {total} GB",
"DASHBOARD_DISK_SPACE_TITLE": "Disk utrymme",
"DASHBOARD_MEMORY_TITLE": "Använt minne",
"DASHBOARD_TITLE": "Kontrollpanel",
"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",
"GUEST_DASHBOARD": "Gästkontrollpanelen",
"GUEST_DASHBOARD_NO_APPS": "Inga appar att visa",
"GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Be administratören att lägga till appar i gästkontrollpanelen eller logga in för att se dina appar.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Ayarlar",
"APP_ACTION_START": "Başla",
"APP_ACTION_STOP": "Durdur",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Güncelle",
"APP_CATEGORY_AI": "AI",
"APP_CATEGORY_AUTOMATION": "Otomasyon",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_START": "{id} uygulaması başlatılamadı, daha fazla ayrıntı için günlüklere bakın",
"APP_ERROR_APP_FAILED_TO_STOP": "{id} uygulaması durdurulamadı, daha fazla ayrıntı için günlüklere bakın",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "{id} uygulaması kaldırılamadı, daha fazla ayrıntı için günlüklere bakın",
"APP_ERROR_APP_FAILED_TO_UPDATE": "{id} uygulaması güncellenemedi, daha fazla ayrıntı için günlüklere bakın",
"APP_ERROR_APP_FORCE_EXPOSED": "{id} uygulaması yalnızca herkese açık şekilde alan adıyla çalışır",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label}, {pattern} modeliyle eşleşmelidir",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} gereklidir",
"APP_INSTALL_FORM_ERROR_URL": "{label} geçerli bir URL olmalıdır",
"APP_INSTALL_FORM_EXPOSE_APP": "Uygulamayı herkese açık yap",
"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": "Uygulamayı Sıfırla",
"APP_INSTALL_FORM_SUBMIT_INSTALL": "Yükle",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Güncelle",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Başlatılıyor",
"APP_STATUS_STOPPED": "Durduruldu",
"APP_STATUS_STOPPING": "Durduruluyor",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Kaldırılıyor",
"APP_STATUS_UPDATING": "Güncelleniyor",
"APP_STOP_FORM_SUBMIT": "Durdur",
"APP_STOP_FORM_SUBTITLE": "Tüm veriler saklanacak",
"APP_STOP_FORM_TITLE": "{name} durdurulsun mu ?",
"APP_STOP_SUCCESS": "Uygulama {id} başarıyla durduruldu",
"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": "Bir kategori seç",
"APP_STORE_NO_RESULTS": "Uygulama bulunamadı",
"APP_STORE_NO_RESULTS_SUBTITLE": "Aramanı değiştirmeyi dene",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Emin misiniz? Bu işlem geri alınamaz.",
"APP_UNINSTALL_SUCCESS": "Uygulama {id} başarıyla kaldırıldı",
"APP_UPDATE_CONFIG_SUCCESS": "Uygulama ayarları başarıyla güncellendi. Değişiklikleri uygulamak için uygulamayı yeniden başlatın",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Güncelle",
"APP_UPDATE_FORM_SUBTITLE_1": "Uygulamayı en son sürüme güncelleyin :",
"APP_UPDATE_FORM_SUBTITLE_2": "Bu, özel ayarlarınızı sıfırlayacaktır (ör. docker-compose. yml'deki değişiklikler).",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Kimlik doğrulama uygulamanızdan alacağınız kodu yazın",
"AUTH_TOTP_SUBMIT": "Onayla",
"AUTH_TOTP_TITLE": "İki faktörlü kimlik doğrulama",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Yükü azaltmak için uygulamaları kaldırın",
"DASHBOARD_CPU_TITLE": "İşlemci yükü",
"DASHBOARD_DISK_SPACE_SUBTITLE": "{total} GB'tan kullanıldı",
"DASHBOARD_DISK_SPACE_TITLE": "Disk Alanı",
"DASHBOARD_MEMORY_TITLE": "Bellek kullanıldı",
"DASHBOARD_TITLE": "Kontrol Paneli",
"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",
"GUEST_DASHBOARD": "Misafir panosu",
"GUEST_DASHBOARD_NO_APPS": "Görüntülenecek uygulama yok",
"GUEST_DASHBOARD_NO_APPS_SUBTITLE": "Yöneticinizden misafir panosuna uygulamalar eklemesini isteyin veya uygulamalarınızı görmek için giriş yapın.",

View File

@ -7,6 +7,7 @@
"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",
@ -41,6 +42,7 @@
"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",
@ -65,7 +67,11 @@
"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",
"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",
@ -84,12 +90,17 @@
"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_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",
@ -101,6 +112,7 @@
"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.",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Enter the code from your authenticator app",
"AUTH_TOTP_SUBMIT": "Confirm",
"AUTH_TOTP_TITLE": "Two-factor authentication",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "Cài đặt",
"APP_ACTION_START": "Bắt đầu",
"APP_ACTION_STOP": "Dừng",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "Cập nhật",
"APP_CATEGORY_AI": "Trí tuệ nhân tạo",
"APP_CATEGORY_AUTOMATION": "Tự động hóa",
@ -26,7 +27,7 @@
"APP_DETAILS_AUTHOR": "Tác giả",
"APP_DETAILS_BASE_INFO": "Thông tin cơ bản",
"APP_DETAILS_CATEGORIES_TITLE": "Danh mục",
"APP_DETAILS_CHOOSE_OPEN_METHOD": "Choose open method",
"APP_DETAILS_CHOOSE_OPEN_METHOD": "Chọn phương thức mở",
"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": "Mô tả",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_START": "Khởi động ứng dụng {id} thất bại, vui lòng xem log để biết chi tiết",
"APP_ERROR_APP_FAILED_TO_STOP": "Dừng ứng dụng {id} thất bại, vui lòng xem log để biết chi tiết",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "Gỡ cài đặt ứng dụng {id} thất bại, vui lòng xem log để biết chi tiết",
"APP_ERROR_APP_FAILED_TO_UPDATE": "Cập nhật ứng dụng {id} thất bại, vui lòng xem log để biết chi tiết",
"APP_ERROR_APP_FORCE_EXPOSED": "Ứng dụng {id} chỉ hoạt động với tên miền công khai",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} phải theo mẫu {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} là bắt buộc",
"APP_INSTALL_FORM_ERROR_URL": "{label} phải là một đường dẫn",
"APP_INSTALL_FORM_EXPOSE_APP": "Expose app",
"APP_INSTALL_FORM_EXPOSE_APP": "Mở ứng dụng để truy cập được từ internet",
"APP_INSTALL_FORM_OPEN_PORT": "Mở cổng",
"APP_INSTALL_FORM_OPEN_PORT_HINT": "Mở một cổng trên máy chủ? Ứng dụng này có thể truy cập tại {internalIp}:{port}. (Dễ nhất và kém an toàn nhất)",
"APP_INSTALL_FORM_EXPOSE_LOCAL": "Mở ứng dụng trên mạng cục bộ",
"APP_INSTALL_FORM_EXPOSE_LOCAL_HINT": "Phơi bày ứng dụng trên mạng nội bộ? Ứng dụng này sẽ có thể truy cập tại {appId}.{domain}. (Truy cập trang cài đặt để thiết lập tên miền nội bộ của bạn)",
"APP_INSTALL_FORM_RESET": "Thiết lập lại ứng dụng",
"APP_INSTALL_FORM_SUBMIT_INSTALL": "Cài đặt",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "Cập nhật",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "Đang khởi động",
"APP_STATUS_STOPPED": "Đã dừng",
"APP_STATUS_STOPPING": "Đang dừng",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "Đang gỡ cài đặt",
"APP_STATUS_UPDATING": "Đang cập nhật",
"APP_STOP_FORM_SUBMIT": "Dừng",
"APP_STOP_FORM_SUBTITLE": "Tất cả dữ liệu sẽ được giữ lại",
"APP_STOP_FORM_TITLE": "Dừng {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": "Chọn một danh mục",
"APP_STORE_NO_RESULTS": "Không tìm thấy ứng dụng",
"APP_STORE_NO_RESULTS_SUBTITLE": "Hãy thử tìm kiếm lại",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "Bạn chắc chứ? Hành động này không thể hoàn tác.",
"APP_UNINSTALL_SUCCESS": "App {id} uninstalled successfully",
"APP_UPDATE_CONFIG_SUCCESS": "Cài đặt của ứng dụng đã được cập nhật. Khởi động lại ứng dụng để áp dụng thay đổi",
"APP_UPDATE_ERROR_MIN_TIPI_VERSION": "App {id} update requires Tipi version {minVersion} or higher. Please update your instance.",
"APP_UPDATE_FORM_SUBMIT": "Cập nhật",
"APP_UPDATE_FORM_SUBTITLE_1": "Cập nhật ứng dụng lên phiên bản mới nhất:",
"APP_UPDATE_FORM_SUBTITLE_2": "Việc này sẽ thay đổi cấu hình tùy chọn của bạn (ví dụ: các thay đổi trong file docker-compose.yml).",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "Nhập mã từ ứng dụng xác thực của bạn",
"AUTH_TOTP_SUBMIT": "Xác nhận",
"AUTH_TOTP_TITLE": "Xác thực 2 bước",
"COMMON_CLOSE": "Close",
"DASHBOARD_CPU_SUBTITLE": "Gỡ cài đặt ứng dụng để giảm thiểu việc quá tải",
"DASHBOARD_CPU_TITLE": "Tải CPU",
"DASHBOARD_DISK_SPACE_SUBTITLE": "Đã dùng trên tổng số {total} GB",
"DASHBOARD_DISK_SPACE_TITLE": "Dung lượng đĩa",
"DASHBOARD_MEMORY_TITLE": "Đã sử dụng bộ nhớ",
"DASHBOARD_TITLE": "Bảng điều khiển",
"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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "设置",
"APP_ACTION_START": "开始",
"APP_ACTION_STOP": "停止",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "更新",
"APP_CATEGORY_AI": "AI",
"APP_CATEGORY_AUTOMATION": "自动化",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "重置应用程序 {id} 失败,查看日志获取更多详细信息",
"APP_ERROR_APP_FAILED_TO_START": "启动应用程序 {id} 失败,查看日志获取更多详细信息",
"APP_ERROR_APP_FAILED_TO_STOP": "停止应用程序 {id} 失败,查看日志获取更多详细信息",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "卸载应用程序 {id} 失败,查看日志获取更多详细信息",
"APP_ERROR_APP_FAILED_TO_UPDATE": "更新应用程序 {id} 失败,查看日志获取更多详细信息",
"APP_ERROR_APP_FORCE_EXPOSED": "App {id} 只能使用公开域名",
@ -65,7 +67,11 @@
"APP_INSTALL_FORM_ERROR_REGEX": "{label} 必须匹配模式 {pattern}",
"APP_INSTALL_FORM_ERROR_REQUIRED": "{label} 是必须的",
"APP_INSTALL_FORM_ERROR_URL": "{label} 必须是一个有效的 URL",
"APP_INSTALL_FORM_EXPOSE_APP": "公开应用程序",
"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": "重置应用",
"APP_INSTALL_FORM_SUBMIT_INSTALL": "安装",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "更新",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "正在启动",
"APP_STATUS_STOPPED": "已停止",
"APP_STATUS_STOPPING": "正在停止",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "正在卸载",
"APP_STATUS_UPDATING": "正在更新",
"APP_STOP_FORM_SUBMIT": "停止",
"APP_STOP_FORM_SUBTITLE": "所有数据将被保留",
"APP_STOP_FORM_TITLE": "停止 {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": "选择类别",
"APP_STORE_NO_RESULTS": "未找到应用",
"APP_STORE_NO_RESULTS_SUBTITLE": "尝试完善您的搜索",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "您确定吗?这个操作无法撤销。",
"APP_UNINSTALL_SUCCESS": "App {id} uninstalled successfully",
"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_FORM_SUBMIT": "更新",
"APP_UPDATE_FORM_SUBTITLE_1": "更新应用到最新版本:",
"APP_UPDATE_FORM_SUBTITLE_2": "这将重置您的自定义配置(如docker-compose. yml中的更改)",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "请输入您的双重验证应用中显示的验证码",
"AUTH_TOTP_SUBMIT": "确认",
"AUTH_TOTP_TITLE": "两步验证",
"COMMON_CLOSE": "Close",
"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": "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",
"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.",

View File

@ -7,6 +7,7 @@
"APP_ACTION_SETTINGS": "設定",
"APP_ACTION_START": "开始",
"APP_ACTION_STOP": "停止",
"APP_ACTION_RESTART": "Restart",
"APP_ACTION_UPDATE": "更新",
"APP_CATEGORY_AI": "AI",
"APP_CATEGORY_AUTOMATION": "自動化",
@ -41,6 +42,7 @@
"APP_ERROR_APP_FAILED_TO_RESET": "Failed to reset app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_START": "無法啟動應用程序 {id},請查看日誌以獲取更多詳細信息",
"APP_ERROR_APP_FAILED_TO_STOP": "無法停止應用程序 {id},請查看日誌以獲取更多詳細信息",
"APP_ERROR_APP_FAILED_TO_RESTART": "Failed to restart app {id}, see logs for more details",
"APP_ERROR_APP_FAILED_TO_UNINSTALL": "無法卸載應用程序 {id},請參閱日誌以獲取更多詳細信息",
"APP_ERROR_APP_FAILED_TO_UPDATE": "無法更新應用 {id},請查看日誌了解更多詳情",
"APP_ERROR_APP_FORCE_EXPOSED": "應用程序 {id} 僅適用於公開域名",
@ -65,7 +67,11 @@
"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_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": "安裝",
"APP_INSTALL_FORM_SUBMIT_UPDATE": "更新",
@ -84,12 +90,17 @@
"APP_STATUS_STARTING": "啟動中",
"APP_STATUS_STOPPED": "已停止",
"APP_STATUS_STOPPING": "停止中",
"APP_STATUS_RESTARTING": "Restarting",
"APP_STATUS_UNINSTALLING": "解除安裝中",
"APP_STATUS_UPDATING": "正在更新",
"APP_STOP_FORM_SUBMIT": "停止",
"APP_STOP_FORM_SUBTITLE": "所有數據將被保留",
"APP_STOP_FORM_TITLE": "停止{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": "選擇一個類別",
"APP_STORE_NO_RESULTS": "未找到应用",
"APP_STORE_NO_RESULTS_SUBTITLE": "嘗試優化您的搜索",
@ -101,6 +112,7 @@
"APP_UNINSTALL_FORM_WARNING": "您確定嗎?這個動作無法被復原!",
"APP_UNINSTALL_SUCCESS": "App {id} uninstalled successfully",
"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_FORM_SUBMIT": "更新",
"APP_UPDATE_FORM_SUBTITLE_1": "更新到最新版本:",
"APP_UPDATE_FORM_SUBTITLE_2": "這將重置您的自定義配置 (例如 docker-compose. yml 中的更改)",
@ -153,12 +165,15 @@
"AUTH_TOTP_INSTRUCTIONS": "輸入驗證應用程式中的驗證代碼",
"AUTH_TOTP_SUBMIT": "確認",
"AUTH_TOTP_TITLE": "兩步驟驗證",
"COMMON_CLOSE": "Close",
"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": "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",
"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.",

View File

@ -1,3 +1,4 @@
import { expect, describe, test } from 'vitest';
import { nonNullable, objectKeys } from '../typescript';
describe('objectKeys and nonNullable', () => {

View File

@ -2,11 +2,10 @@ import { faker } from '@faker-js/faker';
import fs from 'fs-extra';
import path from 'path';
import { DATA_DIR } from '@/config/constants';
import { describe, it, expect } from 'vitest';
import { TipiConfigClass } from './TipiConfig';
import { readJsonFile } from '../../common/fs.helpers';
jest.mock('fs-extra');
describe('Test: getConfig', () => {
it('It should return config from .env', () => {
// arrange

View File

@ -42,7 +42,8 @@ export class TipiConfigClass {
const envMap = envStringToMap(envFile.toString());
const conf = { ...process.env, ...Object.fromEntries(envMap) } as Record<string, string>;
const conf = { ...Object.fromEntries(envMap), ...process.env } as Record<string, string>;
const envConfig = {
postgresHost: conf.POSTGRES_HOST,
postgresDatabase: conf.POSTGRES_DBNAME,

View File

@ -5,13 +5,13 @@ import { TestDatabase, clearDatabase, closeDatabase, createDatabase } from '@/se
import { appInfoSchema } from '@runtipi/shared';
import path from 'path';
import { DATA_DIR } from '@/config/constants';
import { beforeAll, beforeEach, afterAll, describe, it, expect } from 'vitest';
import { TipiConfig } from '../../core/TipiConfig';
import { checkAppRequirements, getAppInfo, getAvailableApps, getUpdateInfo } from './apps.helpers';
import { createAppConfig, insertApp } from '../../tests/apps.factory';
let db: TestDatabase;
const TEST_SUITE = 'appshelpers';
jest.mock('fs-extra');
beforeAll(async () => {
db = await createDatabase(TEST_SUITE);

View File

@ -97,6 +97,7 @@ export const getUpdateInfo = (id: string) => {
if (parsedConfig.success) {
return {
latestVersion: parsedConfig.data.tipi_version,
minTipiVersion: parsedConfig.data.min_tipi_version,
latestDockerVersion: parsedConfig.data.version,
};
}

View File

@ -5,6 +5,7 @@ import { faker } from '@faker-js/faker';
import { castAppConfig } from '@/lib/helpers/castAppConfig';
import path from 'path';
import { DATA_DIR } from '@/config/constants';
import { vi, beforeEach, beforeAll, afterAll, describe, it, expect } from 'vitest';
import { AppServiceClass } from './apps.service';
import { EventDispatcher } from '../../core/EventDispatcher';
import { getAllApps, getAppById, updateApp, createAppConfig, insertApp } from '../../tests/apps.factory';
@ -22,7 +23,8 @@ beforeAll(async () => {
beforeEach(async () => {
await clearDatabase(db);
dispatcher.dispatchEventAsync = jest.fn().mockResolvedValue({ success: true });
await TipiConfig.setConfig('version', 'test');
dispatcher.dispatchEventAsync = vi.fn().mockResolvedValue({ success: true });
});
afterAll(async () => {
@ -51,7 +53,7 @@ describe('Install app', () => {
const appConfig = createAppConfig({ exposable: true });
// act & assert
await expect(AppsService.installApp(appConfig.id, { exposed: true })).rejects.toThrowError('APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP');
await expect(AppsService.installApp(appConfig.id, { exposed: true })).rejects.toThrow('APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP');
});
it('Should throw if app is exposed and config does not allow it', async () => {
@ -59,7 +61,7 @@ describe('Install app', () => {
const appConfig = createAppConfig({ exposable: false });
// act & assert
await expect(AppsService.installApp(appConfig.id, { exposed: true, domain: 'test.com' })).rejects.toThrowError('APP_ERROR_APP_NOT_EXPOSABLE');
await expect(AppsService.installApp(appConfig.id, { exposed: true, domain: 'test.com' })).rejects.toThrow('APP_ERROR_APP_NOT_EXPOSABLE');
});
it('Should throw if app is exposed and domain is not valid', async () => {
@ -67,7 +69,7 @@ describe('Install app', () => {
const appConfig = createAppConfig({ exposable: true });
// act & assert
await expect(AppsService.installApp(appConfig.id, { exposed: true, domain: 'test' })).rejects.toThrowError('APP_ERROR_DOMAIN_NOT_VALID');
await expect(AppsService.installApp(appConfig.id, { exposed: true, domain: 'test' })).rejects.toThrow('APP_ERROR_DOMAIN_NOT_VALID');
});
it('Should throw if app is exposed and domain is already used by another exposed app', async () => {
@ -78,21 +80,21 @@ describe('Install app', () => {
await insertApp({ domain, exposed: true }, appConfig2, db);
// act & assert
await expect(AppsService.installApp(appConfig.id, { exposed: true, domain })).rejects.toThrowError('APP_ERROR_DOMAIN_ALREADY_IN_USE');
await expect(AppsService.installApp(appConfig.id, { exposed: true, domain })).rejects.toThrow('APP_ERROR_DOMAIN_ALREADY_IN_USE');
});
it('Should throw if architecure is not supported', async () => {
// arrange
TipiConfig.setConfig('architecture', 'amd64');
await TipiConfig.setConfig('architecture', 'amd64');
const appConfig = createAppConfig({ supported_architectures: ['arm64'] });
// act & assert
await expect(AppsService.installApp(appConfig.id, {})).rejects.toThrowError(`App ${appConfig.id} is not supported on this architecture`);
await expect(AppsService.installApp(appConfig.id, {})).rejects.toThrow(`App ${appConfig.id} is not supported on this architecture`);
});
it('Can install if architecture is supported', async () => {
// arrange
TipiConfig.setConfig('architecture', 'arm');
await TipiConfig.setConfig('architecture', 'arm');
const appConfig = createAppConfig({ supported_architectures: ['arm'] });
// act
@ -104,7 +106,7 @@ describe('Install app', () => {
it('Can install if no architecture is specified', async () => {
// arrange
TipiConfig.setConfig('architecture', 'arm');
await TipiConfig.setConfig('architecture', 'arm');
const appConfig = createAppConfig({ supported_architectures: undefined });
// act
@ -121,7 +123,7 @@ describe('Install app', () => {
fs.writeFileSync(path.join(DATA_DIR, 'repos', 'repo-id', 'apps', appConfig.id, 'config.json'), 'test');
// act & assert
await expect(AppsService.installApp(appConfig.id, {})).rejects.toThrowError(`App ${appConfig.id} has invalid config.json file`);
await expect(AppsService.installApp(appConfig.id, {})).rejects.toThrow(`App ${appConfig.id} has invalid config.json file`);
});
it('should throw if app is not exposed and config has force_expose set to true', async () => {
@ -129,32 +131,77 @@ describe('Install app', () => {
const appConfig = createAppConfig({ force_expose: true });
// act & assert
await expect(AppsService.installApp(appConfig.id, {})).rejects.toThrowError();
await expect(AppsService.installApp(appConfig.id, {})).rejects.toThrow();
});
it('should throw if the current tipi version is lower than min_tipi_version', async () => {
// arrange
await TipiConfig.setConfig('version', '3.1.0');
const appConfig = createAppConfig({ min_tipi_version: '3.2.0' });
// act & assert
await expect(AppsService.installApp(appConfig.id, {})).rejects.toThrow('APP_UPDATE_ERROR_MIN_TIPI_VERSION');
});
it('should not throw if the current tipi version is equal to min_tipi_version', async () => {
// arrange
await TipiConfig.setConfig('version', '3.2.0');
const appConfig = createAppConfig({ min_tipi_version: '3.2.0' });
// act & assert
await expect(AppsService.installApp(appConfig.id, {})).resolves.not.toThrow();
});
it('should not throw if the current tipi version is higher than min_tipi_version', async () => {
// arrange
await TipiConfig.setConfig('version', '3.3.0');
const appConfig = createAppConfig({ min_tipi_version: '3.2.0' });
// act & assert
await expect(AppsService.installApp(appConfig.id, {})).resolves.not.toThrow();
});
it('should throw if the version format is invalid', async () => {
// arrange
await TipiConfig.setConfig('version', '3.3.0');
const appConfig = createAppConfig({ min_tipi_version: 'invalid' });
// act & assert
await expect(AppsService.installApp(appConfig.id, {})).rejects.toThrow();
});
it('should work with a version including a v prefix', async () => {
// arrange
await TipiConfig.setConfig('version', '3.3.0');
const appConfig = createAppConfig({ min_tipi_version: 'v3.2.0' });
// act & assert
await expect(AppsService.installApp(appConfig.id, {})).resolves.not.toThrow();
});
});
describe('Uninstall app', () => {
it('Should throw if app is not installed', async () => {
// act & assert
await expect(AppsService.uninstallApp('any')).rejects.toThrowError('APP_ERROR_APP_NOT_FOUND');
await expect(AppsService.uninstallApp('any')).rejects.toThrow('APP_ERROR_APP_NOT_FOUND');
});
});
describe('Start app', () => {
it('Should throw if app is not installed', async () => {
await expect(AppsService.startApp('any')).rejects.toThrowError('APP_ERROR_APP_NOT_FOUND');
await expect(AppsService.startApp('any')).rejects.toThrow('APP_ERROR_APP_NOT_FOUND');
});
});
describe('Stop app', () => {
it('Should throw if app is not installed', async () => {
await expect(AppsService.stopApp('any')).rejects.toThrowError('APP_ERROR_APP_NOT_FOUND');
await expect(AppsService.stopApp('any')).rejects.toThrow('APP_ERROR_APP_NOT_FOUND');
});
});
describe('Restart app', () => {
it('Should throw if app is not installed', async () => {
await expect(AppsService.restartApp('any')).rejects.toThrowError('APP_ERROR_APP_NOT_FOUND');
await expect(AppsService.restartApp('any')).rejects.toThrow('APP_ERROR_APP_NOT_FOUND');
});
});
@ -175,7 +222,7 @@ describe('Update app config', () => {
});
it('Should throw if app is not installed', async () => {
await expect(AppsService.updateAppConfig('test-app-2', { test: 'test' })).rejects.toThrowError('APP_ERROR_APP_NOT_FOUND');
await expect(AppsService.updateAppConfig('test-app-2', { test: 'test' })).rejects.toThrow('APP_ERROR_APP_NOT_FOUND');
});
it('Should throw if app is exposed and domain is not provided', async () => {
@ -184,7 +231,7 @@ describe('Update app config', () => {
await insertApp({}, appConfig, db);
// act & assert
expect(AppsService.updateAppConfig(appConfig.id, { exposed: true })).rejects.toThrowError('APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP');
await expect(AppsService.updateAppConfig(appConfig.id, { exposed: true })).rejects.toThrow('APP_ERROR_DOMAIN_REQUIRED_IF_EXPOSE_APP');
});
it('Should throw if app is exposed and domain is not valid', async () => {
@ -193,10 +240,10 @@ describe('Update app config', () => {
await insertApp({}, appConfig, db);
// act & assert
expect(AppsService.updateAppConfig(appConfig.id, { exposed: true, domain: 'test' })).rejects.toThrowError('APP_ERROR_DOMAIN_NOT_VALID');
await expect(AppsService.updateAppConfig(appConfig.id, { exposed: true, domain: 'test' })).rejects.toThrow('APP_ERROR_DOMAIN_NOT_VALID');
});
it.skip('Should throw if app is exposed and domain is already used', async () => {
it('Should throw if app is exposed and domain is already used', async () => {
// arrange
const domain = faker.internet.domainName();
const appConfig = createAppConfig({ exposable: true });
@ -205,7 +252,7 @@ describe('Update app config', () => {
await insertApp({}, appConfig2, db);
// act & assert
await expect(AppsService.updateAppConfig(appConfig2.id, { exposed: true, domain })).rejects.toThrowError('APP_ERROR_DOMAIN_ALREADY_IN_USE');
await expect(AppsService.updateAppConfig(appConfig2.id, { exposed: true, domain })).rejects.toThrow('APP_ERROR_DOMAIN_ALREADY_IN_USE');
});
it('should throw if app is not exposed and config has force_expose set to true', async () => {
@ -214,7 +261,7 @@ describe('Update app config', () => {
await insertApp({ exposed: true }, appConfig, db);
// act & assert
await expect(AppsService.updateAppConfig(appConfig.id, {})).rejects.toThrowError('APP_ERROR_APP_FORCE_EXPOSED');
await expect(AppsService.updateAppConfig(appConfig.id, {})).rejects.toThrow('APP_ERROR_APP_FORCE_EXPOSED');
});
it('Should throw if app is exposed and config does not allow it', async () => {
@ -223,9 +270,7 @@ describe('Update app config', () => {
await insertApp({}, appConfig, db);
// act & assert
await expect(AppsService.updateAppConfig(appConfig.id, { exposed: true, domain: 'test.com' })).rejects.toThrowError(
'APP_ERROR_APP_NOT_EXPOSABLE',
);
await expect(AppsService.updateAppConfig(appConfig.id, { exposed: true, domain: 'test.com' })).rejects.toThrow('APP_ERROR_APP_NOT_EXPOSABLE');
});
});
@ -284,7 +329,7 @@ describe('List apps', () => {
it('Should not list apps that have supportedArchitectures and are not supported', async () => {
// arrange
TipiConfig.setConfig('architecture', 'arm64');
await TipiConfig.setConfig('architecture', 'arm64');
createAppConfig({ supported_architectures: ['amd64'] });
// act
@ -297,7 +342,7 @@ describe('List apps', () => {
it.skip('Should list apps that have supportedArchitectures and are supported', async () => {
// arrange
TipiConfig.setConfig('architecture', 'arm');
await TipiConfig.setConfig('architecture', 'arm');
createAppConfig({ supported_architectures: ['arm'] });
// act
@ -310,7 +355,7 @@ describe('List apps', () => {
it.skip('Should list apps that have no supportedArchitectures specified', async () => {
// Arrange
TipiConfig.setConfig('architecture', 'arm');
await TipiConfig.setConfig('architecture', 'arm');
createAppConfig({ supported_architectures: undefined });
// act
@ -351,6 +396,26 @@ describe('Update app', () => {
const app = await getAppById(appConfig.id, db);
expect(app?.status).toBe('stopped');
});
it('should throw if the current tipi version is lower than min_tipi_version', async () => {
// arrange
await TipiConfig.setConfig('version', '3.1.0');
const appConfig = createAppConfig({ min_tipi_version: '3.2.0' });
await insertApp({ status: 'running' }, appConfig, db);
// act & assert
await expect(AppsService.updateApp(appConfig.id)).rejects.toThrow('APP_UPDATE_ERROR_MIN_TIPI_VERSION');
});
it.skip('should not throw if the current tipi version is equal to min_tipi_version', async () => {
// arrange
await TipiConfig.setConfig('version', '3.2.0');
const appConfig = createAppConfig({ min_tipi_version: '3.2.0' });
await insertApp({ status: 'running' }, appConfig, db);
// act & assert
await expect(AppsService.updateApp(appConfig.id)).resolves.not.toThrow();
});
});
describe('installedApps', () => {
@ -397,7 +462,7 @@ describe('startAllApps', () => {
// arrange
const appConfig = createAppConfig({});
await insertApp({ status: 'stopped' }, appConfig, db);
const spy = jest.spyOn(dispatcher, 'dispatchEventAsync');
const spy = vi.spyOn(dispatcher, 'dispatchEventAsync');
spy.mockResolvedValueOnce({ success: false, stdout: 'error' });
// act

View File

@ -1,12 +1,13 @@
import validator from 'validator';
import MiniSearch from 'minisearch';
import { App } from '@/server/db/schema';
import { type App } from '@/server/db/schema';
import { AppQueries } from '@/server/queries/apps/apps.queries';
import { TranslatedError } from '@/server/utils/errors';
import { Database, db } from '@/server/db';
import { AppEventFormInput } from '@runtipi/shared';
import { type Database, db } from '@/server/db';
import { type AppEventFormInput } from '@runtipi/shared';
import { EventDispatcher } from '@/server/core/EventDispatcher/EventDispatcher';
import { castAppConfig } from '@/lib/helpers/castAppConfig';
import semver from 'semver';
import { checkAppRequirements, getAvailableApps as slow_getAvailableApps, getAppInfo, getUpdateInfo } from './apps.helpers';
import { TipiConfig } from '../../core/TipiConfig';
import { Logger } from '../../core/Logger';
@ -165,7 +166,7 @@ export class AppServiceClass {
public installApp = async (id: string, form: AppEventFormInput) => {
const app = await this.queries.getApp(id);
const { exposed, domain, isVisibleOnGuestDashboard } = form;
const { exposed, exposedLocal, openPort, domain, isVisibleOnGuestDashboard } = form;
if (app) {
await this.startApp(id);
@ -208,6 +209,11 @@ export class AppServiceClass {
}
}
const { version } = TipiConfig.getConfig();
if (appInfo?.min_tipi_version && semver.valid(version) && semver.lt(version, appInfo.min_tipi_version)) {
throw new TranslatedError('APP_UPDATE_ERROR_MIN_TIPI_VERSION', { id, minVersion: appInfo.min_tipi_version });
}
await this.queries.createApp({
id,
status: 'installing',
@ -215,8 +221,8 @@ export class AppServiceClass {
version: appInfo.tipi_version,
exposed: exposed || false,
domain: domain || null,
openPort: form.openPort || false,
exposedLocal: form.exposedLocal || false,
openPort: openPort || false,
exposedLocal: exposedLocal || false,
isVisibleOnGuestDashboard,
});
@ -492,6 +498,13 @@ export class AppServiceClass {
throw new TranslatedError('APP_ERROR_APP_NOT_FOUND', { id });
}
const { version } = TipiConfig.getConfig();
const { minTipiVersion } = getUpdateInfo(app.id);
if (minTipiVersion && semver.valid(version) && semver.lt(version, minTipiVersion)) {
throw new TranslatedError('APP_UPDATE_ERROR_MIN_TIPI_VERSION', { id, minVersion: minTipiVersion });
}
await this.queries.updateApp(id, { status: 'updating' });
const eventDispatcher = new EventDispatcher('updateApp');

View File

@ -10,6 +10,7 @@ import { v4 } from 'uuid';
import { tipiCache } from '@/server/core/TipiCache';
import path from 'path';
import { DATA_DIR } from '@/config/constants';
import { vi, beforeAll, beforeEach, afterAll, describe, it, expect } from 'vitest';
import { encrypt } from '../../utils/encryption';
import { TipiConfig } from '../../core/TipiConfig';
import { createUser, getUserByEmail, getUserById } from '../../tests/user.factory';
@ -20,8 +21,8 @@ let database: TestDatabase;
const TEST_SUITE = 'authservice';
let cookieStore: Record<string, string> = {};
jest.mock('next/headers', () => ({
cookies: jest.fn(() => ({
vi.mock('next/headers', () => ({
cookies: vi.fn(() => ({
set: (name: string, value: string) => {
cookieStore[name] = value;
},
@ -540,7 +541,7 @@ describe('Test: changeOperatorPassword', () => {
const user = await createUser({ email }, database);
const newPassword = faker.internet.password();
// @ts-expect-error - mocking fs
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: '' });
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: new Date().getTime().toString() });
// Act
const result = await AuthService.changeOperatorPassword({ newPassword });
@ -569,7 +570,7 @@ describe('Test: changeOperatorPassword', () => {
await createUser({ email, operator: false }, database);
const newPassword = faker.internet.password();
// @ts-expect-error - mocking fs
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: '' });
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: new Date().getTime().toString() });
// Act & Assert
await expect(AuthService.changeOperatorPassword({ newPassword })).rejects.toThrowError('AUTH_ERROR_OPERATOR_NOT_FOUND');
@ -581,7 +582,7 @@ describe('Test: changeOperatorPassword', () => {
const user = await createUser({ email, totpEnabled: true }, database);
const newPassword = faker.internet.password();
// @ts-expect-error - mocking fs
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: '' });
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: new Date().getTime().toString() });
// Act
const result = await AuthService.changeOperatorPassword({ newPassword });
@ -599,10 +600,10 @@ describe('Test: checkPasswordChangeRequest', () => {
it('should return true if the password change request file exists', async () => {
// Arrange
// @ts-expect-error - mocking fs
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: '' });
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: new Date().getTime().toString() });
// Act
const result = AuthServiceClass.checkPasswordChangeRequest();
const result = await AuthService.checkPasswordChangeRequest();
// Assert
expect(result).toBe(true);
@ -614,7 +615,7 @@ describe('Test: checkPasswordChangeRequest', () => {
fs.__createMockFiles({});
// Act
const result = AuthServiceClass.checkPasswordChangeRequest();
const result = await AuthService.checkPasswordChangeRequest();
// Assert
expect(result).toBe(false);

View File

@ -1,4 +1,6 @@
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';
import { pathExists } from '@runtipi/shared/node';
import * as argon2 from 'argon2';
import validator from 'validator';
import { TotpAuthenticator } from '@/server/utils/totp';
@ -6,11 +8,11 @@ import { AuthQueries } from '@/server/queries/auth/auth.queries';
import { TranslatedError } from '@/server/utils/errors';
import { Locales, getLocaleFromString } from '@/shared/internationalization/locales';
import { generateSessionId, setSession } from '@/server/common/session.helpers';
import { Database } from '@/server/db';
import { Database, db } from '@/server/db';
import { tipiCache } from '@/server/core/TipiCache/TipiCache';
import { DATA_DIR } from '@/config/constants';
import path from 'path';
import { TipiConfig } from '../../core/TipiConfig';
import { fileExists, unlinkFile } from '../../common/fs.helpers';
import { decrypt, encrypt } from '../../utils/encryption';
type UsernamePasswordInput = {
@ -22,7 +24,7 @@ type UsernamePasswordInput = {
export class AuthServiceClass {
private queries;
constructor(p: Database) {
constructor(p: Database = db) {
this.queries = new AuthQueries(p);
}
@ -279,7 +281,9 @@ export class AuthServiceClass {
* @param {string} params.newPassword - The new password
*/
public changeOperatorPassword = async (params: { newPassword: string }) => {
if (!AuthServiceClass.checkPasswordChangeRequest()) {
const isRequested = await this.checkPasswordChangeRequest();
if (!isRequested) {
throw new TranslatedError('AUTH_ERROR_NO_CHANGE_PASSWORD_REQUEST');
}
@ -295,7 +299,9 @@ export class AuthServiceClass {
await this.queries.updateUser(user.id, { password: hash, totpEnabled: false, totpSecret: null });
await unlinkFile(`${DATA_DIR}/state/password-change-request`);
await fs.promises.unlink(path.join(DATA_DIR, 'state', 'password-change-request'));
await this.destroyAllSessionsByUserId(user.id);
return { email: user.username };
};
@ -306,9 +312,19 @@ export class AuthServiceClass {
*
* @returns {boolean} - A boolean indicating if there is a password change request or not
*/
public static checkPasswordChangeRequest = () => {
if (fileExists(`${DATA_DIR}/state/password-change-request`)) {
return true;
public checkPasswordChangeRequest = async () => {
const REQUEST_TIMEOUT_SECS = 15 * 60; // 15 minutes
const resetPasswordFilePath = path.join(DATA_DIR, 'state', 'password-change-request');
try {
if (await pathExists(resetPasswordFilePath)) {
const timestamp = await fs.promises.readFile(resetPasswordFilePath, 'utf8');
const requestCreation = Number(timestamp);
return requestCreation + REQUEST_TIMEOUT_SECS > Date.now() / 1000;
}
} catch {
return false;
}
return false;
@ -322,8 +338,10 @@ export class AuthServiceClass {
* @throws {Error} - If the file cannot be removed
*/
public static cancelPasswordChangeRequest = async () => {
if (fileExists(`${DATA_DIR}/state/password-change-request`)) {
await unlinkFile(`${DATA_DIR}/state/password-change-request`);
const changeRequestPath = path.join(DATA_DIR, 'state', 'password-change-request');
if (await pathExists(changeRequestPath)) {
await fs.promises.unlink(changeRequestPath);
}
return true;

View File

@ -2,6 +2,7 @@ import { HttpResponse, http } from 'msw';
import { setupServer } from 'msw/node';
import { faker } from '@faker-js/faker';
import { TipiCacheClass } from '@/server/core/TipiCache/TipiCache';
import { afterAll, beforeAll, beforeEach, describe, it, expect } from 'vitest';
import { TipiConfig } from '../../core/TipiConfig';
import { SystemServiceClass } from '.';

View File

@ -1,5 +1,7 @@
export const mockSelect = <T>(returnValue: T) => jest.fn(() => ({ from: jest.fn(() => ({ where: jest.fn(() => returnValue) })) }));
import { vi } from 'vitest';
export const mockInsert = <T>(returnValue: T) => jest.fn(() => ({ values: jest.fn(() => ({ returning: jest.fn(() => returnValue) })) }));
export const mockSelect = <T>(returnValue: T) => vi.fn(() => ({ from: vi.fn(() => ({ where: vi.fn(() => returnValue) })) }));
export const mockQuery = <T>(returnValue: T) => ({ userTable: { findFirst: jest.fn(() => returnValue) } });
export const mockInsert = <T>(returnValue: T) => vi.fn(() => ({ values: vi.fn(() => ({ returning: vi.fn(() => returnValue) })) }));
export const mockQuery = <T>(returnValue: T) => ({ userTable: { findFirst: vi.fn(() => returnValue) } });

View File

@ -59,19 +59,4 @@ const closeDatabase = async (database: TestDatabase) => {
await database.client.end();
};
/**
* Setup a test suite by mocking the database.
*
* @param {string} testSuite - name of the test suite
*/
export async function setupTestSuite(testSuite: string) {
const db = await createDatabase(testSuite);
jest.mock('../db', () => {
return { db: db.db };
});
return { db: db.db, client: db.client };
}
export { createDatabase, clearDatabase, closeDatabase };

View File

@ -1,19 +0,0 @@
import { redisMock } from '@/tests/mocks/redis';
import { vi } from 'vitest';
vi.mock('../core/Logger', () => ({
Logger: {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
},
}));
vi.mock('redis', () => redisMock);
vi.mock('fs-extra', async () => {
const { fsExtraMock } = await import('@/tests/mocks/fs-extra');
return {
...fsExtraMock,
};
});

View File

@ -1,4 +1,5 @@
import { faker } from '@faker-js/faker';
import { describe, it, expect } from 'vitest';
import { TipiConfig } from '../../core/TipiConfig';
import { encrypt, decrypt } from '../encryption';

View File

@ -0,0 +1,25 @@
import { cookies, headers } from 'next/headers';
import * as ipaddrjs from 'ipaddr.js';
export const isInstanceInsecure = () => {
const cookieStore = cookies();
const isInsecureBannerHidden = cookieStore.get('hide-insecure-instance')?.value;
if (isInsecureBannerHidden) {
return false;
}
const myHeaders = headers();
const ip = myHeaders.get('x-forwarded-host')?.split(':')[0] || '';
if (ipaddrjs.isValid(ip)) {
const range = ipaddrjs.parse(ip!).range();
if (range !== 'private' && range !== 'carrierGradeNat' && range !== 'loopback') {
return true;
}
} else if (ip !== 'localhost' && myHeaders.get('x-forwarded-proto') === 'http') {
return true;
}
return false;
};

View File

@ -14,4 +14,4 @@ done
pm2 start npm --name dashboard -- run dev
# Log apps realtime
pm2 logs --raw
pm2 logs --raw --lines 1000

View File

@ -1,18 +1,36 @@
/* eslint-disable no-console */
import React from 'react';
import '@testing-library/jest-dom';
import { vi } from 'vitest';
import '@testing-library/jest-dom/vitest';
// Mock next/router
// eslint-disable-next-line global-require
jest.mock('next/router', () => require('next-router-mock'));
jest.mock('react-markdown', () => ({
vi.mock('next/navigation', async () => {
const router = await import('next-router-mock');
return router;
});
vi.mock('react-markdown', () => ({
__esModule: true,
default: () => <div data-testid="markdown" />,
}));
jest.mock('remark-breaks', () => () => ({}));
jest.mock('remark-gfm', () => () => ({}));
jest.mock('rehype-raw', () => () => ({}));
vi.mock('remark-breaks', () => () => ({}));
vi.mock('remark-gfm', () => () => ({}));
vi.mock('rehype-raw', () => () => ({}));
vi.mock('fs-extra', async () => {
const { fsMock } = await import('@/tests/mocks/fs');
return {
...fsMock,
};
});
vi.mock('fs', async () => {
const { fsMock } = await import('@/tests/mocks/fs');
return {
...fsMock,
};
});
console.error = jest.fn();
console.error = vi.fn();
class ResizeObserver {
observe() {}
@ -44,6 +62,7 @@ const localStorageMock = (() => {
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
Object.defineProperty(window, 'ResizeObserver', { value: ResizeObserver });
Object.defineProperty(window, 'MutationObserver', { value: ResizeObserver });
Object.defineProperty(window.HTMLElement.prototype, 'scrollIntoView', { value: vi.fn() });
Object.defineProperty(window, 'matchMedia', {
value: () => {

View File

@ -1,33 +0,0 @@
import path from 'path';
let mockFiles = Object.create(null);
export const fsExtraMock = {
default: {
__createMockFiles: (newMockFiles: Record<string, string>) => {
mockFiles = Object.create(null);
// Create folder tree
Object.keys(newMockFiles).forEach((file) => {
const dir = path.dirname(file);
if (!mockFiles[dir]) {
mockFiles[dir] = [];
}
mockFiles[dir].push(path.basename(file));
mockFiles[file] = newMockFiles[file];
});
},
existsSync: (p: string) => mockFiles[p] !== undefined,
promises: {
unlink: async (p: string) => {
if (mockFiles[p] instanceof Array) {
mockFiles[p].forEach((file: string) => {
delete mockFiles[path.join(p, file)];
});
}
delete mockFiles[p];
},
},
},
};

41
tests/mocks/fs.ts Normal file
View File

@ -0,0 +1,41 @@
import { fs, vol } from 'memfs';
const copyFolderRecursiveSync = (src: string, dest: string) => {
const exists = vol.existsSync(src);
const stats = vol.statSync(src);
const isDirectory = exists && stats.isDirectory();
if (isDirectory) {
vol.mkdirSync(dest, { recursive: true });
vol.readdirSync(src).forEach((childItemName) => {
copyFolderRecursiveSync(`${src}/${childItemName}`, `${dest}/${childItemName}`);
});
} else {
vol.copyFileSync(src, dest);
}
};
export const fsMock = {
default: {
...fs,
promises: {
...fs.promises,
cp: copyFolderRecursiveSync,
},
copySync: (src: string, dest: string) => {
copyFolderRecursiveSync(src, dest);
},
__resetAllMocks: () => {
vol.reset();
},
__applyMockFiles: (newMockFiles: Record<string, string>) => {
// Create folder tree
vol.fromJSON(newMockFiles, 'utf8');
},
__createMockFiles: (newMockFiles: Record<string, string>) => {
vol.reset();
// Create folder tree
vol.fromJSON(newMockFiles, 'utf8');
},
__printVol: () => console.log(vol.toTree()),
},
};

Some files were not shown because too many files have changed in this diff Show More