mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-21 16:25:19 +00:00
Compare commits
203 Commits
1.14.0-rc.
...
23cf7bf745
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23cf7bf745 | ||
|
|
d63de9ba40 | ||
|
|
e7ac5c34a2 | ||
|
|
1f88876e3c | ||
|
|
e0425e2458 | ||
|
|
10e9f017fb | ||
|
|
6d2cb69e45 | ||
|
|
2fab9b65a8 | ||
|
|
47a7a3f230 | ||
|
|
61835ca0e6 | ||
|
|
f4b22c5b31 | ||
|
|
65afe23dd8 | ||
|
|
371e44e235 | ||
|
|
d349795995 | ||
|
|
7d106294bc | ||
|
|
8c3e094534 | ||
|
|
b20a7231bc | ||
|
|
84998fdb6e | ||
|
|
0d9ece1329 | ||
|
|
eb7107016b | ||
|
|
a7608424b7 | ||
|
|
6710657789 | ||
|
|
b25069ab49 | ||
|
|
6e8d1f9149 | ||
|
|
825da82e56 | ||
|
|
8d28063724 | ||
|
|
a7eed886ab | ||
|
|
4e965f1e83 | ||
|
|
ea3011afca | ||
|
|
b20e5b4af1 | ||
|
|
577660c656 | ||
|
|
f9a9007ed9 | ||
|
|
19873a0b3d | ||
|
|
4f13267bc8 | ||
|
|
c820fcc518 | ||
|
|
c612972f38 | ||
|
|
395c4eff53 | ||
|
|
6aa40bfd61 | ||
|
|
f5744d0dcd | ||
|
|
a975b33b7e | ||
|
|
92be668a55 | ||
|
|
15153d5e3a | ||
|
|
212800d365 | ||
|
|
24ac9a1623 | ||
|
|
78bcfed668 | ||
|
|
bf8be14c3a | ||
|
|
fe1f7bee60 | ||
|
|
fbe14acdd1 | ||
|
|
11c0afd9ab | ||
|
|
52e6b36c2d | ||
|
|
dcce15036c | ||
|
|
fd27682bbb | ||
|
|
0cffeda5da | ||
|
|
aff486ca57 | ||
|
|
d3eb950888 | ||
|
|
1b98f44588 | ||
|
|
8a5a0c7c18 | ||
|
|
d2f0825498 | ||
|
|
ac88b79066 | ||
|
|
9160d94c7b | ||
|
|
de6ce7aa10 | ||
|
|
91a4b11632 | ||
|
|
99ed4ea683 | ||
|
|
1a0d4870ed | ||
|
|
f1bd315a96 | ||
|
|
42d8e932ff | ||
|
|
658ec87d1b | ||
|
|
e9bbee13c0 | ||
|
|
99164bf7ab | ||
|
|
6a3cc578a7 | ||
|
|
720c3d7c41 | ||
|
|
72733a9b77 | ||
|
|
1295141eaf | ||
|
|
e8099795b3 | ||
|
|
d65fbcc28b | ||
|
|
ad0a17f642 | ||
|
|
c2ecabab33 | ||
|
|
7170b14b1f | ||
|
|
343ba64eb4 | ||
|
|
242c314e42 | ||
|
|
16f9191bc5 | ||
|
|
6ec51e50d1 | ||
|
|
f1d73c5f8c | ||
|
|
6093add470 | ||
|
|
4af6bd43ef | ||
|
|
99ad7c6bef | ||
|
|
fc192d955b | ||
|
|
914f1b41c6 | ||
|
|
0601eacfd5 | ||
|
|
5481eae606 | ||
|
|
d868c85ebe | ||
|
|
ff0f0aa4e0 | ||
|
|
d4f9f20ef9 | ||
|
|
30808ce2db | ||
|
|
db5f7ed731 | ||
|
|
6fd69f1293 | ||
|
|
9f1dd42a85 | ||
|
|
7b55ef1cc8 | ||
|
|
b988f21b15 | ||
|
|
a688757f5c | ||
|
|
0ed1852a35 | ||
|
|
a5c3702b8d | ||
|
|
b25f43da9a | ||
|
|
ea0f0de802 | ||
|
|
476131859d | ||
|
|
8573c38332 | ||
|
|
bc9d2835be | ||
|
|
df582b0409 | ||
|
|
e3865dcd4d | ||
|
|
0eba5cdb0b | ||
|
|
2284f2cafc | ||
|
|
80215a7de0 | ||
|
|
ddd829064a | ||
|
|
982c692c40 | ||
|
|
0c3ce7836c | ||
|
|
7ef86c5707 | ||
|
|
f62b88b930 | ||
|
|
03a326c841 | ||
|
|
4df4cafd70 | ||
|
|
4b9539cc6d | ||
|
|
87135c90bd | ||
|
|
853d416b2f | ||
|
|
bfd14b87bd | ||
|
|
88aba4e169 | ||
|
|
99e2fcb2e8 | ||
|
|
1f138ab68c | ||
|
|
99ded7454e | ||
|
|
f82cacac6d | ||
|
|
a548f61ea6 | ||
|
|
bfae715076 | ||
|
|
358e25b7c2 | ||
|
|
2c3fa54933 | ||
|
|
00cdd5833e | ||
|
|
52b1164e58 | ||
|
|
657bc9cdf0 | ||
|
|
ec6bcd41b0 | ||
|
|
1721cce040 | ||
|
|
e41a5ad6b0 | ||
|
|
ee1eca9e66 | ||
|
|
d049369172 | ||
|
|
6280a68d51 | ||
|
|
32054dc4f6 | ||
|
|
831c631048 | ||
|
|
e23711bcce | ||
|
|
440bff57d0 | ||
|
|
7345cc81c1 | ||
|
|
164ab26069 | ||
|
|
4b6ace80d3 | ||
|
|
653127a0f7 | ||
|
|
bf3a1e20fc | ||
|
|
d7a44e7589 | ||
|
|
6c0d583557 | ||
|
|
13f0fb25da | ||
|
|
818aca9ec8 | ||
|
|
1c7fb476b0 | ||
|
|
93843ed733 | ||
|
|
0973313703 | ||
|
|
bfbfbe8b11 | ||
|
|
8c62d9fe78 | ||
|
|
d5558f55ed | ||
|
|
a96ad6bd07 | ||
|
|
00d9482a99 | ||
|
|
0f90e2a30f | ||
|
|
3eed636404 | ||
|
|
a67f88381f | ||
|
|
808fd856d1 | ||
|
|
5b9b532458 | ||
|
|
9fba9bd6b7 | ||
|
|
c5ece144d0 | ||
|
|
b64e2e11db | ||
|
|
40eeb9b7cb | ||
|
|
8fa62a0908 | ||
|
|
2bb94e24eb | ||
|
|
ca89c5feca | ||
|
|
729c2adb3f | ||
|
|
a21f49cb02 | ||
|
|
ef697c4864 | ||
|
|
2652dea09a | ||
|
|
efa9312fca | ||
|
|
074ee70025 | ||
|
|
77117e48e3 | ||
|
|
da112d3417 | ||
|
|
ddaaf34dbd | ||
|
|
373e35324e | ||
|
|
09b2f27749 | ||
|
|
7e9f18bf24 | ||
|
|
ab3be26790 | ||
|
|
5c67a1cb12 | ||
|
|
e28ab19ed4 | ||
|
|
59f8334cfd | ||
|
|
718bec4bbc | ||
|
|
2d731cb24b | ||
|
|
1905936950 | ||
|
|
c362bc673c | ||
|
|
4da0a752ef | ||
|
|
221ee6a1c2 | ||
|
|
2e60ecec87 | ||
|
|
71386d3b05 | ||
|
|
89a7e2e4dc | ||
|
|
27440700a5 | ||
|
|
b5019cef12 | ||
|
|
7e48cbe1aa | ||
|
|
4b2c570e73 |
11
.github/workflows/cicd.yml
vendored
11
.github/workflows/cicd.yml
vendored
@@ -99,7 +99,7 @@ jobs:
|
||||
id: check-rc
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
if [[ "$TAG" == *".rc."* ]]; then
|
||||
if [[ "$TAG" == *"-rc."* ]]; then
|
||||
echo "IS_RC=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "IS_RC=false" >> $GITHUB_ENV
|
||||
@@ -171,7 +171,7 @@ jobs:
|
||||
id: check-rc
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
if [[ "$TAG" == *".rc."* ]]; then
|
||||
if [[ "$TAG" == *"-rc."* ]]; then
|
||||
echo "IS_RC=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "IS_RC=false" >> $GITHUB_ENV
|
||||
@@ -219,7 +219,7 @@ jobs:
|
||||
id: check-rc
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
if [[ "$TAG" == *".rc."* ]]; then
|
||||
if [[ "$TAG" == *"-rc."* ]]; then
|
||||
echo "IS_RC=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "IS_RC=false" >> $GITHUB_ENV
|
||||
@@ -322,13 +322,18 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Login to GHCR
|
||||
env:
|
||||
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
|
||||
run: |
|
||||
mkdir -p "$(dirname "$REGISTRY_AUTH_FILE")"
|
||||
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
|
||||
shell: bash
|
||||
|
||||
- name: Copy tag from Docker Hub to GHCR
|
||||
# Mirror the already-built image (all architectures) to GHCR so we can sign it
|
||||
# Wait a bit for both architectures to be available in Docker Hub manifest
|
||||
env:
|
||||
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG=${{ env.TAG }}
|
||||
|
||||
2
.github/workflows/mirror.yaml
vendored
2
.github/workflows/mirror.yaml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
skopeo list-tags --retry-times 3 docker://"${SOURCE_IMAGE}" \
|
||||
| jq -r '.Tags[]' | sort -u > src-tags.txt
|
||||
| jq -r '.Tags[]' | grep -v -e '-arm64' -e '-amd64' | sort -u > src-tags.txt
|
||||
echo "Found source tags: $(wc -l < src-tags.txt)"
|
||||
head -n 20 src-tags.txt || true
|
||||
|
||||
|
||||
96
:w
96
:w
@@ -1,96 +0,0 @@
|
||||
import { db } from "@server/db/pg/driver";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { __DIRNAME } from "@server/lib/consts";
|
||||
|
||||
const version = "1.14.0";
|
||||
|
||||
export default async function migration() {
|
||||
console.log(`Running setup script ${version}...`);
|
||||
|
||||
try {
|
||||
await db.execute(sql`BEGIN`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "loginPageBranding" (
|
||||
"loginPageBrandingId" serial PRIMARY KEY NOT NULL,
|
||||
"logoUrl" text NOT NULL,
|
||||
"logoWidth" integer NOT NULL,
|
||||
"logoHeight" integer NOT NULL,
|
||||
"primaryColor" text,
|
||||
"resourceTitle" text NOT NULL,
|
||||
"resourceSubtitle" text,
|
||||
"orgTitle" text,
|
||||
"orgSubtitle" text
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "loginPageBrandingOrg" (
|
||||
"loginPageBrandingId" integer NOT NULL,
|
||||
"orgId" varchar NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "resourceHeaderAuthExtendedCompatibility" (
|
||||
"headerAuthExtendedCompatibilityId" serial PRIMARY KEY NOT NULL,
|
||||
"resourceId" integer NOT NULL,
|
||||
"extendedCompatibilityIsActivated" boolean DEFAULT false NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceModeEnabled" boolean DEFAULT false NOT NULL;`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceModeType" text DEFAULT 'forced';`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceTitle" text;`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceMessage" text;`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceEstimatedTime" text;`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "siteResources" ADD COLUMN "tcpPortRangeString" varchar;`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "siteResources" ADD COLUMN "udpPortRangeString" varchar;`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "siteResources" ADD COLUMN "disableIcmp" boolean DEFAULT false NOT NULL;`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "loginPageBrandingOrg" ADD CONSTRAINT "loginPageBrandingOrg_loginPageBrandingId_loginPageBranding_loginPageBrandingId_fk" FOREIGN KEY ("loginPageBrandingId") REFERENCES "public"."loginPageBranding"("loginPageBrandingId") ON DELETE cascade ON UPDATE no action;`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "loginPageBrandingOrg" ADD CONSTRAINT "loginPageBrandingOrg_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "resourceHeaderAuthExtendedCompatibility" ADD CONSTRAINT "resourceHeaderAuthExtendedCompatibility_resourceId_resources_resourceId_fk" FOREIGN KEY ("resourceId") REFERENCES "public"."resources"("resourceId") ON DELETE cascade ON UPDATE no action;`
|
||||
);
|
||||
|
||||
await db.execute(sql`COMMIT`);
|
||||
console.log("Migrated database");
|
||||
} catch (e) {
|
||||
await db.execute(sql`ROLLBACK`);
|
||||
console.log("Unable to migrate database");
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.log(`${version} migration complete`);
|
||||
}
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Търсене на роли...",
|
||||
"accessRolesAdd": "Добавете роля",
|
||||
"accessRoleDelete": "Изтриване на роля",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Описание",
|
||||
"inviteTitle": "Отворени покани",
|
||||
"inviteDescription": "Управлявайте покани за други потребители да се присъединят към организацията",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Изберете продължителност",
|
||||
"selectResource": "Изберете Ресурс",
|
||||
"filterByResource": "Филтрирай По Ресурс",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Нулиране на Филтрите",
|
||||
"totalBlocked": "Заявки Блокирани От Pangolin",
|
||||
"totalRequests": "Общо Заявки",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Държави",
|
||||
"accessRoleCreate": "Създайте роля",
|
||||
"accessRoleCreateDescription": "Създайте нова роля за групиране на потребители и управление на техните разрешения.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Създайте роля",
|
||||
"accessRoleCreated": "Ролята е създадена",
|
||||
"accessRoleCreatedDescription": "Ролята беше успешно създадена.",
|
||||
"accessRoleErrorCreate": "Неуспешно създаване на роля",
|
||||
"accessRoleErrorCreateDescription": "Възникна грешка при създаването на ролята.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Нова роля е необходима",
|
||||
"accessRoleErrorRemove": "Неуспешно премахване на роля",
|
||||
"accessRoleErrorRemoveDescription": "Възникна грешка при премахването на роля.",
|
||||
"accessRoleName": "Име на роля",
|
||||
"accessRoleQuestionRemove": "Ще изтриете ролята {name}. Не можете да отмените това действие.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Премахни роля",
|
||||
"accessRoleRemoveDescription": "Премахни роля от организацията",
|
||||
"accessRoleRemoveSubmit": "Премахни роля",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Конфигуриране на достъп за организация",
|
||||
"idpUpdatedDescription": "Идентификационният доставчик беше актуализиран успешно",
|
||||
"redirectUrl": "URL за пренасочване",
|
||||
"orgIdpRedirectUrls": "URL адреси за пренасочване",
|
||||
"redirectUrlAbout": "За URL за пренасочване",
|
||||
"redirectUrlAboutDescription": "Това е URL адресът, към който потребителите ще бъдат пренасочени след удостоверяване. Трябва да конфигурирате този URL адрес в настройките на доставчика на идентичност.",
|
||||
"pangolinAuth": "Authent - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Изглежда, че сте били поканени!",
|
||||
"inviteAlreadyDescription": "За да приемете поканата, трябва да влезете или да създадете акаунт.",
|
||||
"signupQuestion": "Вече имате акаунт?",
|
||||
"login": "Влизане",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Ресурсът не е намерен",
|
||||
"resourceNotFoundDescription": "Ресурсът, който се опитвате да достъпите, не съществува.",
|
||||
"pincodeRequirementsLength": "ПИН трябва да бъде точно 6 цифри",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Тази организация изисква да сменяте паролата си на всеки {maxDays} дни.",
|
||||
"changePasswordNow": "Сменете паролата сега",
|
||||
"pincodeAuth": "Код на удостоверителя",
|
||||
"pincodeSubmit2": "Изпрати код",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Заявка за нулиране",
|
||||
"passwordResetAlreadyHaveCode": "Въведете код.",
|
||||
"passwordResetSmtpRequired": "Моля, свържете се с вашия администратор",
|
||||
"passwordResetSmtpRequiredDescription": "Кодът за нулиране на парола е задължителен за нулиране на паролата ви. Моля, свържете се с вашия администратор за помощ.",
|
||||
"passwordBack": "Назад към Парола",
|
||||
"loginBack": "Връщане към вход",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Регистрация",
|
||||
"loginStart": "Влезте, за да започнете",
|
||||
"idpOidcTokenValidating": "Валидиране на OIDC токен",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Актуализиране на IdP организация",
|
||||
"actionCreateClient": "Създаване на клиент",
|
||||
"actionDeleteClient": "Изтриване на клиент",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Актуализиране на клиент",
|
||||
"actionListClients": "Списък с клиенти",
|
||||
"actionGetClient": "Получаване на клиент",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Търсене...",
|
||||
"create": "Създаване",
|
||||
"orgs": "Организации",
|
||||
"loginError": "Възникна грешка при влизане",
|
||||
"loginRequiredForDevice": "Необходим е вход за удостоверяване на вашето устройство.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Забравена парола?",
|
||||
"otpAuth": "Двуфакторно удостоверяване",
|
||||
"otpAuthDescription": "Въведете кода от приложението за удостоверяване или един от вашите резервни кодове за еднократна употреба.",
|
||||
"otpAuthSubmit": "Изпрати код",
|
||||
"idpContinue": "Или продължете със",
|
||||
"otpAuthBack": "Назад към Вход",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Навигационно меню",
|
||||
"navbarDescription": "Главно навигационно меню за приложението",
|
||||
"navbarDocsLink": "Документация",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Общ преглед",
|
||||
"sidebarHome": "Начало",
|
||||
"sidebarSites": "Сайтове",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Ресурси",
|
||||
"sidebarProxyResources": "Публично",
|
||||
"sidebarClientResources": "Частно",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Идентификационни доставчици",
|
||||
"sidebarLicense": "Лиценз",
|
||||
"sidebarClients": "Клиенти",
|
||||
"sidebarUserDevices": "Потребители",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Машини",
|
||||
"sidebarDomains": "Домейни",
|
||||
"sidebarGeneral": "Управление.",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Неуспешно обновяване на данни",
|
||||
"verified": "Потвърдено",
|
||||
"pending": "Чакащо",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Фактуриране",
|
||||
"billing": "Фактуриране",
|
||||
"orgBillingDescription": "Управлявайте информацията за плащане и абонаментите",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Ключът за защита е премахнат успешно",
|
||||
"securityKeyRemoveError": "Неуспешно премахване на ключ за защита",
|
||||
"securityKeyLoadError": "Неуспешно зареждане на ключове за защита",
|
||||
"securityKeyLogin": "Продължете с ключа за сигурност",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Неуспешно удостоверяване с ключ за сигурност",
|
||||
"securityKeyRecommendation": "Регистрирайте резервен ключ за безопасност на друго устройство, за да сте сигурни, че винаги ще имате достъп до профила си",
|
||||
"registering": "Регистрация...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Съгласен съм с",
|
||||
"termsOfService": "условията за ползване",
|
||||
"and": "и",
|
||||
"privacyPolicy": "политиката за поверителност"
|
||||
"privacyPolicy": "политика за поверителност."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Дръж ме в течение с новини, актуализации и нови функции чрез имейл."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Интервал за здраве",
|
||||
"timeoutSeconds": "Време за изчакване (сек)",
|
||||
"timeIsInSeconds": "Времето е в секунди",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Опити за повторно",
|
||||
"expectedResponseCodes": "Очаквани кодове за отговор",
|
||||
"expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Изберете своя доставчик на идентичност, за да продължите",
|
||||
"orgAuthNoIdpConfigured": "Тази организация няма конфигурирани доставчици на идентичност. Можете да влезете с вашата Pangolin идентичност.",
|
||||
"orgAuthSignInWithPangolin": "Впишете се с Pangolin",
|
||||
"orgAuthSignInToOrg": "Влезте в организация.",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Вход в организация.",
|
||||
"orgAuthSelectOrgDescription": "Въведете идентификатора на вашата организация, за да продължите.",
|
||||
"orgAuthOrgIdPlaceholder": "вашата-организация",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Кодът трябва да бъде 9 символа (напр. A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Невалиден или изтекъл код",
|
||||
"deviceCodeVerifyFailed": "Неуспешна проверка на кода на устройството",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Вписан като",
|
||||
"deviceCodeEnterPrompt": "Въведете кода, показан на устройството",
|
||||
"continue": "Продължете",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Достъп до всички организации, до които има достъп акаунтът ви",
|
||||
"deviceAuthorize": "Разрешете {applicationName}",
|
||||
"deviceConnected": "Устройството е свързано!",
|
||||
"deviceAuthorizedMessage": "Устройството е разрешено да има достъп до вашия акаунт.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Pangolin Cloud",
|
||||
"viewDevices": "Преглед на устройствата",
|
||||
"viewDevicesDescription": "Управлявайте свързаните си устройства",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Идентификатор",
|
||||
"deviceLoginUseDifferentAccount": "Не сте вие? Използвайте друг акаунт.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запитващо достъп до този акаунт.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Няма Данни",
|
||||
"machineClients": "Машинни клиенти",
|
||||
"install": "Инсталирай",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Въведете потвърждение.",
|
||||
"blueprintViewDetails": "Подробности.",
|
||||
"defaultIdentityProvider": "По подразбиране доставчик на идентичност.",
|
||||
"defaultIdentityProviderDescription": "Когато е избран основен доставчик на идентичност, потребителят ще бъде автоматично пренасочен към доставчика за удостоверяване.",
|
||||
"editInternalResourceDialogNetworkSettings": "Мрежови настройки.",
|
||||
"editInternalResourceDialogAccessPolicy": "Политика за достъп.",
|
||||
"editInternalResourceDialogAddRoles": "Добавяне на роли.",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Услугата временно недостъпна.",
|
||||
"maintenanceScreenMessage": "В момента срещаме технически затруднения. Моля, проверете отново скоро.",
|
||||
"maintenanceScreenEstimatedCompletion": "Прогнозно завършване:",
|
||||
"createInternalResourceDialogDestinationRequired": "Дестинацията е задължителна."
|
||||
"createInternalResourceDialogDestinationRequired": "Дестинацията е задължителна.",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Hledat role...",
|
||||
"accessRolesAdd": "Přidat roli",
|
||||
"accessRoleDelete": "Odstranit roli",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "L 343, 22.12.2009, s. 1).",
|
||||
"inviteTitle": "Otevřít pozvánky",
|
||||
"inviteDescription": "Spravovat pozvánky pro ostatní uživatele do organizace",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Vyberte dobu trvání",
|
||||
"selectResource": "Vybrat dokument",
|
||||
"filterByResource": "Filtrovat podle zdroje",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Resetovat filtry",
|
||||
"totalBlocked": "Požadavky blokovány Pangolinem",
|
||||
"totalRequests": "Celkem požadavků",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Země",
|
||||
"accessRoleCreate": "Vytvořit roli",
|
||||
"accessRoleCreateDescription": "Vytvořte novou roli pro seskupení uživatelů a spravujte jejich oprávnění.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Vytvořit roli",
|
||||
"accessRoleCreated": "Role vytvořena",
|
||||
"accessRoleCreatedDescription": "Role byla úspěšně vytvořena.",
|
||||
"accessRoleErrorCreate": "Nepodařilo se vytvořit roli",
|
||||
"accessRoleErrorCreateDescription": "Došlo k chybě při vytváření role.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Je vyžadována nová role",
|
||||
"accessRoleErrorRemove": "Nepodařilo se odstranit roli",
|
||||
"accessRoleErrorRemoveDescription": "Došlo k chybě při odstraňování role.",
|
||||
"accessRoleName": "Název role",
|
||||
"accessRoleQuestionRemove": "Chystáte se odstranit {name} roli. Tuto akci nelze vrátit zpět.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Odstranit roli",
|
||||
"accessRoleRemoveDescription": "Odebrat roli z organizace",
|
||||
"accessRoleRemoveSubmit": "Odstranit roli",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Konfigurace přístupu pro organizaci",
|
||||
"idpUpdatedDescription": "Poskytovatel identity byl úspěšně aktualizován",
|
||||
"redirectUrl": "Přesměrovat URL",
|
||||
"orgIdpRedirectUrls": "Přesměrovat URL",
|
||||
"redirectUrlAbout": "O přesměrování URL",
|
||||
"redirectUrlAboutDescription": "Toto je URL, na kterou budou uživatelé po ověření přesměrováni. Tuto URL je třeba nastavit v nastavení poskytovatele identity.",
|
||||
"pangolinAuth": "Auth - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Vypadá to, že jste byli pozváni!",
|
||||
"inviteAlreadyDescription": "Chcete-li přijmout pozvánku, musíte se přihlásit nebo vytvořit účet.",
|
||||
"signupQuestion": "Již máte účet?",
|
||||
"login": "Přihlásit se",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Zdroj nebyl nalezen",
|
||||
"resourceNotFoundDescription": "Dokument, ke kterému se snažíte přistupovat, neexistuje.",
|
||||
"pincodeRequirementsLength": "PIN musí být přesně 6 číslic",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Tato organizace vyžaduje změnu hesla každých {maxDays} dní.",
|
||||
"changePasswordNow": "Změnit heslo",
|
||||
"pincodeAuth": "Ověřovací kód",
|
||||
"pincodeSubmit2": "Odeslat kód",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Žádost o obnovení",
|
||||
"passwordResetAlreadyHaveCode": "Zadejte kód",
|
||||
"passwordResetSmtpRequired": "Obraťte se na správce",
|
||||
"passwordResetSmtpRequiredDescription": "Pro obnovení hesla je vyžadován kód pro obnovení hesla. Kontaktujte prosím svého administrátora.",
|
||||
"passwordBack": "Zpět na heslo",
|
||||
"loginBack": "Přejít zpět na přihlášení",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Zaregistrovat se",
|
||||
"loginStart": "Přihlaste se a začněte",
|
||||
"idpOidcTokenValidating": "Ověřování OIDC tokenu",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Aktualizovat IDP Org",
|
||||
"actionCreateClient": "Vytvořit klienta",
|
||||
"actionDeleteClient": "Odstranit klienta",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Aktualizovat klienta",
|
||||
"actionListClients": "Seznam klientů",
|
||||
"actionGetClient": "Získat klienta",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Hledat...",
|
||||
"create": "Vytvořit",
|
||||
"orgs": "Organizace",
|
||||
"loginError": "Při přihlášení došlo k chybě",
|
||||
"loginRequiredForDevice": "Pro ověření vašeho zařízení je nutné se přihlásit.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Zapomněli jste heslo?",
|
||||
"otpAuth": "Dvoufaktorové ověření",
|
||||
"otpAuthDescription": "Zadejte kód z vaší autentizační aplikace nebo jeden z vlastních záložních kódů.",
|
||||
"otpAuthSubmit": "Odeslat kód",
|
||||
"idpContinue": "Nebo pokračovat s",
|
||||
"otpAuthBack": "Zpět na přihlášení",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Navigation Menu",
|
||||
"navbarDescription": "Hlavní navigační menu aplikace",
|
||||
"navbarDocsLink": "Dokumentace",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Přehled",
|
||||
"sidebarHome": "Domů",
|
||||
"sidebarSites": "Stránky",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Zdroje",
|
||||
"sidebarProxyResources": "Veřejnost",
|
||||
"sidebarClientResources": "Soukromé",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Poskytovatelé identity",
|
||||
"sidebarLicense": "Licence",
|
||||
"sidebarClients": "Klienti",
|
||||
"sidebarUserDevices": "Uživatelé",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Stroje a přístroje",
|
||||
"sidebarDomains": "Domény",
|
||||
"sidebarGeneral": "Spravovat",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Obnovení dat se nezdařilo",
|
||||
"verified": "Ověřeno",
|
||||
"pending": "Nevyřízeno",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Fakturace",
|
||||
"billing": "Fakturace",
|
||||
"orgBillingDescription": "Spravovat fakturační informace a předplatné",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Bezpečnostní klíč byl úspěšně odstraněn",
|
||||
"securityKeyRemoveError": "Odstranění bezpečnostního klíče se nezdařilo",
|
||||
"securityKeyLoadError": "Nepodařilo se načíst bezpečnostní klíče",
|
||||
"securityKeyLogin": "Pokračovat s bezpečnostním klíčem",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Ověření bezpečnostním klíčem se nezdařilo",
|
||||
"securityKeyRecommendation": "Registrujte záložní bezpečnostní klíč na jiném zařízení, abyste zajistili, že budete mít vždy přístup ke svému účtu.",
|
||||
"registering": "Registrace...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Souhlasím s",
|
||||
"termsOfService": "podmínky služby",
|
||||
"and": "a",
|
||||
"privacyPolicy": "zásady ochrany osobních údajů"
|
||||
"privacyPolicy": "zásady ochrany osobních údajů."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Udržujte mě ve smyčce s novinkami, aktualizacemi a novými funkcemi e-mailem."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Interval zdraví",
|
||||
"timeoutSeconds": "Časový limit (sek)",
|
||||
"timeIsInSeconds": "Čas je v sekundách",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Opakovat pokusy",
|
||||
"expectedResponseCodes": "Očekávané kódy odezvy",
|
||||
"expectedResponseCodesDescription": "HTTP kód stavu, který označuje zdravý stav. Ponecháte-li prázdné, 200-300 je považováno za zdravé.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Chcete-li pokračovat, vyberte svého poskytovatele identity",
|
||||
"orgAuthNoIdpConfigured": "Tato organizace nemá nakonfigurovány žádné poskytovatele identity. Místo toho se můžete přihlásit s vaší Pangolinovou identitou.",
|
||||
"orgAuthSignInWithPangolin": "Přihlásit se pomocí Pangolinu",
|
||||
"orgAuthSignInToOrg": "Přihlaste se do organizace",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Přihlášení do organizace",
|
||||
"orgAuthSelectOrgDescription": "Zadejte ID vaší organizace pro pokračování",
|
||||
"orgAuthOrgIdPlaceholder": "vaše-organizace",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Kód musí být 9 znaků (např. A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Neplatný nebo prošlý kód",
|
||||
"deviceCodeVerifyFailed": "Ověření kódu zařízení se nezdařilo",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Přihlášen jako",
|
||||
"deviceCodeEnterPrompt": "Zadejte kód zobrazený na zařízení",
|
||||
"continue": "Pokračovat",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Přístup ke všem organizacím má přístup k vašemu účtu",
|
||||
"deviceAuthorize": "Autorizovat {applicationName}",
|
||||
"deviceConnected": "Zařízení připojeno!",
|
||||
"deviceAuthorizedMessage": "Zařízení má oprávnění k přístupu k vašemu účtu.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Pangolin Cloud",
|
||||
"viewDevices": "Zobrazit zařízení",
|
||||
"viewDevicesDescription": "Spravovat připojená zařízení",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "Nejste vy? Použijte jiný účet.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Zařízení žádá o přístup k tomuto účtu.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Žádná data",
|
||||
"machineClients": "Strojoví klienti",
|
||||
"install": "Instalovat",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Zadejte potvrzení",
|
||||
"blueprintViewDetails": "Detaily",
|
||||
"defaultIdentityProvider": "Výchozí poskytovatel identity",
|
||||
"defaultIdentityProviderDescription": "Pokud je vybrán výchozí poskytovatel identity, uživatel bude automaticky přesměrován na poskytovatele pro ověření.",
|
||||
"editInternalResourceDialogNetworkSettings": "Nastavení sítě",
|
||||
"editInternalResourceDialogAccessPolicy": "Přístupová politika",
|
||||
"editInternalResourceDialogAddRoles": "Přidat role",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Služba dočasně nedostupná",
|
||||
"maintenanceScreenMessage": "Momentálně máme technické potíže. Zkontrolujte později.",
|
||||
"maintenanceScreenEstimatedCompletion": "Odhadované dokončení:",
|
||||
"createInternalResourceDialogDestinationRequired": "Cíl je povinný"
|
||||
"createInternalResourceDialogDestinationRequired": "Cíl je povinný",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Rollen suchen...",
|
||||
"accessRolesAdd": "Rolle hinzufügen",
|
||||
"accessRoleDelete": "Rolle löschen",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Beschreibung",
|
||||
"inviteTitle": "Einladungen öffnen",
|
||||
"inviteDescription": "Einladungen für andere Benutzer verwalten, der Organisation beizutreten",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Dauer auswählen",
|
||||
"selectResource": "Ressource auswählen",
|
||||
"filterByResource": "Nach Ressource filtern",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Filter zurücksetzen",
|
||||
"totalBlocked": "Anfragen blockiert von Pangolin",
|
||||
"totalRequests": "Gesamte Anfragen",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Länder",
|
||||
"accessRoleCreate": "Rolle erstellen",
|
||||
"accessRoleCreateDescription": "Erstellen Sie eine neue Rolle, um Benutzer zu gruppieren und ihre Berechtigungen zu verwalten.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Rolle erstellen",
|
||||
"accessRoleCreated": "Rolle erstellt",
|
||||
"accessRoleCreatedDescription": "Die Rolle wurde erfolgreich erstellt.",
|
||||
"accessRoleErrorCreate": "Fehler beim Erstellen der Rolle",
|
||||
"accessRoleErrorCreateDescription": "Beim Erstellen der Rolle ist ein Fehler aufgetreten.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Neue Rolle ist erforderlich",
|
||||
"accessRoleErrorRemove": "Fehler beim Entfernen der Rolle",
|
||||
"accessRoleErrorRemoveDescription": "Beim Entfernen der Rolle ist ein Fehler aufgetreten.",
|
||||
"accessRoleName": "Rollenname",
|
||||
"accessRoleQuestionRemove": "Sie sind dabei, die Rolle {name} zu löschen. Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Rolle entfernen",
|
||||
"accessRoleRemoveDescription": "Eine Rolle aus der Organisation entfernen",
|
||||
"accessRoleRemoveSubmit": "Rolle entfernen",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Zugriff für eine Organisation konfigurieren",
|
||||
"idpUpdatedDescription": "Identitätsanbieter erfolgreich aktualisiert",
|
||||
"redirectUrl": "Weiterleitungs-URL",
|
||||
"orgIdpRedirectUrls": "Umleitungs-URLs",
|
||||
"redirectUrlAbout": "Über die Weiterleitungs-URL",
|
||||
"redirectUrlAboutDescription": "Dies ist die URL, zu der Benutzer nach der Authentifizierung umgeleitet werden. Sie müssen diese URL in den Einstellungen des Identity Providers konfigurieren.",
|
||||
"pangolinAuth": "Authentifizierung - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Sieht aus, als wären Sie eingeladen worden!",
|
||||
"inviteAlreadyDescription": "Um die Einladung anzunehmen, müssen Sie sich einloggen oder ein Konto erstellen.",
|
||||
"signupQuestion": "Haben Sie bereits ein Konto?",
|
||||
"login": "Anmelden",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Ressource nicht gefunden",
|
||||
"resourceNotFoundDescription": "Die Ressource, auf die Sie zugreifen möchten, existiert nicht.",
|
||||
"pincodeRequirementsLength": "PIN muss genau 6 Ziffern lang sein",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Diese Organisation erfordert, dass Sie Ihr Passwort alle {maxDays} Tage ändern.",
|
||||
"changePasswordNow": "Passwort jetzt ändern",
|
||||
"pincodeAuth": "Authentifizierungscode",
|
||||
"pincodeSubmit2": "Code absenden",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Zurücksetzung anfordern",
|
||||
"passwordResetAlreadyHaveCode": "Code eingeben",
|
||||
"passwordResetSmtpRequired": "Bitte kontaktieren Sie Ihren Administrator",
|
||||
"passwordResetSmtpRequiredDescription": "Zum Zurücksetzen Ihres Passworts ist ein Passwort erforderlich. Bitte wenden Sie sich an Ihren Administrator.",
|
||||
"passwordBack": "Zurück zum Passwort",
|
||||
"loginBack": "Zurück zur Anmeldung",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Registrieren",
|
||||
"loginStart": "Melden Sie sich an, um zu beginnen",
|
||||
"idpOidcTokenValidating": "OIDC-Token wird validiert",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "IDP-Organisation aktualisieren",
|
||||
"actionCreateClient": "Client erstellen",
|
||||
"actionDeleteClient": "Client löschen",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Client aktualisieren",
|
||||
"actionListClients": "Clients auflisten",
|
||||
"actionGetClient": "Clients abrufen",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Suche...",
|
||||
"create": "Erstellen",
|
||||
"orgs": "Organisationen",
|
||||
"loginError": "Beim Anmelden ist ein Fehler aufgetreten",
|
||||
"loginRequiredForDevice": "Zur Authentifizierung Ihres Geräts ist eine Anmeldung erforderlich",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Anmeldung ist für Ihr Gerät erforderlich.",
|
||||
"passwordForgot": "Passwort vergessen?",
|
||||
"otpAuth": "Zwei-Faktor-Authentifizierung",
|
||||
"otpAuthDescription": "Geben Sie den Code aus Ihrer Authenticator-App oder einen Ihrer einmaligen Backup-Codes ein.",
|
||||
"otpAuthSubmit": "Code absenden",
|
||||
"idpContinue": "Oder weiter mit",
|
||||
"otpAuthBack": "Zurück zur Anmeldung",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Navigationsmenü",
|
||||
"navbarDescription": "Hauptnavigationsmenü für die Anwendung",
|
||||
"navbarDocsLink": "Dokumentation",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Übersicht",
|
||||
"sidebarHome": "Zuhause",
|
||||
"sidebarSites": "Standorte",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Ressourcen",
|
||||
"sidebarProxyResources": "Öffentlich",
|
||||
"sidebarClientResources": "Privat",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Identitätsanbieter",
|
||||
"sidebarLicense": "Lizenz",
|
||||
"sidebarClients": "Clients",
|
||||
"sidebarUserDevices": "Benutzergeräte",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Maschinen",
|
||||
"sidebarDomains": "Domänen",
|
||||
"sidebarGeneral": "Verwalten",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Datenaktualisierung fehlgeschlagen",
|
||||
"verified": "Verifiziert",
|
||||
"pending": "Ausstehend",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Abrechnung",
|
||||
"billing": "Abrechnung",
|
||||
"orgBillingDescription": "Zahlungsinformationen und Abonnements verwalten",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Sicherheitsschlüssel erfolgreich entfernt",
|
||||
"securityKeyRemoveError": "Fehler beim Entfernen des Sicherheitsschlüssels",
|
||||
"securityKeyLoadError": "Fehler beim Laden der Sicherheitsschlüssel",
|
||||
"securityKeyLogin": "Mit dem Sicherheitsschlüssel fortfahren",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Fehler bei der Authentifizierung mit Sicherheitsschlüssel",
|
||||
"securityKeyRecommendation": "Erwägen Sie die Registrierung eines weiteren Sicherheitsschlüssels auf einem anderen Gerät, um sicherzustellen, dass Sie sich nicht aus Ihrem Konto aussperren.",
|
||||
"registering": "Registrierung...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Ich stimme den",
|
||||
"termsOfService": "Nutzungsbedingungen zu",
|
||||
"and": "und",
|
||||
"privacyPolicy": "Datenschutzrichtlinie"
|
||||
"privacyPolicy": "datenschutzrichtlinie."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Halten Sie mich auf dem Laufenden mit Neuigkeiten, Updates und neuen Funktionen per E-Mail."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Gesunder Intervall",
|
||||
"timeoutSeconds": "Timeout (Sek.)",
|
||||
"timeIsInSeconds": "Zeit ist in Sekunden",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Wiederholungsversuche",
|
||||
"expectedResponseCodes": "Erwartete Antwortcodes",
|
||||
"expectedResponseCodesDescription": "HTTP-Statuscode, der einen gesunden Zustand anzeigt. Wenn leer gelassen, wird 200-300 als gesund angesehen.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Wähle deinen Identitätsanbieter um fortzufahren",
|
||||
"orgAuthNoIdpConfigured": "Diese Organisation hat keine Identitätsanbieter konfiguriert. Sie können sich stattdessen mit Ihrer Pangolin-Identität anmelden.",
|
||||
"orgAuthSignInWithPangolin": "Mit Pangolin anmelden",
|
||||
"orgAuthSignInToOrg": "Bei einer Organisation anmelden",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Organisations-Anmeldung",
|
||||
"orgAuthSelectOrgDescription": "Geben Sie Ihre Organisations-ID ein, um fortzufahren",
|
||||
"orgAuthOrgIdPlaceholder": "Ihre Organisation",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Code muss 9 Zeichen lang sein (z.B. A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Ungültiger oder abgelaufener Code",
|
||||
"deviceCodeVerifyFailed": "Fehler beim Überprüfen des Gerätecodes",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Angemeldet als",
|
||||
"deviceCodeEnterPrompt": "Geben Sie den auf dem Gerät angezeigten Code ein",
|
||||
"continue": "Weiter",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Zugriff auf alle Organisationen, auf die Ihr Konto Zugriff hat",
|
||||
"deviceAuthorize": "{applicationName} autorisieren",
|
||||
"deviceConnected": "Gerät verbunden!",
|
||||
"deviceAuthorizedMessage": "Gerät ist berechtigt, auf Ihr Konto zuzugreifen.",
|
||||
"deviceAuthorizedMessage": "Gerät ist berechtigt, auf Ihr Konto zuzugreifen. Bitte kehren Sie zur Client-Anwendung zurück.",
|
||||
"pangolinCloud": "Pangolin Cloud",
|
||||
"viewDevices": "Geräte anzeigen",
|
||||
"viewDevicesDescription": "Verwalten Sie Ihre verbundenen Geräte",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "Nicht du? Verwenden Sie ein anderes Konto.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Ein Gerät fordert Zugriff auf dieses Konto an.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Keine Daten",
|
||||
"machineClients": "Maschinen-Clients",
|
||||
"install": "Installieren",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Bestätigung eingeben",
|
||||
"blueprintViewDetails": "Details",
|
||||
"defaultIdentityProvider": "Standard Identitätsanbieter",
|
||||
"defaultIdentityProviderDescription": "Wenn ein Standard-Identity Provider ausgewählt ist, wird der Benutzer zur Authentifizierung automatisch an den Anbieter weitergeleitet.",
|
||||
"editInternalResourceDialogNetworkSettings": "Netzwerkeinstellungen",
|
||||
"editInternalResourceDialogAccessPolicy": "Zugriffsrichtlinie",
|
||||
"editInternalResourceDialogAddRoles": "Rollen hinzufügen",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Dienst vorübergehend nicht verfügbar",
|
||||
"maintenanceScreenMessage": "Wir haben derzeit technische Schwierigkeiten. Bitte schauen Sie bald noch einmal vorbei.",
|
||||
"maintenanceScreenEstimatedCompletion": "Geschätzter Abschluss:",
|
||||
"createInternalResourceDialogDestinationRequired": "Ziel ist erforderlich"
|
||||
"createInternalResourceDialogDestinationRequired": "Ziel ist erforderlich",
|
||||
"available": "Verfügbar",
|
||||
"archived": "Archiviert",
|
||||
"noArchivedDevices": "Keine archivierten Geräte gefunden",
|
||||
"deviceArchived": "Gerät archiviert",
|
||||
"deviceArchivedDescription": "Das Gerät wurde erfolgreich archiviert.",
|
||||
"errorArchivingDevice": "Fehler beim Archivieren des Geräts",
|
||||
"failedToArchiveDevice": "Archivierung des Geräts fehlgeschlagen",
|
||||
"deviceQuestionArchive": "Sind Sie sicher, dass Sie dieses Gerät archivieren möchten?",
|
||||
"deviceMessageArchive": "Das Gerät wird archiviert und aus Ihrer Liste der aktiven Geräte entfernt.",
|
||||
"deviceArchiveConfirm": "Gerät archivieren",
|
||||
"archiveDevice": "Gerät archivieren",
|
||||
"archive": "Archiv",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -850,6 +850,7 @@
|
||||
"orgPolicyConfig": "Configure access for an organization",
|
||||
"idpUpdatedDescription": "Identity provider updated successfully",
|
||||
"redirectUrl": "Redirect URL",
|
||||
"orgIdpRedirectUrls": "Redirect URLs",
|
||||
"redirectUrlAbout": "About Redirect URL",
|
||||
"redirectUrlAboutDescription": "This is the URL to which users will be redirected after authentication. You need to configure this URL in the identity provider's settings.",
|
||||
"pangolinAuth": "Auth - Pangolin",
|
||||
@@ -1479,7 +1480,7 @@
|
||||
"IAgreeToThe": "I agree to the",
|
||||
"termsOfService": "terms of service",
|
||||
"and": "and",
|
||||
"privacyPolicy": "privacy policy"
|
||||
"privacyPolicy": "privacy policy."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Keep me in the loop with news, updates, and new features by email."
|
||||
@@ -2349,6 +2350,7 @@
|
||||
"enterConfirmation": "Enter confirmation",
|
||||
"blueprintViewDetails": "Details",
|
||||
"defaultIdentityProvider": "Default Identity Provider",
|
||||
"defaultIdentityProviderDescription": "When a default identity provider is selected, the user will be automatically redirected to the provider for authentication.",
|
||||
"editInternalResourceDialogNetworkSettings": "Network Settings",
|
||||
"editInternalResourceDialogAccessPolicy": "Access Policy",
|
||||
"editInternalResourceDialogAddRoles": "Add Roles",
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Buscar roles...",
|
||||
"accessRolesAdd": "Añadir rol",
|
||||
"accessRoleDelete": "Eliminar rol",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Descripción",
|
||||
"inviteTitle": "Invitaciones abiertas",
|
||||
"inviteDescription": "Administrar invitaciones para que otros usuarios se unan a la organización",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Seleccionar duración",
|
||||
"selectResource": "Seleccionar Recurso",
|
||||
"filterByResource": "Filtrar por Recurso",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Reiniciar filtros",
|
||||
"totalBlocked": "Solicitudes bloqueadas por Pangolin",
|
||||
"totalRequests": "Solicitudes totales",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Países",
|
||||
"accessRoleCreate": "Crear rol",
|
||||
"accessRoleCreateDescription": "Crear un nuevo rol para agrupar usuarios y administrar sus permisos.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Crear rol",
|
||||
"accessRoleCreated": "Rol creado",
|
||||
"accessRoleCreatedDescription": "El rol se ha creado correctamente.",
|
||||
"accessRoleErrorCreate": "Error al crear el rol",
|
||||
"accessRoleErrorCreateDescription": "Se ha producido un error al crear el rol.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Se requiere un nuevo rol",
|
||||
"accessRoleErrorRemove": "Error al eliminar el rol",
|
||||
"accessRoleErrorRemoveDescription": "Ocurrió un error mientras se eliminaba el rol.",
|
||||
"accessRoleName": "Nombre del Rol",
|
||||
"accessRoleQuestionRemove": "Estás a punto de eliminar el rol {name} . No puedes deshacer esta acción.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Quitar rol",
|
||||
"accessRoleRemoveDescription": "Eliminar un rol de la organización",
|
||||
"accessRoleRemoveSubmit": "Quitar rol",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Configurar acceso para una organización",
|
||||
"idpUpdatedDescription": "Proveedor de identidad actualizado correctamente",
|
||||
"redirectUrl": "URL de redirección",
|
||||
"orgIdpRedirectUrls": "Redirigir URL",
|
||||
"redirectUrlAbout": "Acerca de la URL de redirección",
|
||||
"redirectUrlAboutDescription": "Esta es la URL a la que los usuarios serán redireccionados después de la autenticación. Necesitas configurar esta URL en la configuración del proveedor de identidad.",
|
||||
"pangolinAuth": "Autenticación - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "¡Parece que has sido invitado!",
|
||||
"inviteAlreadyDescription": "Para aceptar la invitación, debes iniciar sesión o crear una cuenta.",
|
||||
"signupQuestion": "¿Ya tienes una cuenta?",
|
||||
"login": "Iniciar sesión",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Recurso no encontrado",
|
||||
"resourceNotFoundDescription": "El recurso al que intentas acceder no existe.",
|
||||
"pincodeRequirementsLength": "El PIN debe tener exactamente 6 dígitos",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Esta organización requiere que cambies tu contraseña cada {maxDays} días.",
|
||||
"changePasswordNow": "Cambiar Contraseña Ahora",
|
||||
"pincodeAuth": "Código de autenticación",
|
||||
"pincodeSubmit2": "Enviar código",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Reiniciar Solicitud",
|
||||
"passwordResetAlreadyHaveCode": "Ingresar código",
|
||||
"passwordResetSmtpRequired": "Póngase en contacto con su administrador",
|
||||
"passwordResetSmtpRequiredDescription": "Se requiere un código de restablecimiento de contraseña para restablecer su contraseña. Póngase en contacto con su administrador para obtener asistencia.",
|
||||
"passwordBack": "Volver a la contraseña",
|
||||
"loginBack": "Volver a iniciar sesión",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Regístrate",
|
||||
"loginStart": "Inicia sesión para empezar",
|
||||
"idpOidcTokenValidating": "Validando token OIDC",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Actualizar IDP Org",
|
||||
"actionCreateClient": "Crear cliente",
|
||||
"actionDeleteClient": "Eliminar cliente",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Actualizar cliente",
|
||||
"actionListClients": "Listar clientes",
|
||||
"actionGetClient": "Obtener cliente",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Buscar...",
|
||||
"create": "Crear",
|
||||
"orgs": "Organizaciones",
|
||||
"loginError": "Se ha producido un error al iniciar sesión",
|
||||
"loginRequiredForDevice": "Es necesario iniciar sesión para autenticar tu dispositivo.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "¿Olvidaste tu contraseña?",
|
||||
"otpAuth": "Autenticación de dos factores",
|
||||
"otpAuthDescription": "Introduzca el código de su aplicación de autenticación o uno de sus códigos de copia de seguridad de un solo uso.",
|
||||
"otpAuthSubmit": "Enviar código",
|
||||
"idpContinue": "O continuar con",
|
||||
"otpAuthBack": "Volver a iniciar sesión",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Menú de navegación",
|
||||
"navbarDescription": "Menú de navegación principal para la aplicación",
|
||||
"navbarDocsLink": "Documentación",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Resumen",
|
||||
"sidebarHome": "Inicio",
|
||||
"sidebarSites": "Sitios",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Recursos",
|
||||
"sidebarProxyResources": "Público",
|
||||
"sidebarClientResources": "Privado",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Proveedores de identidad",
|
||||
"sidebarLicense": "Licencia",
|
||||
"sidebarClients": "Clientes",
|
||||
"sidebarUserDevices": "Usuarios",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Máquinas",
|
||||
"sidebarDomains": "Dominios",
|
||||
"sidebarGeneral": "Gestionar",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Error al actualizar datos",
|
||||
"verified": "Verificado",
|
||||
"pending": "Pendiente",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Facturación",
|
||||
"billing": "Facturación",
|
||||
"orgBillingDescription": "Administrar información de facturación y suscripciones",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Llave de seguridad eliminada exitosamente",
|
||||
"securityKeyRemoveError": "Error al eliminar la llave de seguridad",
|
||||
"securityKeyLoadError": "Error al cargar las llaves de seguridad",
|
||||
"securityKeyLogin": "Continuar con clave de seguridad",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Error al autenticar con llave de seguridad",
|
||||
"securityKeyRecommendation": "Considere registrar otra llave de seguridad en un dispositivo diferente para asegurarse de no quedar bloqueado de su cuenta.",
|
||||
"registering": "Registrando...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Estoy de acuerdo con los",
|
||||
"termsOfService": "términos del servicio",
|
||||
"and": "y",
|
||||
"privacyPolicy": "política de privacidad"
|
||||
"privacyPolicy": "política de privacidad."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Mantenerme en el bucle con noticias, actualizaciones y nuevas características por correo electrónico."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Intervalo Saludable",
|
||||
"timeoutSeconds": "Tiempo agotado (seg)",
|
||||
"timeIsInSeconds": "El tiempo está en segundos",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Intentos de Reintento",
|
||||
"expectedResponseCodes": "Códigos de respuesta esperados",
|
||||
"expectedResponseCodesDescription": "Código de estado HTTP que indica un estado saludable. Si se deja en blanco, se considera saludable de 200 a 300.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Elige tu proveedor de identidad para continuar",
|
||||
"orgAuthNoIdpConfigured": "Esta organización no tiene ningún proveedor de identidad configurado. En su lugar puedes iniciar sesión con tu identidad de Pangolin.",
|
||||
"orgAuthSignInWithPangolin": "Iniciar sesión con Pangolin",
|
||||
"orgAuthSignInToOrg": "Iniciar sesión en una organización",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Inicio de sesión de organización",
|
||||
"orgAuthSelectOrgDescription": "Ingrese el ID de su organización para continuar",
|
||||
"orgAuthOrgIdPlaceholder": "tu-organización",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "El código debe tener 9 caracteres (por ejemplo, A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Código no válido o caducado",
|
||||
"deviceCodeVerifyFailed": "Error al verificar el código del dispositivo",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Conectado como",
|
||||
"deviceCodeEnterPrompt": "Introduzca el código mostrado en el dispositivo",
|
||||
"continue": "Continuar",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Acceso a todas las organizaciones a las que su cuenta tiene acceso",
|
||||
"deviceAuthorize": "Autorizar a {applicationName}",
|
||||
"deviceConnected": "¡Dispositivo conectado!",
|
||||
"deviceAuthorizedMessage": "El dispositivo está autorizado para acceder a su cuenta.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Nube de Pangolin",
|
||||
"viewDevices": "Ver dispositivos",
|
||||
"viewDevicesDescription": "Administra tus dispositivos conectados",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "¿No tú? Utilice una cuenta diferente.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo está solicitando acceso a esta cuenta.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Sin datos",
|
||||
"machineClients": "Clientes de la máquina",
|
||||
"install": "Instalar",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Ingresar confirmación",
|
||||
"blueprintViewDetails": "Detalles",
|
||||
"defaultIdentityProvider": "Proveedor de identidad predeterminado",
|
||||
"defaultIdentityProviderDescription": "Cuando se selecciona un proveedor de identidad por defecto, el usuario será redirigido automáticamente al proveedor de autenticación.",
|
||||
"editInternalResourceDialogNetworkSettings": "Configuración de red",
|
||||
"editInternalResourceDialogAccessPolicy": "Política de acceso",
|
||||
"editInternalResourceDialogAddRoles": "Agregar roles",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Servicio temporalmente no disponible",
|
||||
"maintenanceScreenMessage": "Actualmente estamos experimentando dificultades técnicas. Por favor regrese pronto.",
|
||||
"maintenanceScreenEstimatedCompletion": "Estimado completado:",
|
||||
"createInternalResourceDialogDestinationRequired": "Se requiere destino"
|
||||
"createInternalResourceDialogDestinationRequired": "Se requiere destino",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Chercher des rôles...",
|
||||
"accessRolesAdd": "Ajouter un rôle",
|
||||
"accessRoleDelete": "Supprimer le rôle",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Libellé",
|
||||
"inviteTitle": "Invitations actives",
|
||||
"inviteDescription": "Gérer les invitations des autres utilisateurs à rejoindre l'organisation",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Sélectionner la durée",
|
||||
"selectResource": "Sélectionner une ressource",
|
||||
"filterByResource": "Filtrer par ressource",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Réinitialiser les filtres",
|
||||
"totalBlocked": "Demandes bloquées par le Pangolin",
|
||||
"totalRequests": "Total des demandes",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Pays",
|
||||
"accessRoleCreate": "Créer un rôle",
|
||||
"accessRoleCreateDescription": "Créer un nouveau rôle pour regrouper les utilisateurs et gérer leurs permissions.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Créer un rôle",
|
||||
"accessRoleCreated": "Rôle créé",
|
||||
"accessRoleCreatedDescription": "Le rôle a été créé avec succès.",
|
||||
"accessRoleErrorCreate": "Échec de la création du rôle",
|
||||
"accessRoleErrorCreateDescription": "Une erreur s'est produite lors de la création du rôle.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Un nouveau rôle est requis",
|
||||
"accessRoleErrorRemove": "Échec de la suppression du rôle",
|
||||
"accessRoleErrorRemoveDescription": "Une erreur s'est produite lors de la suppression du rôle.",
|
||||
"accessRoleName": "Nom du rôle",
|
||||
"accessRoleQuestionRemove": "Vous êtes sur le point de supprimer le rôle {name}. Cette action est irréversible.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Supprimer le rôle",
|
||||
"accessRoleRemoveDescription": "Retirer un rôle de l'organisation",
|
||||
"accessRoleRemoveSubmit": "Supprimer le rôle",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Configurer l'accès pour une organisation",
|
||||
"idpUpdatedDescription": "Fournisseur d'identité mis à jour avec succès",
|
||||
"redirectUrl": "URL de redirection",
|
||||
"orgIdpRedirectUrls": "URL de redirection",
|
||||
"redirectUrlAbout": "À propos de l'URL de redirection",
|
||||
"redirectUrlAboutDescription": "C'est l'URL vers laquelle les utilisateurs seront redirigés après l'authentification. Vous devez configurer cette URL dans les paramètres du fournisseur d'identité.",
|
||||
"pangolinAuth": "Auth - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "On dirait que vous avez été invité !",
|
||||
"inviteAlreadyDescription": "Pour accepter l'invitation, vous devez vous connecter ou créer un compte.",
|
||||
"signupQuestion": "Vous avez déjà un compte ?",
|
||||
"login": "Se connecter",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Ressource introuvable",
|
||||
"resourceNotFoundDescription": "La ressource que vous essayez d'accéder n'existe pas.",
|
||||
"pincodeRequirementsLength": "Le code PIN doit comporter exactement 6 chiffres",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Cette organisation vous demande de changer votre mot de passe tous les {maxDays} jours.",
|
||||
"changePasswordNow": "Changer le mot de passe maintenant",
|
||||
"pincodeAuth": "Code d'authentification",
|
||||
"pincodeSubmit2": "Soumettre le code",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Demander la réinitialisation",
|
||||
"passwordResetAlreadyHaveCode": "Entrer le code",
|
||||
"passwordResetSmtpRequired": "Veuillez contacter votre administrateur",
|
||||
"passwordResetSmtpRequiredDescription": "Un code de réinitialisation du mot de passe est requis pour réinitialiser votre mot de passe. Veuillez contacter votre administrateur pour obtenir de l'aide.",
|
||||
"passwordBack": "Retour au mot de passe",
|
||||
"loginBack": "Retour à la connexion",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "S'inscrire",
|
||||
"loginStart": "Connectez-vous pour commencer",
|
||||
"idpOidcTokenValidating": "Validation du jeton OIDC",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Mettre à jour une organisation IDP",
|
||||
"actionCreateClient": "Créer un client",
|
||||
"actionDeleteClient": "Supprimer le client",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Mettre à jour le client",
|
||||
"actionListClients": "Liste des clients",
|
||||
"actionGetClient": "Obtenir le client",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Rechercher...",
|
||||
"create": "Créer",
|
||||
"orgs": "Organisations",
|
||||
"loginError": "Une erreur s'est produite lors de la connexion",
|
||||
"loginRequiredForDevice": "La connexion est requise pour authentifier votre appareil.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Mot de passe oublié ?",
|
||||
"otpAuth": "Authentification à deux facteurs",
|
||||
"otpAuthDescription": "Entrez le code de votre application d'authentification ou l'un de vos codes de secours à usage unique.",
|
||||
"otpAuthSubmit": "Soumettre le code",
|
||||
"idpContinue": "Ou continuer avec",
|
||||
"otpAuthBack": "Retour à la connexion",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Menu de navigation",
|
||||
"navbarDescription": "Menu de navigation principal de l'application",
|
||||
"navbarDocsLink": "Documentation",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Aperçu",
|
||||
"sidebarHome": "Domicile",
|
||||
"sidebarSites": "Nœuds",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Ressource",
|
||||
"sidebarProxyResources": "Publique",
|
||||
"sidebarClientResources": "Privé",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Fournisseurs d'identité",
|
||||
"sidebarLicense": "Licence",
|
||||
"sidebarClients": "Clients",
|
||||
"sidebarUserDevices": "Utilisateurs",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Machines",
|
||||
"sidebarDomains": "Domaines",
|
||||
"sidebarGeneral": "Gérer",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Échec de l'actualisation des données",
|
||||
"verified": "Vérifié",
|
||||
"pending": "En attente",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Facturation",
|
||||
"billing": "Facturation",
|
||||
"orgBillingDescription": "Gérer les informations de facturation et les abonnements",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Clé de sécurité supprimée avec succès",
|
||||
"securityKeyRemoveError": "Échec de la suppression de la clé de sécurité",
|
||||
"securityKeyLoadError": "Échec du chargement des clés de sécurité",
|
||||
"securityKeyLogin": "Continuer avec une clé de sécurité",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Échec de l'authentification avec la clé de sécurité",
|
||||
"securityKeyRecommendation": "Envisagez d'enregistrer une autre clé de sécurité sur un appareil différent pour vous assurer de ne pas être bloqué de votre compte.",
|
||||
"registering": "Enregistrement...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Je suis d'accord avec",
|
||||
"termsOfService": "les conditions d'utilisation",
|
||||
"and": "et",
|
||||
"privacyPolicy": "la politique de confidentialité"
|
||||
"privacyPolicy": "politique de confidentialité."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Gardez-moi dans la boucle avec des nouvelles, des mises à jour et de nouvelles fonctionnalités par courriel."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Intervalle sain",
|
||||
"timeoutSeconds": "Délai d'attente (sec)",
|
||||
"timeIsInSeconds": "Le temps est exprimé en secondes",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Tentatives de réessai",
|
||||
"expectedResponseCodes": "Codes de réponse attendus",
|
||||
"expectedResponseCodesDescription": "Code de statut HTTP indiquant un état de santé satisfaisant. Si non renseigné, 200-300 est considéré comme satisfaisant.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Choisissez votre fournisseur d'identité pour continuer",
|
||||
"orgAuthNoIdpConfigured": "Cette organisation n'a aucun fournisseur d'identité configuré. Vous pouvez vous connecter avec votre identité Pangolin à la place.",
|
||||
"orgAuthSignInWithPangolin": "Se connecter avec Pangolin",
|
||||
"orgAuthSignInToOrg": "Connectez-vous à une organisation",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Connexion à l'organisation",
|
||||
"orgAuthSelectOrgDescription": "Entrez votre identifiant d'organisation pour continuer",
|
||||
"orgAuthOrgIdPlaceholder": "votre-organisation",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Le code doit contenir 9 caractères (par exemple, A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Code invalide ou expiré",
|
||||
"deviceCodeVerifyFailed": "Impossible de vérifier le code de l'appareil",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Connecté en tant que",
|
||||
"deviceCodeEnterPrompt": "Entrez le code affiché sur l'appareil",
|
||||
"continue": "Continuer",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Accès à toutes les organisations auxquelles votre compte a accès",
|
||||
"deviceAuthorize": "Autoriser {applicationName}",
|
||||
"deviceConnected": "Appareil connecté !",
|
||||
"deviceAuthorizedMessage": "L'appareil est autorisé à accéder à votre compte.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Nuage de Pangolin",
|
||||
"viewDevices": "Voir les appareils",
|
||||
"viewDevicesDescription": "Gérer vos appareils connectés",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifiant",
|
||||
"deviceLoginUseDifferentAccount": "Pas vous ? Utilisez un autre compte.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Un appareil demande l'accès à ce compte.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Aucune donnée",
|
||||
"machineClients": "Clients Machines",
|
||||
"install": "Installer",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Entrez la confirmation",
|
||||
"blueprintViewDetails": "Détails",
|
||||
"defaultIdentityProvider": "Fournisseur d'identité par défaut",
|
||||
"defaultIdentityProviderDescription": "Lorsqu'un fournisseur d'identité par défaut est sélectionné, l'utilisateur sera automatiquement redirigé vers le fournisseur pour authentification.",
|
||||
"editInternalResourceDialogNetworkSettings": "Paramètres réseau",
|
||||
"editInternalResourceDialogAccessPolicy": "Politique d'accès",
|
||||
"editInternalResourceDialogAddRoles": "Ajouter des rôles",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Service temporairement indisponible",
|
||||
"maintenanceScreenMessage": "Nous rencontrons actuellement des difficultés techniques. Veuillez vérifier ultérieurement.",
|
||||
"maintenanceScreenEstimatedCompletion": "Achèvement estimé :",
|
||||
"createInternalResourceDialogDestinationRequired": "La destination est requise"
|
||||
"createInternalResourceDialogDestinationRequired": "La destination est requise",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Ricerca ruoli...",
|
||||
"accessRolesAdd": "Aggiungi Ruolo",
|
||||
"accessRoleDelete": "Elimina Ruolo",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Descrizione",
|
||||
"inviteTitle": "Inviti Aperti",
|
||||
"inviteDescription": "Gestisci gli inviti per gli altri utenti a unirsi all'organizzazione",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Seleziona durata",
|
||||
"selectResource": "Seleziona Risorsa",
|
||||
"filterByResource": "Filtra Per Risorsa",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Ripristina Filtri",
|
||||
"totalBlocked": "Richieste Bloccate Da Pangolino",
|
||||
"totalRequests": "Totale Richieste",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Paesi",
|
||||
"accessRoleCreate": "Crea Ruolo",
|
||||
"accessRoleCreateDescription": "Crea un nuovo ruolo per raggruppare gli utenti e gestire i loro permessi.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Crea Ruolo",
|
||||
"accessRoleCreated": "Ruolo creato",
|
||||
"accessRoleCreatedDescription": "Il ruolo è stato creato con successo.",
|
||||
"accessRoleErrorCreate": "Impossibile creare il ruolo",
|
||||
"accessRoleErrorCreateDescription": "Si è verificato un errore durante la creazione del ruolo.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Nuovo ruolo richiesto",
|
||||
"accessRoleErrorRemove": "Impossibile rimuovere il ruolo",
|
||||
"accessRoleErrorRemoveDescription": "Si è verificato un errore durante la rimozione del ruolo.",
|
||||
"accessRoleName": "Nome Del Ruolo",
|
||||
"accessRoleQuestionRemove": "Stai per eliminare il ruolo {name}. Non puoi annullare questa azione.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Rimuovi Ruolo",
|
||||
"accessRoleRemoveDescription": "Rimuovi un ruolo dall'organizzazione",
|
||||
"accessRoleRemoveSubmit": "Rimuovi Ruolo",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Configura l'accesso per un'organizzazione",
|
||||
"idpUpdatedDescription": "Provider di identità aggiornato con successo",
|
||||
"redirectUrl": "URL di Reindirizzamento",
|
||||
"orgIdpRedirectUrls": "Reindirizza URL",
|
||||
"redirectUrlAbout": "Informazioni sull'URL di Reindirizzamento",
|
||||
"redirectUrlAboutDescription": "Questo è l'URL a cui gli utenti saranno reindirizzati dopo l'autenticazione. È necessario configurare questo URL nelle impostazioni del provider di identità.",
|
||||
"pangolinAuth": "Autenticazione - Pangolina",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Sembra che sei stato invitato!",
|
||||
"inviteAlreadyDescription": "Per accettare l'invito, devi accedere o creare un account.",
|
||||
"signupQuestion": "Hai già un account?",
|
||||
"login": "Accedi",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Risorsa Non Trovata",
|
||||
"resourceNotFoundDescription": "La risorsa che stai cercando di accedere non esiste.",
|
||||
"pincodeRequirementsLength": "Il PIN deve essere esattamente di 6 cifre",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Questa organizzazione richiede di cambiare la password ogni {maxDays} giorni.",
|
||||
"changePasswordNow": "Cambia Password Ora",
|
||||
"pincodeAuth": "Codice Autenticatore",
|
||||
"pincodeSubmit2": "Invia Codice",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Richiedi Reset",
|
||||
"passwordResetAlreadyHaveCode": "Inserisci Codice",
|
||||
"passwordResetSmtpRequired": "Si prega di contattare l'amministratore",
|
||||
"passwordResetSmtpRequiredDescription": "Per reimpostare la password è necessario un codice di reimpostazione della password. Si prega di contattare l'amministratore per assistenza.",
|
||||
"passwordBack": "Torna alla Password",
|
||||
"loginBack": "Torna al login",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Registrati",
|
||||
"loginStart": "Accedi per iniziare",
|
||||
"idpOidcTokenValidating": "Convalida token OIDC",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Aggiorna Org IDP",
|
||||
"actionCreateClient": "Crea Client",
|
||||
"actionDeleteClient": "Elimina Client",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Aggiorna Client",
|
||||
"actionListClients": "Elenco Clienti",
|
||||
"actionGetClient": "Ottieni Client",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Ricerca...",
|
||||
"create": "Crea",
|
||||
"orgs": "Organizzazioni",
|
||||
"loginError": "Si è verificato un errore durante l'accesso",
|
||||
"loginRequiredForDevice": "È richiesto il login per autenticare il dispositivo.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Password dimenticata?",
|
||||
"otpAuth": "Autenticazione a Due Fattori",
|
||||
"otpAuthDescription": "Inserisci il codice dalla tua app di autenticazione o uno dei tuoi codici di backup monouso.",
|
||||
"otpAuthSubmit": "Invia Codice",
|
||||
"idpContinue": "O continua con",
|
||||
"otpAuthBack": "Torna al Login",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Menu di Navigazione",
|
||||
"navbarDescription": "Menu di navigazione principale dell'applicazione",
|
||||
"navbarDocsLink": "Documentazione",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Panoramica",
|
||||
"sidebarHome": "Home",
|
||||
"sidebarSites": "Siti",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Risorse",
|
||||
"sidebarProxyResources": "Pubblico",
|
||||
"sidebarClientResources": "Privato",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Fornitori Di Identità",
|
||||
"sidebarLicense": "Licenza",
|
||||
"sidebarClients": "Client",
|
||||
"sidebarUserDevices": "Utenti",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Macchine",
|
||||
"sidebarDomains": "Domini",
|
||||
"sidebarGeneral": "Gestisci",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Impossibile aggiornare i dati",
|
||||
"verified": "Verificato",
|
||||
"pending": "In attesa",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Fatturazione",
|
||||
"billing": "Fatturazione",
|
||||
"orgBillingDescription": "Gestisci le informazioni di fatturazione e gli abbonamenti",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Chiave di sicurezza rimossa con successo",
|
||||
"securityKeyRemoveError": "Errore durante la rimozione della chiave di sicurezza",
|
||||
"securityKeyLoadError": "Errore durante il caricamento delle chiavi di sicurezza",
|
||||
"securityKeyLogin": "Continua con la chiave di sicurezza",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Errore durante l'autenticazione con chiave di sicurezza",
|
||||
"securityKeyRecommendation": "Considera di registrare un'altra chiave di sicurezza su un dispositivo diverso per assicurarti di non rimanere bloccato fuori dal tuo account.",
|
||||
"registering": "Registrazione in corso...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Accetto i",
|
||||
"termsOfService": "termini di servizio",
|
||||
"and": "e",
|
||||
"privacyPolicy": "informativa sulla privacy"
|
||||
"privacyPolicy": "informativa sulla privacy."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Tienimi in loop con notizie, aggiornamenti e nuove funzionalità via e-mail."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Intervallo Sano",
|
||||
"timeoutSeconds": "Timeout (sec)",
|
||||
"timeIsInSeconds": "Il tempo è in secondi",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Tentativi di Riprova",
|
||||
"expectedResponseCodes": "Codici di Risposta Attesi",
|
||||
"expectedResponseCodesDescription": "Codice di stato HTTP che indica lo stato di salute. Se lasciato vuoto, considerato sano è compreso tra 200-300.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Scegli il tuo provider di identità per continuare",
|
||||
"orgAuthNoIdpConfigured": "Questa organizzazione non ha nessun provider di identità configurato. Puoi accedere con la tua identità Pangolin.",
|
||||
"orgAuthSignInWithPangolin": "Accedi con Pangolino",
|
||||
"orgAuthSignInToOrg": "Accedi a un'organizzazione",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Accesso Organizzazione",
|
||||
"orgAuthSelectOrgDescription": "Inserisci l'ID dell'organizzazione per continuare",
|
||||
"orgAuthOrgIdPlaceholder": "la-tua-organizzazione",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Il codice deve contenere 9 caratteri (es. A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Codice non valido o scaduto",
|
||||
"deviceCodeVerifyFailed": "Impossibile verificare il codice del dispositivo",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Accesso come",
|
||||
"deviceCodeEnterPrompt": "Inserisci il codice visualizzato sul dispositivo",
|
||||
"continue": "Continua",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Accesso a tutte le organizzazioni a cui il tuo account ha accesso",
|
||||
"deviceAuthorize": "Autorizza {applicationName}",
|
||||
"deviceConnected": "Dispositivo Connesso!",
|
||||
"deviceAuthorizedMessage": "Il dispositivo è autorizzato ad accedere al tuo account.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Pangolin Cloud",
|
||||
"viewDevices": "Visualizza Dispositivi",
|
||||
"viewDevicesDescription": "Gestisci i tuoi dispositivi connessi",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "Non tu? Usa un account diverso.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo sta richiedendo l'accesso a questo account.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Nessun Dato",
|
||||
"machineClients": "Machine Clients",
|
||||
"install": "Installa",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Inserisci conferma",
|
||||
"blueprintViewDetails": "Dettagli",
|
||||
"defaultIdentityProvider": "Provider di Identità Predefinito",
|
||||
"defaultIdentityProviderDescription": "Quando viene selezionato un provider di identità predefinito, l'utente verrà automaticamente reindirizzato al provider per l'autenticazione.",
|
||||
"editInternalResourceDialogNetworkSettings": "Impostazioni di Rete",
|
||||
"editInternalResourceDialogAccessPolicy": "Politica di Accesso",
|
||||
"editInternalResourceDialogAddRoles": "Aggiungi Ruoli",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Servizio Temporaneamente Non Disponibile",
|
||||
"maintenanceScreenMessage": "Stiamo attualmente riscontrando difficoltà tecniche. Si prega di ricontrollare a breve.",
|
||||
"maintenanceScreenEstimatedCompletion": "Completamento Stimato:",
|
||||
"createInternalResourceDialogDestinationRequired": "Destinazione richiesta"
|
||||
"createInternalResourceDialogDestinationRequired": "Destinazione richiesta",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "역할 검색...",
|
||||
"accessRolesAdd": "역할 추가",
|
||||
"accessRoleDelete": "역할 삭제",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "설명",
|
||||
"inviteTitle": "열린 초대",
|
||||
"inviteDescription": "다른 사용자가 조직에 참여하도록 초대장을 관리합니다.",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "지속 시간 선택",
|
||||
"selectResource": "리소스 선택",
|
||||
"filterByResource": "리소스별 필터",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "필터 재설정",
|
||||
"totalBlocked": "Pangolin으로 차단된 요청",
|
||||
"totalRequests": "총 요청 수",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "국가",
|
||||
"accessRoleCreate": "역할 생성",
|
||||
"accessRoleCreateDescription": "사용자를 그룹화하고 권한을 관리하기 위해 새 역할을 생성하세요.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "역할 생성",
|
||||
"accessRoleCreated": "역할이 생성되었습니다.",
|
||||
"accessRoleCreatedDescription": "역할이 성공적으로 생성되었습니다.",
|
||||
"accessRoleErrorCreate": "역할 생성 실패",
|
||||
"accessRoleErrorCreateDescription": "역할 생성 중 오류가 발생했습니다.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "새 역할이 필요합니다.",
|
||||
"accessRoleErrorRemove": "역할 제거에 실패했습니다.",
|
||||
"accessRoleErrorRemoveDescription": "역할을 제거하는 동안 오류가 발생했습니다.",
|
||||
"accessRoleName": "역할 이름",
|
||||
"accessRoleQuestionRemove": "{name} 역할을 삭제하려고 합니다. 이 작업은 취소할 수 없습니다.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "역할 제거",
|
||||
"accessRoleRemoveDescription": "조직에서 역할 제거",
|
||||
"accessRoleRemoveSubmit": "역할 제거",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "조직에 대한 접근을 구성하십시오.",
|
||||
"idpUpdatedDescription": "아이덴티티 제공자가 성공적으로 업데이트되었습니다",
|
||||
"redirectUrl": "리디렉션 URL",
|
||||
"orgIdpRedirectUrls": "리디렉션 URL",
|
||||
"redirectUrlAbout": "리디렉션 URL에 대한 정보",
|
||||
"redirectUrlAboutDescription": "사용자가 인증 후 리디렉션될 URL입니다. 이 URL을 신원 제공자 설정에서 구성해야 합니다.",
|
||||
"pangolinAuth": "인증 - 판골린",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "초대받은 것 같습니다!",
|
||||
"inviteAlreadyDescription": "초대를 수락하려면 로그인하거나 계정을 생성해야 합니다.",
|
||||
"signupQuestion": "이미 계정이 있습니까?",
|
||||
"login": "로그인",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "리소스를 찾을 수 없습니다",
|
||||
"resourceNotFoundDescription": "접근하려는 리소스가 존재하지 않습니다.",
|
||||
"pincodeRequirementsLength": "PIN은 정확히 6자리여야 합니다",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "이 조직은 {maxDays}일마다 비밀번호 변경을 요구합니다.",
|
||||
"changePasswordNow": "지금 비밀번호 변경",
|
||||
"pincodeAuth": "인증 코드",
|
||||
"pincodeSubmit2": "코드 제출",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "재설정 요청",
|
||||
"passwordResetAlreadyHaveCode": "코드를 입력하십시오.",
|
||||
"passwordResetSmtpRequired": "관리자에게 문의하십시오",
|
||||
"passwordResetSmtpRequiredDescription": "비밀번호를 재설정하려면 비밀번호 초기화 코드가 필요합니다. 지원을 받으려면 관리자에게 문의하십시오.",
|
||||
"passwordBack": "비밀번호로 돌아가기",
|
||||
"loginBack": "로그인으로 돌아가기",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "가입하기",
|
||||
"loginStart": "시작하려면 로그인하세요.",
|
||||
"idpOidcTokenValidating": "OIDC 토큰 검증 중",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "IDP 조직 업데이트",
|
||||
"actionCreateClient": "클라이언트 생성",
|
||||
"actionDeleteClient": "클라이언트 삭제",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "클라이언트 업데이트",
|
||||
"actionListClients": "클라이언트 목록",
|
||||
"actionGetClient": "클라이언트 가져오기",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "검색...",
|
||||
"create": "생성",
|
||||
"orgs": "조직",
|
||||
"loginError": "로그인 중 오류가 발생했습니다",
|
||||
"loginRequiredForDevice": "장치를 인증하려면 로그인이 필요합니다.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "비밀번호를 잊으셨나요?",
|
||||
"otpAuth": "이중 인증",
|
||||
"otpAuthDescription": "인증 앱에서 코드를 입력하거나 단일 사용 백업 코드 중 하나를 입력하세요.",
|
||||
"otpAuthSubmit": "코드 제출",
|
||||
"idpContinue": "또는 계속 진행하십시오.",
|
||||
"otpAuthBack": "로그인으로 돌아가기",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "탐색 메뉴",
|
||||
"navbarDescription": "애플리케이션의 주요 탐색 메뉴",
|
||||
"navbarDocsLink": "문서",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "개요",
|
||||
"sidebarHome": "홈",
|
||||
"sidebarSites": "사이트",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "리소스",
|
||||
"sidebarProxyResources": "공유",
|
||||
"sidebarClientResources": "비공개",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "신원 공급자",
|
||||
"sidebarLicense": "라이선스",
|
||||
"sidebarClients": "클라이언트",
|
||||
"sidebarUserDevices": "사용자",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "기계",
|
||||
"sidebarDomains": "도메인",
|
||||
"sidebarGeneral": "관리",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "데이터 새로고침 실패",
|
||||
"verified": "검증됨",
|
||||
"pending": "대기 중",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "청구",
|
||||
"billing": "청구",
|
||||
"orgBillingDescription": "청구 정보 및 구독을 관리하세요",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "보안 키가 성공적으로 제거되었습니다",
|
||||
"securityKeyRemoveError": "보안 키 제거 실패",
|
||||
"securityKeyLoadError": "보안 키를 불러오는 데 실패했습니다",
|
||||
"securityKeyLogin": "보안 키로 계속하기",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "보안 키를 사용한 인증 실패",
|
||||
"securityKeyRecommendation": "항상 계정에 액세스할 수 있도록 다른 장치에 백업 보안 키를 등록하세요.",
|
||||
"registering": "등록 중...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "동의합니다",
|
||||
"termsOfService": "서비스 약관",
|
||||
"and": "및",
|
||||
"privacyPolicy": "개인 정보 보호 정책"
|
||||
"privacyPolicy": "개인 정보 보호 정책."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "이메일을 통해 소식, 업데이트 및 새로운 기능을 받아보세요."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "정상 간격",
|
||||
"timeoutSeconds": "타임아웃(초)",
|
||||
"timeIsInSeconds": "시간은 초 단위입니다",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "재시도 횟수",
|
||||
"expectedResponseCodes": "예상 응답 코드",
|
||||
"expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "계속하려면 신원 공급자를 선택하세요.",
|
||||
"orgAuthNoIdpConfigured": "이 조직은 구성된 신원 공급자가 없습니다. 대신 Pangolin 아이덴티티로 로그인할 수 있습니다.",
|
||||
"orgAuthSignInWithPangolin": "Pangolin으로 로그인",
|
||||
"orgAuthSignInToOrg": "조직에 로그인합니다.",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "조직 로그인",
|
||||
"orgAuthSelectOrgDescription": "계속하려면 조직 ID를 입력하십시오.",
|
||||
"orgAuthOrgIdPlaceholder": "your-organization",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "코드는 9자리여야 합니다 (예: A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "무효하거나 만료된 코드",
|
||||
"deviceCodeVerifyFailed": "이메일 확인에 실패했습니다:",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "로그인한 사용자",
|
||||
"deviceCodeEnterPrompt": "기기에 표시된 코드를 입력하세요",
|
||||
"continue": "계속 진행하기",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "계정이 접근할 수 있는 모든 조직에 대한 접근",
|
||||
"deviceAuthorize": "{applicationName} 권한 부여",
|
||||
"deviceConnected": "장치가 연결되었습니다!",
|
||||
"deviceAuthorizedMessage": "장치가 계정에 액세스할 수 있도록 승인되었습니다.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "판골린 클라우드",
|
||||
"viewDevices": "장치 보기",
|
||||
"viewDevicesDescription": "연결된 장치를 관리하십시오",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "식별자",
|
||||
"deviceLoginUseDifferentAccount": "본인이 아닙니까? 다른 계정을 사용하세요.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "장치가 이 계정에 접근하려고 합니다.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "데이터 없음",
|
||||
"machineClients": "기계 클라이언트",
|
||||
"install": "설치",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "확인 입력",
|
||||
"blueprintViewDetails": "세부 정보",
|
||||
"defaultIdentityProvider": "기본 아이덴티티 공급자",
|
||||
"defaultIdentityProviderDescription": "기본 ID 공급자가 선택되면, 사용자는 인증을 위해 자동으로 해당 공급자로 리디렉션됩니다.",
|
||||
"editInternalResourceDialogNetworkSettings": "네트워크 설정",
|
||||
"editInternalResourceDialogAccessPolicy": "액세스 정책",
|
||||
"editInternalResourceDialogAddRoles": "역할 추가",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "서비스 일시 중단",
|
||||
"maintenanceScreenMessage": "현재 기술적 문제를 겪고 있습니다. 곧 다시 확인하십시오.",
|
||||
"maintenanceScreenEstimatedCompletion": "예상 완료:",
|
||||
"createInternalResourceDialogDestinationRequired": "목적지가 필요합니다."
|
||||
"createInternalResourceDialogDestinationRequired": "목적지가 필요합니다.",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Søk etter roller...",
|
||||
"accessRolesAdd": "Legg til rolle",
|
||||
"accessRoleDelete": "Slett rolle",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Beskrivelse",
|
||||
"inviteTitle": "Åpne invitasjoner",
|
||||
"inviteDescription": "Administrer invitasjoner til andre brukere for å bli med i organisasjonen",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Velg varighet",
|
||||
"selectResource": "Velg ressurs",
|
||||
"filterByResource": "Filtrer etter ressurser",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Tilbakestill filtre",
|
||||
"totalBlocked": "Forespørsler blokkert av Pangolin",
|
||||
"totalRequests": "Totalt antall forespørsler",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Land",
|
||||
"accessRoleCreate": "Opprett rolle",
|
||||
"accessRoleCreateDescription": "Opprett en ny rolle for å gruppere brukere og administrere deres tillatelser.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Opprett rolle",
|
||||
"accessRoleCreated": "Rolle opprettet",
|
||||
"accessRoleCreatedDescription": "Rollen er vellykket opprettet.",
|
||||
"accessRoleErrorCreate": "Klarte ikke å opprette rolle",
|
||||
"accessRoleErrorCreateDescription": "Det oppstod en feil under opprettelse av rollen.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Ny rolle kreves",
|
||||
"accessRoleErrorRemove": "Kunne ikke fjerne rolle",
|
||||
"accessRoleErrorRemoveDescription": "Det oppstod en feil under fjerning av rollen.",
|
||||
"accessRoleName": "Rollenavn",
|
||||
"accessRoleQuestionRemove": "Du er i ferd med å slette rollen {name}. Du kan ikke angre denne handlingen.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Fjern Rolle",
|
||||
"accessRoleRemoveDescription": "Fjern en rolle fra organisasjonen",
|
||||
"accessRoleRemoveSubmit": "Fjern Rolle",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Konfigurer tilgang for en organisasjon",
|
||||
"idpUpdatedDescription": "Identitetsleverandør vellykket oppdatert",
|
||||
"redirectUrl": "Omdirigerings-URL",
|
||||
"orgIdpRedirectUrls": "Omadressere URL'er",
|
||||
"redirectUrlAbout": "Om omdirigerings-URL",
|
||||
"redirectUrlAboutDescription": "Dette er URLen som brukere vil bli omdirigert etter autentisering. Du må konfigurere denne URLen i identitetsleverandørens innstillinger.",
|
||||
"pangolinAuth": "Autentisering - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Ser ut til at du har blitt invitert!",
|
||||
"inviteAlreadyDescription": "For å godta invitasjonen, må du logge inn eller opprette en konto.",
|
||||
"signupQuestion": "Har du allerede en konto?",
|
||||
"login": "Logg inn",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Ressurs ikke funnet",
|
||||
"resourceNotFoundDescription": "Ressursen du prøver å få tilgang til eksisterer ikke.",
|
||||
"pincodeRequirementsLength": "PIN må være nøyaktig 6 siffer",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Denne organisasjonen krever at du bytter passord hver {maxDays} dag.",
|
||||
"changePasswordNow": "Bytt passord nå",
|
||||
"pincodeAuth": "Autentiseringskode",
|
||||
"pincodeSubmit2": "Send inn kode",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Be om tilbakestilling",
|
||||
"passwordResetAlreadyHaveCode": "Skriv inn koden",
|
||||
"passwordResetSmtpRequired": "Kontakt din administrator",
|
||||
"passwordResetSmtpRequiredDescription": "En passord tilbakestillingskode kreves for å tilbakestille passordet. Kontakt systemansvarlig for assistanse.",
|
||||
"passwordBack": "Tilbake til passord",
|
||||
"loginBack": "Gå tilbake til innlogging",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Registrer deg",
|
||||
"loginStart": "Logg inn for å komme i gang",
|
||||
"idpOidcTokenValidating": "Validerer OIDC-token",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Oppdater IDP-organisasjon",
|
||||
"actionCreateClient": "Opprett Klient",
|
||||
"actionDeleteClient": "Slett klient",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Oppdater klient",
|
||||
"actionListClients": "List klienter",
|
||||
"actionGetClient": "Hent klient",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Søker...",
|
||||
"create": "Opprett",
|
||||
"orgs": "Organisasjoner",
|
||||
"loginError": "En feil oppstod under innlogging",
|
||||
"loginRequiredForDevice": "Innlogging kreves for å godkjenne enheten.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Glemt passordet ditt?",
|
||||
"otpAuth": "Tofaktorautentisering",
|
||||
"otpAuthDescription": "Skriv inn koden fra autentiseringsappen din eller en av dine engangs reservekoder.",
|
||||
"otpAuthSubmit": "Send inn kode",
|
||||
"idpContinue": "Eller fortsett med",
|
||||
"otpAuthBack": "Tilbake til innlogging",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Navigasjonsmeny",
|
||||
"navbarDescription": "Hovednavigasjonsmeny for applikasjonen",
|
||||
"navbarDocsLink": "Dokumentasjon",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Oversikt",
|
||||
"sidebarHome": "Hjem",
|
||||
"sidebarSites": "Områder",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Ressurser",
|
||||
"sidebarProxyResources": "Offentlig",
|
||||
"sidebarClientResources": "Privat",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Identitetsleverandører",
|
||||
"sidebarLicense": "Lisens",
|
||||
"sidebarClients": "Klienter",
|
||||
"sidebarUserDevices": "Brukere",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Maskiner",
|
||||
"sidebarDomains": "Domener",
|
||||
"sidebarGeneral": "Administrer",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Klarte ikke å oppdatere data",
|
||||
"verified": "Verifisert",
|
||||
"pending": "Venter",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Fakturering",
|
||||
"billing": "Fakturering",
|
||||
"orgBillingDescription": "Administrer faktureringsinformasjon og abonnementer",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Sikkerhetsnøkkel fjernet",
|
||||
"securityKeyRemoveError": "Klarte ikke å fjerne sikkerhetsnøkkel",
|
||||
"securityKeyLoadError": "Klarte ikke å laste inn sikkerhetsnøkler",
|
||||
"securityKeyLogin": "Fortsett med sikkerhetsnøkkel",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Klarte ikke å autentisere med sikkerhetsnøkkel",
|
||||
"securityKeyRecommendation": "Registrer en reservesikkerhetsnøkkel på en annen enhet for å sikre at du alltid har tilgang til kontoen din.",
|
||||
"registering": "Registrerer...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Jeg godtar",
|
||||
"termsOfService": "brukervilkårene",
|
||||
"and": "og",
|
||||
"privacyPolicy": "personvernerklæringen"
|
||||
"privacyPolicy": "retningslinjer for personvern"
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Hold meg i løken med nyheter, oppdateringer og nye funksjoner via e-post."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Sunt intervall",
|
||||
"timeoutSeconds": "Tidsavbrudd (sek)",
|
||||
"timeIsInSeconds": "Tid er i sekunder",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Forsøk på nytt",
|
||||
"expectedResponseCodes": "Forventede svarkoder",
|
||||
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Velg din identitet leverandør for å fortsette",
|
||||
"orgAuthNoIdpConfigured": "Denne organisasjonen har ikke noen identitetstjeneste konfigurert. Du kan i stedet logge inn med Pangolin identiteten din.",
|
||||
"orgAuthSignInWithPangolin": "Logg inn med Pangolin",
|
||||
"orgAuthSignInToOrg": "Logg inn på en organisasjon",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Organisasjonsinnlogging",
|
||||
"orgAuthSelectOrgDescription": "Skriv inn organisasjons-ID-en din for å fortsette",
|
||||
"orgAuthOrgIdPlaceholder": "din-organisasjon",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Kode må inneholde 9 tegn (f.eks A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Ugyldig eller utløpt kode",
|
||||
"deviceCodeVerifyFailed": "Klarte ikke å bekrefte enhetskoden",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Logget inn som",
|
||||
"deviceCodeEnterPrompt": "Skriv inn koden som vises på enheten",
|
||||
"continue": "Fortsett",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Tilgang til alle organisasjoner din konto har tilgang til",
|
||||
"deviceAuthorize": "Autoriser {applicationName}",
|
||||
"deviceConnected": "Enhet tilkoblet!",
|
||||
"deviceAuthorizedMessage": "Enhet er autorisert for tilgang til kontoen din.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Pangolin Sky",
|
||||
"viewDevices": "Vis enheter",
|
||||
"viewDevicesDescription": "Administrer tilkoblede enheter",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Ingen data",
|
||||
"machineClients": "Maskinklienter",
|
||||
"install": "Installer",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Skriv inn bekreftelse",
|
||||
"blueprintViewDetails": "Detaljer",
|
||||
"defaultIdentityProvider": "Standard identitetsleverandør",
|
||||
"defaultIdentityProviderDescription": "Når en standard identitetsleverandør er valgt, vil brukeren automatisk bli omdirigert til leverandøren for autentisering.",
|
||||
"editInternalResourceDialogNetworkSettings": "Nettverksinnstillinger",
|
||||
"editInternalResourceDialogAccessPolicy": "Tilgangsregler for tilgang",
|
||||
"editInternalResourceDialogAddRoles": "Legg til roller",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Tjenesten er midlertidig utilgjengelig",
|
||||
"maintenanceScreenMessage": "Vi opplever for øyeblikket tekniske problemer. Vennligst sjekk igjen snart.",
|
||||
"maintenanceScreenEstimatedCompletion": "Estimert ferdigstillelse:",
|
||||
"createInternalResourceDialogDestinationRequired": "Destinasjonen er nødvendig"
|
||||
"createInternalResourceDialogDestinationRequired": "Destinasjonen er nødvendig",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Rollen zoeken...",
|
||||
"accessRolesAdd": "Rol toevoegen",
|
||||
"accessRoleDelete": "Verwijder rol",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Beschrijving",
|
||||
"inviteTitle": "Open uitnodigingen",
|
||||
"inviteDescription": "Beheer uitnodigingen voor andere gebruikers om deel te nemen aan de organisatie",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Selecteer duur",
|
||||
"selectResource": "Selecteer Document",
|
||||
"filterByResource": "Filter op pagina",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Filters resetten",
|
||||
"totalBlocked": "Verzoeken geblokkeerd door Pangolin",
|
||||
"totalRequests": "Totaal verzoeken",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Landen",
|
||||
"accessRoleCreate": "Rol aanmaken",
|
||||
"accessRoleCreateDescription": "Maak een nieuwe rol aan om gebruikers te groeperen en hun rechten te beheren.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Rol aanmaken",
|
||||
"accessRoleCreated": "Rol aangemaakt",
|
||||
"accessRoleCreatedDescription": "De rol is succesvol aangemaakt.",
|
||||
"accessRoleErrorCreate": "Rol aanmaken mislukt",
|
||||
"accessRoleErrorCreateDescription": "Fout opgetreden tijdens het aanmaken van de rol.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Nieuwe rol is vereist",
|
||||
"accessRoleErrorRemove": "Rol verwijderen mislukt",
|
||||
"accessRoleErrorRemoveDescription": "Er is een fout opgetreden tijdens het verwijderen van de rol.",
|
||||
"accessRoleName": "Rol naam",
|
||||
"accessRoleQuestionRemove": "U staat op het punt de {name} rol te verwijderen. U kunt deze actie niet ongedaan maken.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Rol verwijderen",
|
||||
"accessRoleRemoveDescription": "Verwijder een rol van de organisatie",
|
||||
"accessRoleRemoveSubmit": "Rol verwijderen",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Toegang voor een organisatie configureren",
|
||||
"idpUpdatedDescription": "Identity provider succesvol bijgewerkt",
|
||||
"redirectUrl": "Omleidings URL",
|
||||
"orgIdpRedirectUrls": "URL's omleiden",
|
||||
"redirectUrlAbout": "Over omleidings-URL",
|
||||
"redirectUrlAboutDescription": "Dit is de URL waarnaar gebruikers worden doorverwezen na verificatie. U moet deze URL configureren in de instellingen van de identiteitsprovider.",
|
||||
"pangolinAuth": "Authenticatie - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Het lijkt erop dat je bent uitgenodigd!",
|
||||
"inviteAlreadyDescription": "Om de uitnodiging te accepteren, moet je inloggen of een account aanmaken.",
|
||||
"signupQuestion": "Heeft u al een account?",
|
||||
"login": "Inloggen",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Bron niet gevonden",
|
||||
"resourceNotFoundDescription": "De bron die u probeert te benaderen bestaat niet.",
|
||||
"pincodeRequirementsLength": "Pincode moet precies 6 cijfers zijn",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Deze organisatie vereist dat u om de {maxDays} dagen uw wachtwoord wijzigt.",
|
||||
"changePasswordNow": "Wijzig wachtwoord nu",
|
||||
"pincodeAuth": "Authenticatiecode",
|
||||
"pincodeSubmit2": "Code indienen",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Opnieuw instellen aanvragen",
|
||||
"passwordResetAlreadyHaveCode": "Code invoeren",
|
||||
"passwordResetSmtpRequired": "Neem contact op met uw beheerder",
|
||||
"passwordResetSmtpRequiredDescription": "Er is een wachtwoord reset code nodig om uw wachtwoord opnieuw in te stellen. Neem contact op met uw beheerder voor hulp.",
|
||||
"passwordBack": "Terug naar wachtwoord",
|
||||
"loginBack": "Ga terug naar login",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Registreer nu",
|
||||
"loginStart": "Log in om te beginnen",
|
||||
"idpOidcTokenValidating": "Valideer OIDC-token",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "IDP-org bijwerken",
|
||||
"actionCreateClient": "Client aanmaken",
|
||||
"actionDeleteClient": "Verwijder klant",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Klant bijwerken",
|
||||
"actionListClients": "Lijst klanten",
|
||||
"actionGetClient": "Client ophalen",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Zoeken...",
|
||||
"create": "Aanmaken",
|
||||
"orgs": "Organisaties",
|
||||
"loginError": "Er is een fout opgetreden tijdens het inloggen",
|
||||
"loginRequiredForDevice": "Inloggen is vereist om je apparaat te verifiëren.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Wachtwoord vergeten?",
|
||||
"otpAuth": "Tweestapsverificatie verificatie",
|
||||
"otpAuthDescription": "Voer de code van je authenticator-app of een van je reservekopiecodes voor het eenmalig gebruik in.",
|
||||
"otpAuthSubmit": "Code indienen",
|
||||
"idpContinue": "Of ga verder met",
|
||||
"otpAuthBack": "Terug naar inloggen",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Navigatiemenu",
|
||||
"navbarDescription": "Hoofd navigatie menu voor de applicatie",
|
||||
"navbarDocsLink": "Documentatie",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Overzicht.",
|
||||
"sidebarHome": "Startpagina",
|
||||
"sidebarSites": "Werkruimtes",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Bronnen",
|
||||
"sidebarProxyResources": "Openbaar",
|
||||
"sidebarClientResources": "Privé",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Identiteit aanbieders",
|
||||
"sidebarLicense": "Licentie",
|
||||
"sidebarClients": "Clienten",
|
||||
"sidebarUserDevices": "Gebruikers",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Machines",
|
||||
"sidebarDomains": "Domeinen",
|
||||
"sidebarGeneral": "Beheren",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Het vernieuwen van gegevens is mislukt",
|
||||
"verified": "Gecontroleerd",
|
||||
"pending": "In afwachting",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Facturering",
|
||||
"billing": "Facturering",
|
||||
"orgBillingDescription": "Beheer factureringsinformatie en abonnementen",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Beveiligingssleutel succesvol verwijderd",
|
||||
"securityKeyRemoveError": "Fout bij verwijderen van beveiligingssleutel",
|
||||
"securityKeyLoadError": "Fout bij laden van beveiligingssleutels",
|
||||
"securityKeyLogin": "Doorgaan met beveiligingssleutel",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Fout bij authenticatie met beveiligingssleutel",
|
||||
"securityKeyRecommendation": "Overweeg om een andere beveiligingssleutel te registreren op een ander apparaat om ervoor te zorgen dat u niet buitengesloten raakt van uw account.",
|
||||
"registering": "Registreren...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Ik ga akkoord met de",
|
||||
"termsOfService": "servicevoorwaarden",
|
||||
"and": "en",
|
||||
"privacyPolicy": "privacybeleid"
|
||||
"privacyPolicy": "privacy beleid"
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Houd me op de hoogte met nieuws, updates en nieuwe functies per e-mail."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Gezonde Interval",
|
||||
"timeoutSeconds": "Timeout (sec)",
|
||||
"timeIsInSeconds": "Tijd is in seconden",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Herhaal Pogingen",
|
||||
"expectedResponseCodes": "Verwachte Reactiecodes",
|
||||
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Kies uw identiteitsprovider om door te gaan",
|
||||
"orgAuthNoIdpConfigured": "Deze organisatie heeft geen identiteitsproviders geconfigureerd. Je kunt in plaats daarvan inloggen met je Pangolin-identiteit.",
|
||||
"orgAuthSignInWithPangolin": "Log in met Pangolin",
|
||||
"orgAuthSignInToOrg": "Meld u aan bij een organisatie",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Organisatie Inloggen",
|
||||
"orgAuthSelectOrgDescription": "Voer je organisatie-ID in om verder te gaan",
|
||||
"orgAuthOrgIdPlaceholder": "jouw-organisatie",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Code moet 9 tekens bevatten (bijv. A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Ongeldige of verlopen code",
|
||||
"deviceCodeVerifyFailed": "Apparaatcode verifiëren mislukt",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Ingelogd als",
|
||||
"deviceCodeEnterPrompt": "Voer de op het apparaat weergegeven code in",
|
||||
"continue": "Doorgaan",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Toegang tot alle organisaties waar uw account toegang tot heeft",
|
||||
"deviceAuthorize": "Autoriseer {applicationName}",
|
||||
"deviceConnected": "Apparaat verbonden!",
|
||||
"deviceAuthorizedMessage": "Apparaat is gemachtigd om toegang te krijgen tot je account.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Pangoline Cloud",
|
||||
"viewDevices": "Bekijk apparaten",
|
||||
"viewDevicesDescription": "Beheer uw aangesloten apparaten",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "Niet u? Gebruik een ander account.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Een apparaat vraagt om toegang tot dit account.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Geen gegevens",
|
||||
"machineClients": "Machine Clienten",
|
||||
"install": "Installeren",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Bevestiging invoeren",
|
||||
"blueprintViewDetails": "Details",
|
||||
"defaultIdentityProvider": "Standaard Identiteitsprovider",
|
||||
"defaultIdentityProviderDescription": "Wanneer een standaard identity provider is geselecteerd, zal de gebruiker automatisch worden doorgestuurd naar de provider voor authenticatie.",
|
||||
"editInternalResourceDialogNetworkSettings": "Netwerkinstellingen",
|
||||
"editInternalResourceDialogAccessPolicy": "Toegangsbeleid",
|
||||
"editInternalResourceDialogAddRoles": "Rollen toevoegen",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Dienst tijdelijk niet beschikbaar",
|
||||
"maintenanceScreenMessage": "We hebben momenteel technische problemen. Probeer het later opnieuw.",
|
||||
"maintenanceScreenEstimatedCompletion": "Geschatte voltooiing:",
|
||||
"createInternalResourceDialogDestinationRequired": "Bestemming is vereist"
|
||||
"createInternalResourceDialogDestinationRequired": "Bestemming is vereist",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Szukaj ról...",
|
||||
"accessRolesAdd": "Dodaj rolę",
|
||||
"accessRoleDelete": "Usuń rolę",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Opis",
|
||||
"inviteTitle": "Otwórz zaproszenia",
|
||||
"inviteDescription": "Zarządzaj zaproszeniami dla innych użytkowników do dołączenia do organizacji",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Wybierz okres",
|
||||
"selectResource": "Wybierz zasób",
|
||||
"filterByResource": "Filtruj według zasobów",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Resetuj filtry",
|
||||
"totalBlocked": "Żądania zablokowane przez Pangolina",
|
||||
"totalRequests": "Wszystkich Żądań",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Kraje",
|
||||
"accessRoleCreate": "Utwórz rolę",
|
||||
"accessRoleCreateDescription": "Utwórz nową rolę aby zgrupować użytkowników i zarządzać ich uprawnieniami.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Utwórz rolę",
|
||||
"accessRoleCreated": "Rola utworzona",
|
||||
"accessRoleCreatedDescription": "Rola została pomyślnie utworzona.",
|
||||
"accessRoleErrorCreate": "Nie udało się utworzyć roli",
|
||||
"accessRoleErrorCreateDescription": "Wystąpił błąd podczas tworzenia roli.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Nowa rola jest wymagana",
|
||||
"accessRoleErrorRemove": "Nie udało się usunąć roli",
|
||||
"accessRoleErrorRemoveDescription": "Wystąpił błąd podczas usuwania roli.",
|
||||
"accessRoleName": "Nazwa roli",
|
||||
"accessRoleQuestionRemove": "Zamierzasz usunąć rolę {name}. Tej akcji nie można cofnąć.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Usuń rolę",
|
||||
"accessRoleRemoveDescription": "Usuń rolę z organizacji",
|
||||
"accessRoleRemoveSubmit": "Usuń rolę",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Skonfiguruj dostęp dla organizacji",
|
||||
"idpUpdatedDescription": "Dostawca tożsamości został pomyślnie zaktualizowany",
|
||||
"redirectUrl": "URL przekierowania",
|
||||
"orgIdpRedirectUrls": "Przekieruj adresy URL",
|
||||
"redirectUrlAbout": "O URL przekierowania",
|
||||
"redirectUrlAboutDescription": "Jest to adres URL, na który użytkownicy zostaną przekierowani po uwierzytelnieniu. Musisz skonfigurować ten adres URL w ustawieniach dostawcy tożsamości.",
|
||||
"pangolinAuth": "Autoryzacja - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Wygląda na to, że zostałeś już zaproszony!",
|
||||
"inviteAlreadyDescription": "Aby zaakceptować zaproszenie, musisz się zalogować lub utworzyć konto.",
|
||||
"signupQuestion": "Masz już konto?",
|
||||
"login": "Zaloguj się",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Nie znaleziono zasobu",
|
||||
"resourceNotFoundDescription": "Zasób, do którego próbujesz uzyskać dostęp, nie istnieje.",
|
||||
"pincodeRequirementsLength": "PIN musi składać się dokładnie z 6 cyfr",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Organizacja wymaga zmiany hasła co {maxDays} dni.",
|
||||
"changePasswordNow": "Zmień hasło teraz",
|
||||
"pincodeAuth": "Kod uwierzytelniający",
|
||||
"pincodeSubmit2": "Wyślij kod",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Zażądaj resetowania",
|
||||
"passwordResetAlreadyHaveCode": "Wprowadź kod",
|
||||
"passwordResetSmtpRequired": "Skontaktuj się z administratorem",
|
||||
"passwordResetSmtpRequiredDescription": "Aby zresetować hasło, wymagany jest kod resetowania hasła. Skontaktuj się z administratorem.",
|
||||
"passwordBack": "Powrót do hasła",
|
||||
"loginBack": "Wróć do logowania",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Zarejestruj się",
|
||||
"loginStart": "Zaloguj się, aby rozpocząć",
|
||||
"idpOidcTokenValidating": "Walidacja tokena OIDC",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Aktualizuj organizację IDP",
|
||||
"actionCreateClient": "Utwórz klienta",
|
||||
"actionDeleteClient": "Usuń klienta",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Aktualizuj klienta",
|
||||
"actionListClients": "Lista klientów",
|
||||
"actionGetClient": "Pobierz klienta",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Szukaj...",
|
||||
"create": "Utwórz",
|
||||
"orgs": "Organizacje",
|
||||
"loginError": "Wystąpił błąd podczas logowania",
|
||||
"loginRequiredForDevice": "Logowanie jest wymagane do uwierzytelnienia urządzenia.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Zapomniałeś hasła?",
|
||||
"otpAuth": "Uwierzytelnianie dwuskładnikowe",
|
||||
"otpAuthDescription": "Wprowadź kod z aplikacji uwierzytelniającej lub jeden z jednorazowych kodów zapasowych.",
|
||||
"otpAuthSubmit": "Wyślij kod",
|
||||
"idpContinue": "Lub kontynuuj z",
|
||||
"otpAuthBack": "Powrót do logowania",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Menu nawigacyjne",
|
||||
"navbarDescription": "Główne menu nawigacyjne aplikacji",
|
||||
"navbarDocsLink": "Dokumentacja",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Przegląd",
|
||||
"sidebarHome": "Strona główna",
|
||||
"sidebarSites": "Witryny",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Zasoby",
|
||||
"sidebarProxyResources": "Publiczne",
|
||||
"sidebarClientResources": "Prywatny",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Dostawcy tożsamości",
|
||||
"sidebarLicense": "Licencja",
|
||||
"sidebarClients": "Klienty",
|
||||
"sidebarUserDevices": "Użytkownicy",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Maszyny",
|
||||
"sidebarDomains": "Domeny",
|
||||
"sidebarGeneral": "Zarządzaj",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Nie udało się odświeżyć danych",
|
||||
"verified": "Zatwierdzony",
|
||||
"pending": "Oczekuje",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Fakturowanie",
|
||||
"billing": "Fakturowanie",
|
||||
"orgBillingDescription": "Zarządzaj informacjami rozliczeniowymi i subskrypcjami",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Klucz bezpieczeństwa został pomyślnie usunięty",
|
||||
"securityKeyRemoveError": "Błąd podczas usuwania klucza bezpieczeństwa",
|
||||
"securityKeyLoadError": "Błąd podczas ładowania kluczy bezpieczeństwa",
|
||||
"securityKeyLogin": "Zaloguj się kluczem bezpieczeństwa",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Błąd podczas uwierzytelniania kluczem bezpieczeństwa",
|
||||
"securityKeyRecommendation": "Rozważ zarejestrowanie innego klucza bezpieczeństwa na innym urządzeniu, aby upewnić się, że nie zostaniesz zablokowany z dostępu do swojego konta.",
|
||||
"registering": "Rejestracja...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Zgadzam się z",
|
||||
"termsOfService": "warunkami usługi",
|
||||
"and": "oraz",
|
||||
"privacyPolicy": "polityką prywatności"
|
||||
"privacyPolicy": "polityka prywatności."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Zachowaj mnie w pętli z wiadomościami, aktualizacjami i nowymi funkcjami przez e-mail."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Interwał Zdrowy",
|
||||
"timeoutSeconds": "Limit czasu (sek)",
|
||||
"timeIsInSeconds": "Czas w sekundach",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Próby Ponowienia",
|
||||
"expectedResponseCodes": "Oczekiwane Kody Odpowiedzi",
|
||||
"expectedResponseCodesDescription": "Kod statusu HTTP, który wskazuje zdrowy status. Jeśli pozostanie pusty, uznaje się 200-300 za zdrowy.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Wybierz swojego dostawcę tożsamości, aby kontynuować",
|
||||
"orgAuthNoIdpConfigured": "Ta organizacja nie ma skonfigurowanych żadnych dostawców tożsamości. Zamiast tego możesz zalogować się za pomocą swojej tożsamości Pangolin.",
|
||||
"orgAuthSignInWithPangolin": "Zaloguj się używając Pangolin",
|
||||
"orgAuthSignInToOrg": "Zaloguj się do organizacji",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Logowanie do organizacji",
|
||||
"orgAuthSelectOrgDescription": "Wprowadź identyfikator organizacji, aby kontynuować",
|
||||
"orgAuthOrgIdPlaceholder": "twoja-organizacja",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Kod musi mieć 9 znaków (np. A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Nieprawidłowy lub wygasły kod",
|
||||
"deviceCodeVerifyFailed": "Nie udało się zweryfikować kodu urządzenia",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Zalogowany jako",
|
||||
"deviceCodeEnterPrompt": "Wprowadź kod wyświetlany na urządzeniu",
|
||||
"continue": "Kontynuuj",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Dostęp do wszystkich organizacji, do których Twoje konto ma dostęp",
|
||||
"deviceAuthorize": "Autoryzuj {applicationName}",
|
||||
"deviceConnected": "Urządzenie podłączone!",
|
||||
"deviceAuthorizedMessage": "Urządzenie jest upoważnione do dostępu do Twojego konta.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Chmura Pangolin",
|
||||
"viewDevices": "Zobacz urządzenia",
|
||||
"viewDevicesDescription": "Zarządzaj podłączonymi urządzeniami",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "Nie ty? Użyj innego konta.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Urządzenie żąda dostępu do tego konta.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Brak danych",
|
||||
"machineClients": "Klienci maszyn",
|
||||
"install": "Zainstaluj",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Wprowadź potwierdzenie",
|
||||
"blueprintViewDetails": "Szczegóły",
|
||||
"defaultIdentityProvider": "Domyślny dostawca tożsamości",
|
||||
"defaultIdentityProviderDescription": "Gdy zostanie wybrany domyślny dostawca tożsamości, użytkownik zostanie automatycznie przekierowany do dostawcy w celu uwierzytelnienia.",
|
||||
"editInternalResourceDialogNetworkSettings": "Ustawienia sieci",
|
||||
"editInternalResourceDialogAccessPolicy": "Polityka dostępowa",
|
||||
"editInternalResourceDialogAddRoles": "Dodaj role",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Usługa chwilowo niedostępna",
|
||||
"maintenanceScreenMessage": "Obecnie doświadczamy problemów technicznych. Proszę sprawdzić ponownie wkrótce.",
|
||||
"maintenanceScreenEstimatedCompletion": "Szacowane zakończenie:",
|
||||
"createInternalResourceDialogDestinationRequired": "Miejsce docelowe jest wymagane"
|
||||
"createInternalResourceDialogDestinationRequired": "Miejsce docelowe jest wymagane",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Pesquisar funções...",
|
||||
"accessRolesAdd": "Adicionar função",
|
||||
"accessRoleDelete": "Excluir Papel",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Descrição:",
|
||||
"inviteTitle": "Convites Abertos",
|
||||
"inviteDescription": "Gerenciar convites para outros usuários participarem da organização",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Selecionar duração",
|
||||
"selectResource": "Selecionar Recurso",
|
||||
"filterByResource": "Filtrar por Recurso",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Redefinir filtros",
|
||||
"totalBlocked": "Solicitações bloqueadas pelo Pangolin",
|
||||
"totalRequests": "Total de pedidos",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Países",
|
||||
"accessRoleCreate": "Criar Função",
|
||||
"accessRoleCreateDescription": "Crie uma nova função para agrupar utilizadores e gerir suas permissões.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Criar Função",
|
||||
"accessRoleCreated": "Função criada",
|
||||
"accessRoleCreatedDescription": "A função foi criada com sucesso.",
|
||||
"accessRoleErrorCreate": "Falha ao criar função",
|
||||
"accessRoleErrorCreateDescription": "Ocorreu um erro ao criar a função.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Nova função é necessária",
|
||||
"accessRoleErrorRemove": "Falha ao remover função",
|
||||
"accessRoleErrorRemoveDescription": "Ocorreu um erro ao remover a função.",
|
||||
"accessRoleName": "Nome da Função",
|
||||
"accessRoleQuestionRemove": "Você está prestes a apagar a função {name}. Você não pode desfazer esta ação.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Remover Função",
|
||||
"accessRoleRemoveDescription": "Remover uma função da organização",
|
||||
"accessRoleRemoveSubmit": "Remover Função",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Configurar acesso para uma organização",
|
||||
"idpUpdatedDescription": "Provedor de identidade atualizado com sucesso",
|
||||
"redirectUrl": "URL de Redirecionamento",
|
||||
"orgIdpRedirectUrls": "Redirecionar URLs",
|
||||
"redirectUrlAbout": "Sobre o URL de Redirecionamento",
|
||||
"redirectUrlAboutDescription": "Essa é a URL para a qual os usuários serão redirecionados após a autenticação. Você precisa configurar esta URL nas configurações do provedor de identidade.",
|
||||
"pangolinAuth": "Autenticação - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Parece que já foi convidado!",
|
||||
"inviteAlreadyDescription": "Para aceitar o convite, deve iniciar sessão ou criar uma conta.",
|
||||
"signupQuestion": "Já tem uma conta?",
|
||||
"login": "Iniciar sessão",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Recurso Não Encontrado",
|
||||
"resourceNotFoundDescription": "O recurso que está a tentar aceder não existe.",
|
||||
"pincodeRequirementsLength": "O PIN deve ter exatamente 6 dígitos",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Esta organização exige que você altere sua senha a cada {maxDays} dias.",
|
||||
"changePasswordNow": "Alterar a senha agora",
|
||||
"pincodeAuth": "Código do Autenticador",
|
||||
"pincodeSubmit2": "Submeter Código",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Solicitar Redefinição",
|
||||
"passwordResetAlreadyHaveCode": "Inserir Código",
|
||||
"passwordResetSmtpRequired": "Por favor, contate o administrador",
|
||||
"passwordResetSmtpRequiredDescription": "É necessário um código de redefinição de senha para redefinir sua senha. Por favor, contate o administrador para assistência.",
|
||||
"passwordBack": "Voltar à Palavra-passe",
|
||||
"loginBack": "Voltar ao início de sessão",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Registar",
|
||||
"loginStart": "Inicie sessão para começar",
|
||||
"idpOidcTokenValidating": "A validar token OIDC",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Atualizar Organização IDP",
|
||||
"actionCreateClient": "Criar Cliente",
|
||||
"actionDeleteClient": "Excluir Cliente",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Atualizar Cliente",
|
||||
"actionListClients": "Listar Clientes",
|
||||
"actionGetClient": "Obter Cliente",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Pesquisar...",
|
||||
"create": "Criar",
|
||||
"orgs": "Organizações",
|
||||
"loginError": "Ocorreu um erro ao iniciar sessão",
|
||||
"loginRequiredForDevice": "É necessário entrar para autenticar seu dispositivo.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Esqueceu a sua palavra-passe?",
|
||||
"otpAuth": "Autenticação de Dois Fatores",
|
||||
"otpAuthDescription": "Insira o código da sua aplicação de autenticação ou um dos seus códigos de backup de uso único.",
|
||||
"otpAuthSubmit": "Submeter Código",
|
||||
"idpContinue": "Ou continuar com",
|
||||
"otpAuthBack": "Voltar ao Início de Sessão",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Menu de Navegação",
|
||||
"navbarDescription": "Menu de navegação principal da aplicação",
|
||||
"navbarDocsLink": "Documentação",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Geral",
|
||||
"sidebarHome": "Residencial",
|
||||
"sidebarSites": "sites",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Recursos",
|
||||
"sidebarProxyResources": "Público",
|
||||
"sidebarClientResources": "Privado",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Provedores de identidade",
|
||||
"sidebarLicense": "Tipo:",
|
||||
"sidebarClients": "Clientes",
|
||||
"sidebarUserDevices": "Utilizadores",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Máquinas",
|
||||
"sidebarDomains": "Domínios",
|
||||
"sidebarGeneral": "Gerir",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Falha ao atualizar dados",
|
||||
"verified": "Verificado",
|
||||
"pending": "Pendente",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Faturamento",
|
||||
"billing": "Faturamento",
|
||||
"orgBillingDescription": "Gerenciar informações e assinaturas de cobrança",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Chave de segurança removida com sucesso",
|
||||
"securityKeyRemoveError": "Erro ao remover chave de segurança",
|
||||
"securityKeyLoadError": "Erro ao carregar chaves de segurança",
|
||||
"securityKeyLogin": "Continuar com a chave de segurança",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Erro ao autenticar com chave de segurança",
|
||||
"securityKeyRecommendation": "Considere registrar outra chave de segurança em um dispositivo diferente para garantir que você não fique bloqueado da sua conta.",
|
||||
"registering": "Registrando...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Concordo com",
|
||||
"termsOfService": "os termos de serviço",
|
||||
"and": "e",
|
||||
"privacyPolicy": "política de privacidade"
|
||||
"privacyPolicy": "política de privacidade."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Mantenha-me à disposição com notícias, atualizações e novos recursos por e-mail."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Intervalo Saudável",
|
||||
"timeoutSeconds": "Tempo limite (seg)",
|
||||
"timeIsInSeconds": "O tempo está em segundos",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Tentativas de Repetição",
|
||||
"expectedResponseCodes": "Códigos de Resposta Esperados",
|
||||
"expectedResponseCodesDescription": "Código de status HTTP que indica estado saudável. Se deixado em branco, 200-300 é considerado saudável.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Escolha o seu provedor de identidade para continuar",
|
||||
"orgAuthNoIdpConfigured": "Esta organização não tem nenhum provedor de identidade configurado. Você pode entrar com a identidade do seu Pangolin.",
|
||||
"orgAuthSignInWithPangolin": "Entrar com o Pangolin",
|
||||
"orgAuthSignInToOrg": "Entrar em uma organização",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Entrada da Organização",
|
||||
"orgAuthSelectOrgDescription": "Digite seu ID da organização para continuar",
|
||||
"orgAuthOrgIdPlaceholder": "sua-organização",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "O código deve ter 9 caracteres (ex.: A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Código inválido ou expirado",
|
||||
"deviceCodeVerifyFailed": "Falha ao verificar o código do dispositivo",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Sessão iniciada como",
|
||||
"deviceCodeEnterPrompt": "Digite o código exibido no dispositivo",
|
||||
"continue": "Continuar",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Acesso a todas as organizações que sua conta tem acesso a",
|
||||
"deviceAuthorize": "Autorizar {applicationName}",
|
||||
"deviceConnected": "Dispositivo Conectado!",
|
||||
"deviceAuthorizedMessage": "O dispositivo está autorizado a acessar sua conta.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Nuvem do Pangolin",
|
||||
"viewDevices": "Ver Dispositivos",
|
||||
"viewDevicesDescription": "Gerencie seus dispositivos conectados",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "Não é você? Use uma conta diferente.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Um dispositivo está solicitando acesso a essa conta.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Nenhum dado encontrado",
|
||||
"machineClients": "Clientes de máquina",
|
||||
"install": "Instale",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Inserir confirmação",
|
||||
"blueprintViewDetails": "Detalhes",
|
||||
"defaultIdentityProvider": "Provedor de Identidade Padrão",
|
||||
"defaultIdentityProviderDescription": "Quando um provedor de identidade padrão for selecionado, o usuário será automaticamente redirecionado para o provedor de autenticação.",
|
||||
"editInternalResourceDialogNetworkSettings": "Configurações de Rede",
|
||||
"editInternalResourceDialogAccessPolicy": "Política de Acesso",
|
||||
"editInternalResourceDialogAddRoles": "Adicionar Funções",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Serviço Temporariamente Indisponível",
|
||||
"maintenanceScreenMessage": "Estamos enfrentando dificuldades técnicas no momento. Por favor, volte em breve.",
|
||||
"maintenanceScreenEstimatedCompletion": "Conclusão Estimada:",
|
||||
"createInternalResourceDialogDestinationRequired": "Destino é obrigatório"
|
||||
"createInternalResourceDialogDestinationRequired": "Destino é obrigatório",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Поиск ролей...",
|
||||
"accessRolesAdd": "Добавить роль",
|
||||
"accessRoleDelete": "Удалить роль",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Описание",
|
||||
"inviteTitle": "Открытые приглашения",
|
||||
"inviteDescription": "Управление приглашениями для присоединения других пользователей к организации",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Укажите срок действия",
|
||||
"selectResource": "Выберите ресурс",
|
||||
"filterByResource": "Фильтровать по ресурсам",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Сбросить фильтры",
|
||||
"totalBlocked": "Запросы заблокированы Панголином",
|
||||
"totalRequests": "Всего запросов",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Страны",
|
||||
"accessRoleCreate": "Создание роли",
|
||||
"accessRoleCreateDescription": "Создайте новую роль для группы пользователей и выдавайте им разрешения.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Создать роль",
|
||||
"accessRoleCreated": "Роль создана",
|
||||
"accessRoleCreatedDescription": "Роль была успешно создана.",
|
||||
"accessRoleErrorCreate": "Не удалось создать роль",
|
||||
"accessRoleErrorCreateDescription": "Произошла ошибка при создании роли.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Новая роль обязательна",
|
||||
"accessRoleErrorRemove": "Не удалось удалить роль",
|
||||
"accessRoleErrorRemoveDescription": "Произошла ошибка при удалении роли.",
|
||||
"accessRoleName": "Название роли",
|
||||
"accessRoleQuestionRemove": "Вы собираетесь удалить роль {name}. Это действие нельзя отменить.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Удалить роль",
|
||||
"accessRoleRemoveDescription": "Удалить роль из организации",
|
||||
"accessRoleRemoveSubmit": "Удалить роль",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Настроить доступ для организации",
|
||||
"idpUpdatedDescription": "Поставщик удостоверений успешно обновлён",
|
||||
"redirectUrl": "URL редиректа",
|
||||
"orgIdpRedirectUrls": "Перенаправление URL",
|
||||
"redirectUrlAbout": "О редиректе URL",
|
||||
"redirectUrlAboutDescription": "Это URL, на который пользователи будут перенаправлены после аутентификации. Вам нужно настроить этот URL в настройках провайдера.",
|
||||
"pangolinAuth": "Аутентификация - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Похоже, вы были приглашены!",
|
||||
"inviteAlreadyDescription": "Чтобы принять приглашение, вы должны войти или создать учётную запись.",
|
||||
"signupQuestion": "Уже есть учётная запись?",
|
||||
"login": "Войти",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "Ресурс не найден",
|
||||
"resourceNotFoundDescription": "Ресурс, к которому вы пытаетесь получить доступ, не существует.",
|
||||
"pincodeRequirementsLength": "PIN должен состоять ровно из 6 цифр",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Эта организация требует смены пароля каждые {maxDays} дней.",
|
||||
"changePasswordNow": "Изменить пароль сейчас",
|
||||
"pincodeAuth": "Код аутентификатора",
|
||||
"pincodeSubmit2": "Отправить код",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Запросить сброс",
|
||||
"passwordResetAlreadyHaveCode": "Введите код",
|
||||
"passwordResetSmtpRequired": "Пожалуйста, обратитесь к администратору",
|
||||
"passwordResetSmtpRequiredDescription": "Для сброса пароля необходим код сброса пароля. Обратитесь к администратору за помощью.",
|
||||
"passwordBack": "Назад к паролю",
|
||||
"loginBack": "Вернуться к входу",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Регистрация",
|
||||
"loginStart": "Войдите для начала работы",
|
||||
"idpOidcTokenValidating": "Проверка OIDC токена",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Обновить организацию IDP",
|
||||
"actionCreateClient": "Создать Клиента",
|
||||
"actionDeleteClient": "Удалить Клиента",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Обновить Клиента",
|
||||
"actionListClients": "Список Клиентов",
|
||||
"actionGetClient": "Получить Клиента",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Поиск...",
|
||||
"create": "Создать",
|
||||
"orgs": "Организации",
|
||||
"loginError": "Произошла ошибка при входе",
|
||||
"loginRequiredForDevice": "Для аутентификации устройства необходимо войти в систему.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Забыли пароль?",
|
||||
"otpAuth": "Двухфакторная аутентификация",
|
||||
"otpAuthDescription": "Введите код из вашего приложения-аутентификатора или один из ваших одноразовых резервных кодов.",
|
||||
"otpAuthSubmit": "Отправить код",
|
||||
"idpContinue": "Или продолжить с",
|
||||
"otpAuthBack": "Вернуться к входу",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Навигационное меню",
|
||||
"navbarDescription": "Главное навигационное меню приложения",
|
||||
"navbarDocsLink": "Документация",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Обзор",
|
||||
"sidebarHome": "Главная",
|
||||
"sidebarSites": "Сайты",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Ресурсы",
|
||||
"sidebarProxyResources": "Публичный",
|
||||
"sidebarClientResources": "Приватный",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Поставщики удостоверений",
|
||||
"sidebarLicense": "Лицензия",
|
||||
"sidebarClients": "Клиенты",
|
||||
"sidebarUserDevices": "Пользователи",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Машины",
|
||||
"sidebarDomains": "Домены",
|
||||
"sidebarGeneral": "Управление",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Не удалось обновить данные",
|
||||
"verified": "Подтверждено",
|
||||
"pending": "В ожидании",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Выставление счетов",
|
||||
"billing": "Выставление счетов",
|
||||
"orgBillingDescription": "Управление платежной информацией и подписками",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Ключ безопасности успешно удален",
|
||||
"securityKeyRemoveError": "Не удалось удалить ключ безопасности",
|
||||
"securityKeyLoadError": "Не удалось загрузить ключи безопасности",
|
||||
"securityKeyLogin": "Продолжить с ключом безопасности",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Не удалось аутентифицироваться с ключом безопасности",
|
||||
"securityKeyRecommendation": "Зарегистрируйте резервный ключ безопасности на другом устройстве, чтобы всегда иметь доступ к вашему аккаунту.",
|
||||
"registering": "Регистрация...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Я согласен с",
|
||||
"termsOfService": "условия использования",
|
||||
"and": "и",
|
||||
"privacyPolicy": "политика конфиденциальности"
|
||||
"privacyPolicy": "политика конфиденциальности."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Держите меня в цикле с новостями, обновлениями и новыми функциями по электронной почте."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Интервал здоровых состояний",
|
||||
"timeoutSeconds": "Таймаут (сек)",
|
||||
"timeIsInSeconds": "Время указано в секундах",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Количество попыток повторного запроса",
|
||||
"expectedResponseCodes": "Ожидаемые коды ответов",
|
||||
"expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Выберите своего поставщика удостоверений личности для продолжения",
|
||||
"orgAuthNoIdpConfigured": "Эта организация не имеет настроенных поставщиков идентификационных данных. Вместо этого вы можете войти в свой Pangolin.",
|
||||
"orgAuthSignInWithPangolin": "Войти через Pangolin",
|
||||
"orgAuthSignInToOrg": "Войдите в организацию",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Вход в организацию",
|
||||
"orgAuthSelectOrgDescription": "Введите ID вашей организации, чтобы продолжить",
|
||||
"orgAuthOrgIdPlaceholder": "ваша-организация",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Код должен быть 9 символов (например, A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Неверный или просроченный код",
|
||||
"deviceCodeVerifyFailed": "Не удалось проверить код устройства",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Вы вошли как",
|
||||
"deviceCodeEnterPrompt": "Введите код, отображаемый на устройстве",
|
||||
"continue": "Продолжить",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Доступ ко всем организациям, к которым ваш аккаунт имеет доступ",
|
||||
"deviceAuthorize": "Авторизовать {applicationName}",
|
||||
"deviceConnected": "Устройство подключено!",
|
||||
"deviceAuthorizedMessage": "Устройство авторизовано для доступа к вашей учетной записи.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Облако Панголина",
|
||||
"viewDevices": "Просмотр устройств",
|
||||
"viewDevicesDescription": "Управление подключенными устройствами",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "Не вы? Используйте другую учетную запись.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запрашивает доступ к этой учетной записи.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Нет данных",
|
||||
"machineClients": "Машинные клиенты",
|
||||
"install": "Установить",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Введите подтверждение",
|
||||
"blueprintViewDetails": "Подробности",
|
||||
"defaultIdentityProvider": "Поставщик удостоверений по умолчанию",
|
||||
"defaultIdentityProviderDescription": "Когда выбран поставщик идентификации по умолчанию, пользователь будет автоматически перенаправлен на провайдер для аутентификации.",
|
||||
"editInternalResourceDialogNetworkSettings": "Настройки сети",
|
||||
"editInternalResourceDialogAccessPolicy": "Политика доступа",
|
||||
"editInternalResourceDialogAddRoles": "Добавить роли",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Сервис временно недоступен",
|
||||
"maintenanceScreenMessage": "В настоящее время мы испытываем технические трудности. Пожалуйста, зайдите позже.",
|
||||
"maintenanceScreenEstimatedCompletion": "Предполагаемое завершение:",
|
||||
"createInternalResourceDialogDestinationRequired": "Укажите адрес назначения. Это может быть имя хоста или IP-адрес."
|
||||
"createInternalResourceDialogDestinationRequired": "Укажите адрес назначения. Это может быть имя хоста или IP-адрес.",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "Rolleri ara...",
|
||||
"accessRolesAdd": "Rol Ekle",
|
||||
"accessRoleDelete": "Rolü Sil",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "Açıklama",
|
||||
"inviteTitle": "Açık Davetiyeler",
|
||||
"inviteDescription": "Organizasyona katılmak için diğer kullanıcılar için davetleri yönetin",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "Süreyi seçin",
|
||||
"selectResource": "Kaynak Seçin",
|
||||
"filterByResource": "Kaynağa Göre Filtrele",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "Filtreleri Sıfırla",
|
||||
"totalBlocked": "Pangolin Tarafından Engellenen İstekler",
|
||||
"totalRequests": "Toplam İstekler",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "Ülkeler",
|
||||
"accessRoleCreate": "Rol Oluştur",
|
||||
"accessRoleCreateDescription": "Kullanıcıları gruplamak ve izinlerini yönetmek için yeni bir rol oluşturun.",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "Rol Oluştur",
|
||||
"accessRoleCreated": "Rol oluşturuldu",
|
||||
"accessRoleCreatedDescription": "Rol başarıyla oluşturuldu.",
|
||||
"accessRoleErrorCreate": "Rol oluşturulamadı",
|
||||
"accessRoleErrorCreateDescription": "Rol oluşturulurken bir hata oluştu.",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "Yeni rol gerekli",
|
||||
"accessRoleErrorRemove": "Rol kaldırılamadı",
|
||||
"accessRoleErrorRemoveDescription": "Rol kaldırılırken bir hata oluştu.",
|
||||
"accessRoleName": "Rol Adı",
|
||||
"accessRoleQuestionRemove": "{name} rolünü silmek üzeresiniz. Bu eylemi geri alamazsınız.",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "Rolü Kaldır",
|
||||
"accessRoleRemoveDescription": "Kuruluştan bir rol kaldır",
|
||||
"accessRoleRemoveSubmit": "Rolü Kaldır",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "Bir kuruluş için erişimi yapılandırın",
|
||||
"idpUpdatedDescription": "Kimlik sağlayıcı başarıyla güncellendi",
|
||||
"redirectUrl": "Yönlendirme URL'si",
|
||||
"orgIdpRedirectUrls": "Yönlendirme URL'leri",
|
||||
"redirectUrlAbout": "Yönlendirme URL'si Hakkında",
|
||||
"redirectUrlAboutDescription": "Bu, kimlik doğrulamasından sonra kullanıcıların yönlendirileceği URL'dir. Bu URL'yi kimlik sağlayıcınızın ayarlarında yapılandırmanız gerekir.",
|
||||
"pangolinAuth": "Yetkilendirme - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "Davetiye gönderilmiş gibi görünüyor!",
|
||||
"inviteAlreadyDescription": "Daveti kabul etmek için giriş yapmalı veya bir hesap oluşturmalısınız.",
|
||||
"signupQuestion": "Zaten bir hesabınız var mı?",
|
||||
"login": "Giriş yap",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "No resources found",
|
||||
"resourceNotFoundDescription": "Erişmeye çalıştığınız kaynak mevcut değil.",
|
||||
"pincodeRequirementsLength": "PIN kesinlikle 6 haneli olmalıdır",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "Bu kuruluş, parolanızı {maxDays} günde bir değiştirmenizi gerektirir.",
|
||||
"changePasswordNow": "Şifrenizi Şimdi Değiştirin",
|
||||
"pincodeAuth": "Kimlik Doğrulama Kodu",
|
||||
"pincodeSubmit2": "Kodu Gönder",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "Sıfırlama İsteği",
|
||||
"passwordResetAlreadyHaveCode": "Kodu Girin",
|
||||
"passwordResetSmtpRequired": "Yönetici ile iletişime geçin",
|
||||
"passwordResetSmtpRequiredDescription": "Parolanızı sıfırlamak için bir parola sıfırlama kodu gereklidir. Yardım için yönetici ile iletişime geçin.",
|
||||
"passwordBack": "Şifreye Geri Dön",
|
||||
"loginBack": "Girişe geri dön",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "Kaydol",
|
||||
"loginStart": "Başlamak için giriş yapın",
|
||||
"idpOidcTokenValidating": "OIDC token'ı doğrulanıyor",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "Kimlik Sağlayıcı Organizasyonu Güncelle",
|
||||
"actionCreateClient": "Müşteri Oluştur",
|
||||
"actionDeleteClient": "Müşteri Sil",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "Müşteri Güncelle",
|
||||
"actionListClients": "Müşterileri Listele",
|
||||
"actionGetClient": "Müşteriyi Al",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "Ara...",
|
||||
"create": "Oluştur",
|
||||
"orgs": "Organizasyonlar",
|
||||
"loginError": "Giriş yaparken bir hata oluştu",
|
||||
"loginRequiredForDevice": "Cihazınızı kimlik doğrulamak için giriş yapılması gereklidir.",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "Şifrenizi mi unuttunuz?",
|
||||
"otpAuth": "İki Faktörlü Kimlik Doğrulama",
|
||||
"otpAuthDescription": "Authenticator uygulamanızdan veya tek kullanımlık yedek kodlarınızdan birini girin.",
|
||||
"otpAuthSubmit": "Kodu Gönder",
|
||||
"idpContinue": "Veya devam et:",
|
||||
"otpAuthBack": "Girişe Dön",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Navigasyon Menüsü",
|
||||
"navbarDescription": "Uygulamanın ana navigasyon menüsü",
|
||||
"navbarDocsLink": "Dokümantasyon",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "Genel Bakış",
|
||||
"sidebarHome": "Ana Sayfa",
|
||||
"sidebarSites": "Siteler",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "Kaynaklar",
|
||||
"sidebarProxyResources": "Herkese Açık",
|
||||
"sidebarClientResources": "Özel",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "Kimlik Sağlayıcılar",
|
||||
"sidebarLicense": "Lisans",
|
||||
"sidebarClients": "İstemciler",
|
||||
"sidebarUserDevices": "Kullanıcılar",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "Makineler",
|
||||
"sidebarDomains": "Alan Adları",
|
||||
"sidebarGeneral": "Yönet",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "Veriler yenilenemedi",
|
||||
"verified": "Doğrulandı",
|
||||
"pending": "Beklemede",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "Faturalama",
|
||||
"billing": "Faturalama",
|
||||
"orgBillingDescription": "Fatura bilgilerinizi ve aboneliklerinizi yönetin",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "Güvenlik anahtarı başarıyla kaldırıldı",
|
||||
"securityKeyRemoveError": "Güvenlik anahtarı kaldırılırken hata oluştu",
|
||||
"securityKeyLoadError": "Güvenlik anahtarları yüklenirken hata oluştu",
|
||||
"securityKeyLogin": "Güvenlik anahtarı ile devam edin",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "Güvenlik anahtarı ile kimlik doğrulama başarısız oldu",
|
||||
"securityKeyRecommendation": "Hesabınızdan kilitlenmediğinizden emin olmak için farklı bir cihazda başka bir güvenlik anahtarı kaydetmeyi düşünün.",
|
||||
"registering": "Kaydediliyor...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "Kabul ediyorum",
|
||||
"termsOfService": "hizmet şartları",
|
||||
"and": "ve",
|
||||
"privacyPolicy": "gizlilik politikası"
|
||||
"privacyPolicy": "gizlilik politikası."
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "Bana e-posta yoluyla haberler, güncellemeler ve yeni özellikler hakkında bilgi verin."
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "Sağlıklı Aralık",
|
||||
"timeoutSeconds": "Zaman Aşımı (saniye)",
|
||||
"timeIsInSeconds": "Zaman saniye cinsindendir",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "Tekrar Deneme Girişimleri",
|
||||
"expectedResponseCodes": "Beklenen Yanıt Kodları",
|
||||
"expectedResponseCodesDescription": "Sağlıklı durumu gösteren HTTP durum kodu. Boş bırakılırsa, 200-300 arası sağlıklı kabul edilir.",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "Devam etmek için kimlik sağlayıcınızı seçin",
|
||||
"orgAuthNoIdpConfigured": "Bu kuruluşta yapılandırılmış kimlik sağlayıcı yok. Bunun yerine Pangolin kimliğinizle giriş yapabilirsiniz.",
|
||||
"orgAuthSignInWithPangolin": "Pangolin ile Giriş Yap",
|
||||
"orgAuthSignInToOrg": "Bir kuruluşa giriş yapın",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "Kuruluş Giriş",
|
||||
"orgAuthSelectOrgDescription": "Devam etmek için kuruluş kimliğinizi girin",
|
||||
"orgAuthOrgIdPlaceholder": "kuruluşunuz",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "Kod 9 karakter olmalı (ör. A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "Geçersiz veya süresi dolmuş kod",
|
||||
"deviceCodeVerifyFailed": "Cihaz kodu doğrulanamadı",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "Olarak giriş yapıldı",
|
||||
"deviceCodeEnterPrompt": "Cihazda gösterilen kodu girin",
|
||||
"continue": "Devam Et",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "Hesabınızın erişim hakkına sahip olduğu tüm organizasyonlara erişim",
|
||||
"deviceAuthorize": "{uygulamaAdi} yetkilendir",
|
||||
"deviceConnected": "Cihaz Bağlandı!",
|
||||
"deviceAuthorizedMessage": "Cihazınız, hesabınıza erişim izni almıştır.",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "Pangolin Cloud",
|
||||
"viewDevices": "Cihazları Görüntüle",
|
||||
"viewDevicesDescription": "Bağlantılı cihazlarınızı yönetin",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Tanımlayıcı",
|
||||
"deviceLoginUseDifferentAccount": "Siz değil misiniz? Farklı bir hesap kullanın.",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "Bir cihaz bu hesaba erişim talep ediyor.",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "Veri Yok",
|
||||
"machineClients": "Makine İstemcileri",
|
||||
"install": "Yükle",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "Onayı girin",
|
||||
"blueprintViewDetails": "Detaylar",
|
||||
"defaultIdentityProvider": "Varsayılan Kimlik Sağlayıcı",
|
||||
"defaultIdentityProviderDescription": "Varsayılan bir kimlik sağlayıcı seçildiğinde, kullanıcı kimlik doğrulaması için otomatik olarak sağlayıcıya yönlendirilecektir.",
|
||||
"editInternalResourceDialogNetworkSettings": "Ağ Ayarları",
|
||||
"editInternalResourceDialogAccessPolicy": "Erişim Politikası",
|
||||
"editInternalResourceDialogAddRoles": "Roller Ekle",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "Servis Geçici Olarak Kullanılamıyor",
|
||||
"maintenanceScreenMessage": "Şu anda teknik zorluklar yaşıyoruz. Lütfen yakında tekrar kontrol edin.",
|
||||
"maintenanceScreenEstimatedCompletion": "Tahmini Tamamlama:",
|
||||
"createInternalResourceDialogDestinationRequired": "Hedef gereklidir"
|
||||
"createInternalResourceDialogDestinationRequired": "Hedef gereklidir",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@
|
||||
"accessRolesSearch": "搜索角色...",
|
||||
"accessRolesAdd": "添加角色",
|
||||
"accessRoleDelete": "删除角色",
|
||||
"accessApprovalsManage": "Manage Approvals",
|
||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
||||
"description": "描述",
|
||||
"inviteTitle": "打开邀请",
|
||||
"inviteDescription": "管理其他用户加入机构的邀请",
|
||||
@@ -450,6 +452,18 @@
|
||||
"selectDuration": "选择持续时间",
|
||||
"selectResource": "选择资源",
|
||||
"filterByResource": "按资源过滤",
|
||||
"selectApprovalState": "Select Approval State",
|
||||
"filterByApprovalState": "Filter By Approval State",
|
||||
"approvalListEmpty": "No approvals",
|
||||
"approvalState": "Approval State",
|
||||
"approve": "Approve",
|
||||
"approved": "Approved",
|
||||
"denied": "Denied",
|
||||
"deniedApproval": "Denied Approval",
|
||||
"all": "All",
|
||||
"deny": "Deny",
|
||||
"viewDetails": "View Details",
|
||||
"requestingNewDeviceApproval": "requested a new device",
|
||||
"resetFilters": "重置过滤器",
|
||||
"totalBlocked": "被Pangolin阻止的请求",
|
||||
"totalRequests": "总请求",
|
||||
@@ -729,16 +743,28 @@
|
||||
"countries": "国家",
|
||||
"accessRoleCreate": "创建角色",
|
||||
"accessRoleCreateDescription": "创建一个新角色来分组用户并管理他们的权限。",
|
||||
"accessRoleEdit": "Edit Role",
|
||||
"accessRoleEditDescription": "Edit role information.",
|
||||
"accessRoleCreateSubmit": "创建角色",
|
||||
"accessRoleCreated": "角色已创建",
|
||||
"accessRoleCreatedDescription": "角色已成功创建。",
|
||||
"accessRoleErrorCreate": "创建角色失败",
|
||||
"accessRoleErrorCreateDescription": "创建角色时出错。",
|
||||
"accessRoleUpdateSubmit": "Update Role",
|
||||
"accessRoleUpdated": "Role updated",
|
||||
"accessRoleUpdatedDescription": "The role has been successfully updated.",
|
||||
"accessApprovalUpdated": "Approval processed",
|
||||
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
|
||||
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
|
||||
"accessRoleErrorUpdate": "Failed to update role",
|
||||
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
|
||||
"accessApprovalErrorUpdate": "Failed to process approval",
|
||||
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
|
||||
"accessRoleErrorNewRequired": "需要新角色",
|
||||
"accessRoleErrorRemove": "删除角色失败",
|
||||
"accessRoleErrorRemoveDescription": "删除角色时出错。",
|
||||
"accessRoleName": "角色名称",
|
||||
"accessRoleQuestionRemove": "您即将删除 {name} 角色。 此操作无法撤销。",
|
||||
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
|
||||
"accessRoleRemove": "删除角色",
|
||||
"accessRoleRemoveDescription": "从组织中删除角色",
|
||||
"accessRoleRemoveSubmit": "删除角色",
|
||||
@@ -850,6 +876,7 @@
|
||||
"orgPolicyConfig": "配置组织访问权限",
|
||||
"idpUpdatedDescription": "身份提供商更新成功",
|
||||
"redirectUrl": "重定向网址",
|
||||
"orgIdpRedirectUrls": "重定向URL",
|
||||
"redirectUrlAbout": "关于重定向网址",
|
||||
"redirectUrlAboutDescription": "这是用户在验证后将被重定向到的URL。您需要在身份提供者的设置中配置此URL。",
|
||||
"pangolinAuth": "认证 - Pangolin",
|
||||
@@ -873,7 +900,7 @@
|
||||
"inviteAlready": "看起来您已被邀请!",
|
||||
"inviteAlreadyDescription": "要接受邀请,您必须登录或创建一个帐户。",
|
||||
"signupQuestion": "已经有一个帐户?",
|
||||
"login": "登录",
|
||||
"login": "Log In",
|
||||
"resourceNotFound": "找不到资源",
|
||||
"resourceNotFoundDescription": "您要访问的资源不存在。",
|
||||
"pincodeRequirementsLength": "PIN码必须是6位数字",
|
||||
@@ -953,13 +980,13 @@
|
||||
"passwordExpiryDescription": "该机构要求您每 {maxDays} 天更改一次密码。",
|
||||
"changePasswordNow": "现在更改密码",
|
||||
"pincodeAuth": "验证器代码",
|
||||
"pincodeSubmit2": "提交代码",
|
||||
"pincodeSubmit2": "Submit code",
|
||||
"passwordResetSubmit": "请求重置",
|
||||
"passwordResetAlreadyHaveCode": "输入代码",
|
||||
"passwordResetSmtpRequired": "请联系您的管理员",
|
||||
"passwordResetSmtpRequiredDescription": "需要密码重置密码。请联系您的管理员寻求帮助。",
|
||||
"passwordBack": "回到密码",
|
||||
"loginBack": "返回登录",
|
||||
"loginBack": "Go back to main login page",
|
||||
"signup": "注册",
|
||||
"loginStart": "登录以开始",
|
||||
"idpOidcTokenValidating": "正在验证 OIDC 令牌",
|
||||
@@ -1117,6 +1144,10 @@
|
||||
"actionUpdateIdpOrg": "更新 IDP组织",
|
||||
"actionCreateClient": "创建客户端",
|
||||
"actionDeleteClient": "删除客户端",
|
||||
"actionArchiveClient": "Archive Client",
|
||||
"actionUnarchiveClient": "Unarchive Client",
|
||||
"actionBlockClient": "Block Client",
|
||||
"actionUnblockClient": "Unblock Client",
|
||||
"actionUpdateClient": "更新客户端",
|
||||
"actionListClients": "列出客户端",
|
||||
"actionGetClient": "获取客户端",
|
||||
@@ -1133,14 +1164,14 @@
|
||||
"searchProgress": "搜索中...",
|
||||
"create": "创建",
|
||||
"orgs": "组织",
|
||||
"loginError": "登录时出错",
|
||||
"loginRequiredForDevice": "需要登录才能验证您的设备。",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
"loginRequiredForDevice": "Login is required for your device.",
|
||||
"passwordForgot": "忘记密码?",
|
||||
"otpAuth": "两步验证",
|
||||
"otpAuthDescription": "从您的身份验证程序中输入代码或您的单次备份代码。",
|
||||
"otpAuthSubmit": "提交代码",
|
||||
"idpContinue": "或者继续",
|
||||
"otpAuthBack": "返回登录",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "导航菜单",
|
||||
"navbarDescription": "应用程序的主导航菜单",
|
||||
"navbarDocsLink": "文件",
|
||||
@@ -1188,6 +1219,7 @@
|
||||
"sidebarOverview": "概览",
|
||||
"sidebarHome": "首页",
|
||||
"sidebarSites": "站点",
|
||||
"sidebarApprovals": "Approval Requests",
|
||||
"sidebarResources": "资源",
|
||||
"sidebarProxyResources": "公开的",
|
||||
"sidebarClientResources": "非公开的",
|
||||
@@ -1204,7 +1236,7 @@
|
||||
"sidebarIdentityProviders": "身份提供商",
|
||||
"sidebarLicense": "证书",
|
||||
"sidebarClients": "客户端",
|
||||
"sidebarUserDevices": "用户",
|
||||
"sidebarUserDevices": "User Devices",
|
||||
"sidebarMachineClients": "机",
|
||||
"sidebarDomains": "域",
|
||||
"sidebarGeneral": "管理",
|
||||
@@ -1303,6 +1335,7 @@
|
||||
"refreshError": "刷新数据失败",
|
||||
"verified": "已验证",
|
||||
"pending": "待定",
|
||||
"pendingApproval": "Pending Approval",
|
||||
"sidebarBilling": "计费",
|
||||
"billing": "计费",
|
||||
"orgBillingDescription": "管理账单信息和订阅",
|
||||
@@ -1419,7 +1452,7 @@
|
||||
"securityKeyRemoveSuccess": "安全密钥删除成功",
|
||||
"securityKeyRemoveError": "删除安全密钥失败",
|
||||
"securityKeyLoadError": "加载安全密钥失败",
|
||||
"securityKeyLogin": "使用安全密钥继续",
|
||||
"securityKeyLogin": "Use Security Key",
|
||||
"securityKeyAuthError": "使用安全密钥认证失败",
|
||||
"securityKeyRecommendation": "考虑在其他设备上注册另一个安全密钥,以确保不会被锁定在您的账户之外。",
|
||||
"registering": "注册中...",
|
||||
@@ -1479,7 +1512,7 @@
|
||||
"IAgreeToThe": "我同意",
|
||||
"termsOfService": "服务条款",
|
||||
"and": "和",
|
||||
"privacyPolicy": "隐私政策"
|
||||
"privacyPolicy": "隐私政策。"
|
||||
},
|
||||
"signUpMarketing": {
|
||||
"keepMeInTheLoop": "通过电子邮件让我在循环中保持新闻、更新和新功能。"
|
||||
@@ -1546,6 +1579,8 @@
|
||||
"IntervalSeconds": "正常间隔",
|
||||
"timeoutSeconds": "超时(秒)",
|
||||
"timeIsInSeconds": "时间以秒为单位",
|
||||
"requireDeviceApproval": "Require Device Approvals",
|
||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||
"retryAttempts": "重试次数",
|
||||
"expectedResponseCodes": "期望响应代码",
|
||||
"expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空,200-300 被视为健康。",
|
||||
@@ -1875,7 +1910,7 @@
|
||||
"orgAuthChooseIdpDescription": "选择您的身份提供商以继续",
|
||||
"orgAuthNoIdpConfigured": "此机构没有配置任何身份提供者。您可以使用您的 Pangolin 身份登录。",
|
||||
"orgAuthSignInWithPangolin": "使用 Pangolin 登录",
|
||||
"orgAuthSignInToOrg": "登录到一个组织",
|
||||
"orgAuthSignInToOrg": "Sign in to an organization",
|
||||
"orgAuthSelectOrgTitle": "组织登录",
|
||||
"orgAuthSelectOrgDescription": "输入您的组织ID以继续",
|
||||
"orgAuthOrgIdPlaceholder": "您的组织",
|
||||
@@ -2231,6 +2266,8 @@
|
||||
"deviceCodeInvalidFormat": "代码必须是9个字符(如A1AJ-N5JD)",
|
||||
"deviceCodeInvalidOrExpired": "无效或过期的代码",
|
||||
"deviceCodeVerifyFailed": "验证设备代码失败",
|
||||
"deviceCodeValidating": "Validating device code...",
|
||||
"deviceCodeVerifying": "Verifying device authorization...",
|
||||
"signedInAs": "登录为",
|
||||
"deviceCodeEnterPrompt": "输入设备上显示的代码",
|
||||
"continue": "继续",
|
||||
@@ -2243,7 +2280,7 @@
|
||||
"deviceOrganizationsAccess": "访问您的帐户拥有访问权限的所有组织",
|
||||
"deviceAuthorize": "授权{applicationName}",
|
||||
"deviceConnected": "设备已连接!",
|
||||
"deviceAuthorizedMessage": "设备被授权访问您的帐户。",
|
||||
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
|
||||
"pangolinCloud": "邦戈林云",
|
||||
"viewDevices": "查看设备",
|
||||
"viewDevicesDescription": "管理您已连接的设备",
|
||||
@@ -2305,6 +2342,7 @@
|
||||
"identifier": "Identifier",
|
||||
"deviceLoginUseDifferentAccount": "不是你?使用一个不同的帐户。",
|
||||
"deviceLoginDeviceRequestingAccessToAccount": "设备正在请求访问此帐户。",
|
||||
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
|
||||
"noData": "无数据",
|
||||
"machineClients": "机器客户端",
|
||||
"install": "安装",
|
||||
@@ -2349,6 +2387,7 @@
|
||||
"enterConfirmation": "输入确认",
|
||||
"blueprintViewDetails": "详细信息",
|
||||
"defaultIdentityProvider": "默认身份提供商",
|
||||
"defaultIdentityProviderDescription": "当选择默认身份提供商时,用户将自动重定向到提供商进行身份验证。",
|
||||
"editInternalResourceDialogNetworkSettings": "网络设置",
|
||||
"editInternalResourceDialogAccessPolicy": "访问策略",
|
||||
"editInternalResourceDialogAddRoles": "添加角色",
|
||||
@@ -2392,5 +2431,56 @@
|
||||
"maintenanceScreenTitle": "服务暂时不可用",
|
||||
"maintenanceScreenMessage": "我们目前遇到技术问题。 请稍后再回来查看。",
|
||||
"maintenanceScreenEstimatedCompletion": "预计完成时间:",
|
||||
"createInternalResourceDialogDestinationRequired": "需要目标地址"
|
||||
"createInternalResourceDialogDestinationRequired": "需要目标地址",
|
||||
"available": "Available",
|
||||
"archived": "Archived",
|
||||
"noArchivedDevices": "No archived devices found",
|
||||
"deviceArchived": "Device archived",
|
||||
"deviceArchivedDescription": "The device has been successfully archived.",
|
||||
"errorArchivingDevice": "Error archiving device",
|
||||
"failedToArchiveDevice": "Failed to archive device",
|
||||
"deviceQuestionArchive": "Are you sure you want to archive this device?",
|
||||
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
|
||||
"deviceArchiveConfirm": "Archive Device",
|
||||
"archiveDevice": "Archive Device",
|
||||
"archive": "Archive",
|
||||
"deviceUnarchived": "Device unarchived",
|
||||
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
|
||||
"errorUnarchivingDevice": "Error unarchiving device",
|
||||
"failedToUnarchiveDevice": "Failed to unarchive device",
|
||||
"unarchive": "Unarchive",
|
||||
"archiveClient": "Archive Client",
|
||||
"archiveClientQuestion": "Are you sure you want to archive this client?",
|
||||
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
|
||||
"archiveClientConfirm": "Archive Client",
|
||||
"blockClient": "Block Client",
|
||||
"blockClientQuestion": "Are you sure you want to block this client?",
|
||||
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
|
||||
"blockClientConfirm": "Block Client",
|
||||
"active": "Active",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"selectYourOrganization": "Select your organization",
|
||||
"signInTo": "Log in in to",
|
||||
"signInWithPassword": "Continue with Password",
|
||||
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
|
||||
"enterPassword": "Enter your password",
|
||||
"enterMfaCode": "Enter the code from your authenticator app",
|
||||
"securityKeyRequired": "Please use your security key to sign in.",
|
||||
"needToUseAnotherAccount": "Need to use a different account?",
|
||||
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"userNotFoundWithUsername": "No user found with that username.",
|
||||
"verify": "Verify",
|
||||
"signIn": "Sign In",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"orgSignInNotice": "Did you know?",
|
||||
"signupOrgNotice": "Trying to sign in?",
|
||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||
"logIn": "Log In"
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS36351",
|
||||
asn: 36351
|
||||
},
|
||||
|
||||
|
||||
// CDNs
|
||||
{
|
||||
name: "Cloudflare",
|
||||
@@ -90,7 +90,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS16625",
|
||||
asn: 16625
|
||||
},
|
||||
|
||||
|
||||
// Mobile Carriers - US
|
||||
{
|
||||
name: "T-Mobile USA",
|
||||
@@ -117,7 +117,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS6430",
|
||||
asn: 6430
|
||||
},
|
||||
|
||||
|
||||
// Mobile Carriers - Europe
|
||||
{
|
||||
name: "Vodafone UK",
|
||||
@@ -144,7 +144,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS12430",
|
||||
asn: 12430
|
||||
},
|
||||
|
||||
|
||||
// Mobile Carriers - Asia
|
||||
{
|
||||
name: "NTT DoCoMo (Japan)",
|
||||
@@ -176,7 +176,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS9808",
|
||||
asn: 9808
|
||||
},
|
||||
|
||||
|
||||
// Major US ISPs
|
||||
{
|
||||
name: "AT&T Services",
|
||||
@@ -208,7 +208,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS209",
|
||||
asn: 209
|
||||
},
|
||||
|
||||
|
||||
// Major European ISPs
|
||||
{
|
||||
name: "Deutsche Telekom",
|
||||
@@ -235,7 +235,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS12956",
|
||||
asn: 12956
|
||||
},
|
||||
|
||||
|
||||
// Major Asian ISPs
|
||||
{
|
||||
name: "China Telecom",
|
||||
@@ -262,7 +262,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS55836",
|
||||
asn: 55836
|
||||
},
|
||||
|
||||
|
||||
// VPN/Proxy Providers
|
||||
{
|
||||
name: "Private Internet Access",
|
||||
@@ -279,7 +279,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS213281",
|
||||
asn: 213281
|
||||
},
|
||||
|
||||
|
||||
// Social Media / Major Tech
|
||||
{
|
||||
name: "Facebook/Meta",
|
||||
@@ -301,7 +301,7 @@ export const MAJOR_ASNS = [
|
||||
code: "AS2906",
|
||||
asn: 2906
|
||||
},
|
||||
|
||||
|
||||
// Academic/Research
|
||||
{
|
||||
name: "MIT",
|
||||
|
||||
@@ -134,13 +134,15 @@ export const resources = pgTable("resources", {
|
||||
proxyProtocol: boolean("proxyProtocol").notNull().default(false),
|
||||
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
|
||||
|
||||
maintenanceModeEnabled: boolean("maintenanceModeEnabled").notNull().default(false),
|
||||
maintenanceModeEnabled: boolean("maintenanceModeEnabled")
|
||||
.notNull()
|
||||
.default(false),
|
||||
maintenanceModeType: text("maintenanceModeType", {
|
||||
enum: ["forced", "automatic"]
|
||||
}).default("forced"), // "forced" = always show, "automatic" = only when down
|
||||
maintenanceTitle: text("maintenanceTitle"),
|
||||
maintenanceMessage: text("maintenanceMessage"),
|
||||
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
|
||||
maintenanceEstimatedTime: text("maintenanceEstimatedTime")
|
||||
});
|
||||
|
||||
export const targets = pgTable("targets", {
|
||||
@@ -223,8 +225,8 @@ export const siteResources = pgTable("siteResources", {
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
alias: varchar("alias"),
|
||||
aliasAddress: varchar("aliasAddress"),
|
||||
tcpPortRangeString: varchar("tcpPortRangeString"),
|
||||
udpPortRangeString: varchar("udpPortRangeString"),
|
||||
tcpPortRangeString: varchar("tcpPortRangeString").notNull().default("*"),
|
||||
udpPortRangeString: varchar("udpPortRangeString").notNull().default("*"),
|
||||
disableIcmp: boolean("disableIcmp").notNull().default(false)
|
||||
});
|
||||
|
||||
@@ -464,13 +466,22 @@ export const resourceHeaderAuth = pgTable("resourceHeaderAuth", {
|
||||
headerAuthHash: varchar("headerAuthHash").notNull()
|
||||
});
|
||||
|
||||
export const resourceHeaderAuthExtendedCompatibility = pgTable("resourceHeaderAuthExtendedCompatibility", {
|
||||
headerAuthExtendedCompatibilityId: serial("headerAuthExtendedCompatibilityId").primaryKey(),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
extendedCompatibilityIsActivated: boolean("extendedCompatibilityIsActivated").notNull().default(true),
|
||||
});
|
||||
export const resourceHeaderAuthExtendedCompatibility = pgTable(
|
||||
"resourceHeaderAuthExtendedCompatibility",
|
||||
{
|
||||
headerAuthExtendedCompatibilityId: serial(
|
||||
"headerAuthExtendedCompatibilityId"
|
||||
).primaryKey(),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
extendedCompatibilityIsActivated: boolean(
|
||||
"extendedCompatibilityIsActivated"
|
||||
)
|
||||
.notNull()
|
||||
.default(true)
|
||||
}
|
||||
);
|
||||
|
||||
export const resourceAccessToken = pgTable("resourceAccessToken", {
|
||||
accessTokenId: varchar("accessTokenId").primaryKey(),
|
||||
@@ -872,7 +883,9 @@ export type ResourceSession = InferSelectModel<typeof resourceSessions>;
|
||||
export type ResourcePincode = InferSelectModel<typeof resourcePincode>;
|
||||
export type ResourcePassword = InferSelectModel<typeof resourcePassword>;
|
||||
export type ResourceHeaderAuth = InferSelectModel<typeof resourceHeaderAuth>;
|
||||
export type ResourceHeaderAuthExtendedCompatibility = InferSelectModel<typeof resourceHeaderAuthExtendedCompatibility>;
|
||||
export type ResourceHeaderAuthExtendedCompatibility = InferSelectModel<
|
||||
typeof resourceHeaderAuthExtendedCompatibility
|
||||
>;
|
||||
export type ResourceOtp = InferSelectModel<typeof resourceOtp>;
|
||||
export type ResourceAccessToken = InferSelectModel<typeof resourceAccessToken>;
|
||||
export type ResourceWhitelist = InferSelectModel<typeof resourceWhitelist>;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import {
|
||||
db, loginPage, LoginPage, loginPageOrg, Org, orgs,
|
||||
} from "@server/db";
|
||||
import { db, loginPage, LoginPage, loginPageOrg, Org, orgs } from "@server/db";
|
||||
import {
|
||||
Resource,
|
||||
ResourcePassword,
|
||||
@@ -27,7 +25,7 @@ export type ResourceWithAuth = {
|
||||
pincode: ResourcePincode | null;
|
||||
password: ResourcePassword | null;
|
||||
headerAuth: ResourceHeaderAuth | null;
|
||||
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null
|
||||
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
|
||||
org: Org;
|
||||
};
|
||||
|
||||
@@ -59,12 +57,12 @@ export async function getResourceByDomain(
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
eq(resourceHeaderAuthExtendedCompatibility.resourceId, resources.resourceId)
|
||||
)
|
||||
.innerJoin(
|
||||
orgs,
|
||||
eq(orgs.orgId, resources.orgId)
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.innerJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.where(eq(resources.fullDomain, domain))
|
||||
.limit(1);
|
||||
|
||||
@@ -77,7 +75,8 @@ export async function getResourceByDomain(
|
||||
pincode: result.resourcePincode,
|
||||
password: result.resourcePassword,
|
||||
headerAuth: result.resourceHeaderAuth,
|
||||
headerAuthExtendedCompatibility: result.resourceHeaderAuthExtendedCompatibility,
|
||||
headerAuthExtendedCompatibility:
|
||||
result.resourceHeaderAuthExtendedCompatibility,
|
||||
org: result.orgs
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,22 +12,22 @@ import { no } from "zod/v4/locales";
|
||||
export const domains = sqliteTable("domains", {
|
||||
domainId: text("domainId").primaryKey(),
|
||||
baseDomain: text("baseDomain").notNull(),
|
||||
configManaged: integer("configManaged", {mode: "boolean"})
|
||||
configManaged: integer("configManaged", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
type: text("type"), // "ns", "cname", "wildcard"
|
||||
verified: integer("verified", {mode: "boolean"}).notNull().default(false),
|
||||
failed: integer("failed", {mode: "boolean"}).notNull().default(false),
|
||||
verified: integer("verified", { mode: "boolean" }).notNull().default(false),
|
||||
failed: integer("failed", { mode: "boolean" }).notNull().default(false),
|
||||
tries: integer("tries").notNull().default(0),
|
||||
certResolver: text("certResolver"),
|
||||
preferWildcardCert: integer("preferWildcardCert", {mode: "boolean"})
|
||||
preferWildcardCert: integer("preferWildcardCert", { mode: "boolean" })
|
||||
});
|
||||
|
||||
export const dnsRecords = sqliteTable("dnsRecords", {
|
||||
id: integer("id").primaryKey({autoIncrement: true}),
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
domainId: text("domainId")
|
||||
.notNull()
|
||||
.references(() => domains.domainId, {onDelete: "cascade"}),
|
||||
.references(() => domains.domainId, { onDelete: "cascade" }),
|
||||
|
||||
recordType: text("recordType").notNull(), // "NS" | "CNAME" | "A" | "TXT"
|
||||
baseDomain: text("baseDomain"),
|
||||
@@ -41,7 +41,7 @@ export const orgs = sqliteTable("orgs", {
|
||||
subnet: text("subnet"),
|
||||
utilitySubnet: text("utilitySubnet"), // this is the subnet for utility addresses
|
||||
createdAt: text("createdAt"),
|
||||
requireTwoFactor: integer("requireTwoFactor", {mode: "boolean"}),
|
||||
requireTwoFactor: integer("requireTwoFactor", { mode: "boolean" }),
|
||||
maxSessionLengthHours: integer("maxSessionLengthHours"), // hours
|
||||
passwordExpiryDays: integer("passwordExpiryDays"), // days
|
||||
settingsLogRetentionDaysRequest: integer("settingsLogRetentionDaysRequest") // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year
|
||||
@@ -58,23 +58,23 @@ export const orgs = sqliteTable("orgs", {
|
||||
export const userDomains = sqliteTable("userDomains", {
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
domainId: text("domainId")
|
||||
.notNull()
|
||||
.references(() => domains.domainId, {onDelete: "cascade"})
|
||||
.references(() => domains.domainId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const orgDomains = sqliteTable("orgDomains", {
|
||||
orgId: text("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, {onDelete: "cascade"}),
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
domainId: text("domainId")
|
||||
.notNull()
|
||||
.references(() => domains.domainId, {onDelete: "cascade"})
|
||||
.references(() => domains.domainId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const sites = sqliteTable("sites", {
|
||||
siteId: integer("siteId").primaryKey({autoIncrement: true}),
|
||||
siteId: integer("siteId").primaryKey({ autoIncrement: true }),
|
||||
orgId: text("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
@@ -91,7 +91,7 @@ export const sites = sqliteTable("sites", {
|
||||
megabytesOut: integer("bytesOut").default(0),
|
||||
lastBandwidthUpdate: text("lastBandwidthUpdate"),
|
||||
type: text("type").notNull(), // "newt" or "wireguard"
|
||||
online: integer("online", {mode: "boolean"}).notNull().default(false),
|
||||
online: integer("online", { mode: "boolean" }).notNull().default(false),
|
||||
|
||||
// exit node stuff that is how to connect to the site when it has a wg server
|
||||
address: text("address"), // this is the address of the wireguard interface in newt
|
||||
@@ -99,14 +99,14 @@ export const sites = sqliteTable("sites", {
|
||||
publicKey: text("publicKey"), // TODO: Fix typo in publicKey
|
||||
lastHolePunch: integer("lastHolePunch"),
|
||||
listenPort: integer("listenPort"),
|
||||
dockerSocketEnabled: integer("dockerSocketEnabled", {mode: "boolean"})
|
||||
dockerSocketEnabled: integer("dockerSocketEnabled", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(true)
|
||||
});
|
||||
|
||||
export const resources = sqliteTable("resources", {
|
||||
resourceId: integer("resourceId").primaryKey({autoIncrement: true}),
|
||||
resourceGuid: text("resourceGuid", {length: 36})
|
||||
resourceId: integer("resourceId").primaryKey({ autoIncrement: true }),
|
||||
resourceGuid: text("resourceGuid", { length: 36 })
|
||||
.unique()
|
||||
.notNull()
|
||||
.$defaultFn(() => randomUUID()),
|
||||
@@ -122,35 +122,39 @@ export const resources = sqliteTable("resources", {
|
||||
domainId: text("domainId").references(() => domains.domainId, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
ssl: integer("ssl", {mode: "boolean"}).notNull().default(false),
|
||||
blockAccess: integer("blockAccess", {mode: "boolean"})
|
||||
ssl: integer("ssl", { mode: "boolean" }).notNull().default(false),
|
||||
blockAccess: integer("blockAccess", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
sso: integer("sso", {mode: "boolean"}).notNull().default(true),
|
||||
http: integer("http", {mode: "boolean"}).notNull().default(true),
|
||||
sso: integer("sso", { mode: "boolean" }).notNull().default(true),
|
||||
http: integer("http", { mode: "boolean" }).notNull().default(true),
|
||||
protocol: text("protocol").notNull(),
|
||||
proxyPort: integer("proxyPort"),
|
||||
emailWhitelistEnabled: integer("emailWhitelistEnabled", {mode: "boolean"})
|
||||
emailWhitelistEnabled: integer("emailWhitelistEnabled", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
applyRules: integer("applyRules", {mode: "boolean"})
|
||||
applyRules: integer("applyRules", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
enabled: integer("enabled", {mode: "boolean"}).notNull().default(true),
|
||||
stickySession: integer("stickySession", {mode: "boolean"})
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
stickySession: integer("stickySession", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
tlsServerName: text("tlsServerName"),
|
||||
setHostHeader: text("setHostHeader"),
|
||||
enableProxy: integer("enableProxy", {mode: "boolean"}).default(true),
|
||||
enableProxy: integer("enableProxy", { mode: "boolean" }).default(true),
|
||||
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
headers: text("headers"), // comma-separated list of headers to add to the request
|
||||
proxyProtocol: integer("proxyProtocol", { mode: "boolean" }).notNull().default(false),
|
||||
proxyProtocol: integer("proxyProtocol", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
|
||||
|
||||
maintenanceModeEnabled: integer("maintenanceModeEnabled", { mode: "boolean" })
|
||||
maintenanceModeEnabled: integer("maintenanceModeEnabled", {
|
||||
mode: "boolean"
|
||||
})
|
||||
.notNull()
|
||||
.default(false),
|
||||
maintenanceModeType: text("maintenanceModeType", {
|
||||
@@ -158,12 +162,11 @@ export const resources = sqliteTable("resources", {
|
||||
}).default("forced"), // "forced" = always show, "automatic" = only when down
|
||||
maintenanceTitle: text("maintenanceTitle"),
|
||||
maintenanceMessage: text("maintenanceMessage"),
|
||||
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
|
||||
|
||||
maintenanceEstimatedTime: text("maintenanceEstimatedTime")
|
||||
});
|
||||
|
||||
export const targets = sqliteTable("targets", {
|
||||
targetId: integer("targetId").primaryKey({autoIncrement: true}),
|
||||
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
|
||||
resourceId: integer("resourceId")
|
||||
.references(() => resources.resourceId, {
|
||||
onDelete: "cascade"
|
||||
@@ -178,7 +181,7 @@ export const targets = sqliteTable("targets", {
|
||||
method: text("method"),
|
||||
port: integer("port").notNull(),
|
||||
internalPort: integer("internalPort"),
|
||||
enabled: integer("enabled", {mode: "boolean"}).notNull().default(true),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
path: text("path"),
|
||||
pathMatchType: text("pathMatchType"), // exact, prefix, regex
|
||||
rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target
|
||||
@@ -192,8 +195,8 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
|
||||
}),
|
||||
targetId: integer("targetId")
|
||||
.notNull()
|
||||
.references(() => targets.targetId, {onDelete: "cascade"}),
|
||||
hcEnabled: integer("hcEnabled", {mode: "boolean"})
|
||||
.references(() => targets.targetId, { onDelete: "cascade" }),
|
||||
hcEnabled: integer("hcEnabled", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
hcPath: text("hcPath"),
|
||||
@@ -215,7 +218,7 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
|
||||
});
|
||||
|
||||
export const exitNodes = sqliteTable("exitNodes", {
|
||||
exitNodeId: integer("exitNodeId").primaryKey({autoIncrement: true}),
|
||||
exitNodeId: integer("exitNodeId").primaryKey({ autoIncrement: true }),
|
||||
name: text("name").notNull(),
|
||||
address: text("address").notNull(), // this is the address of the wireguard interface in gerbil
|
||||
endpoint: text("endpoint").notNull(), // this is how to reach gerbil externally - gets put into the wireguard config
|
||||
@@ -223,7 +226,7 @@ export const exitNodes = sqliteTable("exitNodes", {
|
||||
listenPort: integer("listenPort").notNull(),
|
||||
reachableAt: text("reachableAt"), // this is the internal address of the gerbil http server for command control
|
||||
maxConnections: integer("maxConnections"),
|
||||
online: integer("online", {mode: "boolean"}).notNull().default(false),
|
||||
online: integer("online", { mode: "boolean" }).notNull().default(false),
|
||||
lastPing: integer("lastPing"),
|
||||
type: text("type").default("gerbil"), // gerbil, remoteExitNode
|
||||
region: text("region")
|
||||
@@ -236,10 +239,10 @@ export const siteResources = sqliteTable("siteResources", {
|
||||
}),
|
||||
siteId: integer("siteId")
|
||||
.notNull()
|
||||
.references(() => sites.siteId, {onDelete: "cascade"}),
|
||||
.references(() => sites.siteId, { onDelete: "cascade" }),
|
||||
orgId: text("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, {onDelete: "cascade"}),
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
niceId: text("niceId").notNull(),
|
||||
name: text("name").notNull(),
|
||||
mode: text("mode").notNull(), // "host" | "cidr" | "port"
|
||||
@@ -250,9 +253,9 @@ export const siteResources = sqliteTable("siteResources", {
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
alias: text("alias"),
|
||||
aliasAddress: text("aliasAddress"),
|
||||
tcpPortRangeString: text("tcpPortRangeString"),
|
||||
udpPortRangeString: text("udpPortRangeString"),
|
||||
disableIcmp: integer("disableIcmp", { mode: "boolean" })
|
||||
tcpPortRangeString: text("tcpPortRangeString").notNull().default("*"),
|
||||
udpPortRangeString: text("udpPortRangeString").notNull().default("*"),
|
||||
disableIcmp: integer("disableIcmp", { mode: "boolean" }).notNull().default(false)
|
||||
});
|
||||
|
||||
export const clientSiteResources = sqliteTable("clientSiteResources", {
|
||||
@@ -292,20 +295,20 @@ export const users = sqliteTable("user", {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
passwordHash: text("passwordHash"),
|
||||
twoFactorEnabled: integer("twoFactorEnabled", {mode: "boolean"})
|
||||
twoFactorEnabled: integer("twoFactorEnabled", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
twoFactorSetupRequested: integer("twoFactorSetupRequested", {
|
||||
mode: "boolean"
|
||||
}).default(false),
|
||||
twoFactorSecret: text("twoFactorSecret"),
|
||||
emailVerified: integer("emailVerified", {mode: "boolean"})
|
||||
emailVerified: integer("emailVerified", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
dateCreated: text("dateCreated").notNull(),
|
||||
termsAcceptedTimestamp: text("termsAcceptedTimestamp"),
|
||||
termsVersion: text("termsVersion"),
|
||||
serverAdmin: integer("serverAdmin", {mode: "boolean"})
|
||||
serverAdmin: integer("serverAdmin", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
lastPasswordChange: integer("lastPasswordChange")
|
||||
@@ -339,7 +342,7 @@ export const webauthnChallenge = sqliteTable("webauthnChallenge", {
|
||||
export const setupTokens = sqliteTable("setupTokens", {
|
||||
tokenId: text("tokenId").primaryKey(),
|
||||
token: text("token").notNull(),
|
||||
used: integer("used", {mode: "boolean"}).notNull().default(false),
|
||||
used: integer("used", { mode: "boolean" }).notNull().default(false),
|
||||
dateCreated: text("dateCreated").notNull(),
|
||||
dateUsed: text("dateUsed")
|
||||
});
|
||||
@@ -378,7 +381,7 @@ export const clients = sqliteTable("clients", {
|
||||
lastBandwidthUpdate: text("lastBandwidthUpdate"),
|
||||
lastPing: integer("lastPing"),
|
||||
type: text("type").notNull(), // "olm"
|
||||
online: integer("online", {mode: "boolean"}).notNull().default(false),
|
||||
online: integer("online", { mode: "boolean" }).notNull().default(false),
|
||||
// endpoint: text("endpoint"),
|
||||
lastHolePunch: integer("lastHolePunch")
|
||||
});
|
||||
@@ -424,10 +427,10 @@ export const olms = sqliteTable("olms", {
|
||||
});
|
||||
|
||||
export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", {
|
||||
codeId: integer("id").primaryKey({autoIncrement: true}),
|
||||
codeId: integer("id").primaryKey({ autoIncrement: true }),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
codeHash: text("codeHash").notNull()
|
||||
});
|
||||
|
||||
@@ -435,7 +438,7 @@ export const sessions = sqliteTable("session", {
|
||||
sessionId: text("id").primaryKey(),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
expiresAt: integer("expiresAt").notNull(),
|
||||
issuedAt: integer("issuedAt"),
|
||||
deviceAuthUsed: integer("deviceAuthUsed", { mode: "boolean" })
|
||||
@@ -447,7 +450,7 @@ export const newtSessions = sqliteTable("newtSession", {
|
||||
sessionId: text("id").primaryKey(),
|
||||
newtId: text("newtId")
|
||||
.notNull()
|
||||
.references(() => newts.newtId, {onDelete: "cascade"}),
|
||||
.references(() => newts.newtId, { onDelete: "cascade" }),
|
||||
expiresAt: integer("expiresAt").notNull()
|
||||
});
|
||||
|
||||
@@ -455,14 +458,14 @@ export const olmSessions = sqliteTable("clientSession", {
|
||||
sessionId: text("id").primaryKey(),
|
||||
olmId: text("olmId")
|
||||
.notNull()
|
||||
.references(() => olms.olmId, {onDelete: "cascade"}),
|
||||
.references(() => olms.olmId, { onDelete: "cascade" }),
|
||||
expiresAt: integer("expiresAt").notNull()
|
||||
});
|
||||
|
||||
export const userOrgs = sqliteTable("userOrgs", {
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
orgId: text("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
@@ -471,28 +474,28 @@ export const userOrgs = sqliteTable("userOrgs", {
|
||||
roleId: integer("roleId")
|
||||
.notNull()
|
||||
.references(() => roles.roleId),
|
||||
isOwner: integer("isOwner", {mode: "boolean"}).notNull().default(false),
|
||||
isOwner: integer("isOwner", { mode: "boolean" }).notNull().default(false),
|
||||
autoProvisioned: integer("autoProvisioned", {
|
||||
mode: "boolean"
|
||||
}).default(false)
|
||||
});
|
||||
|
||||
export const emailVerificationCodes = sqliteTable("emailVerificationCodes", {
|
||||
codeId: integer("id").primaryKey({autoIncrement: true}),
|
||||
codeId: integer("id").primaryKey({ autoIncrement: true }),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
email: text("email").notNull(),
|
||||
code: text("code").notNull(),
|
||||
expiresAt: integer("expiresAt").notNull()
|
||||
});
|
||||
|
||||
export const passwordResetTokens = sqliteTable("passwordResetTokens", {
|
||||
tokenId: integer("id").primaryKey({autoIncrement: true}),
|
||||
tokenId: integer("id").primaryKey({ autoIncrement: true }),
|
||||
email: text("email").notNull(),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
tokenHash: text("tokenHash").notNull(),
|
||||
expiresAt: integer("expiresAt").notNull()
|
||||
});
|
||||
@@ -504,13 +507,13 @@ export const actions = sqliteTable("actions", {
|
||||
});
|
||||
|
||||
export const roles = sqliteTable("roles", {
|
||||
roleId: integer("roleId").primaryKey({autoIncrement: true}),
|
||||
roleId: integer("roleId").primaryKey({ autoIncrement: true }),
|
||||
orgId: text("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
})
|
||||
.notNull(),
|
||||
isAdmin: integer("isAdmin", {mode: "boolean"}),
|
||||
isAdmin: integer("isAdmin", { mode: "boolean" }),
|
||||
name: text("name").notNull(),
|
||||
description: text("description")
|
||||
});
|
||||
@@ -518,92 +521,92 @@ export const roles = sqliteTable("roles", {
|
||||
export const roleActions = sqliteTable("roleActions", {
|
||||
roleId: integer("roleId")
|
||||
.notNull()
|
||||
.references(() => roles.roleId, {onDelete: "cascade"}),
|
||||
.references(() => roles.roleId, { onDelete: "cascade" }),
|
||||
actionId: text("actionId")
|
||||
.notNull()
|
||||
.references(() => actions.actionId, {onDelete: "cascade"}),
|
||||
.references(() => actions.actionId, { onDelete: "cascade" }),
|
||||
orgId: text("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, {onDelete: "cascade"})
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const userActions = sqliteTable("userActions", {
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
actionId: text("actionId")
|
||||
.notNull()
|
||||
.references(() => actions.actionId, {onDelete: "cascade"}),
|
||||
.references(() => actions.actionId, { onDelete: "cascade" }),
|
||||
orgId: text("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, {onDelete: "cascade"})
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const roleSites = sqliteTable("roleSites", {
|
||||
roleId: integer("roleId")
|
||||
.notNull()
|
||||
.references(() => roles.roleId, {onDelete: "cascade"}),
|
||||
.references(() => roles.roleId, { onDelete: "cascade" }),
|
||||
siteId: integer("siteId")
|
||||
.notNull()
|
||||
.references(() => sites.siteId, {onDelete: "cascade"})
|
||||
.references(() => sites.siteId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const userSites = sqliteTable("userSites", {
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
siteId: integer("siteId")
|
||||
.notNull()
|
||||
.references(() => sites.siteId, {onDelete: "cascade"})
|
||||
.references(() => sites.siteId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const userClients = sqliteTable("userClients", {
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
clientId: integer("clientId")
|
||||
.notNull()
|
||||
.references(() => clients.clientId, {onDelete: "cascade"})
|
||||
.references(() => clients.clientId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const roleClients = sqliteTable("roleClients", {
|
||||
roleId: integer("roleId")
|
||||
.notNull()
|
||||
.references(() => roles.roleId, {onDelete: "cascade"}),
|
||||
.references(() => roles.roleId, { onDelete: "cascade" }),
|
||||
clientId: integer("clientId")
|
||||
.notNull()
|
||||
.references(() => clients.clientId, {onDelete: "cascade"})
|
||||
.references(() => clients.clientId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const roleResources = sqliteTable("roleResources", {
|
||||
roleId: integer("roleId")
|
||||
.notNull()
|
||||
.references(() => roles.roleId, {onDelete: "cascade"}),
|
||||
.references(() => roles.roleId, { onDelete: "cascade" }),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"})
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const userResources = sqliteTable("userResources", {
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.userId, {onDelete: "cascade"}),
|
||||
.references(() => users.userId, { onDelete: "cascade" }),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"})
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const userInvites = sqliteTable("userInvites", {
|
||||
inviteId: text("inviteId").primaryKey(),
|
||||
orgId: text("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, {onDelete: "cascade"}),
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
email: text("email").notNull(),
|
||||
expiresAt: integer("expiresAt").notNull(),
|
||||
tokenHash: text("token").notNull(),
|
||||
roleId: integer("roleId")
|
||||
.notNull()
|
||||
.references(() => roles.roleId, {onDelete: "cascade"})
|
||||
.references(() => roles.roleId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const resourcePincode = sqliteTable("resourcePincode", {
|
||||
@@ -612,7 +615,7 @@ export const resourcePincode = sqliteTable("resourcePincode", {
|
||||
}),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"}),
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
pincodeHash: text("pincodeHash").notNull(),
|
||||
digitLength: integer("digitLength").notNull()
|
||||
});
|
||||
@@ -623,7 +626,7 @@ export const resourcePassword = sqliteTable("resourcePassword", {
|
||||
}),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"}),
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
passwordHash: text("passwordHash").notNull()
|
||||
});
|
||||
|
||||
@@ -633,28 +636,38 @@ export const resourceHeaderAuth = sqliteTable("resourceHeaderAuth", {
|
||||
}),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"}),
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
headerAuthHash: text("headerAuthHash").notNull()
|
||||
});
|
||||
|
||||
export const resourceHeaderAuthExtendedCompatibility = sqliteTable("resourceHeaderAuthExtendedCompatibility", {
|
||||
headerAuthExtendedCompatibilityId: integer("headerAuthExtendedCompatibilityId").primaryKey({
|
||||
autoIncrement: true
|
||||
}),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"}),
|
||||
extendedCompatibilityIsActivated: integer("extendedCompatibilityIsActivated", {mode: "boolean"}).notNull().default(true)
|
||||
});
|
||||
export const resourceHeaderAuthExtendedCompatibility = sqliteTable(
|
||||
"resourceHeaderAuthExtendedCompatibility",
|
||||
{
|
||||
headerAuthExtendedCompatibilityId: integer(
|
||||
"headerAuthExtendedCompatibilityId"
|
||||
).primaryKey({
|
||||
autoIncrement: true
|
||||
}),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
extendedCompatibilityIsActivated: integer(
|
||||
"extendedCompatibilityIsActivated",
|
||||
{ mode: "boolean" }
|
||||
)
|
||||
.notNull()
|
||||
.default(true)
|
||||
}
|
||||
);
|
||||
|
||||
export const resourceAccessToken = sqliteTable("resourceAccessToken", {
|
||||
accessTokenId: text("accessTokenId").primaryKey(),
|
||||
orgId: text("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, {onDelete: "cascade"}),
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"}),
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
tokenHash: text("tokenHash").notNull(),
|
||||
sessionLength: integer("sessionLength").notNull(),
|
||||
expiresAt: integer("expiresAt"),
|
||||
@@ -667,13 +680,13 @@ export const resourceSessions = sqliteTable("resourceSessions", {
|
||||
sessionId: text("id").primaryKey(),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"}),
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
expiresAt: integer("expiresAt").notNull(),
|
||||
sessionLength: integer("sessionLength").notNull(),
|
||||
doNotExtend: integer("doNotExtend", {mode: "boolean"})
|
||||
doNotExtend: integer("doNotExtend", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
isRequestToken: integer("isRequestToken", {mode: "boolean"}),
|
||||
isRequestToken: integer("isRequestToken", { mode: "boolean" }),
|
||||
userSessionId: text("userSessionId").references(() => sessions.sessionId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
@@ -705,11 +718,11 @@ export const resourceSessions = sqliteTable("resourceSessions", {
|
||||
});
|
||||
|
||||
export const resourceWhitelist = sqliteTable("resourceWhitelist", {
|
||||
whitelistId: integer("id").primaryKey({autoIncrement: true}),
|
||||
whitelistId: integer("id").primaryKey({ autoIncrement: true }),
|
||||
email: text("email").notNull(),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"})
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const resourceOtp = sqliteTable("resourceOtp", {
|
||||
@@ -718,7 +731,7 @@ export const resourceOtp = sqliteTable("resourceOtp", {
|
||||
}),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"}),
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
email: text("email").notNull(),
|
||||
otpHash: text("otpHash").notNull(),
|
||||
expiresAt: integer("expiresAt").notNull()
|
||||
@@ -730,11 +743,11 @@ export const versionMigrations = sqliteTable("versionMigrations", {
|
||||
});
|
||||
|
||||
export const resourceRules = sqliteTable("resourceRules", {
|
||||
ruleId: integer("ruleId").primaryKey({autoIncrement: true}),
|
||||
ruleId: integer("ruleId").primaryKey({ autoIncrement: true }),
|
||||
resourceId: integer("resourceId")
|
||||
.notNull()
|
||||
.references(() => resources.resourceId, {onDelete: "cascade"}),
|
||||
enabled: integer("enabled", {mode: "boolean"}).notNull().default(true),
|
||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
priority: integer("priority").notNull(),
|
||||
action: text("action").notNull(), // ACCEPT, DROP, PASS
|
||||
match: text("match").notNull(), // CIDR, PATH, IP
|
||||
@@ -742,17 +755,17 @@ export const resourceRules = sqliteTable("resourceRules", {
|
||||
});
|
||||
|
||||
export const supporterKey = sqliteTable("supporterKey", {
|
||||
keyId: integer("keyId").primaryKey({autoIncrement: true}),
|
||||
keyId: integer("keyId").primaryKey({ autoIncrement: true }),
|
||||
key: text("key").notNull(),
|
||||
githubUsername: text("githubUsername").notNull(),
|
||||
phrase: text("phrase"),
|
||||
tier: text("tier"),
|
||||
valid: integer("valid", {mode: "boolean"}).notNull().default(false)
|
||||
valid: integer("valid", { mode: "boolean" }).notNull().default(false)
|
||||
});
|
||||
|
||||
// Identity Providers
|
||||
export const idp = sqliteTable("idp", {
|
||||
idpId: integer("idpId").primaryKey({autoIncrement: true}),
|
||||
idpId: integer("idpId").primaryKey({ autoIncrement: true }),
|
||||
name: text("name").notNull(),
|
||||
type: text("type").notNull(),
|
||||
defaultRoleMapping: text("defaultRoleMapping"),
|
||||
@@ -772,7 +785,7 @@ export const idpOidcConfig = sqliteTable("idpOidcConfig", {
|
||||
variant: text("variant").notNull().default("oidc"),
|
||||
idpId: integer("idpId")
|
||||
.notNull()
|
||||
.references(() => idp.idpId, {onDelete: "cascade"}),
|
||||
.references(() => idp.idpId, { onDelete: "cascade" }),
|
||||
clientId: text("clientId").notNull(),
|
||||
clientSecret: text("clientSecret").notNull(),
|
||||
authUrl: text("authUrl").notNull(),
|
||||
@@ -800,22 +813,22 @@ export const apiKeys = sqliteTable("apiKeys", {
|
||||
apiKeyHash: text("apiKeyHash").notNull(),
|
||||
lastChars: text("lastChars").notNull(),
|
||||
createdAt: text("dateCreated").notNull(),
|
||||
isRoot: integer("isRoot", {mode: "boolean"}).notNull().default(false)
|
||||
isRoot: integer("isRoot", { mode: "boolean" }).notNull().default(false)
|
||||
});
|
||||
|
||||
export const apiKeyActions = sqliteTable("apiKeyActions", {
|
||||
apiKeyId: text("apiKeyId")
|
||||
.notNull()
|
||||
.references(() => apiKeys.apiKeyId, {onDelete: "cascade"}),
|
||||
.references(() => apiKeys.apiKeyId, { onDelete: "cascade" }),
|
||||
actionId: text("actionId")
|
||||
.notNull()
|
||||
.references(() => actions.actionId, {onDelete: "cascade"})
|
||||
.references(() => actions.actionId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const apiKeyOrg = sqliteTable("apiKeyOrg", {
|
||||
apiKeyId: text("apiKeyId")
|
||||
.notNull()
|
||||
.references(() => apiKeys.apiKeyId, {onDelete: "cascade"}),
|
||||
.references(() => apiKeys.apiKeyId, { onDelete: "cascade" }),
|
||||
orgId: text("orgId")
|
||||
.references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
@@ -826,10 +839,10 @@ export const apiKeyOrg = sqliteTable("apiKeyOrg", {
|
||||
export const idpOrg = sqliteTable("idpOrg", {
|
||||
idpId: integer("idpId")
|
||||
.notNull()
|
||||
.references(() => idp.idpId, {onDelete: "cascade"}),
|
||||
.references(() => idp.idpId, { onDelete: "cascade" }),
|
||||
orgId: text("orgId")
|
||||
.notNull()
|
||||
.references(() => orgs.orgId, {onDelete: "cascade"}),
|
||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||
roleMapping: text("roleMapping"),
|
||||
orgMapping: text("orgMapping")
|
||||
});
|
||||
@@ -847,19 +860,19 @@ export const blueprints = sqliteTable("blueprints", {
|
||||
name: text("name").notNull(),
|
||||
source: text("source").notNull(),
|
||||
createdAt: integer("createdAt").notNull(),
|
||||
succeeded: integer("succeeded", {mode: "boolean"}).notNull(),
|
||||
succeeded: integer("succeeded", { mode: "boolean" }).notNull(),
|
||||
contents: text("contents").notNull(),
|
||||
message: text("message")
|
||||
});
|
||||
export const requestAuditLog = sqliteTable(
|
||||
"requestAuditLog",
|
||||
{
|
||||
id: integer("id").primaryKey({autoIncrement: true}),
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds
|
||||
orgId: text("orgId").references(() => orgs.orgId, {
|
||||
onDelete: "cascade"
|
||||
}),
|
||||
action: integer("action", {mode: "boolean"}).notNull(),
|
||||
action: integer("action", { mode: "boolean" }).notNull(),
|
||||
reason: integer("reason").notNull(),
|
||||
actorType: text("actorType"),
|
||||
actor: text("actor"),
|
||||
@@ -876,7 +889,7 @@ export const requestAuditLog = sqliteTable(
|
||||
host: text("host"),
|
||||
path: text("path"),
|
||||
method: text("method"),
|
||||
tls: integer("tls", {mode: "boolean"})
|
||||
tls: integer("tls", { mode: "boolean" })
|
||||
},
|
||||
(table) => [
|
||||
index("idx_requestAuditLog_timestamp").on(table.timestamp),
|
||||
@@ -932,7 +945,9 @@ export type ResourceSession = InferSelectModel<typeof resourceSessions>;
|
||||
export type ResourcePincode = InferSelectModel<typeof resourcePincode>;
|
||||
export type ResourcePassword = InferSelectModel<typeof resourcePassword>;
|
||||
export type ResourceHeaderAuth = InferSelectModel<typeof resourceHeaderAuth>;
|
||||
export type ResourceHeaderAuthExtendedCompatibility = InferSelectModel<typeof resourceHeaderAuthExtendedCompatibility>;
|
||||
export type ResourceHeaderAuthExtendedCompatibility = InferSelectModel<
|
||||
typeof resourceHeaderAuthExtendedCompatibility
|
||||
>;
|
||||
export type ResourceOtp = InferSelectModel<typeof resourceOtp>;
|
||||
export type ResourceAccessToken = InferSelectModel<typeof resourceAccessToken>;
|
||||
export type ResourceWhitelist = InferSelectModel<typeof resourceWhitelist>;
|
||||
|
||||
3
server/lib/blueprints/MaintenanceSchema.ts
Normal file
3
server/lib/blueprints/MaintenanceSchema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const MaintenanceSchema = z.object({});
|
||||
@@ -1,4 +1,14 @@
|
||||
import { db, newts, blueprints, Blueprint, Site, siteResources, roleSiteResources, userSiteResources, clientSiteResources } from "@server/db";
|
||||
import {
|
||||
db,
|
||||
newts,
|
||||
blueprints,
|
||||
Blueprint,
|
||||
Site,
|
||||
siteResources,
|
||||
roleSiteResources,
|
||||
userSiteResources,
|
||||
clientSiteResources
|
||||
} from "@server/db";
|
||||
import { Config, ConfigSchema } from "./types";
|
||||
import { ProxyResourcesResults, updateProxyResources } from "./proxyResources";
|
||||
import { fromError } from "zod-validation-error";
|
||||
@@ -126,7 +136,7 @@ export async function applyBlueprint({
|
||||
)
|
||||
.then((rows) => rows.map((row) => row.roleId));
|
||||
|
||||
const existingUserIds= await trx
|
||||
const existingUserIds = await trx
|
||||
.select()
|
||||
.from(userSiteResources)
|
||||
.where(
|
||||
@@ -134,7 +144,8 @@ export async function applyBlueprint({
|
||||
userSiteResources.siteResourceId,
|
||||
result.oldSiteResource.siteResourceId
|
||||
)
|
||||
).then((rows) => rows.map((row) => row.userId));
|
||||
)
|
||||
.then((rows) => rows.map((row) => row.userId));
|
||||
|
||||
const existingClientIds = await trx
|
||||
.select()
|
||||
@@ -144,13 +155,19 @@ export async function applyBlueprint({
|
||||
clientSiteResources.siteResourceId,
|
||||
result.oldSiteResource.siteResourceId
|
||||
)
|
||||
).then((rows) => rows.map((row) => row.clientId));
|
||||
)
|
||||
.then((rows) => rows.map((row) => row.clientId));
|
||||
|
||||
// delete the existing site resource
|
||||
await trx
|
||||
.delete(siteResources)
|
||||
.where(
|
||||
and(eq(siteResources.siteResourceId, result.oldSiteResource.siteResourceId))
|
||||
and(
|
||||
eq(
|
||||
siteResources.siteResourceId,
|
||||
result.oldSiteResource.siteResourceId
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
await rebuildClientAssociationsFromSiteResource(
|
||||
@@ -161,7 +178,7 @@ export async function applyBlueprint({
|
||||
const [insertedSiteResource] = await trx
|
||||
.insert(siteResources)
|
||||
.values({
|
||||
...result.newSiteResource,
|
||||
...result.newSiteResource
|
||||
})
|
||||
.returning();
|
||||
|
||||
@@ -172,18 +189,20 @@ export async function applyBlueprint({
|
||||
|
||||
if (existingRoleIds.length > 0) {
|
||||
await trx.insert(roleSiteResources).values(
|
||||
existingRoleIds.map((roleId) => ({
|
||||
existingRoleIds.map((roleId) => ({
|
||||
roleId,
|
||||
siteResourceId: insertedSiteResource!.siteResourceId
|
||||
siteResourceId:
|
||||
insertedSiteResource!.siteResourceId
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
if (existingUserIds.length > 0) {
|
||||
await trx.insert(userSiteResources).values(
|
||||
existingUserIds.map((userId) => ({
|
||||
existingUserIds.map((userId) => ({
|
||||
userId,
|
||||
siteResourceId: insertedSiteResource!.siteResourceId
|
||||
siteResourceId:
|
||||
insertedSiteResource!.siteResourceId
|
||||
}))
|
||||
);
|
||||
}
|
||||
@@ -192,7 +211,8 @@ export async function applyBlueprint({
|
||||
await trx.insert(clientSiteResources).values(
|
||||
existingClientIds.map((clientId) => ({
|
||||
clientId,
|
||||
siteResourceId: insertedSiteResource!.siteResourceId
|
||||
siteResourceId:
|
||||
insertedSiteResource!.siteResourceId
|
||||
}))
|
||||
);
|
||||
}
|
||||
@@ -201,7 +221,6 @@ export async function applyBlueprint({
|
||||
insertedSiteResource,
|
||||
trx
|
||||
);
|
||||
|
||||
} else {
|
||||
const [newSite] = await trx
|
||||
.select()
|
||||
|
||||
@@ -2,7 +2,8 @@ import {
|
||||
domains,
|
||||
orgDomains,
|
||||
Resource,
|
||||
resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility,
|
||||
resourceHeaderAuth,
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
resourcePincode,
|
||||
resourceRules,
|
||||
resourceWhitelist,
|
||||
@@ -16,8 +17,8 @@ import {
|
||||
userResources,
|
||||
users
|
||||
} from "@server/db";
|
||||
import {resources, targets, sites} from "@server/db";
|
||||
import {eq, and, asc, or, ne, count, isNotNull} from "drizzle-orm";
|
||||
import { resources, targets, sites } from "@server/db";
|
||||
import { eq, and, asc, or, ne, count, isNotNull } from "drizzle-orm";
|
||||
import {
|
||||
Config,
|
||||
ConfigSchema,
|
||||
@@ -25,12 +26,13 @@ import {
|
||||
TargetData
|
||||
} from "./types";
|
||||
import logger from "@server/logger";
|
||||
import {createCertificate} from "#dynamic/routers/certificates/createCertificate";
|
||||
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";
|
||||
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
||||
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 { isLicensedOrSubscribed } from "../isLicencedOrSubscribed";
|
||||
import { build } from "@server/build";
|
||||
|
||||
export type ProxyResourcesResults = {
|
||||
proxyResource: Resource;
|
||||
@@ -63,7 +65,7 @@ export async function updateProxyResources(
|
||||
if (targetSiteId) {
|
||||
// Look up site by niceId
|
||||
[site] = await trx
|
||||
.select({siteId: sites.siteId})
|
||||
.select({ siteId: sites.siteId })
|
||||
.from(sites)
|
||||
.where(
|
||||
and(
|
||||
@@ -75,7 +77,7 @@ export async function updateProxyResources(
|
||||
} else if (siteId) {
|
||||
// Use the provided siteId directly, but verify it belongs to the org
|
||||
[site] = await trx
|
||||
.select({siteId: sites.siteId})
|
||||
.select({ siteId: sites.siteId })
|
||||
.from(sites)
|
||||
.where(
|
||||
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
||||
@@ -93,7 +95,7 @@ export async function updateProxyResources(
|
||||
|
||||
let internalPortToCreate;
|
||||
if (!targetData["internal-port"]) {
|
||||
const {internalPort, targetIps} = await pickPort(
|
||||
const { internalPort, targetIps } = await pickPort(
|
||||
site.siteId!,
|
||||
trx
|
||||
);
|
||||
@@ -209,6 +211,16 @@ export async function updateProxyResources(
|
||||
resource = existingResource;
|
||||
} else {
|
||||
// Update existing resource
|
||||
|
||||
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||
if (build == "enterprise" && !isLicensed) {
|
||||
logger.warn(
|
||||
"Server is not licensed! Clearing set maintenance screen values"
|
||||
);
|
||||
// null the maintenance mode fields if not licensed
|
||||
resourceData.maintenance = undefined;
|
||||
}
|
||||
|
||||
[resource] = await trx
|
||||
.update(resources)
|
||||
.set({
|
||||
@@ -228,12 +240,19 @@ export async function updateProxyResources(
|
||||
tlsServerName: resourceData["tls-server-name"] || null,
|
||||
emailWhitelistEnabled: resourceData.auth?.[
|
||||
"whitelist-users"
|
||||
]
|
||||
]
|
||||
? resourceData.auth["whitelist-users"].length > 0
|
||||
: false,
|
||||
headers: headers || null,
|
||||
applyRules:
|
||||
resourceData.rules && resourceData.rules.length > 0
|
||||
resourceData.rules && resourceData.rules.length > 0,
|
||||
maintenanceModeEnabled:
|
||||
resourceData.maintenance?.enabled,
|
||||
maintenanceModeType: resourceData.maintenance?.type,
|
||||
maintenanceTitle: resourceData.maintenance?.title,
|
||||
maintenanceMessage: resourceData.maintenance?.message,
|
||||
maintenanceEstimatedTime:
|
||||
resourceData.maintenance?.["estimated-time"]
|
||||
})
|
||||
.where(
|
||||
eq(resources.resourceId, existingResource.resourceId)
|
||||
@@ -303,8 +322,13 @@ export async function updateProxyResources(
|
||||
const headerAuthPassword =
|
||||
resourceData.auth?.["basic-auth"]?.password;
|
||||
const headerAuthExtendedCompatibility =
|
||||
resourceData.auth?.["basic-auth"]?.extendedCompatibility;
|
||||
if (headerAuthUser && headerAuthPassword && headerAuthExtendedCompatibility !== null) {
|
||||
resourceData.auth?.["basic-auth"]
|
||||
?.extendedCompatibility;
|
||||
if (
|
||||
headerAuthUser &&
|
||||
headerAuthPassword &&
|
||||
headerAuthExtendedCompatibility !== null
|
||||
) {
|
||||
const headerAuthHash = await hashPassword(
|
||||
Buffer.from(
|
||||
`${headerAuthUser}:${headerAuthPassword}`
|
||||
@@ -315,10 +339,13 @@ export async function updateProxyResources(
|
||||
resourceId: existingResource.resourceId,
|
||||
headerAuthHash
|
||||
}),
|
||||
trx.insert(resourceHeaderAuthExtendedCompatibility).values({
|
||||
resourceId: existingResource.resourceId,
|
||||
extendedCompatibilityIsActivated: headerAuthExtendedCompatibility
|
||||
})
|
||||
trx
|
||||
.insert(resourceHeaderAuthExtendedCompatibility)
|
||||
.values({
|
||||
resourceId: existingResource.resourceId,
|
||||
extendedCompatibilityIsActivated:
|
||||
headerAuthExtendedCompatibility
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -380,7 +407,7 @@ export async function updateProxyResources(
|
||||
if (targetSiteId) {
|
||||
// Look up site by niceId
|
||||
[site] = await trx
|
||||
.select({siteId: sites.siteId})
|
||||
.select({ siteId: sites.siteId })
|
||||
.from(sites)
|
||||
.where(
|
||||
and(
|
||||
@@ -392,7 +419,7 @@ export async function updateProxyResources(
|
||||
} else if (siteId) {
|
||||
// Use the provided siteId directly, but verify it belongs to the org
|
||||
[site] = await trx
|
||||
.select({siteId: sites.siteId})
|
||||
.select({ siteId: sites.siteId })
|
||||
.from(sites)
|
||||
.where(
|
||||
and(
|
||||
@@ -437,7 +464,7 @@ export async function updateProxyResources(
|
||||
if (checkIfTargetChanged(existingTarget, updatedTarget)) {
|
||||
let internalPortToUpdate;
|
||||
if (!targetData["internal-port"]) {
|
||||
const {internalPort, targetIps} = await pickPort(
|
||||
const { internalPort, targetIps } = await pickPort(
|
||||
site.siteId!,
|
||||
trx
|
||||
);
|
||||
@@ -622,6 +649,15 @@ export async function updateProxyResources(
|
||||
);
|
||||
}
|
||||
|
||||
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||
if (build == "enterprise" && !isLicensed) {
|
||||
logger.warn(
|
||||
"Server is not licensed! Clearing set maintenance screen values"
|
||||
);
|
||||
// null the maintenance mode fields if not licensed
|
||||
resourceData.maintenance = undefined;
|
||||
}
|
||||
|
||||
// Create new resource
|
||||
const [newResource] = await trx
|
||||
.insert(resources)
|
||||
@@ -643,7 +679,13 @@ export async function updateProxyResources(
|
||||
ssl: resourceSsl,
|
||||
headers: headers || null,
|
||||
applyRules:
|
||||
resourceData.rules && resourceData.rules.length > 0
|
||||
resourceData.rules && resourceData.rules.length > 0,
|
||||
maintenanceModeEnabled: resourceData.maintenance?.enabled,
|
||||
maintenanceModeType: resourceData.maintenance?.type,
|
||||
maintenanceTitle: resourceData.maintenance?.title,
|
||||
maintenanceMessage: resourceData.maintenance?.message,
|
||||
maintenanceEstimatedTime:
|
||||
resourceData.maintenance?.["estimated-time"]
|
||||
})
|
||||
.returning();
|
||||
|
||||
@@ -674,9 +716,14 @@ export async function updateProxyResources(
|
||||
const headerAuthUser = resourceData.auth?.["basic-auth"]?.user;
|
||||
const headerAuthPassword =
|
||||
resourceData.auth?.["basic-auth"]?.password;
|
||||
const headerAuthExtendedCompatibility = resourceData.auth?.["basic-auth"]?.extendedCompatibility;
|
||||
const headerAuthExtendedCompatibility =
|
||||
resourceData.auth?.["basic-auth"]?.extendedCompatibility;
|
||||
|
||||
if (headerAuthUser && headerAuthPassword && headerAuthExtendedCompatibility !== null) {
|
||||
if (
|
||||
headerAuthUser &&
|
||||
headerAuthPassword &&
|
||||
headerAuthExtendedCompatibility !== null
|
||||
) {
|
||||
const headerAuthHash = await hashPassword(
|
||||
Buffer.from(
|
||||
`${headerAuthUser}:${headerAuthPassword}`
|
||||
@@ -688,10 +735,13 @@ export async function updateProxyResources(
|
||||
resourceId: newResource.resourceId,
|
||||
headerAuthHash
|
||||
}),
|
||||
trx.insert(resourceHeaderAuthExtendedCompatibility).values({
|
||||
resourceId: newResource.resourceId,
|
||||
extendedCompatibilityIsActivated: headerAuthExtendedCompatibility
|
||||
}),
|
||||
trx
|
||||
.insert(resourceHeaderAuthExtendedCompatibility)
|
||||
.values({
|
||||
resourceId: newResource.resourceId,
|
||||
extendedCompatibilityIsActivated:
|
||||
headerAuthExtendedCompatibility
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1043,7 +1093,7 @@ async function getDomain(
|
||||
trx: Transaction
|
||||
) {
|
||||
const [fullDomainExists] = await trx
|
||||
.select({resourceId: resources.resourceId})
|
||||
.select({ resourceId: resources.resourceId })
|
||||
.from(resources)
|
||||
.where(
|
||||
and(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { portRangeStringSchema } from "@server/lib/ip";
|
||||
import { MaintenanceSchema } from "#dynamic/lib/blueprints/MaintenanceSchema";
|
||||
|
||||
export const SiteSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
@@ -53,11 +54,13 @@ export const AuthSchema = z.object({
|
||||
// pincode has to have 6 digits
|
||||
pincode: z.number().min(100000).max(999999).optional(),
|
||||
password: z.string().min(1).optional(),
|
||||
"basic-auth": z.object({
|
||||
user: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
extendedCompatibility: z.boolean().default(true)
|
||||
}).optional(),
|
||||
"basic-auth": z
|
||||
.object({
|
||||
user: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
extendedCompatibility: z.boolean().default(true)
|
||||
})
|
||||
.optional(),
|
||||
"sso-enabled": z.boolean().optional().default(false),
|
||||
"sso-roles": z
|
||||
.array(z.string())
|
||||
@@ -108,32 +111,30 @@ export const RuleSchema = z
|
||||
.refine(
|
||||
(rule) => {
|
||||
if (rule.match === "country") {
|
||||
// Check if it's a valid 2-letter country code
|
||||
return /^[A-Z]{2}$/.test(rule.value);
|
||||
// Check if it's a valid 2-letter country code or "ALL"
|
||||
return /^[A-Z]{2}$/.test(rule.value) || rule.value === "ALL";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
path: ["value"],
|
||||
message:
|
||||
"Value must be a 2-letter country code when match is 'country'"
|
||||
"Value must be a 2-letter country code or 'ALL' when match is 'country'"
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(rule) => {
|
||||
if (rule.match === "asn") {
|
||||
// Check if it's either AS<number> format or just a number
|
||||
// Check if it's either AS<number> format or "ALL"
|
||||
const asNumberPattern = /^AS\d+$/i;
|
||||
const isASFormat = asNumberPattern.test(rule.value);
|
||||
const isNumeric = /^\d+$/.test(rule.value);
|
||||
return isASFormat || isNumeric;
|
||||
return asNumberPattern.test(rule.value) || rule.value === "ALL";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
path: ["value"],
|
||||
message:
|
||||
"Value must be either 'AS<number>' format or a number when match is 'asn'"
|
||||
"Value must be 'AS<number>' format or 'ALL' when match is 'asn'"
|
||||
}
|
||||
);
|
||||
|
||||
@@ -156,7 +157,8 @@ export const ResourceSchema = z
|
||||
"host-header": z.string().optional(),
|
||||
"tls-server-name": z.string().optional(),
|
||||
headers: z.array(HeaderSchema).optional(),
|
||||
rules: z.array(RuleSchema).optional()
|
||||
rules: z.array(RuleSchema).optional(),
|
||||
maintenance: MaintenanceSchema.optional()
|
||||
})
|
||||
.refine(
|
||||
(resource) => {
|
||||
|
||||
@@ -84,6 +84,10 @@ export class Config {
|
||||
?.disable_basic_wireguard_sites
|
||||
? "true"
|
||||
: "false";
|
||||
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS = parsedConfig.flags
|
||||
?.disable_product_help_banners
|
||||
? "true"
|
||||
: "false";
|
||||
|
||||
process.env.PRODUCT_UPDATES_NOTIFICATION_ENABLED = parsedConfig.app
|
||||
.notifications.product_updates
|
||||
|
||||
@@ -4,6 +4,7 @@ import { and, eq, isNotNull } from "drizzle-orm";
|
||||
import config from "@server/lib/config";
|
||||
import z from "zod";
|
||||
import logger from "@server/logger";
|
||||
import semver from "semver";
|
||||
|
||||
interface IPRange {
|
||||
start: bigint;
|
||||
@@ -318,10 +319,7 @@ export function doCidrsOverlap(cidr1: string, cidr2: string): boolean {
|
||||
const range2 = cidrToRange(cidr2);
|
||||
|
||||
// Overlap if the ranges intersect
|
||||
return (
|
||||
range1.start <= range2.end &&
|
||||
range2.start <= range1.end
|
||||
);
|
||||
return range1.start <= range2.end && range2.start <= range1.end;
|
||||
}
|
||||
|
||||
export async function getNextAvailableClientSubnet(
|
||||
@@ -686,3 +684,35 @@ export function parsePortRangeString(
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function stripPortFromHost(ip: string, badgerVersion?: string): string {
|
||||
const isNewerBadger =
|
||||
badgerVersion &&
|
||||
semver.valid(badgerVersion) &&
|
||||
semver.gte(badgerVersion, "1.3.1");
|
||||
|
||||
if (isNewerBadger) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
if (ip.startsWith("[") && ip.includes("]")) {
|
||||
// if brackets are found, extract the IPv6 address from between the brackets
|
||||
const ipv6Match = ip.match(/\[(.*?)\]/);
|
||||
if (ipv6Match) {
|
||||
return ipv6Match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it looks like IPv4 (contains dots and matches IPv4 pattern)
|
||||
// IPv4 format: x.x.x.x where x is 0-255
|
||||
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}/;
|
||||
if (ipv4Pattern.test(ip)) {
|
||||
const lastColonIndex = ip.lastIndexOf(":");
|
||||
if (lastColonIndex !== -1) {
|
||||
return ip.substring(0, lastColonIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Return as is
|
||||
return ip;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,10 @@ export const configSchema = z
|
||||
.default(["newt", "wireguard", "local"]),
|
||||
allow_raw_resources: z.boolean().optional().default(true),
|
||||
file_mode: z.boolean().optional().default(false),
|
||||
pp_transport_prefix: z.string().optional().default("pp-transport-v")
|
||||
pp_transport_prefix: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("pp-transport-v")
|
||||
})
|
||||
.optional()
|
||||
.prefault({}),
|
||||
@@ -327,7 +330,8 @@ export const configSchema = z
|
||||
enable_integration_api: z.boolean().optional(),
|
||||
disable_local_sites: z.boolean().optional(),
|
||||
disable_basic_wireguard_sites: z.boolean().optional(),
|
||||
disable_config_managed_domains: z.boolean().optional()
|
||||
disable_config_managed_domains: z.boolean().optional(),
|
||||
disable_product_help_banners: z.boolean().optional()
|
||||
})
|
||||
.optional(),
|
||||
dns: z
|
||||
|
||||
@@ -41,9 +41,10 @@ type TargetWithSite = Target & {
|
||||
export async function getTraefikConfig(
|
||||
exitNodeId: number,
|
||||
siteTypes: string[],
|
||||
filterOutNamespaceDomains = false,
|
||||
generateLoginPageRouters = false,
|
||||
allowRawResources = true
|
||||
filterOutNamespaceDomains = false, // UNUSED BUT USED IN PRIVATE
|
||||
generateLoginPageRouters = false, // UNUSED BUT USED IN PRIVATE
|
||||
allowRawResources = true,
|
||||
allowMaintenancePage = true, // UNUSED BUT USED IN PRIVATE
|
||||
): Promise<any> {
|
||||
// Get resources with their targets and sites in a single optimized query
|
||||
// Start from sites on this exit node, then join to targets and resources
|
||||
@@ -294,12 +295,12 @@ export async function getTraefikConfig(
|
||||
certResolver: resolverName,
|
||||
...(preferWildcard
|
||||
? {
|
||||
domains: [
|
||||
{
|
||||
main: wildCard
|
||||
}
|
||||
]
|
||||
}
|
||||
domains: [
|
||||
{
|
||||
main: wildCard
|
||||
}
|
||||
]
|
||||
}
|
||||
: {})
|
||||
};
|
||||
|
||||
@@ -475,9 +476,9 @@ export async function getTraefikConfig(
|
||||
// RECEIVE BANDWIDTH ENDPOINT.
|
||||
|
||||
// TODO: HOW TO HANDLE ^^^^^^ BETTER
|
||||
const anySitesOnline = (
|
||||
targets
|
||||
).some((target) => target.site.online);
|
||||
const anySitesOnline = targets.some(
|
||||
(target) => target.site.online
|
||||
);
|
||||
|
||||
return (
|
||||
targets
|
||||
@@ -544,14 +545,14 @@ export async function getTraefikConfig(
|
||||
})(),
|
||||
...(resource.stickySession
|
||||
? {
|
||||
sticky: {
|
||||
cookie: {
|
||||
name: "p_sticky", // TODO: make this configurable via config.yml like other cookies
|
||||
secure: resource.ssl,
|
||||
httpOnly: true
|
||||
}
|
||||
}
|
||||
}
|
||||
sticky: {
|
||||
cookie: {
|
||||
name: "p_sticky", // TODO: make this configurable via config.yml like other cookies
|
||||
secure: resource.ssl,
|
||||
httpOnly: true
|
||||
}
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}
|
||||
};
|
||||
@@ -603,9 +604,9 @@ export async function getTraefikConfig(
|
||||
loadBalancer: {
|
||||
servers: (() => {
|
||||
// Check if any sites are online
|
||||
const anySitesOnline = (
|
||||
targets
|
||||
).some((target) => target.site.online);
|
||||
const anySitesOnline = targets.some(
|
||||
(target) => target.site.online
|
||||
);
|
||||
|
||||
return targets
|
||||
.filter((target) => {
|
||||
@@ -654,18 +655,18 @@ export async function getTraefikConfig(
|
||||
})(),
|
||||
...(resource.proxyProtocol && protocol == "tcp"
|
||||
? {
|
||||
serversTransport: `${ppPrefix}${resource.proxyProtocolVersion || 1}@file` // TODO: does @file here cause issues?
|
||||
}
|
||||
serversTransport: `${ppPrefix}${resource.proxyProtocolVersion || 1}@file` // TODO: does @file here cause issues?
|
||||
}
|
||||
: {}),
|
||||
...(resource.stickySession
|
||||
? {
|
||||
sticky: {
|
||||
ipStrategy: {
|
||||
depth: 0,
|
||||
sourcePort: true
|
||||
}
|
||||
}
|
||||
}
|
||||
sticky: {
|
||||
ipStrategy: {
|
||||
depth: 0,
|
||||
sourcePort: true
|
||||
}
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}
|
||||
};
|
||||
|
||||
22
server/private/lib/blueprints/MaintenanceSchema.ts
Normal file
22
server/private/lib/blueprints/MaintenanceSchema.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
export const MaintenanceSchema = z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
type: z.enum(["forced", "automatic"]).optional(),
|
||||
title: z.string().max(255).nullable().optional(),
|
||||
message: z.string().max(2000).nullable().optional(),
|
||||
"estimated-time": z.string().max(100).nullable().optional()
|
||||
});
|
||||
@@ -23,10 +23,10 @@ import {
|
||||
} from "@server/lib/checkOrgAccessPolicy";
|
||||
import { UserType } from "@server/types/UserTypes";
|
||||
|
||||
export async function enforceResourceSessionLength(
|
||||
export function enforceResourceSessionLength(
|
||||
resourceSession: ResourceSession,
|
||||
org: Org
|
||||
): Promise<{ valid: boolean; error?: string }> {
|
||||
): { valid: boolean; error?: string } {
|
||||
if (org.maxSessionLengthHours) {
|
||||
const sessionIssuedAt = resourceSession.issuedAt; // may be null
|
||||
const maxSessionLengthHours = org.maxSessionLengthHours;
|
||||
|
||||
@@ -17,6 +17,7 @@ import logger from "@server/logger";
|
||||
import { and, eq, lt } from "drizzle-orm";
|
||||
import cache from "@server/lib/cache";
|
||||
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
|
||||
import { stripPortFromHost } from "@server/lib/ip";
|
||||
|
||||
async function getAccessDays(orgId: string): Promise<number> {
|
||||
// check cache first
|
||||
@@ -116,19 +117,7 @@ export async function logAccessAudit(data: {
|
||||
}
|
||||
|
||||
const clientIp = data.requestIp
|
||||
? (() => {
|
||||
if (
|
||||
data.requestIp.startsWith("[") &&
|
||||
data.requestIp.includes("]")
|
||||
) {
|
||||
// if brackets are found, extract the IPv6 address from between the brackets
|
||||
const ipv6Match = data.requestIp.match(/\[(.*?)\]/);
|
||||
if (ipv6Match) {
|
||||
return ipv6Match[1];
|
||||
}
|
||||
}
|
||||
return data.requestIp;
|
||||
})()
|
||||
? stripPortFromHost(data.requestIp)
|
||||
: undefined;
|
||||
|
||||
const countryCode = data.requestIp
|
||||
|
||||
@@ -71,9 +71,9 @@ export async function getTraefikConfig(
|
||||
siteTypes: string[],
|
||||
filterOutNamespaceDomains = false,
|
||||
generateLoginPageRouters = false,
|
||||
allowRawResources = true
|
||||
allowRawResources = true,
|
||||
allowMaintenancePage = true
|
||||
): Promise<any> {
|
||||
|
||||
// Get resources with their targets and sites in a single optimized query
|
||||
// Start from sites on this exit node, then join to targets and resources
|
||||
const resourcesWithTargetsAndSites = await db
|
||||
@@ -358,18 +358,6 @@ export async function getTraefikConfig(
|
||||
}
|
||||
}
|
||||
|
||||
if (resource.ssl) {
|
||||
config_output.http.routers![routerName + "-redirect"] = {
|
||||
entryPoints: [
|
||||
config.getRawConfig().traefik.http_entrypoint
|
||||
],
|
||||
middlewares: [redirectHttpsMiddlewareName],
|
||||
service: serviceName,
|
||||
rule: rule,
|
||||
priority: priority
|
||||
};
|
||||
}
|
||||
|
||||
let tls = {};
|
||||
if (!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
|
||||
const domainParts = fullDomain.split(".");
|
||||
@@ -435,17 +423,27 @@ export async function getTraefikConfig(
|
||||
}
|
||||
}
|
||||
|
||||
const availableServers = targets.filter(
|
||||
(target) => {
|
||||
if (!target.enabled) return false;
|
||||
if (resource.ssl) {
|
||||
config_output.http.routers![routerName + "-redirect"] = {
|
||||
entryPoints: [
|
||||
config.getRawConfig().traefik.http_entrypoint
|
||||
],
|
||||
middlewares: [redirectHttpsMiddlewareName],
|
||||
service: serviceName,
|
||||
rule: rule,
|
||||
priority: priority
|
||||
};
|
||||
}
|
||||
|
||||
if (!target.site.online) return false;
|
||||
const availableServers = targets.filter((target) => {
|
||||
if (!target.enabled) return false;
|
||||
|
||||
if (target.health == "unhealthy") return false;
|
||||
if (!target.site.online) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
if (target.health == "unhealthy") return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const hasHealthyServers = availableServers.length > 0;
|
||||
|
||||
@@ -466,7 +464,7 @@ export async function getTraefikConfig(
|
||||
}
|
||||
}
|
||||
|
||||
if (showMaintenancePage) {
|
||||
if (showMaintenancePage && allowMaintenancePage) {
|
||||
const maintenanceServiceName = `${key}-maintenance-service`;
|
||||
const maintenanceRouterName = `${key}-maintenance-router`;
|
||||
const rewriteMiddlewareName = `${key}-maintenance-rewrite`;
|
||||
@@ -794,9 +792,9 @@ export async function getTraefikConfig(
|
||||
loadBalancer: {
|
||||
servers: (() => {
|
||||
// Check if any sites are online
|
||||
const anySitesOnline = (
|
||||
targets
|
||||
).some((target) => target.site.online);
|
||||
const anySitesOnline = targets.some(
|
||||
(target) => target.site.online
|
||||
);
|
||||
|
||||
return targets
|
||||
.filter((target) => {
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
ResourceHeaderAuthExtendedCompatibility,
|
||||
orgs,
|
||||
requestAuditLog,
|
||||
Org
|
||||
} from "@server/db";
|
||||
import {
|
||||
resources,
|
||||
@@ -79,6 +80,7 @@ import { maxmindLookup } from "@server/db/maxmind";
|
||||
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
||||
import semver from "semver";
|
||||
import { maxmindAsnLookup } from "@server/db/maxmindAsn";
|
||||
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy";
|
||||
|
||||
// Zod schemas for request validation
|
||||
const getResourceByDomainParamsSchema = z.strictObject({
|
||||
@@ -94,6 +96,12 @@ const getUserOrgRoleParamsSchema = z.strictObject({
|
||||
orgId: z.string().min(1, "Organization ID is required")
|
||||
});
|
||||
|
||||
const getUserOrgSessionVerifySchema = z.strictObject({
|
||||
userId: z.string().min(1, "User ID is required"),
|
||||
orgId: z.string().min(1, "Organization ID is required"),
|
||||
sessionId: z.string().min(1, "Session ID is required")
|
||||
});
|
||||
|
||||
const getRoleResourceAccessParamsSchema = z.strictObject({
|
||||
roleId: z
|
||||
.string()
|
||||
@@ -178,6 +186,7 @@ export type ResourceWithAuth = {
|
||||
password: ResourcePassword | null;
|
||||
headerAuth: ResourceHeaderAuth | null;
|
||||
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
|
||||
org: Org
|
||||
};
|
||||
|
||||
export type UserSessionWithUser = {
|
||||
@@ -238,7 +247,8 @@ hybridRouter.get(
|
||||
["newt", "local", "wireguard"], // Allow them to use all the site types
|
||||
true, // But don't allow domain namespace resources
|
||||
false, // Dont include login pages,
|
||||
true // allow raw resources
|
||||
true, // allow raw resources
|
||||
false // dont generate maintenance page
|
||||
);
|
||||
|
||||
return response(res, {
|
||||
@@ -503,8 +513,12 @@ hybridRouter.get(
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
eq(resourceHeaderAuthExtendedCompatibility.resourceId, resources.resourceId)
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.innerJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.where(eq(resources.fullDomain, domain))
|
||||
.limit(1);
|
||||
|
||||
@@ -538,7 +552,9 @@ hybridRouter.get(
|
||||
pincode: result.resourcePincode,
|
||||
password: result.resourcePassword,
|
||||
headerAuth: result.resourceHeaderAuth,
|
||||
headerAuthExtendedCompatibility: result.resourceHeaderAuthExtendedCompatibility
|
||||
headerAuthExtendedCompatibility:
|
||||
result.resourceHeaderAuthExtendedCompatibility,
|
||||
org: result.orgs
|
||||
};
|
||||
|
||||
return response<ResourceWithAuth>(res, {
|
||||
@@ -602,6 +618,16 @@ hybridRouter.get(
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!result) {
|
||||
return response<LoginPage | null>(res, {
|
||||
data: null,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Login page not found",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
await checkExitNodeOrg(
|
||||
remoteExitNode.exitNodeId,
|
||||
@@ -617,16 +643,6 @@ hybridRouter.get(
|
||||
);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return response<LoginPage | null>(res, {
|
||||
data: null,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Login page not found",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
}
|
||||
|
||||
return response<LoginPage>(res, {
|
||||
data: result.loginPage,
|
||||
success: true,
|
||||
@@ -818,6 +834,69 @@ hybridRouter.get(
|
||||
}
|
||||
);
|
||||
|
||||
// Get user organization role
|
||||
hybridRouter.get(
|
||||
"/user/:userId/org/:orgId/session/:sessionId/verify",
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const parsedParams = getUserOrgSessionVerifySchema.safeParse(
|
||||
req.params
|
||||
);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { userId, orgId, sessionId } = parsedParams.data;
|
||||
const remoteExitNode = req.remoteExitNode;
|
||||
|
||||
if (!remoteExitNode || !remoteExitNode.exitNodeId) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Remote exit node not found"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (await checkExitNodeOrg(remoteExitNode.exitNodeId, orgId)) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
"User is not authorized to access this organization"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const accessPolicy = await checkOrgAccessPolicy({
|
||||
orgId,
|
||||
userId,
|
||||
sessionId
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
data: accessPolicy,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "User org access policy retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Failed to get user org role"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Check if role has access to resource
|
||||
hybridRouter.get(
|
||||
"/role/:roleId/resource/:resourceId/access",
|
||||
|
||||
@@ -39,4 +39,4 @@ internalRouter.post(
|
||||
|
||||
internalRouter.get(`/license/status`, license.getLicenseStatus);
|
||||
|
||||
internalRouter.get("/maintenance/info", resource.getMaintenanceInfo);
|
||||
internalRouter.get("/maintenance/info", resource.getMaintenanceInfo);
|
||||
|
||||
@@ -40,6 +40,11 @@ async function query(orgId: string | undefined, fullDomain: string) {
|
||||
eq(loginPage.loginPageId, loginPageOrg.loginPageId)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!res) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...res.loginPage,
|
||||
orgId: res.loginPageOrg.orgId
|
||||
@@ -65,6 +70,11 @@ async function query(orgId: string | undefined, fullDomain: string) {
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!res) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...res,
|
||||
orgId: orgLink.orgId
|
||||
|
||||
@@ -48,6 +48,11 @@ async function query(orgId: string) {
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!res) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...res,
|
||||
orgId: orgLink.orgs.orgId,
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
|
||||
export * from "./getMaintenanceInfo";
|
||||
export * from "./getMaintenanceInfo";
|
||||
|
||||
@@ -99,12 +99,13 @@ async function query(query: Q) {
|
||||
.where(and(baseConditions, not(isNull(requestAuditLog.location))))
|
||||
.groupBy(requestAuditLog.location)
|
||||
.orderBy(desc(totalQ))
|
||||
.limit(DISTINCT_LIMIT+1);
|
||||
.limit(DISTINCT_LIMIT + 1);
|
||||
|
||||
if (requestsPerCountry.length > DISTINCT_LIMIT) {
|
||||
// throw an error
|
||||
throw createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
// todo: is this even possible?
|
||||
`Too many distinct countries. Please narrow your query.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -189,22 +189,22 @@ async function queryUniqueFilterAttributes(
|
||||
.selectDistinct({ actor: requestAuditLog.actor })
|
||||
.from(requestAuditLog)
|
||||
.where(baseConditions)
|
||||
.limit(DISTINCT_LIMIT+1),
|
||||
.limit(DISTINCT_LIMIT + 1),
|
||||
primaryDb
|
||||
.selectDistinct({ locations: requestAuditLog.location })
|
||||
.from(requestAuditLog)
|
||||
.where(baseConditions)
|
||||
.limit(DISTINCT_LIMIT+1),
|
||||
.limit(DISTINCT_LIMIT + 1),
|
||||
primaryDb
|
||||
.selectDistinct({ hosts: requestAuditLog.host })
|
||||
.from(requestAuditLog)
|
||||
.where(baseConditions)
|
||||
.limit(DISTINCT_LIMIT+1),
|
||||
.limit(DISTINCT_LIMIT + 1),
|
||||
primaryDb
|
||||
.selectDistinct({ paths: requestAuditLog.path })
|
||||
.from(requestAuditLog)
|
||||
.where(baseConditions)
|
||||
.limit(DISTINCT_LIMIT+1),
|
||||
.limit(DISTINCT_LIMIT + 1),
|
||||
primaryDb
|
||||
.selectDistinct({
|
||||
id: requestAuditLog.resourceId,
|
||||
@@ -216,18 +216,20 @@ async function queryUniqueFilterAttributes(
|
||||
eq(requestAuditLog.resourceId, resources.resourceId)
|
||||
)
|
||||
.where(baseConditions)
|
||||
.limit(DISTINCT_LIMIT+1)
|
||||
.limit(DISTINCT_LIMIT + 1)
|
||||
]);
|
||||
|
||||
if (
|
||||
uniqueActors.length > DISTINCT_LIMIT ||
|
||||
uniqueLocations.length > DISTINCT_LIMIT ||
|
||||
uniqueHosts.length > DISTINCT_LIMIT ||
|
||||
uniquePaths.length > DISTINCT_LIMIT ||
|
||||
uniqueResources.length > DISTINCT_LIMIT
|
||||
) {
|
||||
throw new Error("Too many distinct filter attributes to retrieve. Please refine your time range.");
|
||||
}
|
||||
// TODO: for stuff like the paths this is too restrictive so lets just show some of the paths and the user needs to
|
||||
// refine the time range to see what they need to see
|
||||
// if (
|
||||
// uniqueActors.length > DISTINCT_LIMIT ||
|
||||
// uniqueLocations.length > DISTINCT_LIMIT ||
|
||||
// uniqueHosts.length > DISTINCT_LIMIT ||
|
||||
// uniquePaths.length > DISTINCT_LIMIT ||
|
||||
// uniqueResources.length > DISTINCT_LIMIT
|
||||
// ) {
|
||||
// throw new Error("Too many distinct filter attributes to retrieve. Please refine your time range.");
|
||||
// }
|
||||
|
||||
return {
|
||||
actors: uniqueActors
|
||||
@@ -307,10 +309,12 @@ export async function queryRequestAuditLogs(
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
// if the message is "Too many distinct filter attributes to retrieve. Please refine your time range.", return a 400 and the message
|
||||
if (error instanceof Error && error.message === "Too many distinct filter attributes to retrieve. Please refine your time range.") {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, error.message)
|
||||
);
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message ===
|
||||
"Too many distinct filter attributes to retrieve. Please refine your time range."
|
||||
) {
|
||||
return next(createHttpError(HttpCode.BAD_REQUEST, error.message));
|
||||
}
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
|
||||
@@ -10,6 +10,7 @@ import { eq, and, gt } from "drizzle-orm";
|
||||
import { createSession, generateSessionToken } from "@server/auth/sessions/app";
|
||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { stripPortFromHost } from "@server/lib/ip";
|
||||
|
||||
const paramsSchema = z.object({
|
||||
code: z.string().min(1, "Code is required")
|
||||
@@ -27,30 +28,6 @@ export type PollDeviceWebAuthResponse = {
|
||||
token?: string;
|
||||
};
|
||||
|
||||
// Helper function to extract IP from request (same as in startDeviceWebAuth)
|
||||
function extractIpFromRequest(req: Request): string | undefined {
|
||||
const ip = req.ip || req.socket.remoteAddress;
|
||||
if (!ip) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Handle IPv6 format [::1] or IPv4 format
|
||||
if (ip.startsWith("[") && ip.includes("]")) {
|
||||
const ipv6Match = ip.match(/\[(.*?)\]/);
|
||||
if (ipv6Match) {
|
||||
return ipv6Match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle IPv4 with port (split at last colon)
|
||||
const lastColonIndex = ip.lastIndexOf(":");
|
||||
if (lastColonIndex !== -1) {
|
||||
return ip.substring(0, lastColonIndex);
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
export async function pollDeviceWebAuth(
|
||||
req: Request,
|
||||
res: Response,
|
||||
@@ -70,7 +47,7 @@ export async function pollDeviceWebAuth(
|
||||
try {
|
||||
const { code } = parsedParams.data;
|
||||
const now = Date.now();
|
||||
const requestIp = extractIpFromRequest(req);
|
||||
const requestIp = req.ip ? stripPortFromHost(req.ip) : undefined;
|
||||
|
||||
// Hash the code before querying
|
||||
const hashedCode = hashDeviceCode(code);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { TimeSpan } from "oslo";
|
||||
import { maxmindLookup } from "@server/db/maxmind";
|
||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { stripPortFromHost } from "@server/lib/ip";
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
@@ -39,30 +40,6 @@ function hashDeviceCode(code: string): string {
|
||||
return encodeHexLowerCase(sha256(new TextEncoder().encode(code)));
|
||||
}
|
||||
|
||||
// Helper function to extract IP from request
|
||||
function extractIpFromRequest(req: Request): string | undefined {
|
||||
const ip = req.ip;
|
||||
if (!ip) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Handle IPv6 format [::1] or IPv4 format
|
||||
if (ip.startsWith("[") && ip.includes("]")) {
|
||||
const ipv6Match = ip.match(/\[(.*?)\]/);
|
||||
if (ipv6Match) {
|
||||
return ipv6Match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle IPv4 with port (split at last colon)
|
||||
const lastColonIndex = ip.lastIndexOf(":");
|
||||
if (lastColonIndex !== -1) {
|
||||
return ip.substring(0, lastColonIndex);
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
// Helper function to get city from IP (if available)
|
||||
async function getCityFromIp(ip: string): Promise<string | undefined> {
|
||||
try {
|
||||
@@ -112,7 +89,7 @@ export async function startDeviceWebAuth(
|
||||
const hashedCode = hashDeviceCode(code);
|
||||
|
||||
// Extract IP from request
|
||||
const ip = extractIpFromRequest(req);
|
||||
const ip = req.ip ? stripPortFromHost(req.ip) : undefined;
|
||||
|
||||
// Get city (optional, may return undefined)
|
||||
const city = ip ? await getCityFromIp(ip) : undefined;
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { SESSION_COOKIE_EXPIRES as RESOURCE_SESSION_COOKIE_EXPIRES } from "@server/auth/sessions/resource";
|
||||
import config from "@server/lib/config";
|
||||
import { response } from "@server/lib/response";
|
||||
import { stripPortFromHost } from "@server/lib/ip";
|
||||
|
||||
const exchangeSessionBodySchema = z.object({
|
||||
requestToken: z.string(),
|
||||
@@ -62,7 +63,7 @@ export async function exchangeSession(
|
||||
cleanHost = cleanHost.slice(0, -1 * matched.length);
|
||||
}
|
||||
|
||||
const clientIp = requestIp?.split(":")[0];
|
||||
const clientIp = requestIp ? stripPortFromHost(requestIp) : undefined;
|
||||
|
||||
const [resource] = await db
|
||||
.select()
|
||||
|
||||
@@ -3,6 +3,7 @@ import logger from "@server/logger";
|
||||
import { and, eq, lt } from "drizzle-orm";
|
||||
import cache from "@server/lib/cache";
|
||||
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
|
||||
import { stripPortFromHost } from "@server/lib/ip";
|
||||
|
||||
/**
|
||||
|
||||
@@ -208,26 +209,7 @@ export async function logRequestAudit(
|
||||
}
|
||||
|
||||
const clientIp = body.requestIp
|
||||
? (() => {
|
||||
if (
|
||||
body.requestIp.startsWith("[") &&
|
||||
body.requestIp.includes("]")
|
||||
) {
|
||||
// if brackets are found, extract the IPv6 address from between the brackets
|
||||
const ipv6Match = body.requestIp.match(/\[(.*?)\]/);
|
||||
if (ipv6Match) {
|
||||
return ipv6Match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// ivp4
|
||||
// split at last colon
|
||||
const lastColonIndex = body.requestIp.lastIndexOf(":");
|
||||
if (lastColonIndex !== -1) {
|
||||
return body.requestIp.substring(0, lastColonIndex);
|
||||
}
|
||||
return body.requestIp;
|
||||
})()
|
||||
? stripPortFromHost(body.requestIp)
|
||||
: undefined;
|
||||
|
||||
// Add to buffer instead of writing directly to DB
|
||||
|
||||
@@ -13,14 +13,15 @@ import {
|
||||
LoginPage,
|
||||
Org,
|
||||
Resource,
|
||||
ResourceHeaderAuth, ResourceHeaderAuthExtendedCompatibility,
|
||||
ResourceHeaderAuth,
|
||||
ResourceHeaderAuthExtendedCompatibility,
|
||||
ResourcePassword,
|
||||
ResourcePincode,
|
||||
ResourceRule,
|
||||
resourceSessions
|
||||
} from "@server/db";
|
||||
import config from "@server/lib/config";
|
||||
import { isIpInCidr } from "@server/lib/ip";
|
||||
import { isIpInCidr, stripPortFromHost } from "@server/lib/ip";
|
||||
import { response } from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -39,6 +40,8 @@ import {
|
||||
} from "#dynamic/lib/checkOrgAccessPolicy";
|
||||
import { logRequestAudit } from "./logRequestAudit";
|
||||
import cache from "@server/lib/cache";
|
||||
import semver from "semver";
|
||||
import { APP_VERSION } from "@server/lib/consts";
|
||||
|
||||
const verifyResourceSessionSchema = z.object({
|
||||
sessions: z.record(z.string(), z.string()).optional(),
|
||||
@@ -50,7 +53,8 @@ const verifyResourceSessionSchema = z.object({
|
||||
path: z.string(),
|
||||
method: z.string(),
|
||||
tls: z.boolean(),
|
||||
requestIp: z.string().optional()
|
||||
requestIp: z.string().optional(),
|
||||
badgerVersion: z.string().optional()
|
||||
});
|
||||
|
||||
export type VerifyResourceSessionSchema = z.infer<
|
||||
@@ -69,6 +73,7 @@ export type VerifyUserResponse = {
|
||||
headerAuthChallenged?: boolean;
|
||||
redirectUrl?: string;
|
||||
userData?: BasicUserData;
|
||||
pangolinVersion?: string;
|
||||
};
|
||||
|
||||
export async function verifyResourceSession(
|
||||
@@ -97,31 +102,15 @@ export async function verifyResourceSession(
|
||||
requestIp,
|
||||
path,
|
||||
headers,
|
||||
query
|
||||
query,
|
||||
badgerVersion
|
||||
} = parsedBody.data;
|
||||
|
||||
// Extract HTTP Basic Auth credentials if present
|
||||
const clientHeaderAuth = extractBasicAuth(headers);
|
||||
|
||||
const clientIp = requestIp
|
||||
? (() => {
|
||||
logger.debug("Request IP:", { requestIp });
|
||||
if (requestIp.startsWith("[") && requestIp.includes("]")) {
|
||||
// if brackets are found, extract the IPv6 address from between the brackets
|
||||
const ipv6Match = requestIp.match(/\[(.*?)\]/);
|
||||
if (ipv6Match) {
|
||||
return ipv6Match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// ivp4
|
||||
// split at last colon
|
||||
const lastColonIndex = requestIp.lastIndexOf(":");
|
||||
if (lastColonIndex !== -1) {
|
||||
return requestIp.substring(0, lastColonIndex);
|
||||
}
|
||||
return requestIp;
|
||||
})()
|
||||
? stripPortFromHost(requestIp, badgerVersion)
|
||||
: undefined;
|
||||
|
||||
logger.debug("Client IP:", { clientIp });
|
||||
@@ -130,9 +119,7 @@ export async function verifyResourceSession(
|
||||
? await getCountryCodeFromIp(clientIp)
|
||||
: undefined;
|
||||
|
||||
const ipAsn = clientIp
|
||||
? await getAsnFromIp(clientIp)
|
||||
: undefined;
|
||||
const ipAsn = clientIp ? await getAsnFromIp(clientIp) : undefined;
|
||||
|
||||
let cleanHost = host;
|
||||
// if the host ends with :port, strip it
|
||||
@@ -178,7 +165,13 @@ export async function verifyResourceSession(
|
||||
cache.set(resourceCacheKey, resourceData, 5);
|
||||
}
|
||||
|
||||
const { resource, pincode, password, headerAuth, headerAuthExtendedCompatibility } = resourceData;
|
||||
const {
|
||||
resource,
|
||||
pincode,
|
||||
password,
|
||||
headerAuth,
|
||||
headerAuthExtendedCompatibility
|
||||
} = resourceData;
|
||||
|
||||
if (!resource) {
|
||||
logger.debug(`Resource not found ${cleanHost}`);
|
||||
@@ -474,8 +467,7 @@ export async function verifyResourceSession(
|
||||
|
||||
return notAllowed(res);
|
||||
}
|
||||
}
|
||||
else if (headerAuth) {
|
||||
} else if (headerAuth) {
|
||||
// if there are no other auth methods we need to return unauthorized if nothing is provided
|
||||
if (
|
||||
!sso &&
|
||||
@@ -713,7 +705,11 @@ export async function verifyResourceSession(
|
||||
}
|
||||
|
||||
// If headerAuthExtendedCompatibility is activated but no clientHeaderAuth provided, force client to challenge
|
||||
if (headerAuthExtendedCompatibility && headerAuthExtendedCompatibility.extendedCompatibilityIsActivated && !clientHeaderAuth){
|
||||
if (
|
||||
headerAuthExtendedCompatibility &&
|
||||
headerAuthExtendedCompatibility.extendedCompatibilityIsActivated &&
|
||||
!clientHeaderAuth
|
||||
) {
|
||||
return headerAuthChallenged(res, redirectPath, resource.orgId);
|
||||
}
|
||||
|
||||
@@ -825,7 +821,7 @@ async function notAllowed(
|
||||
}
|
||||
|
||||
const data = {
|
||||
data: { valid: false, redirectUrl },
|
||||
data: { valid: false, redirectUrl, pangolinVersion: APP_VERSION },
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Access denied",
|
||||
@@ -839,8 +835,8 @@ function allowed(res: Response, userData?: BasicUserData) {
|
||||
const data = {
|
||||
data:
|
||||
userData !== undefined && userData !== null
|
||||
? { valid: true, ...userData }
|
||||
: { valid: true },
|
||||
? { valid: true, ...userData, pangolinVersion: APP_VERSION }
|
||||
: { valid: true, pangolinVersion: APP_VERSION },
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Access allowed",
|
||||
@@ -879,7 +875,12 @@ async function headerAuthChallenged(
|
||||
}
|
||||
|
||||
const data = {
|
||||
data: { headerAuthChallenged: true, valid: false, redirectUrl },
|
||||
data: {
|
||||
headerAuthChallenged: true,
|
||||
valid: false,
|
||||
redirectUrl,
|
||||
pangolinVersion: APP_VERSION
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Access denied",
|
||||
|
||||
@@ -36,7 +36,7 @@ async function query(clientId?: number, niceId?: string, orgId?: string) {
|
||||
.select()
|
||||
.from(clients)
|
||||
.where(and(eq(clients.niceId, niceId), eq(clients.orgId, orgId)))
|
||||
.leftJoin(olms, eq(olms.clientId, olms.clientId))
|
||||
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
||||
.limit(1);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ async function getLatestOlmVersion(): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tags = await response.json();
|
||||
let tags = await response.json();
|
||||
if (!Array.isArray(tags) || tags.length === 0) {
|
||||
logger.warn("No tags found for Olm repository");
|
||||
return null;
|
||||
}
|
||||
|
||||
tags = tags.filter((version) => !version.name.includes("rc"));
|
||||
const latestVersion = tags[0].name;
|
||||
|
||||
olmVersionCache.set("latestOlmVersion", latestVersion);
|
||||
|
||||
@@ -52,7 +52,7 @@ export async function getConfig(
|
||||
}
|
||||
|
||||
// clean up the public key - keep only valid base64 characters (A-Z, a-z, 0-9, +, /, =)
|
||||
const cleanedPublicKey = publicKey.replace(/[^A-Za-z0-9+/=]/g, '');
|
||||
const cleanedPublicKey = publicKey.replace(/[^A-Za-z0-9+/=]/g, "");
|
||||
|
||||
const exitNode = await createExitNode(cleanedPublicKey, reachableAt);
|
||||
|
||||
|
||||
@@ -858,7 +858,6 @@ authenticated.put(
|
||||
blueprints.applyJSONBlueprint
|
||||
);
|
||||
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/blueprint/:blueprintId",
|
||||
verifyApiKeyOrgAccess,
|
||||
@@ -866,7 +865,6 @@ authenticated.get(
|
||||
blueprints.getBlueprint
|
||||
);
|
||||
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/blueprints",
|
||||
verifyApiKeyOrgAccess,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { db } from "@server/db";
|
||||
import { MessageHandler } from "@server/routers/ws";
|
||||
import { clients, Newt } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { clients } from "@server/db";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
|
||||
interface PeerBandwidth {
|
||||
@@ -10,13 +10,57 @@ interface PeerBandwidth {
|
||||
bytesOut: number;
|
||||
}
|
||||
|
||||
// Retry configuration for deadlock handling
|
||||
const MAX_RETRIES = 3;
|
||||
const BASE_DELAY_MS = 50;
|
||||
|
||||
/**
|
||||
* Check if an error is a deadlock error
|
||||
*/
|
||||
function isDeadlockError(error: any): boolean {
|
||||
return (
|
||||
error?.code === "40P01" ||
|
||||
error?.cause?.code === "40P01" ||
|
||||
(error?.message && error.message.includes("deadlock"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function with retry logic for deadlock handling
|
||||
*/
|
||||
async function withDeadlockRetry<T>(
|
||||
operation: () => Promise<T>,
|
||||
context: string
|
||||
): Promise<T> {
|
||||
let attempt = 0;
|
||||
while (true) {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error: any) {
|
||||
if (isDeadlockError(error) && attempt < MAX_RETRIES) {
|
||||
attempt++;
|
||||
const baseDelay = Math.pow(2, attempt - 1) * BASE_DELAY_MS;
|
||||
const jitter = Math.random() * baseDelay;
|
||||
const delay = baseDelay + jitter;
|
||||
logger.warn(
|
||||
`Deadlock detected in ${context}, retrying attempt ${attempt}/${MAX_RETRIES} after ${delay.toFixed(0)}ms`
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const handleReceiveBandwidthMessage: MessageHandler = async (
|
||||
context
|
||||
) => {
|
||||
const { message, client, sendToClient } = context;
|
||||
const { message } = context;
|
||||
|
||||
if (!message.data.bandwidthData) {
|
||||
logger.warn("No bandwidth data provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const bandwidthData: PeerBandwidth[] = message.data.bandwidthData;
|
||||
@@ -25,30 +69,40 @@ export const handleReceiveBandwidthMessage: MessageHandler = async (
|
||||
throw new Error("Invalid bandwidth data");
|
||||
}
|
||||
|
||||
await db.transaction(async (trx) => {
|
||||
for (const peer of bandwidthData) {
|
||||
const { publicKey, bytesIn, bytesOut } = peer;
|
||||
// Sort bandwidth data by publicKey to ensure consistent lock ordering across all instances
|
||||
// This is critical for preventing deadlocks when multiple instances update the same clients
|
||||
const sortedBandwidthData = [...bandwidthData].sort((a, b) =>
|
||||
a.publicKey.localeCompare(b.publicKey)
|
||||
);
|
||||
|
||||
// Find the client by public key
|
||||
const [client] = await trx
|
||||
.select()
|
||||
.from(clients)
|
||||
.where(eq(clients.pubKey, publicKey))
|
||||
.limit(1);
|
||||
const currentTime = new Date().toISOString();
|
||||
|
||||
if (!client) {
|
||||
continue;
|
||||
}
|
||||
// Update each client individually with retry logic
|
||||
// This reduces transaction scope and allows retries per-client
|
||||
for (const peer of sortedBandwidthData) {
|
||||
const { publicKey, bytesIn, bytesOut } = peer;
|
||||
|
||||
// Update the client's bandwidth usage
|
||||
await trx
|
||||
.update(clients)
|
||||
.set({
|
||||
megabytesOut: (client.megabytesIn || 0) + bytesIn,
|
||||
megabytesIn: (client.megabytesOut || 0) + bytesOut,
|
||||
lastBandwidthUpdate: new Date().toISOString()
|
||||
})
|
||||
.where(eq(clients.clientId, client.clientId));
|
||||
try {
|
||||
await withDeadlockRetry(async () => {
|
||||
// Use atomic SQL increment to avoid SELECT then UPDATE pattern
|
||||
// This eliminates the need to read the current value first
|
||||
await db
|
||||
.update(clients)
|
||||
.set({
|
||||
// Note: bytesIn from peer goes to megabytesOut (data sent to client)
|
||||
// and bytesOut from peer goes to megabytesIn (data received from client)
|
||||
megabytesOut: sql`COALESCE(${clients.megabytesOut}, 0) + ${bytesIn}`,
|
||||
megabytesIn: sql`COALESCE(${clients.megabytesIn}, 0) + ${bytesOut}`,
|
||||
lastBandwidthUpdate: currentTime
|
||||
})
|
||||
.where(eq(clients.pubKey, publicKey));
|
||||
}, `update client bandwidth ${publicKey}`);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to update bandwidth for client ${publicKey}:`,
|
||||
error
|
||||
);
|
||||
// Continue with other clients even if one fails
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,8 +21,7 @@ export async function pickOrgDefaults(
|
||||
// const subnet = await getNextAvailableOrgSubnet();
|
||||
// Just hard code the subnet for now for everyone
|
||||
const subnet = config.getRawConfig().orgs.subnet_group;
|
||||
const utilitySubnet =
|
||||
config.getRawConfig().orgs.utility_subnet_group;
|
||||
const utilitySubnet = config.getRawConfig().orgs.utility_subnet_group;
|
||||
|
||||
return response<PickOrgDefaultsResponse>(res, {
|
||||
data: {
|
||||
|
||||
@@ -154,4 +154,4 @@ export async function updateOrg(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import {Request, Response, NextFunction} from "express";
|
||||
import {z} from "zod";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
db,
|
||||
resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility,
|
||||
resourceHeaderAuth,
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
resourcePassword,
|
||||
resourcePincode,
|
||||
resources
|
||||
} from "@server/db";
|
||||
import {eq} from "drizzle-orm";
|
||||
import { eq } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import {fromError} from "zod-validation-error";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import logger from "@server/logger";
|
||||
import {build} from "@server/build";
|
||||
import { build } from "@server/build";
|
||||
|
||||
const getResourceAuthInfoSchema = z.strictObject({
|
||||
resourceGuid: z.string()
|
||||
@@ -52,68 +53,68 @@ export async function getResourceAuthInfo(
|
||||
);
|
||||
}
|
||||
|
||||
const {resourceGuid} = parsedParams.data;
|
||||
const { resourceGuid } = parsedParams.data;
|
||||
|
||||
const isGuidInteger = /^\d+$/.test(resourceGuid);
|
||||
|
||||
const [result] =
|
||||
isGuidInteger && build === "saas"
|
||||
? await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
)
|
||||
.select()
|
||||
.from(resources)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
)
|
||||
|
||||
.leftJoin(
|
||||
resourceHeaderAuth,
|
||||
eq(
|
||||
resourceHeaderAuth.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.where(eq(resources.resourceId, Number(resourceGuid)))
|
||||
.limit(1)
|
||||
.leftJoin(
|
||||
resourceHeaderAuth,
|
||||
eq(
|
||||
resourceHeaderAuth.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.where(eq(resources.resourceId, Number(resourceGuid)))
|
||||
.limit(1)
|
||||
: await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
)
|
||||
.select()
|
||||
.from(resources)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
)
|
||||
|
||||
.leftJoin(
|
||||
resourceHeaderAuth,
|
||||
eq(
|
||||
resourceHeaderAuth.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.where(eq(resources.resourceGuid, resourceGuid))
|
||||
.limit(1);
|
||||
.leftJoin(
|
||||
resourceHeaderAuth,
|
||||
eq(
|
||||
resourceHeaderAuth.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.where(eq(resources.resourceGuid, resourceGuid))
|
||||
.limit(1);
|
||||
|
||||
const resource = result?.resources;
|
||||
if (!resource) {
|
||||
@@ -125,7 +126,8 @@ export async function getResourceAuthInfo(
|
||||
const pincode = result?.resourcePincode;
|
||||
const password = result?.resourcePassword;
|
||||
const headerAuth = result?.resourceHeaderAuth;
|
||||
const headerAuthExtendedCompatibility = result?.resourceHeaderAuthExtendedCompatibility;
|
||||
const headerAuthExtendedCompatibility =
|
||||
result?.resourceHeaderAuthExtendedCompatibility;
|
||||
|
||||
const url = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
|
||||
|
||||
@@ -138,7 +140,8 @@ export async function getResourceAuthInfo(
|
||||
password: password !== null,
|
||||
pincode: pincode !== null,
|
||||
headerAuth: headerAuth !== null,
|
||||
headerAuthExtendedCompatibility: headerAuthExtendedCompatibility !== null,
|
||||
headerAuthExtendedCompatibility:
|
||||
headerAuthExtendedCompatibility !== null,
|
||||
sso: resource.sso,
|
||||
blockAccess: resource.blockAccess,
|
||||
url,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import {db, resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility} from "@server/db";
|
||||
import {
|
||||
db,
|
||||
resourceHeaderAuth,
|
||||
resourceHeaderAuthExtendedCompatibility
|
||||
} from "@server/db";
|
||||
import {
|
||||
resources,
|
||||
userResources,
|
||||
@@ -109,7 +113,8 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
|
||||
domainId: resources.domainId,
|
||||
niceId: resources.niceId,
|
||||
headerAuthId: resourceHeaderAuth.headerAuthId,
|
||||
headerAuthExtendedCompatibilityId: resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
|
||||
headerAuthExtendedCompatibilityId:
|
||||
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
|
||||
targetId: targets.targetId,
|
||||
targetIp: targets.ip,
|
||||
targetPort: targets.port,
|
||||
@@ -133,7 +138,10 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
|
||||
)
|
||||
.leftJoin(
|
||||
resourceHeaderAuthExtendedCompatibility,
|
||||
eq(resourceHeaderAuthExtendedCompatibility.resourceId, resources.resourceId)
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resources.resourceId
|
||||
)
|
||||
)
|
||||
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
|
||||
.leftJoin(
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import {Request, Response, NextFunction} from "express";
|
||||
import {z} from "zod";
|
||||
import {db, resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility} from "@server/db";
|
||||
import {eq} from "drizzle-orm";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
db,
|
||||
resourceHeaderAuth,
|
||||
resourceHeaderAuthExtendedCompatibility
|
||||
} from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import {fromError} from "zod-validation-error";
|
||||
import {response} from "@server/lib/response";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { response } from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import {hashPassword} from "@server/auth/password";
|
||||
import {OpenAPITags, registry} from "@server/openApi";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const setResourceAuthMethodsParamsSchema = z.object({
|
||||
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
||||
@@ -67,29 +71,40 @@ export async function setResourceHeaderAuth(
|
||||
);
|
||||
}
|
||||
|
||||
const {resourceId} = parsedParams.data;
|
||||
const {user, password, extendedCompatibility} = parsedBody.data;
|
||||
const { resourceId } = parsedParams.data;
|
||||
const { user, password, extendedCompatibility } = parsedBody.data;
|
||||
|
||||
await db.transaction(async (trx) => {
|
||||
await trx
|
||||
.delete(resourceHeaderAuth)
|
||||
.where(eq(resourceHeaderAuth.resourceId, resourceId));
|
||||
await trx.delete(resourceHeaderAuthExtendedCompatibility).where(eq(resourceHeaderAuthExtendedCompatibility.resourceId, resourceId));
|
||||
await trx
|
||||
.delete(resourceHeaderAuthExtendedCompatibility)
|
||||
.where(
|
||||
eq(
|
||||
resourceHeaderAuthExtendedCompatibility.resourceId,
|
||||
resourceId
|
||||
)
|
||||
);
|
||||
|
||||
if (user && password && extendedCompatibility !== null) {
|
||||
const headerAuthHash = await hashPassword(Buffer.from(`${user}:${password}`).toString("base64"));
|
||||
const headerAuthHash = await hashPassword(
|
||||
Buffer.from(`${user}:${password}`).toString("base64")
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
trx
|
||||
.insert(resourceHeaderAuth)
|
||||
.values({resourceId, headerAuthHash}),
|
||||
.values({ resourceId, headerAuthHash }),
|
||||
trx
|
||||
.insert(resourceHeaderAuthExtendedCompatibility)
|
||||
.values({resourceId, extendedCompatibilityIsActivated: extendedCompatibility})
|
||||
.values({
|
||||
resourceId,
|
||||
extendedCompatibilityIsActivated:
|
||||
extendedCompatibility
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -7,4 +7,4 @@ export type GetMaintenanceInfoResponse = {
|
||||
maintenanceTitle: string | null;
|
||||
maintenanceMessage: string | null;
|
||||
maintenanceEstimatedTime: string | null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -343,7 +343,9 @@ async function updateHttpResource(
|
||||
|
||||
const isLicensed = await isLicensedOrSubscribed(resource.orgId);
|
||||
if (build == "enterprise" && !isLicensed) {
|
||||
logger.warn("Server is not licensed! Clearing set maintenance screen values");
|
||||
logger.warn(
|
||||
"Server is not licensed! Clearing set maintenance screen values"
|
||||
);
|
||||
// null the maintenance mode fields if not licensed
|
||||
updateData.maintenanceModeEnabled = undefined;
|
||||
updateData.maintenanceModeType = undefined;
|
||||
|
||||
@@ -39,12 +39,12 @@ async function getLatestNewtVersion(): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tags = await response.json();
|
||||
let tags = await response.json();
|
||||
if (!Array.isArray(tags) || tags.length === 0) {
|
||||
logger.warn("No tags found for Newt repository");
|
||||
return null;
|
||||
}
|
||||
|
||||
tags = tags.filter((version) => !version.name.includes("rc"));
|
||||
const latestVersion = tags[0].name;
|
||||
|
||||
cache.set("latestNewtVersion", latestVersion);
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
userSiteResources
|
||||
} from "@server/db";
|
||||
import { getUniqueSiteResourceName } from "@server/db/names";
|
||||
import { getNextAvailableAliasAddress, isIpInCidr, portRangeStringSchema } from "@server/lib/ip";
|
||||
import {
|
||||
getNextAvailableAliasAddress,
|
||||
isIpInCidr,
|
||||
portRangeStringSchema
|
||||
} from "@server/lib/ip";
|
||||
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
@@ -69,7 +73,10 @@ const createSiteResourceSchema = z
|
||||
const domainRegex =
|
||||
/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
|
||||
const isValidDomain = domainRegex.test(data.destination);
|
||||
const isValidAlias = data.alias !== undefined && data.alias !== null && data.alias.trim() !== "";
|
||||
const isValidAlias =
|
||||
data.alias !== undefined &&
|
||||
data.alias !== null &&
|
||||
data.alias.trim() !== "";
|
||||
|
||||
return isValidDomain && isValidAlias; // require the alias to be set in the case of domain
|
||||
}
|
||||
@@ -182,7 +189,9 @@ export async function createSiteResource(
|
||||
.limit(1);
|
||||
|
||||
if (!org) {
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "Organization not found"));
|
||||
return next(
|
||||
createHttpError(HttpCode.NOT_FOUND, "Organization not found")
|
||||
);
|
||||
}
|
||||
|
||||
if (!org.subnet || !org.utilitySubnet) {
|
||||
@@ -195,10 +204,13 @@ export async function createSiteResource(
|
||||
}
|
||||
|
||||
// Only check if destination is an IP address
|
||||
const isIp = z.union([z.ipv4(), z.ipv6()]).safeParse(destination).success;
|
||||
const isIp = z
|
||||
.union([z.ipv4(), z.ipv6()])
|
||||
.safeParse(destination).success;
|
||||
if (
|
||||
isIp &&
|
||||
(isIpInCidr(destination, org.subnet) || isIpInCidr(destination, org.utilitySubnet))
|
||||
(isIpInCidr(destination, org.subnet) ||
|
||||
isIpInCidr(destination, org.utilitySubnet))
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
|
||||
@@ -88,9 +88,7 @@ export async function deleteSiteResource(
|
||||
);
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`Deleted site resource ${siteResourceId}`
|
||||
);
|
||||
logger.info(`Deleted site resource ${siteResourceId}`);
|
||||
|
||||
return response(res, {
|
||||
data: { message: "Site resource deleted successfully" },
|
||||
|
||||
@@ -204,7 +204,9 @@ export async function updateSiteResource(
|
||||
.limit(1);
|
||||
|
||||
if (!org) {
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "Organization not found"));
|
||||
return next(
|
||||
createHttpError(HttpCode.NOT_FOUND, "Organization not found")
|
||||
);
|
||||
}
|
||||
|
||||
if (!org.subnet || !org.utilitySubnet) {
|
||||
@@ -217,10 +219,13 @@ export async function updateSiteResource(
|
||||
}
|
||||
|
||||
// Only check if destination is an IP address
|
||||
const isIp = z.union([z.ipv4(), z.ipv6()]).safeParse(destination).success;
|
||||
const isIp = z
|
||||
.union([z.ipv4(), z.ipv6()])
|
||||
.safeParse(destination).success;
|
||||
if (
|
||||
isIp &&
|
||||
(isIpInCidr(destination!, org.subnet) || isIpInCidr(destination!, org.utilitySubnet))
|
||||
(isIpInCidr(destination!, org.subnet) ||
|
||||
isIpInCidr(destination!, org.utilitySubnet))
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
@@ -295,7 +300,7 @@ export async function updateSiteResource(
|
||||
const [insertedSiteResource] = await trx
|
||||
.insert(siteResources)
|
||||
.values({
|
||||
...existingSiteResource,
|
||||
...existingSiteResource
|
||||
})
|
||||
.returning();
|
||||
|
||||
@@ -517,9 +522,14 @@ export async function handleMessagingForUpdatedSiteResource(
|
||||
site: { siteId: number; orgId: string },
|
||||
trx: Transaction
|
||||
) {
|
||||
|
||||
logger.debug("handleMessagingForUpdatedSiteResource: existingSiteResource is: ", existingSiteResource);
|
||||
logger.debug("handleMessagingForUpdatedSiteResource: updatedSiteResource is: ", updatedSiteResource);
|
||||
logger.debug(
|
||||
"handleMessagingForUpdatedSiteResource: existingSiteResource is: ",
|
||||
existingSiteResource
|
||||
);
|
||||
logger.debug(
|
||||
"handleMessagingForUpdatedSiteResource: updatedSiteResource is: ",
|
||||
updatedSiteResource
|
||||
);
|
||||
|
||||
const { mergedAllClients } =
|
||||
await rebuildClientAssociationsFromSiteResource(
|
||||
|
||||
@@ -60,11 +60,11 @@ export default async function migration() {
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "siteResources" ADD COLUMN "tcpPortRangeString" varchar;`
|
||||
sql`ALTER TABLE "siteResources" ADD COLUMN "tcpPortRangeString" varchar NOT NULL DEFAULT '*';`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "siteResources" ADD COLUMN "udpPortRangeString" varchar;`
|
||||
sql`ALTER TABLE "siteResources" ADD COLUMN "udpPortRangeString" varchar NOT NULL DEFAULT '*';`
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
|
||||
@@ -73,16 +73,18 @@ export default async function migration() {
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
`ALTER TABLE 'siteResources' ADD 'tcpPortRangeString' text;`
|
||||
`ALTER TABLE 'siteResources' ADD 'tcpPortRangeString' text DEFAULT '*' NOT NULL;`
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
`ALTER TABLE 'siteResources' ADD 'udpPortRangeString' text;`
|
||||
`ALTER TABLE 'siteResources' ADD 'udpPortRangeString' text DEFAULT '*' NOT NULL;`
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
`ALTER TABLE 'siteResources' ADD 'disableIcmp' integer;`
|
||||
`ALTER TABLE 'siteResources' ADD 'disableIcmp' integer NOT NULL DEFAULT false;`
|
||||
).run();
|
||||
|
||||
|
||||
})();
|
||||
|
||||
db.pragma("foreign_keys = ON");
|
||||
|
||||
@@ -62,6 +62,7 @@ export default function GeneralPage() {
|
||||
const [variant, setVariant] = useState<"oidc" | "google" | "azure">("oidc");
|
||||
const { isUnlocked } = useLicenseStatusContext();
|
||||
|
||||
const dashboardRedirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
|
||||
const [redirectUrl, setRedirectUrl] = useState(
|
||||
`${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`
|
||||
);
|
||||
@@ -423,11 +424,18 @@ export default function GeneralPage() {
|
||||
<InfoSections cols={3}>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("redirectUrl")}
|
||||
{t("orgIdpRedirectUrls")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<CopyToClipboard text={redirectUrl} />
|
||||
</InfoSectionContent>
|
||||
{redirectUrl !== dashboardRedirectUrl && (
|
||||
<InfoSectionContent>
|
||||
<CopyToClipboard
|
||||
text={dashboardRedirectUrl}
|
||||
/>
|
||||
</InfoSectionContent>
|
||||
)}
|
||||
</InfoSection>
|
||||
</InfoSections>
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ export default function Page() {
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
router.push("/admin/idp");
|
||||
router.push(`/${params.orgId}/settings/idp`);
|
||||
}}
|
||||
>
|
||||
{t("idpSeeAll")}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { internal, priv } from "@app/lib/api";
|
||||
import { internal } from "@app/lib/api";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import { AxiosResponse } from "axios";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import IdpTable, { IdpRow } from "@app/components/private/OrgIdpTable";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Alert, AlertDescription } from "@app/components/ui/alert";
|
||||
import { cache } from "react";
|
||||
import {
|
||||
GetOrgSubscriptionResponse,
|
||||
GetOrgTierResponse
|
||||
} from "@server/routers/billing/types";
|
||||
import { TierId } from "@server/lib/billing/tiers";
|
||||
import { build } from "@server/build";
|
||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||
|
||||
type OrgIdpPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
@@ -35,21 +28,6 @@ export default async function OrgIdpPage(props: OrgIdpPageProps) {
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
let subscriptionStatus: GetOrgTierResponse | null = null;
|
||||
try {
|
||||
const getSubscription = cache(() =>
|
||||
priv.get<AxiosResponse<GetOrgTierResponse>>(
|
||||
`/org/${params.orgId}/billing/tier`
|
||||
)
|
||||
);
|
||||
const subRes = await getSubscription();
|
||||
subscriptionStatus = subRes.data.data;
|
||||
} catch {}
|
||||
const subscribed =
|
||||
build === "enterprise"
|
||||
? true
|
||||
: subscriptionStatus?.tier === TierId.STANDARD;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSectionTitle
|
||||
@@ -57,13 +35,7 @@ export default async function OrgIdpPage(props: OrgIdpPageProps) {
|
||||
description={t("idpManageDescription")}
|
||||
/>
|
||||
|
||||
{build === "saas" && !subscribed ? (
|
||||
<Alert variant="info" className="mb-6">
|
||||
<AlertDescription>
|
||||
{t("idpDisabled")} {t("subscriptionRequiredToUse")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
<PaidFeaturesAlert />
|
||||
|
||||
<IdpTable idps={idps} orgId={params.orgId} />
|
||||
</>
|
||||
|
||||
@@ -71,7 +71,9 @@ export default async function GeneralSettingsPage({
|
||||
|
||||
<div className="space-y-6">
|
||||
<OrgInfoCard />
|
||||
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>
|
||||
<HorizontalTabs items={navItems}>
|
||||
{children}
|
||||
</HorizontalTabs>
|
||||
</div>
|
||||
</OrgUserProvider>
|
||||
</OrgProvider>
|
||||
|
||||
@@ -71,7 +71,7 @@ export default async function ClientResourcesPage(
|
||||
niceId: siteResource.niceId,
|
||||
tcpPortRangeString: siteResource.tcpPortRangeString || null,
|
||||
udpPortRangeString: siteResource.udpPortRangeString || null,
|
||||
disableIcmp: siteResource.disableIcmp || false,
|
||||
disableIcmp: siteResource.disableIcmp || false
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -16,7 +16,6 @@ import { SwitchInput } from "@app/components/SwitchInput";
|
||||
import { Tag, TagInput } from "@app/components/tags/tag-input";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { CheckboxWithLabel } from "@app/components/ui/checkbox";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -184,9 +183,6 @@ export default function ResourceAuthenticationPage() {
|
||||
|
||||
const [ssoEnabled, setSsoEnabled] = useState(resource.sso);
|
||||
|
||||
const [autoLoginEnabled, setAutoLoginEnabled] = useState(
|
||||
resource.skipToIdpId !== null && resource.skipToIdpId !== undefined
|
||||
);
|
||||
const [selectedIdpId, setSelectedIdpId] = useState<number | null>(
|
||||
resource.skipToIdpId || null
|
||||
);
|
||||
@@ -243,19 +239,8 @@ export default function ResourceAuthenticationPage() {
|
||||
text: w.email
|
||||
}))
|
||||
);
|
||||
if (autoLoginEnabled && !selectedIdpId && orgIdps.length > 0) {
|
||||
setSelectedIdpId(orgIdps[0].idpId);
|
||||
}
|
||||
hasInitializedRef.current = true;
|
||||
}, [
|
||||
pageLoading,
|
||||
resourceRoles,
|
||||
resourceUsers,
|
||||
whitelist,
|
||||
autoLoginEnabled,
|
||||
selectedIdpId,
|
||||
orgIdps
|
||||
]);
|
||||
}, [pageLoading, resourceRoles, resourceUsers, whitelist, orgIdps]);
|
||||
|
||||
const [, submitUserRolesForm, loadingSaveUsersRoles] = useActionState(
|
||||
onSubmitUsersRoles,
|
||||
@@ -269,16 +254,6 @@ export default function ResourceAuthenticationPage() {
|
||||
const data = usersRolesForm.getValues();
|
||||
|
||||
try {
|
||||
// Validate that an IDP is selected if auto login is enabled
|
||||
if (autoLoginEnabled && !selectedIdpId) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("error"),
|
||||
description: t("selectIdpRequired")
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const jobs = [
|
||||
api.post(`/resource/${resource.resourceId}/roles`, {
|
||||
roleIds: data.roles.map((i) => parseInt(i.id))
|
||||
@@ -288,7 +263,7 @@ export default function ResourceAuthenticationPage() {
|
||||
}),
|
||||
api.post(`/resource/${resource.resourceId}`, {
|
||||
sso: ssoEnabled,
|
||||
skipToIdpId: autoLoginEnabled ? selectedIdpId : null
|
||||
skipToIdpId: selectedIdpId
|
||||
})
|
||||
];
|
||||
|
||||
@@ -296,7 +271,7 @@ export default function ResourceAuthenticationPage() {
|
||||
|
||||
updateResource({
|
||||
sso: ssoEnabled,
|
||||
skipToIdpId: autoLoginEnabled ? selectedIdpId : null
|
||||
skipToIdpId: selectedIdpId
|
||||
});
|
||||
|
||||
updateAuthInfo({
|
||||
@@ -619,88 +594,53 @@ export default function ResourceAuthenticationPage() {
|
||||
)}
|
||||
|
||||
{ssoEnabled && allIdps.length > 0 && (
|
||||
<>
|
||||
<div className="space-y-2 mb-3">
|
||||
<CheckboxWithLabel
|
||||
label={t(
|
||||
"autoLoginExternalIdp"
|
||||
)}
|
||||
checked={autoLoginEnabled}
|
||||
onCheckedChange={(
|
||||
checked
|
||||
) => {
|
||||
setAutoLoginEnabled(
|
||||
checked as boolean
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{t("defaultIdentityProvider")}
|
||||
</label>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
if (value === "none") {
|
||||
setSelectedIdpId(null);
|
||||
} else {
|
||||
setSelectedIdpId(
|
||||
parseInt(value)
|
||||
);
|
||||
if (
|
||||
checked &&
|
||||
allIdps.length > 0
|
||||
) {
|
||||
setSelectedIdpId(
|
||||
allIdps[0].id
|
||||
);
|
||||
} else {
|
||||
setSelectedIdpId(
|
||||
null
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"autoLoginExternalIdpDescription"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{autoLoginEnabled && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{t(
|
||||
"defaultIdentityProvider"
|
||||
}
|
||||
}}
|
||||
value={
|
||||
selectedIdpId
|
||||
? selectedIdpId.toString()
|
||||
: "none"
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full mt-1">
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"selectIdpPlaceholder"
|
||||
)}
|
||||
</label>
|
||||
<Select
|
||||
onValueChange={(
|
||||
value
|
||||
) =>
|
||||
setSelectedIdpId(
|
||||
parseInt(value)
|
||||
)
|
||||
}
|
||||
value={
|
||||
selectedIdpId
|
||||
? selectedIdpId.toString()
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full mt-1">
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"selectIdpPlaceholder"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{allIdps.map(
|
||||
(idp) => (
|
||||
<SelectItem
|
||||
key={
|
||||
idp.id
|
||||
}
|
||||
value={idp.id.toString()}
|
||||
>
|
||||
{
|
||||
idp.text
|
||||
}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">
|
||||
{t("none")}
|
||||
</SelectItem>
|
||||
{allIdps.map((idp) => (
|
||||
<SelectItem
|
||||
key={idp.id}
|
||||
value={idp.id.toString()}
|
||||
>
|
||||
{idp.text}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"defaultIdentityProviderDescription"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -164,6 +164,10 @@ function MaintenanceSectionForm({
|
||||
return isEnterpriseNotLicensed || isSaasNotSubscribed;
|
||||
};
|
||||
|
||||
if (!resource.http) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
@@ -189,7 +193,7 @@ function MaintenanceSectionForm({
|
||||
name="maintenanceModeEnabled"
|
||||
render={({ field }) => {
|
||||
const isDisabled =
|
||||
isSecurityFeatureDisabled();
|
||||
isSecurityFeatureDisabled() || resource.http === false;
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
@@ -437,9 +441,16 @@ export default function GeneralForm() {
|
||||
);
|
||||
|
||||
const resourceFullDomainName = useMemo(() => {
|
||||
const url = new URL(resourceFullDomain);
|
||||
return url.hostname;
|
||||
}, [resourceFullDomain]);
|
||||
if (!resource.fullDomain) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
const url = new URL(resourceFullDomain);
|
||||
return url.hostname;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}, [resourceFullDomain, resource.fullDomain]);
|
||||
|
||||
const [selectedDomain, setSelectedDomain] = useState<{
|
||||
domainId: string;
|
||||
|
||||
@@ -338,7 +338,7 @@ function ProxyResourceTargetsForm({
|
||||
<div
|
||||
className={`flex items-center gap-2 ${status === "healthy" ? "text-green-500" : status === "unhealthy" ? "text-destructive" : ""}`}
|
||||
>
|
||||
<Settings className="h-3 w-3" />
|
||||
<Settings className="h-4 w-4 text-foreground" />
|
||||
{getStatusText(status)}
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
@@ -118,8 +118,7 @@ export default function ResourceRules(props: {
|
||||
const [countrySelectValue, setCountrySelectValue] = useState("");
|
||||
const [openAddRuleCountrySelect, setOpenAddRuleCountrySelect] =
|
||||
useState(false);
|
||||
const [openAddRuleAsnSelect, setOpenAddRuleAsnSelect] =
|
||||
useState(false);
|
||||
const [openAddRuleAsnSelect, setOpenAddRuleAsnSelect] = useState(false);
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
const { env } = useEnvContext();
|
||||
@@ -181,7 +180,7 @@ export default function ResourceRules(props: {
|
||||
// Normalize ASN value
|
||||
if (data.match === "ASN") {
|
||||
const originalValue = data.value.toUpperCase();
|
||||
|
||||
|
||||
// Handle special "ALL" case
|
||||
if (originalValue === "ALL" || originalValue === "AS0") {
|
||||
data.value = "ALL";
|
||||
@@ -542,7 +541,11 @@ export default function ResourceRules(props: {
|
||||
updateRule(row.original.ruleId, {
|
||||
match: value,
|
||||
value:
|
||||
value === "COUNTRY" ? "US" : value === "ASN" ? "AS15169" : row.original.value
|
||||
value === "COUNTRY"
|
||||
? "US"
|
||||
: value === "ASN"
|
||||
? "AS15169"
|
||||
: row.original.value
|
||||
})
|
||||
}
|
||||
>
|
||||
@@ -559,9 +562,7 @@ export default function ResourceRules(props: {
|
||||
</SelectItem>
|
||||
)}
|
||||
{isMaxmindAsnAvailable && (
|
||||
<SelectItem value="ASN">
|
||||
{RuleMatch.ASN}
|
||||
</SelectItem>
|
||||
<SelectItem value="ASN">{RuleMatch.ASN}</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -654,9 +655,7 @@ export default function ResourceRules(props: {
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="min-w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search ASNs or enter custom..."
|
||||
/>
|
||||
<CommandInput placeholder="Search ASNs or enter custom..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
No ASN found. Enter a custom ASN below.
|
||||
@@ -665,7 +664,9 @@ export default function ResourceRules(props: {
|
||||
{MAJOR_ASNS.map((asn) => (
|
||||
<CommandItem
|
||||
key={asn.code}
|
||||
value={asn.name + " " + asn.code}
|
||||
value={
|
||||
asn.name + " " + asn.code
|
||||
}
|
||||
onSelect={() => {
|
||||
updateRule(
|
||||
row.original.ruleId,
|
||||
@@ -1056,8 +1057,8 @@ export default function ResourceRules(props: {
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : addRuleForm.watch(
|
||||
"match"
|
||||
) === "ASN" ? (
|
||||
"match"
|
||||
) === "ASN" ? (
|
||||
<Popover
|
||||
open={
|
||||
openAddRuleAsnSelect
|
||||
@@ -1086,21 +1087,27 @@ export default function ResourceRules(props: {
|
||||
field.value
|
||||
)
|
||||
?.name +
|
||||
" (" +
|
||||
field.value +
|
||||
")" || field.value
|
||||
" (" +
|
||||
field.value +
|
||||
")" ||
|
||||
field.value
|
||||
: "Select ASN"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full p-0">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search ASNs or enter custom..."
|
||||
/>
|
||||
<CommandInput placeholder="Search ASNs or enter custom..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
No ASN found. Use the custom input below.
|
||||
No
|
||||
ASN
|
||||
found.
|
||||
Use
|
||||
the
|
||||
custom
|
||||
input
|
||||
below.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{MAJOR_ASNS.map(
|
||||
@@ -1112,7 +1119,9 @@ export default function ResourceRules(props: {
|
||||
asn.code
|
||||
}
|
||||
value={
|
||||
asn.name + " " + asn.code
|
||||
asn.name +
|
||||
" " +
|
||||
asn.code
|
||||
}
|
||||
onSelect={() => {
|
||||
field.onChange(
|
||||
@@ -1138,6 +1147,7 @@ export default function ResourceRules(props: {
|
||||
{
|
||||
asn.code
|
||||
}
|
||||
|
||||
)
|
||||
</CommandItem>
|
||||
)
|
||||
@@ -1148,14 +1158,32 @@ export default function ResourceRules(props: {
|
||||
<div className="border-t p-2">
|
||||
<Input
|
||||
placeholder="Enter custom ASN (e.g., AS15169)"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
const value = e.currentTarget.value
|
||||
.toUpperCase()
|
||||
.replace(/^AS/, "");
|
||||
if (/^\d+$/.test(value)) {
|
||||
field.onChange("AS" + value);
|
||||
setOpenAddRuleAsnSelect(false);
|
||||
onKeyDown={(
|
||||
e
|
||||
) => {
|
||||
if (
|
||||
e.key ===
|
||||
"Enter"
|
||||
) {
|
||||
const value =
|
||||
e.currentTarget.value
|
||||
.toUpperCase()
|
||||
.replace(
|
||||
/^AS/,
|
||||
""
|
||||
);
|
||||
if (
|
||||
/^\d+$/.test(
|
||||
value
|
||||
)
|
||||
) {
|
||||
field.onChange(
|
||||
"AS" +
|
||||
value
|
||||
);
|
||||
setOpenAddRuleAsnSelect(
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -756,7 +756,9 @@ WantedBy=default.target`
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-start-1 md:col-span-2">
|
||||
<FormLabel>
|
||||
{t("siteAddress")}
|
||||
{t(
|
||||
"siteAddress"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -909,7 +911,7 @@ WantedBy=default.target`
|
||||
? "squareOutlinePrimary"
|
||||
: "squareOutline"
|
||||
}
|
||||
className={`flex-1 min-w-[120px] ${platform === os ? "bg-primary/10" : ""} shadow-none`}
|
||||
className={`flex-1 min-w-30 ${platform === os ? "bg-primary/10" : ""} shadow-none`}
|
||||
onClick={() => {
|
||||
setPlatform(os);
|
||||
}}
|
||||
@@ -940,7 +942,7 @@ WantedBy=default.target`
|
||||
? "squareOutlinePrimary"
|
||||
: "squareOutline"
|
||||
}
|
||||
className={`flex-1 min-w-[120px] ${architecture === arch ? "bg-primary/10" : ""} shadow-none`}
|
||||
className={`flex-1 min-w-30 ${architecture === arch ? "bg-primary/10" : ""} shadow-none`}
|
||||
onClick={() =>
|
||||
setArchitecture(
|
||||
arch
|
||||
|
||||
@@ -87,8 +87,6 @@ export default async function OrgAuthPage(props: {
|
||||
redirect(env.app.dashboardUrl);
|
||||
}
|
||||
|
||||
console.log(user, forceLogin);
|
||||
|
||||
if (user && !forceLogin) {
|
||||
let redirectToken: string | undefined;
|
||||
try {
|
||||
|
||||
@@ -162,3 +162,32 @@ p {
|
||||
#nprogress .bar {
|
||||
background: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
@keyframes dot-pulse {
|
||||
0%, 80%, 100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.animate-dot-pulse {
|
||||
animation: dot-pulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Use JavaScript-set viewport height for mobile to handle keyboard properly */
|
||||
.h-screen-safe {
|
||||
height: 100vh; /* Default for desktop and fallback */
|
||||
}
|
||||
|
||||
/* Only apply custom viewport height on mobile */
|
||||
@media (max-width: 767px) {
|
||||
.h-screen-safe {
|
||||
height: var(--vh, 100vh); /* Use CSS variable set by ViewportHeightFix on mobile */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { TopLoader } from "@app/components/Toploader";
|
||||
import Script from "next/script";
|
||||
import { TanstackQueryProvider } from "@app/components/TanstackQueryProvider";
|
||||
import { TailwindIndicator } from "@app/components/TailwindIndicator";
|
||||
import { ViewportHeightFix } from "@app/components/ViewportHeightFix";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `Dashboard - ${process.env.BRANDING_APP_NAME || "Pangolin"}`,
|
||||
@@ -77,7 +78,7 @@ export default async function RootLayout({
|
||||
|
||||
return (
|
||||
<html suppressHydrationWarning lang={locale}>
|
||||
<body className={`${font.className} h-screen overflow-hidden`}>
|
||||
<body className={`${font.className} h-screen-safe overflow-hidden`}>
|
||||
<TopLoader />
|
||||
{build === "saas" && (
|
||||
<Script
|
||||
@@ -86,6 +87,7 @@ export default async function RootLayout({
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
)}
|
||||
<ViewportHeightFix />
|
||||
<NextIntlClientProvider>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
|
||||
@@ -66,4 +66,3 @@ export const ClientDownloadBanner = () => {
|
||||
};
|
||||
|
||||
export default ClientDownloadBanner;
|
||||
|
||||
|
||||
@@ -99,14 +99,12 @@ export default function ClientResourcesTable({
|
||||
siteId: number
|
||||
) => {
|
||||
try {
|
||||
await api
|
||||
.delete(`/site-resource/${resourceId}`)
|
||||
.then(() => {
|
||||
startTransition(() => {
|
||||
router.refresh();
|
||||
setIsDeleteModalOpen(false);
|
||||
});
|
||||
await api.delete(`/site-resource/${resourceId}`).then(() => {
|
||||
startTransition(() => {
|
||||
router.refresh();
|
||||
setIsDeleteModalOpen(false);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(t("resourceErrorDelete"), e);
|
||||
toast({
|
||||
|
||||
@@ -87,7 +87,12 @@ const isValidPortRangeString = (val: string | undefined | null): boolean => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (startPort < 1 || startPort > 65535 || endPort < 1 || endPort > 65535) {
|
||||
if (
|
||||
startPort < 1 ||
|
||||
startPort > 65535 ||
|
||||
endPort < 1 ||
|
||||
endPort > 65535
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -131,7 +136,10 @@ const getPortModeFromString = (val: string | undefined | null): PortMode => {
|
||||
};
|
||||
|
||||
// Helper to get the port string for API from mode and custom value
|
||||
const getPortStringFromMode = (mode: PortMode, customValue: string): string | undefined => {
|
||||
const getPortStringFromMode = (
|
||||
mode: PortMode,
|
||||
customValue: string
|
||||
): string | undefined => {
|
||||
if (mode === "all") return "*";
|
||||
if (mode === "blocked") return "";
|
||||
return customValue;
|
||||
@@ -1097,8 +1105,7 @@ export default function CreateInternalResourceDialog({
|
||||
size="sm"
|
||||
tags={
|
||||
form.getValues()
|
||||
.roles ||
|
||||
[]
|
||||
.roles || []
|
||||
}
|
||||
setTags={(
|
||||
newRoles
|
||||
@@ -1154,8 +1161,7 @@ export default function CreateInternalResourceDialog({
|
||||
)}
|
||||
tags={
|
||||
form.getValues()
|
||||
.users ||
|
||||
[]
|
||||
.users || []
|
||||
}
|
||||
size="sm"
|
||||
setTags={(
|
||||
@@ -1245,9 +1251,7 @@ export default function CreateInternalResourceDialog({
|
||||
restrictTagsToAutocompleteOptions={
|
||||
true
|
||||
}
|
||||
sortTags={
|
||||
true
|
||||
}
|
||||
sortTags={true}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, type ReactNode } from "react";
|
||||
import React, { useState, useEffect, type ReactNode, useEffectEvent } from "react";
|
||||
import { Card, CardContent } from "@app/components/ui/card";
|
||||
import { X } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
|
||||
type DismissableBannerProps = {
|
||||
storageKey: string;
|
||||
@@ -25,6 +26,12 @@ export const DismissableBanner = ({
|
||||
const [isDismissed, setIsDismissed] = useState(true);
|
||||
const t = useTranslations();
|
||||
|
||||
const { env } = useEnvContext();
|
||||
|
||||
if (env.flags.disableProductHelpBanners) {
|
||||
return null;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const dismissedData = localStorage.getItem(storageKey);
|
||||
if (dismissedData) {
|
||||
@@ -95,4 +102,3 @@ export const DismissableBanner = ({
|
||||
};
|
||||
|
||||
export default DismissableBanner;
|
||||
|
||||
|
||||
@@ -501,13 +501,19 @@ export default function EditInternalResourceDialog({
|
||||
// ]);
|
||||
|
||||
await queryClient.invalidateQueries(
|
||||
resourceQueries.siteResourceRoles({ siteResourceId: resource.id })
|
||||
resourceQueries.siteResourceRoles({
|
||||
siteResourceId: resource.id
|
||||
})
|
||||
);
|
||||
await queryClient.invalidateQueries(
|
||||
resourceQueries.siteResourceUsers({ siteResourceId: resource.id })
|
||||
resourceQueries.siteResourceUsers({
|
||||
siteResourceId: resource.id
|
||||
})
|
||||
);
|
||||
await queryClient.invalidateQueries(
|
||||
resourceQueries.siteResourceClients({ siteResourceId: resource.id })
|
||||
resourceQueries.siteResourceClients({
|
||||
siteResourceId: resource.id
|
||||
})
|
||||
);
|
||||
|
||||
toast({
|
||||
|
||||
@@ -330,7 +330,7 @@ export default function ExitNodesTable({
|
||||
isRefreshing={isRefreshing}
|
||||
columnVisibility={{
|
||||
type: false,
|
||||
address: false,
|
||||
address: false
|
||||
}}
|
||||
enableColumnVisibility={true}
|
||||
/>
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function Layout({
|
||||
(sidebarStateCookie !== "expanded" && defaultSidebarCollapsed);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
<div className="flex h-screen-safe overflow-hidden">
|
||||
{/* Desktop Sidebar */}
|
||||
{showSidebar && (
|
||||
<LayoutSidebar
|
||||
|
||||
@@ -48,7 +48,7 @@ export function LayoutMobileMenu({
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<div className="shrink-0 md:hidden">
|
||||
<div className="shrink-0 md:hidden sticky top-0 z-50">
|
||||
<div className="h-16 flex items-center px-2">
|
||||
<div className="flex items-center gap-4">
|
||||
{showSidebar && (
|
||||
@@ -72,7 +72,7 @@ export function LayoutMobileMenu({
|
||||
<SheetDescription className="sr-only">
|
||||
{t("navbarDescription")}
|
||||
</SheetDescription>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="flex-1 overflow-y-auto relative">
|
||||
<div className="px-3">
|
||||
<OrgSelector
|
||||
orgId={orgId}
|
||||
@@ -83,7 +83,7 @@ export function LayoutMobileMenu({
|
||||
<div className="px-3">
|
||||
{!isAdminPage &&
|
||||
user.serverAdmin && (
|
||||
<div className="pb-3">
|
||||
<div className="py-2">
|
||||
<Link
|
||||
href="/admin"
|
||||
className={cn(
|
||||
@@ -113,6 +113,7 @@ export function LayoutMobileMenu({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="sticky bottom-0 left-0 right-0 h-8 pointer-events-none bg-gradient-to-t from-card to-transparent" />
|
||||
</div>
|
||||
<div className="px-3 pt-3 pb-3 space-y-4 border-t shrink-0">
|
||||
<SupporterStatus />
|
||||
|
||||
@@ -116,10 +116,12 @@ export function LayoutSidebar({
|
||||
isCollapsed={isSidebarCollapsed}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn(
|
||||
"w-full border-b border-border",
|
||||
isSidebarCollapsed && "mb-2"
|
||||
)} />
|
||||
<div
|
||||
className={cn(
|
||||
"w-full border-b border-border",
|
||||
isSidebarCollapsed && "mb-2"
|
||||
)}
|
||||
/>
|
||||
<div className="flex-1 overflow-y-auto relative">
|
||||
<div className="px-2 pt-1">
|
||||
{!isAdminPage && user.serverAdmin && (
|
||||
|
||||
@@ -120,7 +120,9 @@ export default function LoginForm({
|
||||
const focusInput = () => {
|
||||
// Try using the ref first
|
||||
if (otpContainerRef.current) {
|
||||
const hiddenInput = otpContainerRef.current.querySelector('input') as HTMLInputElement;
|
||||
const hiddenInput = otpContainerRef.current.querySelector(
|
||||
"input"
|
||||
) as HTMLInputElement;
|
||||
if (hiddenInput) {
|
||||
hiddenInput.focus();
|
||||
return;
|
||||
@@ -128,17 +130,23 @@ export default function LoginForm({
|
||||
}
|
||||
|
||||
// Fallback: query the DOM
|
||||
const otpContainer = document.querySelector('[data-slot="input-otp"]');
|
||||
const otpContainer = document.querySelector(
|
||||
'[data-slot="input-otp"]'
|
||||
);
|
||||
if (!otpContainer) return;
|
||||
|
||||
const hiddenInput = otpContainer.querySelector('input') as HTMLInputElement;
|
||||
const hiddenInput = otpContainer.querySelector(
|
||||
"input"
|
||||
) as HTMLInputElement;
|
||||
if (hiddenInput) {
|
||||
hiddenInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Last resort: click the first slot
|
||||
const firstSlot = otpContainer.querySelector('[data-slot="input-otp-slot"]') as HTMLElement;
|
||||
const firstSlot = otpContainer.querySelector(
|
||||
'[data-slot="input-otp-slot"]'
|
||||
) as HTMLElement;
|
||||
if (firstSlot) {
|
||||
firstSlot.click();
|
||||
}
|
||||
@@ -508,7 +516,10 @@ export default function LoginForm({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div ref={otpContainerRef} className="flex justify-center">
|
||||
<div
|
||||
ref={otpContainerRef}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
{...field}
|
||||
|
||||
@@ -11,9 +11,7 @@ type MachineClientsBannerProps = {
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export const MachineClientsBanner = ({
|
||||
orgId
|
||||
}: MachineClientsBannerProps) => {
|
||||
export const MachineClientsBanner = ({ orgId }: MachineClientsBannerProps) => {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
@@ -57,4 +55,3 @@ export const MachineClientsBanner = ({
|
||||
};
|
||||
|
||||
export default MachineClientsBanner;
|
||||
|
||||
|
||||
@@ -39,4 +39,3 @@ export default function OrgInfoCard({}: OrgInfoCardProps) {
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -119,4 +119,3 @@ export default async function OrgLoginPage({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ export function OrgSelectionForm() {
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("orgAuthWhatsThis")}{" "}
|
||||
<Link
|
||||
href="https://docs.pangolin.net/manage/identity-providers/add-an-idp"
|
||||
href="https://docs.pangolin.net/manage/organizations/org-id"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
|
||||
@@ -51,4 +51,3 @@ export const PrivateResourcesBanner = ({
|
||||
};
|
||||
|
||||
export default PrivateResourcesBanner;
|
||||
|
||||
|
||||
@@ -41,7 +41,10 @@ export default function ProductUpdates({
|
||||
|
||||
const data = useQueries({
|
||||
queries: [
|
||||
productUpdatesQueries.list(env.app.notifications.product_updates),
|
||||
productUpdatesQueries.list(
|
||||
env.app.notifications.product_updates,
|
||||
env.app.version
|
||||
),
|
||||
productUpdatesQueries.latestVersion(
|
||||
env.app.notifications.new_releases
|
||||
)
|
||||
@@ -189,7 +192,7 @@ function ProductUpdatesListPopup({
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-1 cursor-pointer block group",
|
||||
"rounded-md border border-primary/30 bg-gradient-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
|
||||
"rounded-md border border-primary/30 bg-linear-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
|
||||
"transition duration-300 ease-in-out",
|
||||
"data-closed:opacity-0 data-closed:translate-y-full"
|
||||
)}
|
||||
@@ -343,7 +346,7 @@ function NewVersionAvailable({
|
||||
rel="noopener noreferrer"
|
||||
className={cn(
|
||||
"relative z-2 group cursor-pointer block",
|
||||
"rounded-md border border-primary/30 bg-gradient-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
|
||||
"rounded-md border border-primary/30 bg-linear-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
|
||||
"transition duration-300 ease-in-out",
|
||||
"data-closed:opacity-0 data-closed:translate-y-full"
|
||||
)}
|
||||
|
||||
@@ -20,4 +20,3 @@ export const ProxyResourcesBanner = () => {
|
||||
};
|
||||
|
||||
export default ProxyResourcesBanner;
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ export default function ProxyResourcesTable({
|
||||
|
||||
if (!targets || targets.length === 0) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div id="LOOK_FOR_ME" className="flex items-center gap-2">
|
||||
<StatusIcon status="unknown" />
|
||||
<span className="text-sm">
|
||||
{t("resourcesTableNoTargets")}
|
||||
|
||||
@@ -32,12 +32,6 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
<InfoSections
|
||||
cols={resource.http && env.flags.usePangolinDns ? 5 : 4}
|
||||
>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>URL</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<CopyToClipboard text={fullUrl} isLink={true} />
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t("identifier")}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
@@ -46,6 +40,12 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
</InfoSection>
|
||||
{resource.http ? (
|
||||
<>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>URL</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<CopyToClipboard text={fullUrl} isLink={true} />
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("authentication")}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {Button} from "@app/components/ui/button";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from "@app/components/ui/form";
|
||||
import {Input} from "@app/components/ui/input";
|
||||
import {toast} from "@app/hooks/useToast";
|
||||
import {zodResolver} from "@hookform/resolvers/zod";
|
||||
import {useEffect, useState} from "react";
|
||||
import {useForm} from "react-hook-form";
|
||||
import {z} from "zod";
|
||||
import { Input } from "@app/components/ui/input";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Credenza,
|
||||
CredenzaBody,
|
||||
@@ -25,14 +25,14 @@ import {
|
||||
CredenzaHeader,
|
||||
CredenzaTitle
|
||||
} from "@app/components/Credenza";
|
||||
import {formatAxiosError} from "@app/lib/api";
|
||||
import {AxiosResponse} from "axios";
|
||||
import {Resource} from "@server/db";
|
||||
import {createApiClient} from "@app/lib/api";
|
||||
import {useEnvContext} from "@app/hooks/useEnvContext";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {SwitchInput} from "@/components/SwitchInput";
|
||||
import {InfoPopup} from "@/components/ui/info-popup";
|
||||
import { formatAxiosError } from "@app/lib/api";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Resource } from "@server/db";
|
||||
import { createApiClient } from "@app/lib/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { SwitchInput } from "@/components/SwitchInput";
|
||||
import { InfoPopup } from "@/components/ui/info-popup";
|
||||
|
||||
const setHeaderAuthFormSchema = z.object({
|
||||
user: z.string().min(4).max(100),
|
||||
@@ -56,11 +56,11 @@ type SetHeaderAuthFormProps = {
|
||||
};
|
||||
|
||||
export default function SetResourceHeaderAuthForm({
|
||||
open,
|
||||
setOpen,
|
||||
resourceId,
|
||||
onSetHeaderAuth
|
||||
}: SetHeaderAuthFormProps) {
|
||||
open,
|
||||
setOpen,
|
||||
resourceId,
|
||||
onSetHeaderAuth
|
||||
}: SetHeaderAuthFormProps) {
|
||||
const api = createApiClient(useEnvContext());
|
||||
const t = useTranslations();
|
||||
|
||||
@@ -82,11 +82,14 @@ export default function SetResourceHeaderAuthForm({
|
||||
async function onSubmit(data: SetHeaderAuthFormValues) {
|
||||
setLoading(true);
|
||||
|
||||
api.post<AxiosResponse<Resource>>(`/resource/${resourceId}/header-auth`, {
|
||||
user: data.user,
|
||||
password: data.password,
|
||||
extendedCompatibility: data.extendedCompatibility
|
||||
})
|
||||
api.post<AxiosResponse<Resource>>(
|
||||
`/resource/${resourceId}/header-auth`,
|
||||
{
|
||||
user: data.user,
|
||||
password: data.password,
|
||||
extendedCompatibility: data.extendedCompatibility
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
toast({
|
||||
title: t("resourceHeaderAuthSetup"),
|
||||
@@ -100,10 +103,10 @@ export default function SetResourceHeaderAuthForm({
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('resourceErrorHeaderAuthSetup'),
|
||||
title: t("resourceErrorHeaderAuthSetup"),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
t('resourceErrorHeaderAuthSetupDescription')
|
||||
t("resourceErrorHeaderAuthSetupDescription")
|
||||
)
|
||||
});
|
||||
})
|
||||
@@ -139,7 +142,7 @@ export default function SetResourceHeaderAuthForm({
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="user"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("user")}</FormLabel>
|
||||
<FormControl>
|
||||
@@ -149,14 +152,14 @@ export default function SetResourceHeaderAuthForm({
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("password")}
|
||||
@@ -168,25 +171,31 @@ export default function SetResourceHeaderAuthForm({
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="extendedCompatibility"
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<SwitchInput
|
||||
id="header-auth-compatibility-toggle"
|
||||
label={t("headerAuthCompatibility")}
|
||||
info={t('headerAuthCompatibilityInfo')}
|
||||
label={t(
|
||||
"headerAuthCompatibility"
|
||||
)}
|
||||
info={t(
|
||||
"headerAuthCompatibilityInfo"
|
||||
)}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
onCheckedChange={
|
||||
field.onChange
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -91,10 +91,10 @@ export default function SetResourcePasswordForm({
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('resourceErrorPasswordSetup'),
|
||||
title: t("resourceErrorPasswordSetup"),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
t('resourceErrorPasswordSetupDescription')
|
||||
t("resourceErrorPasswordSetupDescription")
|
||||
)
|
||||
});
|
||||
})
|
||||
|
||||
@@ -97,10 +97,10 @@ export default function SetResourcePincodeForm({
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('resourceErrorPincodeSetup'),
|
||||
title: t("resourceErrorPincodeSetup"),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
t('resourceErrorPincodeSetupDescription')
|
||||
t("resourceErrorPincodeSetupDescription")
|
||||
)
|
||||
});
|
||||
})
|
||||
|
||||
@@ -37,4 +37,3 @@ export const SitesBanner = () => {
|
||||
};
|
||||
|
||||
export default SitesBanner;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user