Compare commits

...

73 Commits

Author SHA1 Message Date
Owen Schwartz
b0ff50a76f Merge pull request #1834 from fosrl/dev
Small Bug Fixes
2025-11-08 16:35:50 -08:00
Owen
37acdc2796 Revert transaction 2025-11-08 16:33:48 -08:00
Owen Schwartz
f3d31cb6de Merge pull request #1833 from fosrl/crowdin_dev
New Crowdin updates
2025-11-08 16:23:11 -08:00
Owen Schwartz
a336955066 New translations en-us.json (Norwegian Bokmal) 2025-11-08 16:22:42 -08:00
Owen Schwartz
a229fc1c61 New translations en-us.json (Chinese Simplified) 2025-11-08 16:22:40 -08:00
Owen Schwartz
7995fd364e New translations en-us.json (Turkish) 2025-11-08 16:22:39 -08:00
Owen Schwartz
5e0d822d45 New translations en-us.json (Russian) 2025-11-08 16:22:38 -08:00
Owen Schwartz
4fddaa8f11 New translations en-us.json (Portuguese) 2025-11-08 16:22:36 -08:00
Owen Schwartz
4a87cecf89 New translations en-us.json (Polish) 2025-11-08 16:22:35 -08:00
Owen Schwartz
ac5ee5c7ca New translations en-us.json (Dutch) 2025-11-08 16:22:34 -08:00
Owen Schwartz
8a8c357563 New translations en-us.json (Korean) 2025-11-08 16:22:32 -08:00
Owen Schwartz
263fd80c18 New translations en-us.json (Italian) 2025-11-08 16:22:31 -08:00
Owen Schwartz
7bdf05bdf5 New translations en-us.json (German) 2025-11-08 16:22:30 -08:00
Owen Schwartz
d00f12967d New translations en-us.json (Czech) 2025-11-08 16:22:28 -08:00
Owen Schwartz
d9991a18e2 New translations en-us.json (Bulgarian) 2025-11-08 16:22:27 -08:00
Owen Schwartz
a51c21cdd2 New translations en-us.json (Spanish) 2025-11-08 16:22:26 -08:00
Owen Schwartz
265cab5b64 New translations en-us.json (French) 2025-11-08 16:22:24 -08:00
Owen
da15e5e77b Remove software-properties-common
Fixes #1828
2025-11-08 16:13:42 -08:00
Owen
a717ca2675 Only uppercase the value if its a country
Fixes #1813
2025-11-08 15:42:46 -08:00
Owen
564b290244 Fix #1830 2025-11-08 14:24:28 -08:00
Owen
84d78df67e Merge branch 'main' into dev 2025-11-08 14:20:40 -08:00
Owen
107053a98f Merge branch 'main' of github.com:fosrl/pangolin 2025-11-08 14:20:35 -08:00
Owen Schwartz
6422a78e6f Merge pull request #1830 from hetlelid/patch-2
Update resourceRawSettingsDescription with details
2025-11-08 14:20:21 -08:00
miloschwartz
5f11630e27 minor adjustments to blueprints screens 2025-11-08 14:15:47 -08:00
Owen
a776b2ea94 Fix: qiery perferWildcardCert from db
Fixes #1816
Fixes #1829
2025-11-08 14:14:17 -08:00
miloschwartz
b83ec1b503 remove target unique check 2025-11-08 13:57:00 -08:00
Owen
83bd5957cd Dont allow editing a config managed domain
Ref #1816
2025-11-08 12:18:36 -08:00
hetlelid
66c14c2d09 Update resourceRawSettingsDescription with details
Expanded the description for resourceRawSettings to include mapping details and a documentation link.
2025-11-08 13:24:51 +01:00
Owen Schwartz
51db267a4a Merge pull request #1779 from fosrl/dependabot/npm_and_yarn/eslint-config-next-16.0.1
Bump eslint-config-next from 15.5.6 to 16.0.1
2025-11-07 12:15:19 -08:00
dependabot[bot]
669817818a Bump eslint-config-next from 15.5.6 to 16.0.1
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.5.6 to 16.0.1.
- [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/v16.0.1/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 16.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 20:07:29 +00:00
Owen Schwartz
b84453bfbe Merge pull request #1825 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-282bba5f0a
Bump the dev-patch-updates group across 1 directory with 5 updates
2025-11-07 12:06:08 -08:00
Owen Schwartz
15d561f59f Merge pull request #1824 from robtec/patch-1
Fix typo in shareSeeOnce message
2025-11-07 12:05:59 -08:00
dependabot[bot]
749cea5a4d Bump the dev-patch-updates group across 1 directory with 5 updates
Bumps the dev-patch-updates group with 4 updates in the / directory: [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx), [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss), [esbuild](https://github.com/evanw/esbuild) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@dotenvx/dotenvx` from 1.51.0 to 1.51.1
- [Release notes](https://github.com/dotenvx/dotenvx/releases)
- [Changelog](https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dotenvx/dotenvx/compare/v1.51.0...v1.51.1)

Updates `@tailwindcss/postcss` from 4.1.16 to 4.1.17
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.17/packages/@tailwindcss-postcss)

Updates `esbuild` from 0.25.11 to 0.25.12
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.11...v0.25.12)

Updates `tailwindcss` from 4.1.16 to 4.1.17
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.17/packages/tailwindcss)

Updates `typescript-eslint` from 8.46.2 to 8.46.3
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.46.3/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 1.51.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.1.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: esbuild
  dependency-version: 0.25.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tailwindcss
  dependency-version: 4.1.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: typescript-eslint
  dependency-version: 8.46.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 01:23:55 +00:00
miloschwartz
2a7529c39e don't delete user 2025-11-06 16:48:53 -08:00
miloschwartz
fce887436d fix bug causing auto provision to override manually created users 2025-11-06 15:46:54 -08:00
Rob
3489107a49 Fix typo in shareSeeOnce message 2025-11-06 23:09:52 +00:00
Owen Schwartz
296b220bf3 Merge pull request #1819 from Pallavikumarimdb/fix/resourceTable-typeError
Fix/Revert column from Resource table to fix type error and match overall styling
2025-11-06 12:03:15 -08:00
Pallavi Kumari
0a9f37c44d revert column from resource table 2025-11-06 22:57:03 +05:30
Owen Schwartz
ef5d72663f Merge pull request #1328 from Pallavikumarimdb/enhancement-#906/dashboard-enhancements
Enhancement #906/Resources Dashboard: Targets Column, Customizable Columns & Status Indicators
2025-11-05 11:41:43 -08:00
Owen
6ddfc9b8fe Revert columns 2025-11-05 11:41:07 -08:00
Owen
301654b63e Fix styling 2025-11-05 11:38:14 -08:00
Owen Schwartz
3b12a77cf0 Merge pull request #1809 from clemone210/patch-2
Update German translations for client and blueprint terms
2025-11-04 10:34:26 -08:00
Timo
7cd31313d8 Update German translations for client and blueprint terms
"Kunden" is generally used for "Customers", so in this case I suggest to stick with Client, as this is a widely used term in german tech sector. The same for "Bauplan" or "Blaupause". "Bauplan" is a "Construction plan" for building houses. "Blaupause" is pretty much the right translation for blueprints, but I would stick with Blueprint here as well.
2025-11-04 07:40:33 +01:00
Milo Schwartz
9822deb4bf Update README.md 2025-11-03 22:56:57 -05:00
Owen
8942cb7aa7 Update const 2025-11-03 17:38:50 -08:00
Owen
f0f219f293 Merge branch 'main' into dev 2025-11-03 17:38:43 -08:00
Owen
6174599754 Allow >30 days on oss 2025-11-03 09:54:41 -08:00
Owen Schwartz
8ba04aeb74 Merge pull request #1802 from fosrl/dependabot/npm_and_yarn/prod-minor-updates-700e856888
Bump the prod-minor-updates group across 1 directory with 9 updates
2025-11-03 09:49:01 -08:00
Owen
43590896e9 Add fosrl 2025-11-02 18:56:46 -08:00
Owen Schwartz
3547c4832b Revert "Refactor CI/CD workflow for improved release process" 2025-11-02 18:56:46 -08:00
Marc Schäfer
1cd098252e Refactor CI/CD workflow for improved release process
Updated CI/CD workflow to include new permissions, job definitions, and steps for version validation, tagging, and artifact management.
2025-11-02 18:56:46 -08:00
Owen
4adbc31dae Fix blueprints not applying
Fixes #1795
2025-11-02 18:56:46 -08:00
Owen
99031feb35 Fix camel case in health checks 2025-11-02 18:56:46 -08:00
Owen
d363b06d0e Fix rewritePath
Closes #1528
2025-11-02 18:56:46 -08:00
Owen
2af100cc86 Warning -> debug 2025-11-02 18:56:46 -08:00
dependabot[bot]
3e90211108 Bump the prod-minor-updates group across 1 directory with 9 updates
Bumps the prod-minor-updates group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.908.0` | `3.922.0` |
| [axios](https://github.com/axios/axios) | `1.12.2` | `1.13.1` |
| [eslint](https://github.com/eslint/eslint) | `9.37.0` | `9.39.0` |
| [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) | `8.1.0` | `8.2.1` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.545.0` | `0.552.0` |
| [next-intl](https://github.com/amannn/next-intl) | `4.3.12` | `4.4.0` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.10.4` | `5.11.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.65.0` | `7.66.0` |
| [resend](https://github.com/resend/resend-node) | `6.1.3` | `6.4.0` |



Updates `@aws-sdk/client-s3` from 3.908.0 to 3.922.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.922.0/clients/client-s3)

Updates `axios` from 1.12.2 to 1.13.1
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.12.2...v1.13.1)

Updates `eslint` from 9.37.0 to 9.39.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.37.0...v9.39.0)

Updates `express-rate-limit` from 8.1.0 to 8.2.1
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v8.1.0...v8.2.1)

Updates `lucide-react` from 0.545.0 to 0.552.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.552.0/packages/lucide-react)

Updates `next-intl` from 4.3.12 to 4.4.0
- [Release notes](https://github.com/amannn/next-intl/releases)
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v4.3.12...v4.4.0)

Updates `posthog-node` from 5.10.4 to 5.11.0
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.11.0/packages/node)

Updates `react-hook-form` from 7.65.0 to 7.66.0
- [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.65.0...v7.66.0)

Updates `resend` from 6.1.3 to 6.4.0
- [Release notes](https://github.com/resend/resend-node/releases)
- [Commits](https://github.com/resend/resend-node/compare/v6.1.3...v6.4.0)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.922.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: axios
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: eslint
  dependency-version: 9.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: express-rate-limit
  dependency-version: 8.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: lucide-react
  dependency-version: 0.552.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: next-intl
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: posthog-node
  dependency-version: 5.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: react-hook-form
  dependency-version: 7.66.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: resend
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 01:34:13 +00:00
Owen
6dd161fe17 Add fosrl 2025-11-02 15:35:02 -08:00
Owen Schwartz
558bd040c6 Merge pull request #1801 from fosrl/revert-1792-main
Revert "Refactor CI/CD workflow for improved release process"
2025-11-02 15:22:12 -08:00
Owen Schwartz
f2c48975f6 Revert "Refactor CI/CD workflow for improved release process" 2025-11-02 15:22:03 -08:00
Owen Schwartz
fc43a56bb3 Merge pull request #1792 from marcschaeferger/main
Refactor CI/CD workflow for improved release process
2025-11-02 15:00:09 -08:00
Owen
ca7f557a3c Fix blueprints not applying
Fixes #1795
2025-11-02 14:56:19 -08:00
Owen
7477713eef Fix camel case in health checks 2025-11-02 14:17:38 -08:00
Owen
c16e762fa4 Fix rewritePath
Closes #1528
2025-11-02 14:05:41 -08:00
Owen Schwartz
41592133a6 Merge pull request #1788 from Pallavikumarimdb/fix/deleting-and-adding-back-a-target
Add transaction while deleting targets
2025-11-02 13:51:08 -08:00
Pallavi Kumari
54f7525f1b add status column in resource table 2025-11-02 13:55:17 +05:30
Pallavi Kumari
ad6bb3da9f fix type error 2025-11-02 13:55:17 +05:30
Pallavi Kumari
49bc2dc5da fix duplicate 2025-11-02 13:55:16 +05:30
Pallavi
cdf77087cd get niceid 2025-11-02 13:55:16 +05:30
Pallavi
8e5dde887c list targes in frontend 2025-11-02 13:55:16 +05:30
Pallavi
f21188000e remove status check and add column filtering on all of the tables 2025-11-02 13:55:16 +05:30
Pallavi
1b3eb32bf4 Show targets and status icons in the dashboard 2025-11-02 13:55:16 +05:30
Marc Schäfer
eec3f183e6 Refactor CI/CD workflow for improved release process
Updated CI/CD workflow to include new permissions, job definitions, and steps for version validation, tagging, and artifact management.
2025-11-02 00:44:03 +01:00
Pallavi Kumari
ad425e8d9e add transaction while deleting targets 2025-11-01 11:58:09 +05:30
39 changed files with 1944 additions and 1365 deletions

View File

@@ -31,7 +31,7 @@ jobs:
timeout-minutes: 120
env:
# Target images
DOCKERHUB_IMAGE: docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ github.event.repository.name }}
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
steps:

View File

@@ -89,3 +89,7 @@ Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License
## Contributions
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
---
WireGuard® is a registered trademark of Jason A. Donenfeld.

View File

@@ -73,7 +73,7 @@ func installDocker() error {
case strings.Contains(osRelease, "ID=ubuntu"):
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
apt-get update &&
apt-get install -y apt-transport-https ca-certificates curl software-properties-common &&
apt-get install -y apt-transport-https ca-certificates curl &&
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg &&
echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list &&
apt-get update &&
@@ -82,7 +82,7 @@ func installDocker() error {
case strings.Contains(osRelease, "ID=debian"):
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
apt-get update &&
apt-get install -y apt-transport-https ca-certificates curl software-properties-common &&
apt-get install -y apt-transport-https ca-certificates curl &&
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg &&
echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list &&
apt-get update &&

View File

@@ -131,7 +131,7 @@
"expireIn": "Изтече",
"neverExpire": "Никога не изтича",
"shareExpireDescription": "Времето на изтичане е колко дълго връзката ще бъде използваема и ще предоставя достъп до ресурса. След това време, връзката няма да работи и потребителите, които са я използвали, ще загубят достъп до ресурса.",
"shareSeeOnce": "Ще можете да видите тази връзка само веднъж. Уверете се да я копирате.",
"shareSeeOnce": "Ще можете да видите този линк само веднъж. Уверете се, че го копирате.",
"shareAccessHint": "Всеки с тази връзка може да има достъп до ресурса. Споделяйте я с внимание.",
"shareTokenUsage": "Вижте използването на токена за достъп",
"createLink": "Създаване на връзка",
@@ -179,7 +179,7 @@
"baseDomain": "Базов домейн",
"subdomnainDescription": "Субдомейнът, в който ще бъде достъпен вашият ресурс.",
"resourceRawSettings": "TCP/UDP настройки",
"resourceRawSettingsDescription": "Конфигурирайте как вашият ресурс ще бъде достъпен през TCP/UDP",
"resourceRawSettingsDescription": "Настройте как ресурсът ви ще бъде достъпен през TCP/UDP. Свързвате ресурса към порт на хост сървъра Pangolin, за да го достъпвате от server-public-ip:mapped-port.",
"protocol": "Протокол",
"protocolSelect": "Изберете протокол",
"resourcePortNumber": "Номер на порт",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Домейни",
"sidebarBluePrints": "Чертежи",
"blueprints": "Чертежи",
"blueprintsDescription": "Чертежите са декларативни YAML конфигурации, които определят вашите ресурси и техните настройки",
"blueprintsDescription": "Прилагайте декларативни конфигурации и преглеждайте предишни изпълнения",
"blueprintAdd": "Добави Чертеж",
"blueprintGoBack": "Виж всички Чертежи",
"blueprintCreate": "Създай Чертеж",
"blueprintCreateDescription2": "Следвайте стъпките по-долу, за да създадете и приложите нов чертеж",
"blueprintDetails": "Детайли за Чертежа",
"blueprintDetailsDescription": "Вижте детайлите за изпълнението на чертежа",
"blueprintDetails": "Детайли на чертежа",
"blueprintDetailsDescription": "Вижте резултата от приложените чертежи и всички възникнали грешки",
"blueprintInfo": "Информация за Чертежа",
"message": "Съобщение",
"blueprintContentsDescription": "Дефинирайте YAML съдържанието, описващо вашата инфраструктура",
@@ -1181,7 +1181,7 @@
"appliedAt": "Приложено във",
"source": "Източник",
"contents": "Съдържание",
"parsedContents": "Анализирано съдържание",
"parsedContents": "Парсирано съдържание (само за четене)",
"enableDockerSocket": "Активиране на Docker Чернова",
"enableDockerSocketDescription": "Активиране на Docker Socket маркировка за изтегляне на етикети на чернова. Пътят на гнездото трябва да бъде предоставен на Newt.",
"enableDockerSocketLink": "Научете повече",
@@ -2080,5 +2080,20 @@
"supportSending": "Изпращане...",
"supportSend": "Изпрати",
"supportMessageSent": "Съобщението е изпратено!",
"supportWillContact": "Ще се свържем с вас скоро!"
}
"supportWillContact": "Ще се свържем с вас скоро!",
"selectLogRetention": "Изберете съхранение на логовете",
"showColumns": "Покажи колони",
"hideColumns": "Скрий колони",
"columnVisibility": "Видимост на колоните",
"toggleColumn": "Превключване на колоната {columnName}",
"allColumns": "Всички колони",
"defaultColumns": "По подразбиране колони",
"customizeView": "Персонализиране на изгледа",
"viewOptions": "Опции за изгледа",
"selectAll": "Избери всички",
"selectNone": "Избери нищо",
"selectedResources": "Избрани ресурси",
"enableSelected": "Разреши избраните",
"disableSelected": "Забрани избраните",
"checkSelectedStatus": "Проверете състоянието на избраните"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Platnost vyprší za",
"neverExpire": "Nikdy nevyprší",
"shareExpireDescription": "Doba platnosti určuje, jak dlouho bude odkaz použitelný a bude poskytovat přístup ke zdroji. Po této době odkaz již nebude fungovat a uživatelé kteří tento odkaz používali ztratí přístup ke zdroji.",
"shareSeeOnce": "Tento odkaz uvidíte pouze jednou. Ujistěte se, že jste jej zkopírovali.",
"shareSeeOnce": "Tento odkaz uvidíte pouze jednou. Nezapomeňte jej zkopírovat.",
"shareAccessHint": "Kdokoli s tímto odkazem může přistupovat ke zdroji. Sdílejte jej s rozvahou.",
"shareTokenUsage": "Zobrazit využití přístupového tokenu",
"createLink": "Vytvořit odkaz",
@@ -179,7 +179,7 @@
"baseDomain": "Základní doména",
"subdomnainDescription": "Subdoména, kde bude váš zdroj přístupný.",
"resourceRawSettings": "Nastavení TCP/UDP",
"resourceRawSettingsDescription": "Nakonfigurujte, jak bude váš dokument přístupný přes TCP/UDP",
"resourceRawSettingsDescription": "Nakonfigurujte, jak bude váš dokument přístupný přes TCP/UDP. Mapováte zdroj na port na serveru Pangolin, takže můžete přistupovat ke zdroji ze serveru-veřejné ip:mapped-port.",
"protocol": "Protokol",
"protocolSelect": "Vybrat protokol",
"resourcePortNumber": "Číslo portu",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Domény",
"sidebarBluePrints": "Plány",
"blueprints": "Plány",
"blueprintsDescription": "Plány jsou deklarativní YAML konfigurace, které definují vaše zdroje a jejich nastavení",
"blueprintsDescription": "Použít deklarativní konfigurace a zobrazit předchozí běhy",
"blueprintAdd": "Přidat plán",
"blueprintGoBack": "Zobrazit všechny plány",
"blueprintCreate": "Vytvořit plán",
"blueprintCreateDescription2": "Postupujte podle níže uvedených kroků pro vytvoření a použití nového plánu",
"blueprintDetails": "Podrobnosti plánu",
"blueprintDetailsDescription": "Podívejte se na detaily běhu plánu",
"blueprintDetailsDescription": "Podívejte se na výsledek použitého plánu a případné chyby, které se vyskytly",
"blueprintInfo": "Informace o plánu",
"message": "Zpráva",
"blueprintContentsDescription": "Definujte obsah YAML popisující vaši infrastrukturu",
@@ -1181,7 +1181,7 @@
"appliedAt": "Použito v",
"source": "Zdroj",
"contents": "Obsah",
"parsedContents": "Parsovaný obsah",
"parsedContents": "Parsed content (Pouze pro čtení)",
"enableDockerSocket": "Povolit Docker plán",
"enableDockerSocketDescription": "Povolte seškrábání štítků na Docker Socket pro popisky plánů. Nová cesta musí být k dispozici.",
"enableDockerSocketLink": "Zjistit více",
@@ -2080,5 +2080,20 @@
"supportSending": "Odesílání...",
"supportSend": "Poslat",
"supportMessageSent": "Zpráva odeslána!",
"supportWillContact": "Brzy budeme v kontaktu!"
}
"supportWillContact": "Brzy budeme v kontaktu!",
"selectLogRetention": "Vyberte záznam",
"showColumns": "Zobrazit sloupce",
"hideColumns": "Skrýt sloupce",
"columnVisibility": "Viditelnost sloupců",
"toggleColumn": "Přepnout sloupec {columnName}",
"allColumns": "Všechny sloupce",
"defaultColumns": "Výchozí sloupce",
"customizeView": "Přizpůsobit zobrazení",
"viewOptions": "Možnosti zobrazení",
"selectAll": "Vybrat vše",
"selectNone": "Nevybrat žádný",
"selectedResources": "Vybrané zdroje",
"enableSelected": "Povolit vybrané",
"disableSelected": "Zakázat vybrané",
"checkSelectedStatus": "Zkontrolovat stav vybraného"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Verfällt in",
"neverExpire": "Nie ablaufen",
"shareExpireDescription": "Ablaufzeit ist, wie lange der Link verwendet werden kann und bietet Zugriff auf die Ressource. Nach dieser Zeit wird der Link nicht mehr funktionieren und Benutzer, die diesen Link benutzt haben, verlieren den Zugriff auf die Ressource.",
"shareSeeOnce": "Sie können diesen Link nur ein einziges Mal sehen. Bitte kopieren Sie ihn.",
"shareSeeOnce": "Sie können diesen Link nur einmal sehen. Bitte kopieren Sie ihn.",
"shareAccessHint": "Jeder mit diesem Link kann auf die Ressource zugreifen. Teilen Sie sie mit Vorsicht.",
"shareTokenUsage": "Zugriffstoken-Nutzung anzeigen",
"createLink": "Link erstellen",
@@ -179,7 +179,7 @@
"baseDomain": "Basis-Domain",
"subdomnainDescription": "Die Subdomain, auf der Ihre Ressource erreichbar sein soll.",
"resourceRawSettings": "TCP/UDP Einstellungen",
"resourceRawSettingsDescription": "Konfigurieren Sie den Zugriff auf Ihre Ressource über TCP/UDP",
"resourceRawSettingsDescription": "Legen Sie fest, wie auf Ihre Ressource über TCP/UDP zugegriffen wird. Sie ordnen die Ressource einem Port auf dem Pangolin-Server zu, so dass Sie auf die Ressource von server-public-ip:mapped-port zugreifen können.",
"protocol": "Protokoll",
"protocolSelect": "Wählen Sie ein Protokoll",
"resourcePortNumber": "Portnummer",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Domänen",
"sidebarBluePrints": "Baupläne",
"blueprints": "Baupläne",
"blueprintsDescription": "Blaupausen sind deklarative YAML-Konfigurationen, die deine Ressourcen und deren Einstellungen definieren",
"blueprintsDescription": "Deklarative Konfigurationen anwenden und vorherige Abläufe anzeigen",
"blueprintAdd": "Blaupause hinzufügen",
"blueprintGoBack": "Alle Blaupausen ansehen",
"blueprintCreate": "Blaupause erstellen",
"blueprintCreateDescription2": "Folge den Schritten unten, um eine neue Blaupause zu erstellen und anzuwenden",
"blueprintDetails": "Blaupausendetails",
"blueprintDetailsDescription": "Siehe die Blaupausenlauf-Details",
"blueprintDetailsDescription": "Siehe das Ergebnis der angewendeten Blaupause und alle aufgetretenen Fehler",
"blueprintInfo": "Blaupauseninformation",
"message": "Nachricht",
"blueprintContentsDescription": "Definieren Sie den YAML-Inhalt, der Ihre Infrastruktur beschreibt",
@@ -1181,7 +1181,7 @@
"appliedAt": "Angewandt am",
"source": "Quelle",
"contents": "Inhalt",
"parsedContents": "Analysierte Inhalte",
"parsedContents": "Analysierte Inhalte (Nur lesen)",
"enableDockerSocket": "Docker Blaupause aktivieren",
"enableDockerSocketDescription": "Aktiviere Docker-Socket-Label-Scraping für Blaupausenbeschriftungen. Der Socket-Pfad muss neu angegeben werden.",
"enableDockerSocketLink": "Mehr erfahren",
@@ -2080,5 +2080,20 @@
"supportSending": "Senden...",
"supportSend": "Senden",
"supportMessageSent": "Nachricht gesendet!",
"supportWillContact": "Wir werden in Kürze kontaktieren!"
}
"supportWillContact": "Wir werden in Kürze kontaktieren!",
"selectLogRetention": "Log-Speicherung auswählen",
"showColumns": "Spalten anzeigen",
"hideColumns": "Spalten ausblenden",
"columnVisibility": "Spaltensichtbarkeit",
"toggleColumn": "{columnName} Spalte umschalten",
"allColumns": "Alle Spalten",
"defaultColumns": "Standardspalten",
"customizeView": "Ansicht anpassen",
"viewOptions": "Optionen anzeigen",
"selectAll": "Alle auswählen",
"selectNone": "Nichts auswählen",
"selectedResources": "Ausgewählte Ressourcen",
"enableSelected": "Ausgewählte aktivieren",
"disableSelected": "Ausgewählte deaktivieren",
"checkSelectedStatus": "Status der Auswahl überprüfen"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Expire In",
"neverExpire": "Never expire",
"shareExpireDescription": "Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource.",
"shareSeeOnce": "You will only be able to see this linkonce. Make sure to copy it.",
"shareSeeOnce": "You will only be able to see this link once. Make sure to copy it.",
"shareAccessHint": "Anyone with this link can access the resource. Share it with care.",
"shareTokenUsage": "See Access Token Usage",
"createLink": "Create Link",
@@ -179,7 +179,7 @@
"baseDomain": "Base Domain",
"subdomnainDescription": "The subdomain where your resource will be accessible.",
"resourceRawSettings": "TCP/UDP Settings",
"resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP",
"resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP. You map the resource to a port on the host Pangolin server, so you can access the resource from server-public-ip:mapped-port.",
"protocol": "Protocol",
"protocolSelect": "Select a protocol",
"resourcePortNumber": "Port Number",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Domains",
"sidebarBluePrints": "Blueprints",
"blueprints": "Blueprints",
"blueprintsDescription": "Blueprints are declarative YAML configurations that define your resources and their settings",
"blueprintsDescription": "Apply declarative configurations and view previous runs",
"blueprintAdd": "Add Blueprint",
"blueprintGoBack": "See all Blueprints",
"blueprintCreate": "Create Blueprint",
"blueprintCreateDescription2": "Follow the steps below to create and apply a new blueprint",
"blueprintDetails": "Blueprint details",
"blueprintDetailsDescription": "See the blueprint run details",
"blueprintDetails": "Blueprint Details",
"blueprintDetailsDescription": "See the result of the applied blueprint and any errors that occurred",
"blueprintInfo": "Blueprint Information",
"message": "Message",
"blueprintContentsDescription": "Define the YAML content describing your infrastructure",
@@ -1181,7 +1181,7 @@
"appliedAt": "Applied At",
"source": "Source",
"contents": "Contents",
"parsedContents": "Parsed Contents",
"parsedContents": "Parsed Contents (Read Only)",
"enableDockerSocket": "Enable Docker Blueprint",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to Newt.",
"enableDockerSocketLink": "Learn More",
@@ -2081,5 +2081,19 @@
"supportSend": "Send",
"supportMessageSent": "Message Sent!",
"supportWillContact": "We'll be in touch shortly!",
"selectLogRetention": "Select log retention"
}
"selectLogRetention": "Select log retention",
"showColumns": "Show Columns",
"hideColumns": "Hide Columns",
"columnVisibility": "Column Visibility",
"toggleColumn": "Toggle {columnName} column",
"allColumns": "All Columns",
"defaultColumns": "Default Columns",
"customizeView": "Customize View",
"viewOptions": "View Options",
"selectAll": "Select All",
"selectNone": "Select None",
"selectedResources": "Selected Resources",
"enableSelected": "Enable Selected",
"disableSelected": "Disable Selected",
"checkSelectedStatus": "Check Status of Selected"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Caduca en",
"neverExpire": "Nunca expirar",
"shareExpireDescription": "El tiempo de caducidad es cuánto tiempo el enlace será utilizable y proporcionará acceso al recurso. Después de este tiempo, el enlace ya no funcionará, y los usuarios que usaron este enlace perderán el acceso al recurso.",
"shareSeeOnce": "Sólo podrá ver este enlace una vez. Asegúrese de copiarlo.",
"shareSeeOnce": "Sólo podrás ver este enlace una vez. Asegúrate de copiarlo.",
"shareAccessHint": "Cualquiera con este enlace puede acceder al recurso. Compártelo con cuidado.",
"shareTokenUsage": "Ver Uso de Token de Acceso",
"createLink": "Crear enlace",
@@ -179,7 +179,7 @@
"baseDomain": "Dominio base",
"subdomnainDescription": "El subdominio al que su recurso será accesible.",
"resourceRawSettings": "Configuración TCP/UDP",
"resourceRawSettingsDescription": "Configurar cómo se accederá a su recurso a través de TCP/UDP",
"resourceRawSettingsDescription": "Configure cómo se accederá a su recurso a través de TCP/UDP. Mapeas el recurso a un puerto en el servidor Pangolin host, así puedes acceder al recurso desde el servidor-public-ip:mapped-port.",
"protocol": "Protocolo",
"protocolSelect": "Seleccionar un protocolo",
"resourcePortNumber": "Número de puerto",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Dominios",
"sidebarBluePrints": "Planos",
"blueprints": "Planos",
"blueprintsDescription": "Los planos son configuraciones YAML declarativas que definen sus recursos y sus configuraciones",
"blueprintsDescription": "Aplicar configuraciones declarativas y ver ejecuciones anteriores",
"blueprintAdd": "Añadir plano",
"blueprintGoBack": "Ver todos los Planos",
"blueprintCreate": "Crear Plano",
"blueprintCreateDescription2": "Siga los siguientes pasos para crear y aplicar un nuevo plano",
"blueprintDetails": "Detalles del plano",
"blueprintDetailsDescription": "Ver los detalles de la ejecución del plano",
"blueprintDetailsDescription": "Ver el resultado del plano aplicado y cualquier error que haya ocurrido",
"blueprintInfo": "Información del plano",
"message": "Mensaje",
"blueprintContentsDescription": "Defina el contenido YAML describiendo su infraestructura",
@@ -1181,7 +1181,7 @@
"appliedAt": "Aplicado en",
"source": "Fuente",
"contents": "Contenido",
"parsedContents": "Contenido analizado",
"parsedContents": "Contenido analizado (Sólo lectura)",
"enableDockerSocket": "Habilitar Plano Docker",
"enableDockerSocketDescription": "Activar el raspado de etiquetas de Socket Docker para etiquetas de planos. La ruta del Socket debe proporcionarse a Newt.",
"enableDockerSocketLink": "Saber más",
@@ -2080,5 +2080,20 @@
"supportSending": "Enviando...",
"supportSend": "Enviar",
"supportMessageSent": "¡Mensaje enviado!",
"supportWillContact": "¡Estaremos en contacto en breve!"
}
"supportWillContact": "¡Estaremos en contacto en breve!",
"selectLogRetention": "Seleccionar retención de registro",
"showColumns": "Mostrar columnas",
"hideColumns": "Ocultar columnas",
"columnVisibility": "Visibilidad de la columna",
"toggleColumn": "Cambiar columna {columnName}",
"allColumns": "Todas las columnas",
"defaultColumns": "Columnas por defecto",
"customizeView": "Personalizar vista",
"viewOptions": "Ver opciones",
"selectAll": "Seleccionar todo",
"selectNone": "No seleccionar",
"selectedResources": "Recursos seleccionados",
"enableSelected": "Habilitar seleccionados",
"disableSelected": "Desactivar Seleccionado",
"checkSelectedStatus": "Comprobar el estado de selección"
}

View File

@@ -10,20 +10,20 @@
"setupErrorIdentifier": "L'ID de l'organisation est déjà pris. Veuillez en choisir un autre.",
"componentsErrorNoMemberCreate": "Vous n'êtes actuellement membre d'aucune organisation. Créez une organisation pour commencer.",
"componentsErrorNoMember": "Vous n'êtes actuellement membre d'aucune organisation.",
"welcome": "Bienvenue à Pangolin",
"welcome": "Bienvenue sur Pangolin !",
"welcomeTo": "Bienvenue chez",
"componentsCreateOrg": "Créer une organisation",
"componentsMember": "Vous êtes membre de {count, plural, =0 {aucune organisation} one {une organisation} other {# organisations}}.",
"componentsInvalidKey": "Clés de licence invalides ou expirées détectées. Suivez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
"dismiss": "Refuser",
"componentsLicenseViolation": "Violation de licence : Ce serveur utilise des sites {usedSites} qui dépassent la limite autorisée des sites {maxSites} . Suivez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
"componentsInvalidKey": "Clés de licence invalides ou expirées détectées. Veuillez respecter les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
"dismiss": "Rejeter",
"componentsLicenseViolation": "Violation de licence : ce serveur utilise {usedSites} sites, ce qui dépasse la limite autorisée de {maxSites} sites. Respectez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
"componentsSupporterMessage": "Merci de soutenir Pangolin en tant que {tier}!",
"inviteErrorNotValid": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder n'ait pas été acceptée ou n'est plus valide.",
"inviteErrorUser": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder ne soit pas pour cet utilisateur.",
"inviteLoginUser": "Assurez-vous que vous êtes bien connecté en tant qu'utilisateur correct.",
"inviteErrorNoUser": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder ne soit pas pour un utilisateur qui existe.",
"inviteErrorNotValid": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder n'ait pas été acceptée ou ne soit plus valide.",
"inviteErrorUser": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder ne soit pas pour cet utilisateur.",
"inviteLoginUser": "Veuillez vous assurer que vous êtes connecté avec le bon utilisateur.",
"inviteErrorNoUser": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder ne concerne pas un utilisateur existant.",
"inviteCreateUser": "Veuillez d'abord créer un compte.",
"goHome": "Retour à la maison",
"goHome": "Retour à l'accueil",
"inviteLogInOtherUser": "Se connecter en tant qu'utilisateur différent",
"createAnAccount": "Créer un compte",
"inviteNotAccepted": "Invitation non acceptée",
@@ -39,12 +39,12 @@
"online": "En ligne",
"offline": "Hors ligne",
"site": "Site",
"dataIn": "Données dans",
"dataOut": "Données épuisées",
"dataIn": "Données entrantes",
"dataOut": "Données sortantes",
"connectionType": "Type de connexion",
"tunnelType": "Type de tunnel",
"local": "Locale",
"edit": "Editer",
"edit": "Éditer",
"siteConfirmDelete": "Confirmer la suppression du site",
"siteDelete": "Supprimer le site",
"siteMessageRemove": "Une fois supprimé, le site ne sera plus accessible. Toutes les cibles associées au site seront également supprimées.",
@@ -63,11 +63,11 @@
"siteLearnNewt": "Apprenez à installer Newt sur votre système",
"siteSeeConfigOnce": "Vous ne pourrez voir la configuration qu'une seule fois.",
"siteLoadWGConfig": "Chargement de la configuration WireGuard...",
"siteDocker": "Développer les détails du déploiement Docker",
"siteDocker": "Développer pour obtenir plus de détails sur le déploiement Docker",
"toggle": "Activer/désactiver",
"dockerCompose": "Composition Docker",
"dockerRun": "Exécution Docker",
"siteLearnLocal": "Les sites locaux ne tunnel, en savoir plus",
"siteLearnLocal": "Les sites locaux ne font pas de tunnel, en savoir plus",
"siteConfirmCopy": "J'ai copié la configuration",
"searchSitesProgress": "Rechercher des sites...",
"siteAdd": "Ajouter un site",
@@ -78,7 +78,7 @@
"operatingSystem": "Système d'exploitation",
"commands": "Commandes",
"recommended": "Recommandé",
"siteNewtDescription": "Pour une meilleure expérience d'utilisateur, utilisez Newt. Il utilise WireGuard sous le capot et vous permet d'adresser vos ressources privées par leur adresse LAN sur votre réseau privé à partir du tableau de bord Pangolin.",
"siteNewtDescription": "Pour une meilleure expérience d'utilisateur, utilisez Newt. Il utilise WireGuard sous le capot et vous permet de vous connecter à vos ressources privées par leur adresse LAN sur votre réseau privé à partir du tableau de bord Pangolin.",
"siteRunsInDocker": "Exécute dans Docker",
"siteRunsInShell": "Exécute en shell sur macOS, Linux et Windows",
"siteErrorDelete": "Erreur lors de la suppression du site",
@@ -93,7 +93,7 @@
"siteNewtTunnelDescription": "La façon la plus simple de créer un point d'entrée dans votre réseau. Pas de configuration supplémentaire.",
"siteWg": "WireGuard basique",
"siteWgDescription": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise.",
"siteWgDescriptionSaas": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES",
"siteWgDescriptionSaas": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES.",
"siteLocalDescription": "Ressources locales seulement. Pas de tunneling.",
"siteLocalDescriptionSaas": "Ressources locales uniquement. Pas de tunneling. Disponible uniquement sur les nœuds distants.",
"siteSeeAll": "Voir tous les sites",
@@ -130,9 +130,9 @@
"shareTitleOptional": "Titre (facultatif)",
"expireIn": "Expire dans",
"neverExpire": "N'expire jamais",
"shareExpireDescription": "Le temps d'expiration est combien de temps le lien sera utilisable et fournira un accès à la ressource. Après cette période, le lien ne fonctionnera plus et les utilisateurs qui ont utilisé ce lien perdront l'accès à la ressource.",
"shareSeeOnce": "Vous ne pourrez voir ce lien. Assurez-vous de le copier.",
"shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec soin.",
"shareExpireDescription": "La durée d'expiration correspond à la période pendant laquelle le lien sera utilisable et permettra d'accéder à la ressource. Passé ce délai, le lien ne fonctionnera plus et les utilisateurs qui l'ont utilisé perdront l'accès à la ressource.",
"shareSeeOnce": "Vous ne pourrez voir ce lien qu'une seule fois. Assurez-vous de le copier.",
"shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec précaution.",
"shareTokenUsage": "Voir Utilisation du jeton d'accès",
"createLink": "Créer un lien",
"resourcesNotFound": "Aucune ressource trouvée",
@@ -155,9 +155,9 @@
"resourceMessageRemove": "Une fois supprimée, la ressource ne sera plus accessible. Toutes les cibles associées à la ressource seront également supprimées.",
"resourceQuestionRemove": "Êtes-vous sûr de vouloir supprimer la ressource de l'organisation ?",
"resourceHTTP": "Ressource HTTPS",
"resourceHTTPDescription": "Requêtes de proxy à votre application via HTTPS en utilisant un sous-domaine ou un domaine de base.",
"resourceHTTPDescription": "Requêtes de proxy vers votre application via HTTPS en utilisant un sous-domaine ou un domaine de base.",
"resourceRaw": "Ressource TCP/UDP brute",
"resourceRawDescription": "Demandes de proxy à votre application via TCP/UDP en utilisant un numéro de port.",
"resourceRawDescription": "Demandes de proxy vers votre application via TCP/UDP en utilisant un numéro de port.",
"resourceCreate": "Créer une ressource",
"resourceCreateDescription": "Suivez les étapes ci-dessous pour créer une nouvelle ressource",
"resourceSeeAll": "Voir toutes les ressources",
@@ -179,7 +179,7 @@
"baseDomain": "Domaine de base",
"subdomnainDescription": "Le sous-domaine où votre ressource sera accessible.",
"resourceRawSettings": "Paramètres TCP/UDP",
"resourceRawSettingsDescription": "Configurer comment votre ressource sera accédée via TCP/UDP",
"resourceRawSettingsDescription": "Configurer comment votre ressource sera accédée via TCP/UDP. Vous mappez la ressource à un port sur le serveur Pangolin, de sorte que vous puissiez accéder à la ressource depuis server-public-ip:mapped-port.",
"protocol": "Protocole",
"protocolSelect": "Sélectionner un protocole",
"resourcePortNumber": "Numéro de port",
@@ -206,7 +206,7 @@
"resourceSetting": "Réglages {resourceName}",
"alwaysAllow": "Toujours autoriser",
"alwaysDeny": "Toujours refuser",
"passToAuth": "Paser à l'authentification",
"passToAuth": "Passer à l'authentification",
"orgSettingsDescription": "Configurer les paramètres généraux de votre organisation",
"orgGeneralSettings": "Paramètres de l'organisation",
"orgGeneralSettingsDescription": "Gérer les détails et la configuration de votre organisation",
@@ -342,7 +342,7 @@
"licenseTitleDescription": "Voir et gérer les clés de licence dans le système",
"licenseHost": "Licence Hôte",
"licenseHostDescription": "Gérer la clé de licence principale de l'hôte.",
"licensedNot": "Non licenc",
"licensedNot": "Pas de licence",
"hostId": "ID de l'hôte",
"licenseReckeckAll": "Revérifier toutes les clés",
"licenseSiteUsage": "Utilisation des sites",
@@ -350,7 +350,7 @@
"licenseNoSiteLimit": "Il n'y a pas de limite sur le nombre de sites utilisant un hôte non autorisé.",
"licensePurchase": "Acheter une licence",
"licensePurchaseSites": "Acheter des sites supplémentaires",
"licenseSitesUsedMax": "{usedSites} des sites {maxSites} utilisés",
"licenseSitesUsedMax": "{usedSites} des {maxSites} sites utilisés",
"licenseSitesUsed": "{count, plural, =0 {# sites} one {# site} other {# sites}} dans le système.",
"licensePurchaseDescription": "Choisissez le nombre de sites que vous voulez {selectedMode, select, license {achetez une licence. Vous pouvez toujours ajouter plus de sites plus tard.} other {ajouter à votre licence existante.}}",
"licenseFee": "Frais de licence",
@@ -371,7 +371,7 @@
"inviteQuestionRemove": "Êtes-vous sûr de vouloir supprimer l'invitation?",
"inviteMessageRemove": "Une fois supprimée, cette invitation ne sera plus valide. Vous pourrez toujours réinviter l'utilisateur plus tard.",
"inviteMessageConfirm": "Pour confirmer, veuillez saisir l'adresse e-mail de l'invitation ci-dessous.",
"inviteQuestionRegenerate": "Êtes-vous sûr de vouloir régénérer l'invitation {email}? Cela révoquera l'invitation précédente.",
"inviteQuestionRegenerate": "Êtes-vous sûr de vouloir régénérer l'invitation pour {email}? Cela révoquera l'invitation précédente.",
"inviteRemoveConfirm": "Confirmer la suppression de l'invitation",
"inviteRegenerated": "Invitation régénérée",
"inviteSent": "Une nouvelle invitation a été envoyée à {email}.",
@@ -465,7 +465,7 @@
"proxyErrorTls": "Nom de serveur TLS invalide. Utilisez le format de nom de domaine, ou laissez vide pour supprimer le nom de serveur TLS.",
"proxyEnableSSL": "Activer SSL",
"proxyEnableSSLDescription": "Activez le cryptage SSL/TLS pour des connexions HTTPS sécurisées vers vos cibles.",
"target": "Target",
"target": "Cible",
"configureTarget": "Configurer les cibles",
"targetErrorFetch": "Échec de la récupération des cibles",
"targetErrorFetchDescription": "Une erreur s'est produite lors de la récupération des cibles",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Domaines",
"sidebarBluePrints": "Plans",
"blueprints": "Plans",
"blueprintsDescription": "Les plans sont des configurations YAML déclaratives qui définissent vos ressources et leurs paramètres",
"blueprintsDescription": "Appliquer les configurations déclaratives et afficher les exécutions précédentes",
"blueprintAdd": "Ajouter un Plan",
"blueprintGoBack": "Voir tous les plans",
"blueprintCreate": "Créer un Plan",
"blueprintCreateDescription2": "Suivez les étapes ci-dessous pour créer et appliquer un nouveau plan",
"blueprintDetails": "Détails du Plan",
"blueprintDetailsDescription": "Voir les détails de l'exécution des plans",
"blueprintDetailsDescription": "Voir le résultat du plan appliqué et les erreurs qui se sont produites",
"blueprintInfo": "Informations sur le Plan",
"message": "Message",
"blueprintContentsDescription": "Définissez le contenu YAML décrivant votre infrastructure",
@@ -1181,7 +1181,7 @@
"appliedAt": "Appliqué à",
"source": "Source",
"contents": "Contenus",
"parsedContents": "Contenu analysé",
"parsedContents": "Contenu analysé (lecture seule)",
"enableDockerSocket": "Activer le Plan Docker",
"enableDockerSocketDescription": "Activer le ramassage d'étiquettes de socket Docker pour les étiquettes de plan. Le chemin de socket doit être fourni à Newt.",
"enableDockerSocketLink": "En savoir plus",
@@ -2080,5 +2080,20 @@
"supportSending": "Envoi...",
"supportSend": "Envoyer",
"supportMessageSent": "Message envoyé !",
"supportWillContact": "Nous vous contacterons sous peu!"
}
"supportWillContact": "Nous vous contacterons sous peu!",
"selectLogRetention": "Sélectionner la durée de rétention du journal",
"showColumns": "Afficher les colonnes",
"hideColumns": "Cacher les colonnes",
"columnVisibility": "Visibilité des colonnes",
"toggleColumn": "Activer/désactiver la colonne {columnName}",
"allColumns": "Toutes les colonnes",
"defaultColumns": "Colonnes par défaut",
"customizeView": "Personnaliser la vue",
"viewOptions": "Voir les options",
"selectAll": "Tout sélectionner",
"selectNone": "Ne rien sélectionner",
"selectedResources": "Ressources sélectionnées",
"enableSelected": "Activer la sélection",
"disableSelected": "Désactiver la sélection",
"checkSelectedStatus": "Vérifier le statut de la sélection"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Scadenza In",
"neverExpire": "Mai scadere",
"shareExpireDescription": "Il tempo di scadenza è per quanto tempo il link sarà utilizzabile e fornirà accesso alla risorsa. Dopo questo tempo, il link non funzionerà più e gli utenti che hanno utilizzato questo link perderanno l'accesso alla risorsa.",
"shareSeeOnce": "Potrai vedere solo questo linkonce. Assicurati di copiarlo.",
"shareSeeOnce": "Potrai vedere questo link solo una volta. Assicurati di copiarlo.",
"shareAccessHint": "Chiunque abbia questo link può accedere alla risorsa. Condividilo con cura.",
"shareTokenUsage": "Vedi Utilizzo Token Di Accesso",
"createLink": "Crea Collegamento",
@@ -179,7 +179,7 @@
"baseDomain": "Dominio Base",
"subdomnainDescription": "Il sottodominio in cui la tua risorsa sarà accessibile.",
"resourceRawSettings": "Impostazioni TCP/UDP",
"resourceRawSettingsDescription": "Configura come accedere alla tua risorsa tramite TCP/UDP",
"resourceRawSettingsDescription": "Configura come sarà possibile accedere alla tua risorsa tramite TCP/UDP. Mappare la risorsa a una porta sul server host Pangolin, in modo da poter accedere alla risorsa dal server-public ip:mapped-port.",
"protocol": "Protocollo",
"protocolSelect": "Seleziona un protocollo",
"resourcePortNumber": "Numero Porta",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Domini",
"sidebarBluePrints": "Progetti",
"blueprints": "Progetti",
"blueprintsDescription": "I progetti sono configurazioni YAML dichiarative che definiscono le tue risorse e le loro impostazioni",
"blueprintsDescription": "Applica le configurazioni dichiarative e visualizza le partite precedenti",
"blueprintAdd": "Aggiungi Progetto",
"blueprintGoBack": "Vedi tutti i progetti",
"blueprintCreate": "Crea Progetto",
"blueprintCreateDescription2": "Segui i passaggi qui sotto per creare e applicare un nuovo progetto",
"blueprintDetails": "Dettagli progetto",
"blueprintDetailsDescription": "Vedi i dettagli dell'esecuzione del progetto",
"blueprintDetails": "Dettagli Progetto",
"blueprintDetailsDescription": "Vedere il risultato del progetto applicato e gli eventuali errori verificatisi",
"blueprintInfo": "Informazioni Sul Progetto",
"message": "Messaggio",
"blueprintContentsDescription": "Definisci il contenuto di YAML che descrive la tua infrastruttura",
@@ -1181,7 +1181,7 @@
"appliedAt": "Applicato Il",
"source": "Fonte",
"contents": "Contenuti",
"parsedContents": "Sommario Analizzato",
"parsedContents": "Sommario Analizzato (Solo Lettura)",
"enableDockerSocket": "Abilita Progetto Docker",
"enableDockerSocketDescription": "Abilita la raschiatura dell'etichetta Docker Socket per le etichette dei progetti. Il percorso del socket deve essere fornito a Newt.",
"enableDockerSocketLink": "Scopri di più",
@@ -2080,5 +2080,20 @@
"supportSending": "Invio...",
"supportSend": "Invia",
"supportMessageSent": "Messaggio Inviato!",
"supportWillContact": "Saremo in contatto a breve!"
}
"supportWillContact": "Saremo in contatto a breve!",
"selectLogRetention": "Seleziona ritenzione log",
"showColumns": "Mostra Colonne",
"hideColumns": "Nascondi Colonne",
"columnVisibility": "Visibilità Colonna",
"toggleColumn": "Attiva/disattiva colonna {columnName}",
"allColumns": "Tutte Le Colonne",
"defaultColumns": "Colonne Predefinite",
"customizeView": "Personalizza Vista",
"viewOptions": "Opzioni Visualizzazione",
"selectAll": "Seleziona Tutto",
"selectNone": "Seleziona Nessuno",
"selectedResources": "Risorse Selezionate",
"enableSelected": "Abilita Selezionati",
"disableSelected": "Disabilita Selezionati",
"checkSelectedStatus": "Controlla lo stato dei selezionati"
}

View File

@@ -179,7 +179,7 @@
"baseDomain": "기본 도메인",
"subdomnainDescription": "리소스에 접근할 수 있는 하위 도메인입니다.",
"resourceRawSettings": "TCP/UDP 설정",
"resourceRawSettingsDescription": "TCP/UDP를 통해 리소스에 접근하는 방법을 구성하세요.",
"resourceRawSettingsDescription": "리소스를 TCP/UDP를 통해 액세스하는 방법을 구성합니다. 리소스를 호스트 Pangolin 서버의 포트에 매핑하여 서버-public-ip:매핑된 포트에서 리소스에 액세스할 수 있습니다.",
"protocol": "프로토콜",
"protocolSelect": "프로토콜 선택",
"resourcePortNumber": "포트 번호",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "도메인",
"sidebarBluePrints": "청사진",
"blueprints": "청사진",
"blueprintsDescription": "청사진은 리소스와 그 설정을 정의하는 선언적인 YAML 구성입니다",
"blueprintsDescription": "선언적 구성을 적용하고 이전 실행을 봅니다",
"blueprintAdd": "청사진 추가",
"blueprintGoBack": "모든 청사진 보기",
"blueprintCreate": "청사진 생성",
"blueprintCreateDescription2": "새 청사진을 생성하고 적용하려면 아래 단계를 따르십시오",
"blueprintDetails": "청사진 세부 사항",
"blueprintDetailsDescription": "청사진 실행 세부 정보 보기",
"blueprintDetails": "청사진 세부사항",
"blueprintDetailsDescription": "적용된 청사진의 결과와 발생한 오류를 확인합니다",
"blueprintInfo": "청사진 정보",
"message": "메시지",
"blueprintContentsDescription": "인프라를 설명하는 YAML 콘텐츠를 정의하십시오",
@@ -1181,7 +1181,7 @@
"appliedAt": "적용 시점",
"source": "출처",
"contents": "콘텐츠",
"parsedContents": "구문 분석된 콘텐츠",
"parsedContents": "구문 분석된 콘텐츠 (읽기 전용)",
"enableDockerSocket": "Docker 청사진 활성화",
"enableDockerSocketDescription": "블루프린트 레이블을 위한 Docker 소켓 레이블 수집을 활성화합니다. 소켓 경로는 Newt에 제공되어야 합니다.",
"enableDockerSocketLink": "자세히 알아보기",
@@ -2080,5 +2080,20 @@
"supportSending": "발송 중...",
"supportSend": "보내기",
"supportMessageSent": "메시지 전송 완료!",
"supportWillContact": "곧 연락드리겠습니다!"
}
"supportWillContact": "곧 연락드리겠습니다!",
"selectLogRetention": "로그 보존 선택",
"showColumns": "열 표시",
"hideColumns": "열 숨기기",
"columnVisibility": "열 가시성",
"toggleColumn": "{columnName} 열 토글",
"allColumns": "모든 열",
"defaultColumns": "기본 열",
"customizeView": "보기 사용자 지정",
"viewOptions": "보기 옵션",
"selectAll": "모두 선택",
"selectNone": "선택하지 않음",
"selectedResources": "선택된 리소스",
"enableSelected": "선택된 항목 활성화",
"disableSelected": "선택된 항목 비활성화",
"checkSelectedStatus": "선택된 항목 상태 확인"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Utløper om",
"neverExpire": "Utløper aldri",
"shareExpireDescription": "Utløpstid er hvor lenge lenken vil være brukbar og gi tilgang til ressursen. Etter denne tiden vil lenken ikke lenger fungere, og brukere som brukte denne lenken vil miste tilgangen til ressursen.",
"shareSeeOnce": "Du får bare se denne lenken én gang. Pass på å kopiere den.",
"shareSeeOnce": "Du vil bare kunne se denne linken én gang. Pass på å kopiere den.",
"shareAccessHint": "Alle med denne lenken kan få tilgang til ressursen. Del forsiktig.",
"shareTokenUsage": "Se tilgangstokenbruk",
"createLink": "Opprett lenke",
@@ -179,7 +179,7 @@
"baseDomain": "Grunndomene",
"subdomnainDescription": "Underdomenet der ressursen din vil være tilgjengelig.",
"resourceRawSettings": "TCP/UDP-innstillinger",
"resourceRawSettingsDescription": "Konfigurer tilgang til ressursen din over TCP/UDP",
"resourceRawSettingsDescription": "Konfigurer hvordan din ressurs vil bli tilgjengelig over TCP/UDP. Du kartlegger ressursen til en port på vertsserveren Pangolin slik at du får tilgang til ressursene fra server-ip:mappet port.",
"protocol": "Protokoll",
"protocolSelect": "Velg en protokoll",
"resourcePortNumber": "Portnummer",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Domener",
"sidebarBluePrints": "Tegninger",
"blueprints": "Tegninger",
"blueprintsDescription": "Tegninger er deklarative YAML konfigurasjoner som definerer dine ressurser og deres innstillinger",
"blueprintsDescription": "Bruk deklarative konfigurasjoner og vis tidligere kjøringer",
"blueprintAdd": "Legg til blåkopi",
"blueprintGoBack": "Se alle blåkopier",
"blueprintCreate": "Opprette mal",
"blueprintCreateDescription2": "Følg trinnene nedenfor for å opprette og bruke en ny plantegning",
"blueprintDetails": "Blåkopi detaljer",
"blueprintDetailsDescription": "Se detaljer om plantegning",
"blueprintDetailsDescription": "Se resultatet av den påførte blåkopien og alle feil som oppstod",
"blueprintInfo": "Blåkopi informasjon",
"message": "Melding",
"blueprintContentsDescription": "Definer innhold av YAML som beskriver din infrastruktur",
@@ -1181,7 +1181,7 @@
"appliedAt": "Anvendt på",
"source": "Kilde",
"contents": "Innhold",
"parsedContents": "Parket innhold",
"parsedContents": "Parastinnhold (kun lese)",
"enableDockerSocket": "Aktiver Docker blåkopi",
"enableDockerSocketDescription": "Aktiver skraping av Docker Socket for blueprint Etiketter. Socket bane må brukes for nye.",
"enableDockerSocketLink": "Lær mer",
@@ -2080,5 +2080,20 @@
"supportSending": "Sender...",
"supportSend": "Sende",
"supportMessageSent": "Melding sendt!",
"supportWillContact": "Vi kommer raskt til å ta kontakt!"
}
"supportWillContact": "Vi kommer raskt til å ta kontakt!",
"selectLogRetention": "Velg oppbevaring av logg",
"showColumns": "Vis kolonner",
"hideColumns": "Skjul kolonner",
"columnVisibility": "Kolonne Synlighet",
"toggleColumn": "Veksle {columnName} kolonne",
"allColumns": "Alle kolonner",
"defaultColumns": "Standard kolonner",
"customizeView": "Tilpass visning",
"viewOptions": "Vis alternativer",
"selectAll": "Velg alle",
"selectNone": "Velg ingen",
"selectedResources": "Valgte ressurser",
"enableSelected": "Aktiver valgte",
"disableSelected": "Deaktiver valgte",
"checkSelectedStatus": "Kontroller status for valgte"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Vervalt in",
"neverExpire": "Nooit verlopen",
"shareExpireDescription": "Vervaltijd is hoe lang de link bruikbaar is en geeft toegang tot de bron. Na deze tijd zal de link niet meer werken en zullen gebruikers die deze link hebben gebruikt de toegang tot de pagina verliezen.",
"shareSeeOnce": "Je kunt deze koppeling alleen zien. Zorg ervoor dat je het kopieert.",
"shareSeeOnce": "U kunt deze link slechts één keer zien. Zorg ervoor dat u deze kopieert.",
"shareAccessHint": "Iedereen met deze link heeft toegang tot de bron. Deel deze met zorg.",
"shareTokenUsage": "Zie Toegangstoken Gebruik",
"createLink": "Koppeling aanmaken",
@@ -179,7 +179,7 @@
"baseDomain": "Basis domein",
"subdomnainDescription": "Het subdomein waar de bron toegankelijk is.",
"resourceRawSettings": "TCP/UDP instellingen",
"resourceRawSettingsDescription": "Stel in hoe je bron wordt benaderd via TCP/UDP",
"resourceRawSettingsDescription": "Stel in hoe uw bron wordt benaderd via TCP/UDP. Je gooit de bron toe aan een poort op de host-Pangolin server, zodat je de bron kan bereiken vanaf server-public-ip:mapped-port.",
"protocol": "Protocol",
"protocolSelect": "Selecteer een protocol",
"resourcePortNumber": "Nummer van poort",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Domeinen",
"sidebarBluePrints": "Blauwdrukken",
"blueprints": "Blauwdrukken",
"blueprintsDescription": "Blauwdrukken zijn declaratieve YAML-configuraties die je bronnen en hun instellingen bepalen",
"blueprintsDescription": "Gebruik declaratieve configuraties en bekijk vorige uitvoeringen.",
"blueprintAdd": "Blauwdruk toevoegen",
"blueprintGoBack": "Bekijk alle Blauwdrukken",
"blueprintCreate": "Creëer blauwdruk",
"blueprintCreateDescription2": "Volg de onderstaande stappen om een nieuwe blauwdruk te maken en toe te passen",
"blueprintDetails": "Blauwdruk details",
"blueprintDetailsDescription": "Bekijk de blauwdruk run details",
"blueprintDetails": "Blauwdruk Details",
"blueprintDetailsDescription": "Bekijk het resultaat van de toegepaste blauwdruk en eventuele fouten",
"blueprintInfo": "Blauwdruk Informatie",
"message": "bericht",
"blueprintContentsDescription": "Definieer de YAML content die je infrastructuur beschrijft",
@@ -1181,7 +1181,7 @@
"appliedAt": "Toegepast op",
"source": "Bron",
"contents": "Inhoud",
"parsedContents": "Geparseerde inhoud",
"parsedContents": "Geparseerde inhoud (alleen lezen)",
"enableDockerSocket": "Schakel Docker Blauwdruk in",
"enableDockerSocketDescription": "Schakel Docker Socket label in voor blauwdruk labels. Pad naar Nieuw.",
"enableDockerSocketLink": "Meer informatie",
@@ -2080,5 +2080,20 @@
"supportSending": "Verzenden...",
"supportSend": "Verzenden",
"supportMessageSent": "Bericht verzonden!",
"supportWillContact": "We nemen binnenkort contact met u op!"
}
"supportWillContact": "We nemen binnenkort contact met u op!",
"selectLogRetention": "Selecteer log retentie",
"showColumns": "Kolommen weergeven",
"hideColumns": "Kolommen verbergen",
"columnVisibility": "Zichtbaarheid kolommen",
"toggleColumn": "{columnName} kolom in-/uitschakelen",
"allColumns": "Alle kolommen",
"defaultColumns": "Standaard Kolommen",
"customizeView": "Weergave aanpassen",
"viewOptions": "Bekijk opties",
"selectAll": "Alles selecteren",
"selectNone": "Niets selecteren",
"selectedResources": "Geselecteerde bronnen",
"enableSelected": "Selectie inschakelen",
"disableSelected": "Selectie uitschakelen",
"checkSelectedStatus": "Controleer de status van de geselecteerde"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Wygasa za",
"neverExpire": "Nigdy nie wygasa",
"shareExpireDescription": "Czas wygaśnięcia to jak długo link będzie mógł być użyty i zapewni dostęp do zasobu. Po tym czasie link nie będzie już działał, a użytkownicy, którzy użyli tego linku, utracą dostęp do zasobu.",
"shareSeeOnce": "Możesz zobaczyć tylko ten link. Upewnij się, że go skopiowało.",
"shareSeeOnce": "Możesz zobaczyć ten link tylko raz. Pamiętaj, aby go skopiować.",
"shareAccessHint": "Każdy z tym linkiem może uzyskać dostęp do zasobu. Podziel się nim ostrożnie.",
"shareTokenUsage": "Zobacz użycie tokenu dostępu",
"createLink": "Utwórz link",
@@ -179,7 +179,7 @@
"baseDomain": "Bazowa domena",
"subdomnainDescription": "Poddomena, w której twój zasób będzie dostępny.",
"resourceRawSettings": "Ustawienia TCP/UDP",
"resourceRawSettingsDescription": "Skonfiguruj jak twój zasób będzie dostępny przez TCP/UDP",
"resourceRawSettingsDescription": "Skonfiguruj jak twój zasób będzie dostępny przez TCP/UDP. Zmapujesz zasób do portu na serwerze hosta Pangolin, dzięki czemu możesz uzyskać dostęp do zasobu z serwera-public ip:mapped-port.",
"protocol": "Protokół",
"protocolSelect": "Wybierz protokół",
"resourcePortNumber": "Numer portu",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Domeny",
"sidebarBluePrints": "Schematy",
"blueprints": "Schematy",
"blueprintsDescription": "Plany to deklaratywne konfiguracje YAML, które definiują twoje zasoby i ich ustawienia",
"blueprintsDescription": "Zastosuj konfiguracje deklaracyjne i wyświetl poprzednie operacje",
"blueprintAdd": "Dodaj schemat",
"blueprintGoBack": "Zobacz wszystkie schematy",
"blueprintCreate": "Utwórz schemat",
"blueprintCreateDescription2": "Wykonaj poniższe kroki, aby utworzyć i zastosować nowy schemat",
"blueprintDetails": "Szczegóły projektu",
"blueprintDetailsDescription": "Zobacz szczegóły uruchomienia schematu",
"blueprintDetails": "Szczegóły Projektu",
"blueprintDetailsDescription": "Zobacz wynik zastosowanego schematu i wszelkie błędy, które wystąpiły",
"blueprintInfo": "Informacje o projekcie",
"message": "Wiadomość",
"blueprintContentsDescription": "Zdefiniuj zawartość YAML opisującą Twoją infrastrukturę",
@@ -1181,7 +1181,7 @@
"appliedAt": "Zastosowano",
"source": "Źródło",
"contents": "Treść",
"parsedContents": "Przetworzona zawartość",
"parsedContents": "Przetworzona zawartość (tylko do odczytu)",
"enableDockerSocket": "Włącz schemat dokera",
"enableDockerSocketDescription": "Włącz etykietowanie kieszeni dokującej dla etykiet schematów. Ścieżka do gniazda musi być dostarczona do Newt.",
"enableDockerSocketLink": "Dowiedz się więcej",
@@ -2080,5 +2080,20 @@
"supportSending": "Wysyłanie...",
"supportSend": "Wyślij",
"supportMessageSent": "Wiadomość wysłana!",
"supportWillContact": "Wkrótce będziemy w kontakcie!"
}
"supportWillContact": "Wkrótce będziemy w kontakcie!",
"selectLogRetention": "Wybierz zatrzymanie dziennika",
"showColumns": "Pokaż kolumny",
"hideColumns": "Ukryj kolumny",
"columnVisibility": "Widoczność kolumn",
"toggleColumn": "Przełącz kolumnę {columnName}",
"allColumns": "Wszystkie kolumny",
"defaultColumns": "Kolumny domyślne",
"customizeView": "Dostosuj widok",
"viewOptions": "Opcje widoku",
"selectAll": "Zaznacz wszystko",
"selectNone": "Nie wybierz żadnego",
"selectedResources": "Wybrane Zasoby",
"enableSelected": "Włącz zaznaczone",
"disableSelected": "Wyłącz zaznaczone",
"checkSelectedStatus": "Sprawdź status zaznaczonych"
}

View File

@@ -179,7 +179,7 @@
"baseDomain": "Domínio Base",
"subdomnainDescription": "O subdomínio onde seu recurso estará acessível.",
"resourceRawSettings": "Configurações TCP/UDP",
"resourceRawSettingsDescription": "Configure como seu recurso será acessado sobre TCP/UDP",
"resourceRawSettingsDescription": "Configure como seu recurso será acessado sobre TCP/UDP. Você mapeia o recurso para uma porta no servidor Pangolin do hospedeiro, para que você possa acessar o recurso do server-public-ip:mapped-port.",
"protocol": "Protocolo",
"protocolSelect": "Selecione um protocolo",
"resourcePortNumber": "Número da Porta",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Domínios",
"sidebarBluePrints": "Diagramas",
"blueprints": "Diagramas",
"blueprintsDescription": "Diagramas são configurações declarativas YAML que definem seus recursos e suas configurações",
"blueprintsDescription": "Aplicar configurações declarativas e ver execuções anteriores",
"blueprintAdd": "Adicionar Diagrama",
"blueprintGoBack": "Ver todos os Diagramas",
"blueprintCreate": "Criar Diagrama",
"blueprintCreateDescription2": "Siga as etapas abaixo para criar e aplicar um novo diagrama",
"blueprintDetails": "Detalhes do Diagrama",
"blueprintDetailsDescription": "Veja os detalhes da execução do diagrama",
"blueprintDetailsDescription": "Veja o resultado do diagrama aplicado e todos os erros que ocorreram",
"blueprintInfo": "Informação do Diagrama",
"message": "mensagem",
"blueprintContentsDescription": "Defina o conteúdo YAML descrevendo a sua infraestrutura",
@@ -1181,7 +1181,7 @@
"appliedAt": "Aplicado em",
"source": "fonte",
"contents": "Conteúdo",
"parsedContents": "Conteúdo analisado",
"parsedContents": "Conteúdo analisado (Somente Leitura)",
"enableDockerSocket": "Habilitar o Diagrama Docker",
"enableDockerSocketDescription": "Ativar a scraping de rótulo Docker para rótulos de diagramas. Caminho de Socket deve ser fornecido para Newt.",
"enableDockerSocketLink": "Saiba mais",
@@ -2080,5 +2080,20 @@
"supportSending": "Enviando...",
"supportSend": "Mandar",
"supportMessageSent": "Mensagem enviada!",
"supportWillContact": "Entraremos em contato em breve!"
}
"supportWillContact": "Entraremos em contato em breve!",
"selectLogRetention": "Selecionar retenção de log",
"showColumns": "Exibir Colunas",
"hideColumns": "Ocultar colunas",
"columnVisibility": "Visibilidade da Coluna",
"toggleColumn": "Alternar coluna {columnName}",
"allColumns": "Todas as colunas",
"defaultColumns": "Colunas padrão",
"customizeView": "Personalizar visualização",
"viewOptions": "Opções de visualização",
"selectAll": "Selecionar Todos",
"selectNone": "Não selecionar nada",
"selectedResources": "Recursos Selecionados",
"enableSelected": "Habilitar Selecionados",
"disableSelected": "Desativar Selecionados",
"checkSelectedStatus": "Status de Verificação dos Selecionados"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Срок действия",
"neverExpire": "Бессрочный доступ",
"shareExpireDescription": "Срок действия - это период, в течение которого ссылка будет работать и предоставлять доступ к ресурсу. После этого времени ссылка перестанет работать, и пользователи, использовавшие эту ссылку, потеряют доступ к ресурсу.",
"shareSeeOnce": "Вы сможете увидеть эту ссылку только один раз. Обязательно скопируйте её.",
"shareSeeOnce": "Вы сможете увидеть эту ссылку только один раз. Обязательно скопируйте ее.",
"shareAccessHint": "Любой, у кого есть эта ссылка, может получить доступ к ресурсу. Делитесь ею с осторожностью.",
"shareTokenUsage": "Посмотреть использование токена доступа",
"createLink": "Создать ссылку",
@@ -179,7 +179,7 @@
"baseDomain": "Базовый домен",
"subdomnainDescription": "Поддомен, на котором будет доступен ресурс.",
"resourceRawSettings": "Настройки TCP/UDP",
"resourceRawSettingsDescription": "Настройте, как будет осуществляться доступ к вашему ресурсу через TCP/UDP",
"resourceRawSettingsDescription": "Настройте доступ к вашему ресурсу по TCP/UDP. Вы соотносите ресурс с портом на сервере хоста Pangolin, так что вы можете получить доступ к ресурсу с сервера server-public-ip:mapped-порта.",
"protocol": "Протокол",
"protocolSelect": "Выберите протокол",
"resourcePortNumber": "Номер порта",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Домены",
"sidebarBluePrints": "Чертежи",
"blueprints": "Чертежи",
"blueprintsDescription": "Чертежи являются декларативными конфигурациями YAML, которые определяют ваши ресурсы и их настройки",
"blueprintsDescription": "Применить декларирующие конфигурации и просмотреть предыдущие запуски",
"blueprintAdd": "Добавить чертёж",
"blueprintGoBack": "Посмотреть все чертежи",
"blueprintCreate": "Создать чертёж",
"blueprintCreateDescription2": "Для создания и применения нового чертежа выполните следующие шаги",
"blueprintDetails": "Подробности чертежа",
"blueprintDetailsDescription": "Посмотреть детали запуска чертежа",
"blueprintDetails": "Детали чертежа",
"blueprintDetailsDescription": "Посмотреть результат примененного чертежа и все возникшие ошибки",
"blueprintInfo": "Информация о чертеже",
"message": "Сообщение",
"blueprintContentsDescription": "Определите содержимое YAML, описывающее вашу инфраструктуру",
@@ -1181,7 +1181,7 @@
"appliedAt": "Заявка на",
"source": "Источник",
"contents": "Содержание",
"parsedContents": "Обработанное содержимое",
"parsedContents": "Переработанное содержимое (только для чтения)",
"enableDockerSocket": "Включить чертёж Docker",
"enableDockerSocketDescription": "Включить scraping ярлыка Docker Socket для ярлыков чертежей. Путь к сокету должен быть предоставлен в Newt.",
"enableDockerSocketLink": "Узнать больше",
@@ -2080,5 +2080,20 @@
"supportSending": "Отправка...",
"supportSend": "Отправить",
"supportMessageSent": "Сообщение отправлено!",
"supportWillContact": "Мы скоро свяжемся с Вами!"
}
"supportWillContact": "Мы скоро свяжемся с Вами!",
"selectLogRetention": "Выберите удержание журнала",
"showColumns": "Показать колонки",
"hideColumns": "Скрыть столбцы",
"columnVisibility": "Видимость столбцов",
"toggleColumn": "Столбец {columnName}",
"allColumns": "Все колонки",
"defaultColumns": "Столбцы по умолчанию",
"customizeView": "Настроить вид",
"viewOptions": "Параметры просмотра",
"selectAll": "Выделить все",
"selectNone": "Не выбирать",
"selectedResources": "Выбранные ресурсы",
"enableSelected": "Включить выбранные",
"disableSelected": "Отключить выбранные",
"checkSelectedStatus": "Проверить статус выбранных"
}

View File

@@ -179,7 +179,7 @@
"baseDomain": "Temel Alan Adı",
"subdomnainDescription": "Kaynağınızın erişilebileceği alt alan adı.",
"resourceRawSettings": "TCP/UDP Ayarları",
"resourceRawSettingsDescription": "Kaynağınıza TCP/UDP üzerinden erişimin nasıl sağlanacağını yapılandırın",
"resourceRawSettingsDescription": "Kaynağınızın TCP/UDP üzerinden nasıl erişileceğini yapılandırın. Kaynağı, sunucudan erişebilmeniz için bir ana bilgisayar Pangolin sunucusundaki bir bağlantı noktasına eşlersiniz: sunucu genel-IP: eşlenen-bağlantı-noktası.",
"protocol": "Protokol",
"protocolSelect": "Bir protokol seçin",
"resourcePortNumber": "Port Numarası",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "Alan Adları",
"sidebarBluePrints": "Planlar",
"blueprints": "Planlar",
"blueprintsDescription": "Planlar, kaynaklarınızı ve ayarlarını tanımlayan bildirimsel YAML yapılandırmalarıdır",
"blueprintsDescription": "Deklaratif yapılandırmaları uygulayın ve önceki çalışmaları görüntüleyin",
"blueprintAdd": "Plan Ekle",
"blueprintGoBack": "Tüm Planları Gör",
"blueprintCreate": "Plan Oluştur",
"blueprintCreateDescription2": "Yeni bir plan oluşturup uygulamak için aşağıdaki adımları izleyin",
"blueprintDetails": "Plan Detayları",
"blueprintDetailsDescription": "Plan çalıştırma detaylarını görün",
"blueprintDetails": "Mavi Yazılım Detayları",
"blueprintDetailsDescription": "Uygulanan mavi yazılımın sonucunu ve oluşan hataları görün",
"blueprintInfo": "Plan Bilgileri",
"message": "Mesaj",
"blueprintContentsDescription": "Altyapınızı tanımlayan YAML içeriğini tanımlayın",
@@ -1181,7 +1181,7 @@
"appliedAt": "Uygulama Zamanı",
"source": "Kaynak",
"contents": "İçerik",
"parsedContents": "Ayrıştırılmış İçerik",
"parsedContents": "Verilerin Ayrıştırılmış İçeriği (Salt Okunur)",
"enableDockerSocket": "Docker Soketini Etkinleştir",
"enableDockerSocketDescription": "Plan etiketleri için Docker Socket etiket toplamasını etkinleştirin. Newt'e soket yolu sağlanmalıdır.",
"enableDockerSocketLink": "Daha fazla bilgi",
@@ -2080,5 +2080,20 @@
"supportSending": "Gönderiliyor...",
"supportSend": "Gönder",
"supportMessageSent": "Mesaj Gönderildi!",
"supportWillContact": "En kısa sürede size geri döneceğiz!"
}
"supportWillContact": "En kısa sürede size geri döneceğiz!",
"selectLogRetention": "Kayıt saklama seç",
"showColumns": "Sütunları Göster",
"hideColumns": "Sütunları Gizle",
"columnVisibility": "Sütun Görünürlüğü",
"toggleColumn": "{columnName} sütununu aç/kapat",
"allColumns": "Tüm Sütunlar",
"defaultColumns": "Varsayılan Sütunlar",
"customizeView": "Görünümü Özelleştir",
"viewOptions": "Görünüm Seçenekleri",
"selectAll": "Tümünü Seç",
"selectNone": "Hiçbirini Seçme",
"selectedResources": "Seçilen Kaynaklar",
"enableSelected": "Seçilenleri Etkinleştir",
"disableSelected": "Seçilenleri Devre Dışı Bırak",
"checkSelectedStatus": "Seçilenlerin Durumunu Kontrol Et"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "过期时间",
"neverExpire": "永不过期",
"shareExpireDescription": "过期时间是链接可以使用并提供对资源的访问时间。 此时间后,链接将不再工作,使用此链接的用户将失去对资源的访问。",
"shareSeeOnce": "您只能看到此链接。请确保复制它。",
"shareSeeOnce": "您只能看到一次此链接。请确保复制它。",
"shareAccessHint": "任何具有此链接的人都可以访问该资源。小心地分享它。",
"shareTokenUsage": "查看访问令牌使用情况",
"createLink": "创建链接",
@@ -179,7 +179,7 @@
"baseDomain": "根域名",
"subdomnainDescription": "您的资源可以访问的子域名。",
"resourceRawSettings": "TCP/UDP 设置",
"resourceRawSettingsDescription": "配置如何通过 TCP/UDP 访问您的资源",
"resourceRawSettingsDescription": "配置如何通过 TCP/UDP 访问您的资源。 您映射资源到主机Pangolin服务器上的端口这样您就可以访问服务器-公共-ip:mapped端口的资源。",
"protocol": "协议",
"protocolSelect": "选择协议",
"resourcePortNumber": "端口号",
@@ -1165,13 +1165,13 @@
"sidebarDomains": "域",
"sidebarBluePrints": "蓝图",
"blueprints": "蓝图",
"blueprintsDescription": "蓝图是用于定义资源及其设置的 YAML 声明配置",
"blueprintsDescription": "应用声明配置并查看先前运行的",
"blueprintAdd": "添加蓝图",
"blueprintGoBack": "查看所有蓝图",
"blueprintCreate": "创建蓝图",
"blueprintCreateDescription2": "按照下面的步骤创建和应用新的蓝图",
"blueprintDetails": "蓝图详细信息",
"blueprintDetailsDescription": "查看蓝图运行详情",
"blueprintDetailsDescription": "查看应用蓝图的结果和发生的任何错误",
"blueprintInfo": "蓝图信息",
"message": "留言",
"blueprintContentsDescription": "定义描述您基础设施的 YAML 内容",
@@ -1181,7 +1181,7 @@
"appliedAt": "应用于",
"source": "来源",
"contents": "目录",
"parsedContents": "解析内容",
"parsedContents": "解析内容 (只读)",
"enableDockerSocket": "启用 Docker 蓝图",
"enableDockerSocketDescription": "启用 Docker Socket 标签擦除蓝图标签。套接字路径必须提供给新的。",
"enableDockerSocketLink": "了解更多",
@@ -2080,5 +2080,20 @@
"supportSending": "正在发送...",
"supportSend": "发送",
"supportMessageSent": "消息已发送!",
"supportWillContact": "我们很快就会联系起来!"
}
"supportWillContact": "我们很快就会联系起来!",
"selectLogRetention": "选择保留日志",
"showColumns": "显示列",
"hideColumns": "隐藏列",
"columnVisibility": "列可见性",
"toggleColumn": "切换 {columnName} 列",
"allColumns": "全部列",
"defaultColumns": "默认列",
"customizeView": "自定义视图",
"viewOptions": "查看选项",
"selectAll": "选择所有",
"selectNone": "没有选择",
"selectedResources": "选定的资源",
"enableSelected": "启用选中的",
"disableSelected": "禁用选中的",
"checkSelectedStatus": "检查选中的状态"
}

1917
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@
},
"dependencies": {
"@asteasolutions/zod-to-openapi": "^7.3.4",
"@aws-sdk/client-s3": "3.908.0",
"@aws-sdk/client-s3": "3.922.0",
"@hookform/resolvers": "5.2.2",
"@monaco-editor/react": "^4.7.0",
"@node-rs/argon2": "^2.0.2",
@@ -65,7 +65,7 @@
"@tailwindcss/forms": "^0.5.10",
"@tanstack/react-table": "8.21.3",
"arctic": "^3.7.0",
"axios": "^1.12.2",
"axios": "^1.13.1",
"better-sqlite3": "11.7.0",
"canvas-confetti": "1.9.4",
"class-variance-authority": "^0.7.1",
@@ -78,10 +78,10 @@
"crypto-js": "^4.2.0",
"date-fns": "4.1.0",
"drizzle-orm": "0.44.7",
"eslint": "9.37.0",
"eslint-config-next": "15.5.6",
"eslint": "9.39.0",
"eslint-config-next": "16.0.1",
"express": "5.1.0",
"express-rate-limit": "8.1.0",
"express-rate-limit": "8.2.1",
"glob": "11.0.3",
"helmet": "8.1.0",
"http-errors": "2.0.0",
@@ -91,11 +91,11 @@
"jmespath": "^0.16.0",
"js-yaml": "4.1.0",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.545.0",
"lucide-react": "^0.552.0",
"maxmind": "5.0.0",
"moment": "2.30.1",
"next": "15.5.6",
"next-intl": "^4.3.12",
"next-intl": "^4.4.0",
"next-themes": "0.4.6",
"nextjs-toploader": "^3.9.17",
"node-cache": "5.1.2",
@@ -105,17 +105,17 @@
"nprogress": "^0.2.0",
"oslo": "1.2.1",
"pg": "^8.16.2",
"posthog-node": "^5.10.4",
"posthog-node": "^5.11.0",
"qrcode.react": "4.2.0",
"react": "19.2.0",
"react-day-picker": "9.11.1",
"react-dom": "19.2.0",
"react-easy-sort": "^1.8.0",
"react-hook-form": "7.65.0",
"react-hook-form": "7.66.0",
"react-icons": "^5.5.0",
"rebuild": "0.1.2",
"reodotdev": "^1.0.0",
"resend": "^6.1.2",
"resend": "^6.4.0",
"semver": "^7.7.3",
"stripe": "18.2.1",
"swagger-ui-express": "^5.0.1",
@@ -133,10 +133,10 @@
"@faker-js/faker": "^10.1.0"
},
"devDependencies": {
"@dotenvx/dotenvx": "1.51.0",
"@dotenvx/dotenvx": "1.51.1",
"@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/preview-server": "4.3.2",
"@tailwindcss/postcss": "^4.1.16",
"@tailwindcss/postcss": "^4.1.17",
"@types/better-sqlite3": "7.6.12",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
@@ -157,7 +157,7 @@
"@types/ws": "8.18.1",
"@types/yargs": "17.0.34",
"drizzle-kit": "0.31.6",
"esbuild": "0.25.11",
"esbuild": "0.25.12",
"esbuild-node-externals": "1.18.0",
"postcss": "^8",
"react-email": "4.3.2",
@@ -165,7 +165,7 @@
"tsc-alias": "1.8.16",
"tsx": "4.20.6",
"typescript": "^5",
"typescript-eslint": "^8.46.2"
"typescript-eslint": "^8.46.3"
},
"overrides": {
"emblor": {

View File

@@ -34,11 +34,7 @@ export async function applyNewtDockerBlueprint(
return;
}
if (isEmptyObject(blueprint["proxy-resources"])) {
return;
}
if (isEmptyObject(blueprint["client-resources"])) {
if (isEmptyObject(blueprint["proxy-resources"]) && isEmptyObject(blueprint["client-resources"])) {
return;
}

View File

@@ -30,6 +30,7 @@ import { pickPort } from "@server/routers/target/helpers";
import { resourcePassword } from "@server/db";
import { hashPassword } from "@server/auth/password";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { get } from "http";
export type ProxyResourcesResults = {
proxyResource: Resource;
@@ -114,7 +115,12 @@ export async function updateProxyResources(
internalPort: internalPortToCreate,
path: targetData.path,
pathMatchType: targetData["path-match"],
rewritePath: targetData.rewritePath,
rewritePath:
targetData.rewritePath ||
targetData["rewrite-path"] ||
(targetData["rewrite-match"] === "stripPrefix"
? "/"
: undefined),
rewritePathType: targetData["rewrite-match"],
priority: targetData.priority
})
@@ -139,10 +145,14 @@ export async function updateProxyResources(
hcHostname: healthcheckData?.hostname,
hcPort: healthcheckData?.port,
hcInterval: healthcheckData?.interval,
hcUnhealthyInterval: healthcheckData?.unhealthyInterval,
hcUnhealthyInterval:
healthcheckData?.unhealthyInterval ||
healthcheckData?.["unhealthy-interval"],
hcTimeout: healthcheckData?.timeout,
hcHeaders: hcHeaders,
hcFollowRedirects: healthcheckData?.followRedirects,
hcFollowRedirects:
healthcheckData?.followRedirects ||
healthcheckData?.["follow-redirects"],
hcMethod: healthcheckData?.method,
hcStatus: healthcheckData?.status,
hcHealth: "unknown"
@@ -392,7 +402,12 @@ export async function updateProxyResources(
enabled: targetData.enabled,
path: targetData.path,
pathMatchType: targetData["path-match"],
rewritePath: targetData.rewritePath,
rewritePath:
targetData.rewritePath ||
targetData["rewrite-path"] ||
(targetData["rewrite-match"] === "stripPrefix"
? "/"
: undefined),
rewritePathType: targetData["rewrite-match"],
priority: targetData.priority
})
@@ -452,10 +467,13 @@ export async function updateProxyResources(
hcPort: healthcheckData?.port,
hcInterval: healthcheckData?.interval,
hcUnhealthyInterval:
healthcheckData?.unhealthyInterval,
healthcheckData?.unhealthyInterval ||
healthcheckData?.["unhealthy-interval"],
hcTimeout: healthcheckData?.timeout,
hcHeaders: hcHeaders,
hcFollowRedirects: healthcheckData?.followRedirects,
hcFollowRedirects:
healthcheckData?.followRedirects ||
healthcheckData?.["follow-redirects"],
hcMethod: healthcheckData?.method,
hcStatus: healthcheckData?.status
})
@@ -527,7 +545,7 @@ export async function updateProxyResources(
if (
existingRule.action !== getRuleAction(rule.action) ||
existingRule.match !== rule.match.toUpperCase() ||
existingRule.value !== rule.value.toUpperCase()
existingRule.value !== getRuleValue(rule.match.toUpperCase(), rule.value)
) {
validateRule(rule);
await trx
@@ -535,7 +553,7 @@ export async function updateProxyResources(
.set({
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value.toUpperCase(),
value: getRuleValue(rule.match.toUpperCase(), rule.value),
})
.where(
eq(resourceRules.ruleId, existingRule.ruleId)
@@ -547,7 +565,7 @@ export async function updateProxyResources(
resourceId: existingResource.resourceId,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value.toUpperCase(),
value: getRuleValue(rule.match.toUpperCase(), rule.value),
priority: index + 1 // start priorities at 1
});
}
@@ -705,7 +723,7 @@ export async function updateProxyResources(
resourceId: newResource.resourceId,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value.toUpperCase(),
value: getRuleValue(rule.match.toUpperCase(), rule.value),
priority: index + 1 // start priorities at 1
});
}
@@ -735,6 +753,14 @@ function getRuleAction(input: string) {
return action;
}
function getRuleValue(match: string, value: string) {
// if the match is a country, uppercase the value
if (match == "COUNTRY") {
return value.toUpperCase();
}
return value;
}
function validateRule(rule: any) {
if (rule.match === "cidr") {
if (!isValidCIDR(rule.value)) {

View File

@@ -13,10 +13,12 @@ export const TargetHealthCheckSchema = z.object({
scheme: z.string().optional(),
mode: z.string().default("http"),
interval: z.number().int().default(30),
unhealthyInterval: z.number().int().default(30),
"unhealthy-interval": z.number().int().default(30),
unhealthyInterval: z.number().int().optional(), // deprecated alias
timeout: z.number().int().default(5),
headers: z.array(z.object({ name: z.string(), value: z.string() })).nullable().optional().default(null),
followRedirects: z.boolean().default(true),
"follow-redirects": z.boolean().default(true),
followRedirects: z.boolean().optional(), // deprecated alias
method: z.string().default("GET"),
status: z.number().int().optional()
});
@@ -32,7 +34,8 @@ export const TargetSchema = z.object({
path: z.string().optional(),
"path-match": z.enum(["exact", "prefix", "regex"]).optional().nullable(),
healthcheck: TargetHealthCheckSchema.optional(),
rewritePath: z.string().optional(),
rewritePath: z.string().optional(), // deprecated alias
"rewrite-path": z.string().optional(),
"rewrite-match": z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable(),
priority: z.number().int().min(1).max(1000).optional().default(100)
});

View File

@@ -2,7 +2,7 @@ import path from "path";
import { fileURLToPath } from "url";
// This is a placeholder value replaced by the build process
export const APP_VERSION = "1.12.0-rc.0";
export const APP_VERSION = "1.12.1";
export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME);

View File

@@ -80,7 +80,8 @@ export async function getTraefikConfig(
subnet: sites.subnet,
exitNodeId: sites.exitNodeId,
// Domain cert resolver fields
domainCertResolver: domains.certResolver
domainCertResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert
})
.from(sites)
.innerJoin(targets, eq(targets.siteId, sites.siteId))
@@ -178,7 +179,8 @@ export async function getTraefikConfig(
rewritePathType: row.rewritePathType,
priority: priority,
// Store domain cert resolver fields
domainCertResolver: row.domainCertResolver
domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert
});
}

View File

@@ -111,7 +111,8 @@ export async function getTraefikConfig(
domainNamespaceId: domainNamespaces.domainNamespaceId,
// Certificate
certificateStatus: certificates.status,
domainCertResolver: domains.certResolver
domainCertResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert
})
.from(sites)
.innerJoin(targets, eq(targets.siteId, sites.siteId))
@@ -218,7 +219,8 @@ export async function getTraefikConfig(
rewritePath: row.rewritePath,
rewritePathType: row.rewritePathType,
priority: priority, // may be null, we fallback later
domainCertResolver: row.domainCertResolver
domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert
});
}

View File

@@ -352,20 +352,38 @@ export async function validateOidcCallback(
if (!userOrgInfo.length) {
if (existingUser) {
// delete the user
// cascade will also delete org users
// get existing user orgs
const existingUserOrgs = await db
.select()
.from(userOrgs)
.where(
and(
eq(userOrgs.userId, existingUser.userId),
eq(userOrgs.autoProvisioned, false)
)
);
await db
.delete(users)
.where(eq(users.userId, existingUser.userId));
if (!existingUserOrgs.length) {
// delete the user
// await db
// .delete(users)
// .where(eq(users.userId, existingUser.userId));
return next(
createHttpError(
HttpCode.UNAUTHORIZED,
`No policies matched for ${userIdentifier}. This user must be added to an organization before logging in.`
)
);
}
} else {
// no orgs to provision and user doesn't exist
return next(
createHttpError(
HttpCode.UNAUTHORIZED,
`No policies matched for ${userIdentifier}. This user must be added to an organization before logging in.`
)
);
}
return next(
createHttpError(
HttpCode.UNAUTHORIZED,
`No policies matched for ${userIdentifier}. This user must be added to an organization before logging in.`
)
);
}
const orgUserCounts: { orgId: string; userCount: number }[] = [];

View File

@@ -92,7 +92,7 @@ export async function updateOrg(
const { orgId } = parsedParams.data;
const isLicensed = await isLicensedOrSubscribed(orgId);
if (!isLicensed) {
if (build == "enterprise" && !isLicensed) {
parsedBody.data.requireTwoFactor = undefined;
parsedBody.data.maxSessionLengthHours = undefined;
parsedBody.data.passwordExpiryDays = undefined;
@@ -100,6 +100,7 @@ export async function updateOrg(
const { tier } = await getOrgTierData(orgId);
if (
build == "saas" &&
tier != TierId.STANDARD &&
parsedBody.data.settingsLogRetentionDaysRequest &&
parsedBody.data.settingsLogRetentionDaysRequest > 30

View File

@@ -6,7 +6,9 @@ import {
userResources,
roleResources,
resourcePassword,
resourcePincode
resourcePincode,
targets,
targetHealthCheck,
} from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -40,6 +42,59 @@ const listResourcesSchema = z.object({
.pipe(z.number().int().nonnegative())
});
// (resource fields + a single joined target)
type JoinedRow = {
resourceId: number;
niceId: string;
name: string;
ssl: boolean;
fullDomain: string | null;
passwordId: number | null;
sso: boolean;
pincodeId: number | null;
whitelist: boolean;
http: boolean;
protocol: string;
proxyPort: number | null;
enabled: boolean;
domainId: string | null;
headerAuthId: number | null;
targetId: number | null;
targetIp: string | null;
targetPort: number | null;
targetEnabled: boolean | null;
hcHealth: string | null;
hcEnabled: boolean | null;
};
// grouped by resource with targets[])
export type ResourceWithTargets = {
resourceId: number;
name: string;
ssl: boolean;
fullDomain: string | null;
passwordId: number | null;
sso: boolean;
pincodeId: number | null;
whitelist: boolean;
http: boolean;
protocol: string;
proxyPort: number | null;
enabled: boolean;
domainId: string | null;
niceId: string;
headerAuthId: number | null;
targets: Array<{
targetId: number;
ip: string;
port: number;
enabled: boolean;
healthStatus?: 'healthy' | 'unhealthy' | 'unknown';
}>;
};
function queryResources(accessibleResourceIds: number[], orgId: string) {
return db
.select({
@@ -57,7 +112,15 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
enabled: resources.enabled,
domainId: resources.domainId,
niceId: resources.niceId,
headerAuthId: resourceHeaderAuth.headerAuthId
headerAuthId: resourceHeaderAuth.headerAuthId,
targetId: targets.targetId,
targetIp: targets.ip,
targetPort: targets.port,
targetEnabled: targets.enabled,
hcHealth: targetHealthCheck.hcHealth,
hcEnabled: targetHealthCheck.hcEnabled,
})
.from(resources)
.leftJoin(
@@ -72,6 +135,11 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
resourceHeaderAuth,
eq(resourceHeaderAuth.resourceId, resources.resourceId)
)
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
.leftJoin(
targetHealthCheck,
eq(targetHealthCheck.targetId, targets.targetId)
)
.where(
and(
inArray(resources.resourceId, accessibleResourceIds),
@@ -81,7 +149,7 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
}
export type ListResourcesResponse = {
resources: NonNullable<Awaited<ReturnType<typeof queryResources>>>;
resources: ResourceWithTargets[];
pagination: { total: number; limit: number; offset: number };
};
@@ -146,7 +214,7 @@ export async function listResources(
);
}
let accessibleResources;
let accessibleResources: Array<{ resourceId: number }>;
if (req.user) {
accessibleResources = await db
.select({
@@ -183,9 +251,56 @@ export async function listResources(
const baseQuery = queryResources(accessibleResourceIds, orgId);
const resourcesList = await baseQuery!.limit(limit).offset(offset);
const rows: JoinedRow[] = await baseQuery.limit(limit).offset(offset);
// avoids TS issues with reduce/never[]
const map = new Map<number, ResourceWithTargets>();
for (const row of rows) {
let entry = map.get(row.resourceId);
if (!entry) {
entry = {
resourceId: row.resourceId,
niceId: row.niceId,
name: row.name,
ssl: row.ssl,
fullDomain: row.fullDomain,
passwordId: row.passwordId,
sso: row.sso,
pincodeId: row.pincodeId,
whitelist: row.whitelist,
http: row.http,
protocol: row.protocol,
proxyPort: row.proxyPort,
enabled: row.enabled,
domainId: row.domainId,
headerAuthId: row.headerAuthId,
targets: [],
};
map.set(row.resourceId, entry);
}
if (row.targetId != null && row.targetIp && row.targetPort != null && row.targetEnabled != null) {
let healthStatus: 'healthy' | 'unhealthy' | 'unknown' = 'unknown';
if (row.hcEnabled && row.hcHealth) {
healthStatus = row.hcHealth as 'healthy' | 'unhealthy' | 'unknown';
}
entry.targets.push({
targetId: row.targetId,
ip: row.targetIp,
port: row.targetPort,
enabled: row.targetEnabled,
healthStatus: healthStatus,
});
}
}
const resourcesList: ResourceWithTargets[] = Array.from(map.values());
const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count;
const totalCount = totalCountResult[0]?.count ?? 0;
return response<ListResourcesResponse>(res, {
data: {

View File

@@ -163,12 +163,8 @@ export async function createTarget(
);
if (existingTarget) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}`
)
);
// log a warning
logger.warn(`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}`);
}
let newTarget: Target[] = [];

View File

@@ -170,12 +170,8 @@ export async function updateTarget(
);
if (foundTarget) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Target with IP ${targetData.ip}, port ${targetData.port}, and method ${targetData.method} already exists on the same site.`
)
);
// log a warning
logger.warn(`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${target.resourceId}`);
}
const { internalPort, targetIps } = await pickPort(site.siteId!, db);

View File

@@ -73,7 +73,7 @@ export default async function DomainSettingsPage({
<DNSRecordsTable records={dnsRecords} type={domain.type} />
{domain.type == "wildcard" && (
{domain.type == "wildcard" && !domain.configManaged && (
<DomainCertForm
orgId={orgId}
domainId={domain.domainId}

View File

@@ -501,25 +501,6 @@ export default function ReverseProxyTargets(props: {
return;
}
// Check if target with same IP, port and method already exists
const isDuplicate = targets.some(
(t) =>
t.targetId !== target.targetId &&
t.ip === target.ip &&
t.port === target.port &&
t.method === target.method &&
t.siteId === target.siteId
);
if (isDuplicate) {
toast({
variant: "destructive",
title: t("targetErrorDuplicate"),
description: t("targetErrorDuplicateDescription")
});
return;
}
try {
setTargetsLoading(true);
@@ -585,24 +566,6 @@ export default function ReverseProxyTargets(props: {
}
async function addTarget(data: z.infer<typeof addTargetSchema>) {
// Check if target with same IP, port and method already exists
const isDuplicate = targets.some(
(target) =>
target.ip === data.ip &&
target.port === data.port &&
target.method === data.method &&
target.siteId === data.siteId
);
if (isDuplicate) {
toast({
variant: "destructive",
title: t("targetErrorDuplicate"),
description: t("targetErrorDuplicateDescription")
});
return;
}
// if (site && site.type == "wireguard" && site.subnet) {
// // make sure that the target IP is within the site subnet
// const targetIp = data.ip;
@@ -899,7 +862,7 @@ export default function ReverseProxyTargets(props: {
const healthCheckColumn: ColumnDef<LocalTarget> = {
accessorKey: "healthCheck",
header: t("healthCheck"),
header: () => (<span className="p-3">{t("healthCheck")}</span>),
cell: ({ row }) => {
const status = row.original.hcHealth || "unknown";
const isEnabled = row.original.hcEnabled;
@@ -971,7 +934,7 @@ export default function ReverseProxyTargets(props: {
const matchPathColumn: ColumnDef<LocalTarget> = {
accessorKey: "path",
header: t("matchPath"),
header: () => (<span className="p-3">{t("matchPath")}</span>),
cell: ({ row }) => {
const hasPathMatch = !!(
row.original.path || row.original.pathMatchType
@@ -1033,7 +996,7 @@ export default function ReverseProxyTargets(props: {
const addressColumn: ColumnDef<LocalTarget> = {
accessorKey: "address",
header: t("address"),
header: () => (<span className="p-3">{t("address")}</span>),
cell: ({ row }) => {
const selectedSite = sites.find(
(site) => site.siteId === row.original.siteId
@@ -1052,7 +1015,7 @@ export default function ReverseProxyTargets(props: {
return (
<div className="flex items-center w-full">
<div className="flex items-center w-full justify-start py-0 space-x-2 px-0 cursor-default border border-input shadow-2xs rounded-md">
<div className="flex items-center w-full justify-start py-0 space-x-2 px-0 cursor-default border border-input rounded-md">
{selectedSite &&
selectedSite.type === "newt" &&
(() => {
@@ -1247,7 +1210,7 @@ export default function ReverseProxyTargets(props: {
const rewritePathColumn: ColumnDef<LocalTarget> = {
accessorKey: "rewritePath",
header: t("rewritePath"),
header: () => (<span className="p-3">{t("rewritePath")}</span>),
cell: ({ row }) => {
const hasRewritePath = !!(
row.original.rewritePath || row.original.rewritePathType
@@ -1317,7 +1280,7 @@ export default function ReverseProxyTargets(props: {
const enabledColumn: ColumnDef<LocalTarget> = {
accessorKey: "enabled",
header: t("enabled"),
header: () => (<span className="p-3">{t("enabled")}</span>),
cell: ({ row }) => (
<div className="flex items-center justify-center w-full">
<Switch
@@ -1338,8 +1301,9 @@ export default function ReverseProxyTargets(props: {
const actionsColumn: ColumnDef<LocalTarget> = {
id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>),
cell: ({ row }) => (
<div className="flex items-center justify-end w-full">
<div className="flex items-center w-full">
<Button
variant="outline"
onClick={() => removeTarget(row.original.targetId)}

View File

@@ -425,24 +425,6 @@ export default function Page() {
};
async function addTarget(data: z.infer<typeof addTargetSchema>) {
// Check if target with same IP, port and method already exists
const isDuplicate = targets.some(
(target) =>
target.ip === data.ip &&
target.port === data.port &&
target.method === data.method &&
target.siteId === data.siteId
);
if (isDuplicate) {
toast({
variant: "destructive",
title: t("targetErrorDuplicate"),
description: t("targetErrorDuplicateDescription")
});
return;
}
const site = sites.find((site) => site.siteId === data.siteId);
const isHttp = baseForm.watch("http");

View File

@@ -1,8 +1,8 @@
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import ResourcesTable, {
ResourceRow,
InternalResourceRow
ResourceRow,
InternalResourceRow
} from "../../../../components/ResourcesTable";
import { AxiosResponse } from "axios";
import { ListResourcesResponse } from "@server/routers/resource";
@@ -17,123 +17,130 @@ import { pullEnv } from "@app/lib/pullEnv";
import { toUnicode } from "punycode";
type ResourcesPageProps = {
params: Promise<{ orgId: string }>;
searchParams: Promise<{ view?: string }>;
params: Promise<{ orgId: string }>;
searchParams: Promise<{ view?: string }>;
};
export const dynamic = "force-dynamic";
export default async function ResourcesPage(props: ResourcesPageProps) {
const params = await props.params;
const searchParams = await props.searchParams;
const t = await getTranslations();
const params = await props.params;
const searchParams = await props.searchParams;
const t = await getTranslations();
const env = pullEnv();
const env = pullEnv();
// Default to 'proxy' view, or use the query param if provided
let defaultView: "proxy" | "internal" = "proxy";
if (env.flags.enableClients) {
defaultView = searchParams.view === "internal" ? "internal" : "proxy";
}
// Default to 'proxy' view, or use the query param if provided
let defaultView: "proxy" | "internal" = "proxy";
if (env.flags.enableClients) {
defaultView = searchParams.view === "internal" ? "internal" : "proxy";
}
let resources: ListResourcesResponse["resources"] = [];
try {
const res = await internal.get<AxiosResponse<ListResourcesResponse>>(
`/org/${params.orgId}/resources`,
await authCookieHeader()
);
resources = res.data.data.resources;
} catch (e) { }
let siteResources: ListAllSiteResourcesByOrgResponse["siteResources"] = [];
try {
const res = await internal.get<
AxiosResponse<ListAllSiteResourcesByOrgResponse>
>(`/org/${params.orgId}/site-resources`, await authCookieHeader());
siteResources = res.data.data.siteResources;
} catch (e) { }
let org = null;
try {
const getOrg = cache(async () =>
internal.get<AxiosResponse<GetOrgResponse>>(
`/org/${params.orgId}`,
await authCookieHeader()
)
);
const res = await getOrg();
org = res.data.data;
} catch {
redirect(`/${params.orgId}/settings/resources`);
}
if (!org) {
redirect(`/${params.orgId}/settings/resources`);
}
const resourceRows: ResourceRow[] = resources.map((resource) => {
return {
id: resource.resourceId,
name: resource.name,
orgId: params.orgId,
nice: resource.niceId,
domain: `${resource.ssl ? "https://" : "http://"}${toUnicode(resource.fullDomain || "")}`,
protocol: resource.protocol,
proxyPort: resource.proxyPort,
http: resource.http,
authState: !resource.http
? "none"
: resource.sso ||
resource.pincodeId !== null ||
resource.passwordId !== null ||
resource.whitelist ||
resource.headerAuthId
? "protected"
: "not_protected",
enabled: resource.enabled,
domainId: resource.domainId || undefined,
ssl: resource.ssl
};
});
const internalResourceRows: InternalResourceRow[] = siteResources.map(
(siteResource) => {
return {
id: siteResource.siteResourceId,
name: siteResource.name,
orgId: params.orgId,
siteName: siteResource.siteName,
protocol: siteResource.protocol,
proxyPort: siteResource.proxyPort,
siteId: siteResource.siteId,
destinationIp: siteResource.destinationIp,
destinationPort: siteResource.destinationPort,
siteNiceId: siteResource.siteNiceId
};
}
let resources: ListResourcesResponse["resources"] = [];
try {
const res = await internal.get<AxiosResponse<ListResourcesResponse>>(
`/org/${params.orgId}/resources`,
await authCookieHeader()
);
resources = res.data.data.resources;
} catch (e) { }
return (
<>
<SettingsSectionTitle
title={t("resourceTitle")}
description={t("resourceDescription")}
/>
let siteResources: ListAllSiteResourcesByOrgResponse["siteResources"] = [];
try {
const res = await internal.get<
AxiosResponse<ListAllSiteResourcesByOrgResponse>
>(`/org/${params.orgId}/site-resources`, await authCookieHeader());
siteResources = res.data.data.siteResources;
} catch (e) { }
<OrgProvider org={org}>
<ResourcesTable
resources={resourceRows}
internalResources={internalResourceRows}
orgId={params.orgId}
defaultView={
env.flags.enableClients ? defaultView : "proxy"
}
defaultSort={{
id: "name",
desc: false
}}
/>
</OrgProvider>
</>
let org = null;
try {
const getOrg = cache(async () =>
internal.get<AxiosResponse<GetOrgResponse>>(
`/org/${params.orgId}`,
await authCookieHeader()
)
);
}
const res = await getOrg();
org = res.data.data;
} catch {
redirect(`/${params.orgId}/settings/resources`);
}
if (!org) {
redirect(`/${params.orgId}/settings/resources`);
}
const resourceRows: ResourceRow[] = resources.map((resource) => {
return {
id: resource.resourceId,
name: resource.name,
orgId: params.orgId,
nice: resource.niceId,
domain: `${resource.ssl ? "https://" : "http://"}${toUnicode(resource.fullDomain || "")}`,
protocol: resource.protocol,
proxyPort: resource.proxyPort,
http: resource.http,
authState: !resource.http
? "none"
: resource.sso ||
resource.pincodeId !== null ||
resource.passwordId !== null ||
resource.whitelist ||
resource.headerAuthId
? "protected"
: "not_protected",
enabled: resource.enabled,
domainId: resource.domainId || undefined,
ssl: resource.ssl,
targets: resource.targets?.map(target => ({
targetId: target.targetId,
ip: target.ip,
port: target.port,
enabled: target.enabled,
healthStatus: target.healthStatus
}))
};
});
const internalResourceRows: InternalResourceRow[] = siteResources.map(
(siteResource) => {
return {
id: siteResource.siteResourceId,
name: siteResource.name,
orgId: params.orgId,
siteName: siteResource.siteName,
protocol: siteResource.protocol,
proxyPort: siteResource.proxyPort,
siteId: siteResource.siteId,
destinationIp: siteResource.destinationIp,
destinationPort: siteResource.destinationPort,
siteNiceId: siteResource.siteNiceId
};
}
);
return (
<>
<SettingsSectionTitle
title={t("resourceTitle")}
description={t("resourceDescription")}
/>
<OrgProvider org={org}>
<ResourcesTable
resources={resourceRows}
internalResources={internalResourceRows}
orgId={params.orgId}
defaultView={
env.flags.enableClients ? defaultView : "proxy"
}
defaultSort={{
id: "name",
desc: false
}}
/>
</OrgProvider>
</>
);
}

View File

@@ -54,22 +54,7 @@ export default function BlueprintDetailsForm({
<div className="flex flex-col gap-6">
<Alert>
<AlertDescription>
<InfoSections cols={2}>
<InfoSection>
<InfoSectionTitle>
{t("appliedAt")}
</InfoSectionTitle>
<InfoSectionContent>
<time
className="text-muted-foreground"
dateTime={blueprint.createdAt.toString()}
>
{new Date(
blueprint.createdAt * 1000
).toLocaleString()}
</time>
</InfoSectionContent>
</InfoSection>
<InfoSections cols={3}>
<InfoSection>
<InfoSectionTitle>
{t("status")}
@@ -88,16 +73,6 @@ export default function BlueprintDetailsForm({
)}
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t("message")}
</InfoSectionTitle>
<InfoSectionContent>
<p className="text-muted-foreground">
{blueprint.message}
</p>
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t("source")}
@@ -106,35 +81,59 @@ export default function BlueprintDetailsForm({
{blueprint.source === "API" && (
<Badge
variant="secondary"
className="-mx-2"
className="inline-flex items-center gap-1 "
>
<span className="inline-flex items-center gap-1 ">
API
<Webhook className="size-4 flex-none" />
</span>
API
<Webhook className="w-3 h-3 flex-none" />
</Badge>
)}
{blueprint.source === "NEWT" && (
<Badge variant="secondary">
<span className="inline-flex items-center gap-1 ">
Newt CLI
<Terminal className="size-4 flex-none" />
</span>
<Badge
variant="secondary"
className="inline-flex items-center gap-1 "
>
<Terminal className="w-3 h-3 flex-none" />
Newt CLI
</Badge>
)}
{blueprint.source === "UI" && (
<Badge
variant="secondary"
className="-mx-1 py-1"
className="inline-flex items-center gap-1 "
>
<span className="inline-flex items-center gap-1 ">
Dashboard{" "}
<Globe className="size-4 flex-none" />
</span>
<Globe className="w-3 h-3 flex-none" />
Dashboard
</Badge>
)}{" "}
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t("appliedAt")}
</InfoSectionTitle>
<InfoSectionContent>
<time
className="text-muted-foreground"
dateTime={blueprint.createdAt.toString()}
>
{new Date(
blueprint.createdAt * 1000
).toLocaleString()}
</time>
</InfoSectionContent>
</InfoSection>
{blueprint.message && (
<InfoSection>
<InfoSectionTitle>
{t("message")}
</InfoSectionTitle>
<InfoSectionContent>
<p className="text-muted-foreground">
{blueprint.message}
</p>
</InfoSectionContent>
</InfoSection>
)}
</InfoSections>
</AlertDescription>
</Alert>
@@ -169,11 +168,6 @@ export default function BlueprintDetailsForm({
<FormLabel>
{t("parsedContents")}
</FormLabel>
<FormDescription>
{t(
"blueprintContentsDescription"
)}
</FormDescription>
<FormControl>
<div
className={cn(

View File

@@ -116,10 +116,13 @@ export default function BlueprintsTable({ blueprints, orgId }: Props) {
}
case "UI": {
return (
<Badge variant="secondary">
<Badge
variant="secondary"
className="inline-flex items-center gap-1"
>
<span className="inline-flex items-center gap-1 ">
Dashboard{" "}
<Globe className="size-4 flex-none" />
<Globe className="w-3 h-3" />
Dashboard
</span>
</Badge>
);
@@ -163,18 +166,14 @@ export default function BlueprintsTable({ blueprints, orgId }: Props) {
cell: ({ row }) => {
return (
<div className="flex justify-end">
<Button
variant="outline"
className="items-center"
asChild
<Link
href={`/${orgId}/settings/blueprints/${row.original.blueprintId}`}
>
<Link
href={`/${orgId}/settings/blueprints/${row.original.blueprintId}`}
>
View details{" "}
<ArrowRight className="size-4 flex-none" />
</Link>
</Button>
<Button variant="outline" className="items-center">
View Details
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div>
);
}

View File

@@ -124,7 +124,6 @@ export function DNSRecordsDataTable<TData, TValue>({
{table.getHeaderGroups().map((headerGroup) => (
<TableRow
key={headerGroup.id}
className="bg-secondary dark:bg-transparent"
>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>

View File

@@ -9,13 +9,15 @@ import {
SortingState,
getSortedRowModel,
ColumnFiltersState,
getFilteredRowModel
getFilteredRowModel,
VisibilityState
} from "@tanstack/react-table";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
DropdownMenuTrigger,
DropdownMenuCheckboxItem,
} from "@app/components/ui/dropdown-menu";
import { Button } from "@app/components/ui/button";
import {
@@ -25,7 +27,16 @@ import {
ArrowUpRight,
ShieldOff,
ShieldCheck,
RefreshCw
RefreshCw,
Settings2,
Plus,
Search,
ChevronDown,
Clock,
Wifi,
WifiOff,
CheckCircle2,
XCircle,
} from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
@@ -44,7 +55,6 @@ import { useTranslations } from "next-intl";
import { InfoPopup } from "@app/components/ui/info-popup";
import { Input } from "@app/components/ui/input";
import { DataTablePagination } from "@app/components/DataTablePagination";
import { Plus, Search } from "lucide-react";
import { Card, CardContent, CardHeader } from "@app/components/ui/card";
import {
Table,
@@ -65,6 +75,15 @@ import EditInternalResourceDialog from "@app/components/EditInternalResourceDial
import CreateInternalResourceDialog from "@app/components/CreateInternalResourceDialog";
import { Alert, AlertDescription } from "@app/components/ui/alert";
export type TargetHealth = {
targetId: number;
ip: string;
port: number;
enabled: boolean;
healthStatus?: 'healthy' | 'unhealthy' | 'unknown';
};
export type ResourceRow = {
id: number;
nice: string | null;
@@ -78,8 +97,54 @@ export type ResourceRow = {
enabled: boolean;
domainId?: string;
ssl: boolean;
targetHost?: string;
targetPort?: number;
targets?: TargetHealth[];
};
function getOverallHealthStatus(targets?: TargetHealth[]): 'online' | 'degraded' | 'offline' | 'unknown' {
if (!targets || targets.length === 0) {
return 'unknown';
}
const monitoredTargets = targets.filter(t => t.enabled && t.healthStatus && t.healthStatus !== 'unknown');
if (monitoredTargets.length === 0) {
return 'unknown';
}
const healthyCount = monitoredTargets.filter(t => t.healthStatus === 'healthy').length;
const unhealthyCount = monitoredTargets.filter(t => t.healthStatus === 'unhealthy').length;
if (healthyCount === monitoredTargets.length) {
return 'online';
} else if (unhealthyCount === monitoredTargets.length) {
return 'offline';
} else {
return 'degraded';
}
}
function StatusIcon({ status, className = "" }: {
status: 'online' | 'degraded' | 'offline' | 'unknown';
className?: string;
}) {
const iconClass = `h-4 w-4 ${className}`;
switch (status) {
case 'online':
return <CheckCircle2 className={`${iconClass} text-green-500`} />;
case 'degraded':
return <CheckCircle2 className={`${iconClass} text-yellow-500`} />;
case 'offline':
return <XCircle className={`${iconClass} text-destructive`} />;
case 'unknown':
return <Clock className={`${iconClass} text-gray-400`} />;
default:
return null;
}
}
export type InternalResourceRow = {
id: number;
name: string;
@@ -143,6 +208,7 @@ const setStoredPageSize = (pageSize: number, tableId?: string): void => {
};
export default function ResourcesTable({
resources,
internalResources,
@@ -158,6 +224,7 @@ export default function ResourcesTable({
const api = createApiClient({ env });
const [proxyPageSize, setProxyPageSize] = useState<number>(() =>
getStoredPageSize('proxy-resources', 20)
);
@@ -179,6 +246,10 @@ export default function ResourcesTable({
const [proxySorting, setProxySorting] = useState<SortingState>(
defaultSort ? [defaultSort] : []
);
const [proxyColumnVisibility, setProxyColumnVisibility] = useState<VisibilityState>({});
const [internalColumnVisibility, setInternalColumnVisibility] = useState<VisibilityState>({});
const [proxyColumnFilters, setProxyColumnFilters] =
useState<ColumnFiltersState>([]);
const [proxyGlobalFilter, setProxyGlobalFilter] = useState<any>([]);
@@ -349,6 +420,76 @@ export default function ResourcesTable({
});
}
function TargetStatusCell({ targets }: { targets?: TargetHealth[] }) {
const overallStatus = getOverallHealthStatus(targets);
if (!targets || targets.length === 0) {
return (
<div className="flex items-center gap-2">
<StatusIcon status="unknown" />
<span className="text-sm text-muted-foreground">No targets</span>
</div>
);
}
const monitoredTargets = targets.filter(t => t.enabled && t.healthStatus && t.healthStatus !== 'unknown');
const unknownTargets = targets.filter(t => !t.enabled || !t.healthStatus || t.healthStatus === 'unknown');
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="flex items-center gap-2 h-8">
<StatusIcon status={overallStatus} />
<span className="text-sm">
{overallStatus === 'online' && 'Healthy'}
{overallStatus === 'degraded' && 'Degraded'}
{overallStatus === 'offline' && 'Offline'}
{overallStatus === 'unknown' && 'Unknown'}
</span>
<ChevronDown className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="min-w-[280px]">
{monitoredTargets.length > 0 && (
<>
{monitoredTargets.map((target) => (
<DropdownMenuItem key={target.targetId} className="flex items-center justify-between gap-4">
<div className="flex items-center gap-2">
<StatusIcon
status={target.healthStatus === 'healthy' ? 'online' : 'offline'}
className="h-3 w-3"
/>
{`${target.ip}:${target.port}`}
</div>
<span className={`capitalize ${target.healthStatus === 'healthy' ? 'text-green-500' : 'text-destructive'
}`}>
{target.healthStatus}
</span>
</DropdownMenuItem>
))}
</>
)}
{unknownTargets.length > 0 && (
<>
{unknownTargets.map((target) => (
<DropdownMenuItem key={target.targetId} className="flex items-center justify-between gap-4">
<div className="flex items-center gap-2">
<StatusIcon status="unknown" className="h-3 w-3" />
{`${target.ip}:${target.port}`}
</div>
<span className="text-muted-foreground">
{!target.enabled ? 'Disabled' : 'Not monitored'}
</span>
</DropdownMenuItem>
))}
</>
)}
</DropdownMenuContent>
</DropdownMenu>
);
}
const proxyColumns: ColumnDef<ResourceRow>[] = [
{
accessorKey: "name",
@@ -390,6 +531,33 @@ export default function ResourcesTable({
return <span>{resourceRow.http ? (resourceRow.ssl ? "HTTPS" : "HTTP") : resourceRow.protocol.toUpperCase()}</span>;
}
},
{
id: "status",
accessorKey: "status",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("status")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const resourceRow = row.original;
return <TargetStatusCell targets={resourceRow.targets} />;
},
sortingFn: (rowA, rowB) => {
const statusA = getOverallHealthStatus(rowA.original.targets);
const statusB = getOverallHealthStatus(rowB.original.targets);
const statusOrder = { online: 3, degraded: 2, offline: 1, unknown: 0 };
return statusOrder[statusA] - statusOrder[statusB];
}
},
{
accessorKey: "domain",
header: t("access"),
@@ -647,6 +815,7 @@ export default function ResourcesTable({
onColumnFiltersChange: setProxyColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
onGlobalFilterChange: setProxyGlobalFilter,
onColumnVisibilityChange: setProxyColumnVisibility,
initialState: {
pagination: {
pageSize: proxyPageSize,
@@ -656,7 +825,8 @@ export default function ResourcesTable({
state: {
sorting: proxySorting,
columnFilters: proxyColumnFilters,
globalFilter: proxyGlobalFilter
globalFilter: proxyGlobalFilter,
columnVisibility: proxyColumnVisibility
}
});
@@ -670,6 +840,7 @@ export default function ResourcesTable({
onColumnFiltersChange: setInternalColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
onGlobalFilterChange: setInternalGlobalFilter,
onColumnVisibilityChange: setInternalColumnVisibility,
initialState: {
pagination: {
pageSize: internalPageSize,
@@ -679,7 +850,8 @@ export default function ResourcesTable({
state: {
sorting: internalSorting,
columnFilters: internalColumnFilters,
globalFilter: internalGlobalFilter
globalFilter: internalGlobalFilter,
columnVisibility: internalColumnVisibility
}
});