Compare commits

...

26 Commits

Author SHA1 Message Date
Owen
92a06e0ea3 Handle jit mode with syncs 2026-05-13 14:00:43 -07:00
Owen
c16d2ff2ed Fix log message 2026-05-13 13:52:35 -07:00
Owen
73a4d7d351 Quiet log message 2026-05-13 11:57:02 -07:00
Owen
efb1d69ac9 Add migration 2026-05-12 20:59:58 -07:00
Owen Schwartz
107986d848 Merge pull request #3059 from fosrl/crowdin_dev
New Crowdin updates
2026-05-12 20:45:53 -07:00
Owen Schwartz
b6c8fbe43b New translations en-us.json (Spanish)
[ci skip]
2026-05-12 20:41:30 -07:00
Owen Schwartz
4208a9f372 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-12 20:41:28 -07:00
Owen Schwartz
3c82a228fb New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-12 20:41:26 -07:00
Owen Schwartz
a4aa29e48a New translations en-us.json (Turkish)
[ci skip]
2026-05-12 20:41:25 -07:00
Owen Schwartz
0f82ba6627 New translations en-us.json (Russian)
[ci skip]
2026-05-12 20:41:23 -07:00
Owen Schwartz
1df5d9fac8 New translations en-us.json (Portuguese)
[ci skip]
2026-05-12 20:41:21 -07:00
Owen Schwartz
5189583d73 New translations en-us.json (Polish)
[ci skip]
2026-05-12 20:41:20 -07:00
Owen Schwartz
b794d2aa40 New translations en-us.json (Dutch)
[ci skip]
2026-05-12 20:41:18 -07:00
Owen Schwartz
c69059b227 New translations en-us.json (Korean)
[ci skip]
2026-05-12 20:41:17 -07:00
Owen Schwartz
b27b62d4c8 New translations en-us.json (Italian)
[ci skip]
2026-05-12 20:41:15 -07:00
Owen Schwartz
ee8290d68c New translations en-us.json (German)
[ci skip]
2026-05-12 20:41:13 -07:00
Owen Schwartz
82e8e79b16 New translations en-us.json (Czech)
[ci skip]
2026-05-12 20:41:12 -07:00
Owen Schwartz
2d428d2fa0 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-12 20:41:10 -07:00
Owen Schwartz
0005c11a0a New translations en-us.json (French)
[ci skip]
2026-05-12 20:41:08 -07:00
Owen
f91d914ec6 Show when a domain is config managed 2026-05-12 20:14:12 -07:00
miloschwartz
b6caeda0a5 improve targets round robin warning 2026-05-11 22:06:43 -07:00
Owen
77d17af15b Add global hide_powered_by and make it backward 2026-05-11 16:18:57 -07:00
Owen
264c6bf4e8 Use the right param for user 2026-05-11 12:06:36 -07:00
Owen
4aa72eb1a3 Confirm delete of share links 2026-05-11 11:49:51 -07:00
Owen
a066a68e1a Pick the most specific domain
Fixes #3047
2026-05-11 11:28:32 -07:00
miloschwartz
9fb677e952 allow editing self and owner user roles 2026-05-08 17:48:43 -07:00
48 changed files with 792 additions and 173 deletions

View File

@@ -0,0 +1,60 @@
import { CommandModule } from "yargs";
import { db, users } from "@server/db";
import { eq } from "drizzle-orm";
/**
* Disable 2FA for a user by email address.
*/
type DisableUser2faArgs = {
email: string;
};
export const disableUser2fa: CommandModule<{}, DisableUser2faArgs> = {
command: "disable-user-2fa",
describe: "Disable 2FA for a user (sets twoFactorEnabled=false, clears secret)",
builder: (yargs) => {
return yargs.option("email", {
type: "string",
demandOption: true,
describe: "User email address"
});
},
handler: async (argv: { email: string }) => {
try {
const { email } = argv;
console.log(`Looking for user with email: ${email}`);
// Find the user by email
const [user] = await db
.select()
.from(users)
.where(eq(users.email, email))
.limit(1);
if (!user) {
console.error(`User with email '${email}' not found`);
process.exit(1);
}
if (!user.twoFactorEnabled) {
console.log(`2FA is already disabled for user '${email}'.`);
process.exit(0);
}
// Update user: disable 2FA and clear secret
await db.update(users)
.set({
twoFactorEnabled: false,
twoFactorSecret: null,
twoFactorSetupRequested: false
})
.where(eq(users.userId, user.userId));
console.log(`2FA disabled for user '${email}'.`);
process.exit(0);
} catch (error) {
console.error("Error disabling 2FA:", error);
process.exit(1);
}
}
};

View File

@@ -10,6 +10,7 @@ import { clearLicenseKeys } from "./commands/clearLicenseKeys";
import { deleteClient } from "./commands/deleteClient"; import { deleteClient } from "./commands/deleteClient";
import { generateOrgCaKeys } from "./commands/generateOrgCaKeys"; import { generateOrgCaKeys } from "./commands/generateOrgCaKeys";
import { clearCertificates } from "./commands/clearCertificates"; import { clearCertificates } from "./commands/clearCertificates";
import { disableUser2fa } from "./commands/disableUser2fa";
yargs(hideBin(process.argv)) yargs(hideBin(process.argv))
.scriptName("pangctl") .scriptName("pangctl")
@@ -21,5 +22,6 @@ yargs(hideBin(process.argv))
.command(deleteClient) .command(deleteClient)
.command(generateOrgCaKeys) .command(generateOrgCaKeys)
.command(clearCertificates) .command(clearCertificates)
.command(disableUser2fa)
.demandCommand() .demandCommand()
.help().argv; .help().argv;

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Възникна грешка при изтриване на връзката", "shareErrorDeleteMessage": "Възникна грешка при изтриване на връзката",
"shareDeleted": "Връзката беше изтрита", "shareDeleted": "Връзката беше изтрита",
"shareDeletedDescription": "Връзката беше премахната", "shareDeletedDescription": "Връзката беше премахната",
"shareDelete": "Изтрийте споделената връзка",
"shareDeleteConfirm": "Потвърдете изтриването на споделената връзка",
"shareQuestionRemove": "Сигурни ли сте, че искате да изтриете тази споделена връзка?",
"shareMessageRemove": "След изтриване връзката вече няма да работи и всеки, който я използва, ще загуби достъп до ресурса.",
"shareTokenDescription": "Достъпният токен може да бъде предаван по два начина: като параметър или в хедърите на заявките. Те трябва да бъдат предавани от клиента при всяка заявка за удостоверен достъп.", "shareTokenDescription": "Достъпният токен може да бъде предаван по два начина: като параметър или в хедърите на заявките. Те трябва да бъдат предавани от клиента при всяка заявка за удостоверен достъп.",
"accessToken": "Достъп Токен", "accessToken": "Достъп Токен",
"usageExamples": "Примери за използване", "usageExamples": "Примери за използване",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "След като бъде премахнат, този потребител няма да има достъп до организацията. Винаги можете да го поканите отново по-късно, но той ще трябва да приеме отново поканата.", "userMessageOrgRemove": "След като бъде премахнат, този потребител няма да има достъп до организацията. Винаги можете да го поканите отново по-късно, но той ще трябва да приеме отново поканата.",
"userRemoveOrgConfirm": "Потвърдете премахването на потребителя", "userRemoveOrgConfirm": "Потвърдете премахването на потребителя",
"userRemoveOrg": "Премахване на потребителя от организацията", "userRemoveOrg": "Премахване на потребителя от организацията",
"userQuestionOrgRemoveSelf": "Сигурни ли сте, че искате да премахнете себе си от тази организация?",
"userMessageOrgRemoveSelf": "Ще загубите достъп незабавно. Администратор може да ви покани отново по-късно, но ще трябва да приемете нова покана.",
"userRemoveOrgConfirmSelf": "Потвърдете премахването на себе си",
"userRemoveOrgSelf": "Премахнете себе си от организацията",
"userRemoveOrgSelfWarning": "Ще загубите достъп до тази организация незабавно.",
"userRemoveOrgConfirmPhraseSelf": "ПРЕМАХНЕТЕ МЕ ОТ ОРГАНИЗАЦИЯТА",
"users": "Потребители", "users": "Потребители",
"accessRoleMember": "Член", "accessRoleMember": "Член",
"accessRoleOwner": "Собственик", "accessRoleOwner": "Собственик",
@@ -531,6 +541,11 @@
"emailInvalid": "Невалиден имейл адрес", "emailInvalid": "Невалиден имейл адрес",
"inviteValidityDuration": "Моля, изберете продължителност", "inviteValidityDuration": "Моля, изберете продължителност",
"accessRoleSelectPlease": "Моля, изберете роля", "accessRoleSelectPlease": "Моля, изберете роля",
"removeOwnAdminRoleConfirmTitle": "Премахване на административния ви достъп?",
"removeOwnAdminRoleConfirmDescription": "След записване няма да имате повече администраторски права в тази организация. Друг администратор може да възстанови достъпа, ако е необходимо.",
"removeOwnAdminRoleConfirmButton": "Премахнете административния ми достъп",
"removeOwnAdminRoleConfirmPhrase": "ПРЕМАХНЕТЕ АДМИНИСТРАТИВНИЯ МИ ДОСТЪП",
"ownerMustRetainAdminRole": "Собственикът на организацията трябва да запази поне една администраторска роля.",
"usernameRequired": "Необходимо е потребителско име", "usernameRequired": "Необходимо е потребителско име",
"idpSelectPlease": "Моля, изберете доставчик на идентичност", "idpSelectPlease": "Моля, изберете доставчик на идентичност",
"idpGenericOidc": "Основен OAuth2/OIDC доставчик.", "idpGenericOidc": "Основен OAuth2/OIDC доставчик.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Добавянето на повече от една цел ще активира натоварването на баланса.", "targetNoOneDescription": "Добавянето на повече от една цел ще активира натоварването на баланса.",
"targetsSubmit": "Запазване на целите", "targetsSubmit": "Запазване на целите",
"addTarget": "Добавете цел", "addTarget": "Добавете цел",
"proxyMultiSiteRoundRobinNodeHelp": "Роунд Робин маршрутизирането няма да работи между сайтове, които не са свързани към един и същ възел, но автоматичното превключване ще работи.",
"targetErrorInvalidIp": "Невалиден IP адрес", "targetErrorInvalidIp": "Невалиден IP адрес",
"targetErrorInvalidIpDescription": "Моля, въведете валиден IP адрес или име на хост", "targetErrorInvalidIpDescription": "Моля, въведете валиден IP адрес или име на хост",
"targetErrorInvalidPort": "Невалиден порт", "targetErrorInvalidPort": "Невалиден порт",
@@ -2652,6 +2668,8 @@
"validPassword": "Валидна парола", "validPassword": "Валидна парола",
"validEmail": "Валиден имейл", "validEmail": "Валиден имейл",
"validSSO": "Валидно SSO", "validSSO": "Валидно SSO",
"view": "Преглед",
"configManaged": "Управлявана конфигурация",
"connectedClient": "Свързан клиент", "connectedClient": "Свързан клиент",
"resourceBlocked": "Блокирани ресурси", "resourceBlocked": "Блокирани ресурси",
"droppedByRule": "Прекратено от правило", "droppedByRule": "Прекратено от правило",
@@ -3201,7 +3219,7 @@
"publicIpEndpoint": "Крайна точка", "publicIpEndpoint": "Крайна точка",
"lastTriggeredAt": "Последен тригер", "lastTriggeredAt": "Последен тригер",
"reject": "Отхвърляне", "reject": "Отхвърляне",
"uptimeDaysAgo": "{count} days ago", "uptimeDaysAgo": "преди {count} дни",
"uptimeToday": "Днес", "uptimeToday": "Днес",
"uptimeNoDataAvailable": "Няма налични данни", "uptimeNoDataAvailable": "Няма налични данни",
"uptimeSuffix": "време без прекъсване", "uptimeSuffix": "време без прекъсване",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Došlo k chybě při odstraňování odkazu", "shareErrorDeleteMessage": "Došlo k chybě při odstraňování odkazu",
"shareDeleted": "Odkaz odstraněn", "shareDeleted": "Odkaz odstraněn",
"shareDeletedDescription": "Odkaz byl odstraněn", "shareDeletedDescription": "Odkaz byl odstraněn",
"shareDelete": "Smazat odkaz ke sdílení",
"shareDeleteConfirm": "Potvrdit smazání odkazu ke sdílení",
"shareQuestionRemove": "Jste si jisti, že chcete smazat tento odkaz ke sdílení?",
"shareMessageRemove": "Jakmile bude smazán, odkaz přestane fungovat a všichni, kdo jej používají, ztratí přístup k prostředku.",
"shareTokenDescription": "Přístupový token může být předán dvěma způsoby: jako parametr dotazu nebo v záhlaví požadavku. Tyto údaje musí být předány klientovi na každé žádosti o ověřený přístup.", "shareTokenDescription": "Přístupový token může být předán dvěma způsoby: jako parametr dotazu nebo v záhlaví požadavku. Tyto údaje musí být předány klientovi na každé žádosti o ověřený přístup.",
"accessToken": "Přístupový token", "accessToken": "Přístupový token",
"usageExamples": "Příklady použití", "usageExamples": "Příklady použití",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Po odstranění tohoto uživatele již nebude mít přístup k organizaci. Vždy je můžete znovu pozvat později, ale budou muset pozvání znovu přijmout.", "userMessageOrgRemove": "Po odstranění tohoto uživatele již nebude mít přístup k organizaci. Vždy je můžete znovu pozvat později, ale budou muset pozvání znovu přijmout.",
"userRemoveOrgConfirm": "Potvrdit odebrání uživatele", "userRemoveOrgConfirm": "Potvrdit odebrání uživatele",
"userRemoveOrg": "Odebrat uživatele z organizace", "userRemoveOrg": "Odebrat uživatele z organizace",
"userQuestionOrgRemoveSelf": "Jste si jisti, že se chcete odstranit z této organizace?",
"userMessageOrgRemoveSelf": "Okamžitě ztratíte přístup. Administrátor vás může později znovu pozvat, ale budete muset přijmout nové pozvání.",
"userRemoveOrgConfirmSelf": "Potvrdit odstranění sebe",
"userRemoveOrgSelf": "Odstranit se z organizace",
"userRemoveOrgSelfWarning": "Okamžitě ztratíte přístup k této organizaci.",
"userRemoveOrgConfirmPhraseSelf": "ODSTRANIT SE Z ORGANIZACE",
"users": "Uživatelé", "users": "Uživatelé",
"accessRoleMember": "Člen", "accessRoleMember": "Člen",
"accessRoleOwner": "Vlastník", "accessRoleOwner": "Vlastník",
@@ -531,6 +541,11 @@
"emailInvalid": "Neplatná e-mailová adresa", "emailInvalid": "Neplatná e-mailová adresa",
"inviteValidityDuration": "Zvolte prosím dobu trvání", "inviteValidityDuration": "Zvolte prosím dobu trvání",
"accessRoleSelectPlease": "Vyberte prosím roli", "accessRoleSelectPlease": "Vyberte prosím roli",
"removeOwnAdminRoleConfirmTitle": "Odebrat přístup správce?",
"removeOwnAdminRoleConfirmDescription": "Po uložení již nebudete mít oprávnění správce v této organizaci. Další administrátor vám může přístup obnovit, pokud bude potřeba.",
"removeOwnAdminRoleConfirmButton": "Odebrat mé administrátorské oprávnění",
"removeOwnAdminRoleConfirmPhrase": "ODEBRAT MÉ ADMINISTRÁTORSKÉ OPRÁVNĚNÍ",
"ownerMustRetainAdminRole": "Vlastník organizace musí zachovat alespoň jednu roli správce.",
"usernameRequired": "Uživatelské jméno je povinné", "usernameRequired": "Uživatelské jméno je povinné",
"idpSelectPlease": "Vyberte poskytovatele identity", "idpSelectPlease": "Vyberte poskytovatele identity",
"idpGenericOidc": "Generic OAuth2/OIDC provider.", "idpGenericOidc": "Generic OAuth2/OIDC provider.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Přidáním více než jednoho cíle se umožní vyvážení zatížení.", "targetNoOneDescription": "Přidáním více než jednoho cíle se umožní vyvážení zatížení.",
"targetsSubmit": "Uložit cíle", "targetsSubmit": "Uložit cíle",
"addTarget": "Add Target", "addTarget": "Add Target",
"proxyMultiSiteRoundRobinNodeHelp": "Round robin routing nebude fungovat mezi lokalitami, které nejsou připojeny ke stejnému uzlu, ale failover bude fungovat.",
"targetErrorInvalidIp": "Neplatná IP adresa", "targetErrorInvalidIp": "Neplatná IP adresa",
"targetErrorInvalidIpDescription": "Zadejte prosím platnou IP adresu nebo název hostitele", "targetErrorInvalidIpDescription": "Zadejte prosím platnou IP adresu nebo název hostitele",
"targetErrorInvalidPort": "Neplatný port", "targetErrorInvalidPort": "Neplatný port",
@@ -2652,6 +2668,8 @@
"validPassword": "Platné heslo", "validPassword": "Platné heslo",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "Zobrazit",
"configManaged": "Správa konfigurace",
"connectedClient": "Připojený klient", "connectedClient": "Připojený klient",
"resourceBlocked": "Zablokované zdroje", "resourceBlocked": "Zablokované zdroje",
"droppedByRule": "Zrušeno pravidlem", "droppedByRule": "Zrušeno pravidlem",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Fehler beim Löschen des Links", "shareErrorDeleteMessage": "Fehler beim Löschen des Links",
"shareDeleted": "Link gelöscht", "shareDeleted": "Link gelöscht",
"shareDeletedDescription": "Der Link wurde gelöscht", "shareDeletedDescription": "Der Link wurde gelöscht",
"shareDelete": "Freigabelink löschen",
"shareDeleteConfirm": "Löschen des Freigabelinks bestätigen",
"shareQuestionRemove": "Sind Sie sicher, dass Sie diesen Freigabelink löschen möchten?",
"shareMessageRemove": "Nach dem Löschen funktioniert der Link nicht mehr, und jeder, der ihn nutzt, verliert den Zugriff auf die Ressource.",
"shareTokenDescription": "Das Zugriffstoken kann auf zwei Arten übergeben werden: als Abfrageparameter oder in den Request-Headern. Diese müssen vom Client auf jeder Anfrage für authentifizierten Zugriff weitergegeben werden.", "shareTokenDescription": "Das Zugriffstoken kann auf zwei Arten übergeben werden: als Abfrageparameter oder in den Request-Headern. Diese müssen vom Client auf jeder Anfrage für authentifizierten Zugriff weitergegeben werden.",
"accessToken": "Zugangs-Token", "accessToken": "Zugangs-Token",
"usageExamples": "Nutzungsbeispiele", "usageExamples": "Nutzungsbeispiele",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Nach dem Entfernen hat dieser Benutzer keinen Zugriff mehr auf die Organisation. Sie können ihn später jederzeit wieder einladen, aber er muss die Einladung erneut annehmen.", "userMessageOrgRemove": "Nach dem Entfernen hat dieser Benutzer keinen Zugriff mehr auf die Organisation. Sie können ihn später jederzeit wieder einladen, aber er muss die Einladung erneut annehmen.",
"userRemoveOrgConfirm": "Entfernen des Benutzers bestätigen", "userRemoveOrgConfirm": "Entfernen des Benutzers bestätigen",
"userRemoveOrg": "Benutzer aus der Organisation entfernen", "userRemoveOrg": "Benutzer aus der Organisation entfernen",
"userQuestionOrgRemoveSelf": "Sind Sie sicher, dass Sie sich aus dieser Organisation entfernen möchten?",
"userMessageOrgRemoveSelf": "Sie verlieren sofort den Zugriff. Ein Administrator kann Sie später erneut einladen, aber Sie müssen eine neue Einladung annehmen.",
"userRemoveOrgConfirmSelf": "Entfernung bestätigen",
"userRemoveOrgSelf": "Sich selbst aus der Organisation entfernen",
"userRemoveOrgSelfWarning": "Sie verlieren sofort den Zugriff auf diese Organisation.",
"userRemoveOrgConfirmPhraseSelf": "ENTFERNUNG MICH SELBST AUS DER ORGANISATION",
"users": "Benutzer", "users": "Benutzer",
"accessRoleMember": "Mitglied", "accessRoleMember": "Mitglied",
"accessRoleOwner": "Eigentümer", "accessRoleOwner": "Eigentümer",
@@ -531,6 +541,11 @@
"emailInvalid": "Ungültige E-Mail-Adresse", "emailInvalid": "Ungültige E-Mail-Adresse",
"inviteValidityDuration": "Bitte wählen Sie eine Dauer", "inviteValidityDuration": "Bitte wählen Sie eine Dauer",
"accessRoleSelectPlease": "Bitte wählen Sie eine Rolle", "accessRoleSelectPlease": "Bitte wählen Sie eine Rolle",
"removeOwnAdminRoleConfirmTitle": "Möchten Sie Ihren Administratorzugriff entfernen?",
"removeOwnAdminRoleConfirmDescription": "Nach dem Speichern haben Sie keine Administratorrechte mehr in dieser Organisation. Ein anderer Administrator kann den Zugriff bei Bedarf wiederherstellen.",
"removeOwnAdminRoleConfirmButton": "Meinen Administratorzugriff entfernen",
"removeOwnAdminRoleConfirmPhrase": "NIMM MEINEN ADMIN-ZUGRIFF WEG",
"ownerMustRetainAdminRole": "Der Organisationsinhaber muss mindestens eine Administratorrolle behalten.",
"usernameRequired": "Benutzername ist erforderlich", "usernameRequired": "Benutzername ist erforderlich",
"idpSelectPlease": "Bitte wählen Sie einen Identitätsanbieter", "idpSelectPlease": "Bitte wählen Sie einen Identitätsanbieter",
"idpGenericOidc": "Generischer OAuth2/OIDC-Anbieter.", "idpGenericOidc": "Generischer OAuth2/OIDC-Anbieter.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Das Hinzufügen von mehr als einem Ziel aktiviert den Lastausgleich.", "targetNoOneDescription": "Das Hinzufügen von mehr als einem Ziel aktiviert den Lastausgleich.",
"targetsSubmit": "Ziele speichern", "targetsSubmit": "Ziele speichern",
"addTarget": "Ziel hinzufügen", "addTarget": "Ziel hinzufügen",
"proxyMultiSiteRoundRobinNodeHelp": "Round-Robin-Routing funktioniert nicht zwischen Standorten, die nicht mit demselben Knoten verbunden sind, aber Failover funktioniert.",
"targetErrorInvalidIp": "Ungültige IP-Adresse", "targetErrorInvalidIp": "Ungültige IP-Adresse",
"targetErrorInvalidIpDescription": "Bitte geben Sie eine gültige IP-Adresse oder einen Hostnamen ein", "targetErrorInvalidIpDescription": "Bitte geben Sie eine gültige IP-Adresse oder einen Hostnamen ein",
"targetErrorInvalidPort": "Ungültiger Port", "targetErrorInvalidPort": "Ungültiger Port",
@@ -2652,6 +2668,8 @@
"validPassword": "Gültiges Passwort", "validPassword": "Gültiges Passwort",
"validEmail": "Gültige E-Mail-Adresse", "validEmail": "Gültige E-Mail-Adresse",
"validSSO": "Gültige SSO-Anmeldung", "validSSO": "Gültige SSO-Anmeldung",
"view": "Ansehen",
"configManaged": "Konfiguration verwaltet",
"connectedClient": "Verbundenes Gerät", "connectedClient": "Verbundenes Gerät",
"resourceBlocked": "Ressource blockiert", "resourceBlocked": "Ressource blockiert",
"droppedByRule": "Abgelegt durch Regel", "droppedByRule": "Abgelegt durch Regel",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "An error occurred deleting link", "shareErrorDeleteMessage": "An error occurred deleting link",
"shareDeleted": "Link deleted", "shareDeleted": "Link deleted",
"shareDeletedDescription": "The link has been deleted", "shareDeletedDescription": "The link has been deleted",
"shareDelete": "Delete Share Link",
"shareDeleteConfirm": "Confirm Delete Share Link",
"shareQuestionRemove": "Are you sure you want to delete this share link?",
"shareMessageRemove": "Once deleted, the link will no longer work and anyone using it will lose access to the resource.",
"shareTokenDescription": "The access token can be passed in two ways: as a query parameter or in the request headers. These must be passed from the client on every request for authenticated access.", "shareTokenDescription": "The access token can be passed in two ways: as a query parameter or in the request headers. These must be passed from the client on every request for authenticated access.",
"accessToken": "Access Token", "accessToken": "Access Token",
"usageExamples": "Usage Examples", "usageExamples": "Usage Examples",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Once removed, this user will no longer have access to the organization. You can always re-invite them later, but they will need to accept the invitation again.", "userMessageOrgRemove": "Once removed, this user will no longer have access to the organization. You can always re-invite them later, but they will need to accept the invitation again.",
"userRemoveOrgConfirm": "Confirm Remove User", "userRemoveOrgConfirm": "Confirm Remove User",
"userRemoveOrg": "Remove User from Organization", "userRemoveOrg": "Remove User from Organization",
"userQuestionOrgRemoveSelf": "Are you sure you want to remove yourself from this organization?",
"userMessageOrgRemoveSelf": "You will lose access immediately. An administrator can invite you again later, but you will need to accept a new invitation.",
"userRemoveOrgConfirmSelf": "Confirm Remove Myself",
"userRemoveOrgSelf": "Remove yourself from the organization",
"userRemoveOrgSelfWarning": "You will lose access to this organization immediately.",
"userRemoveOrgConfirmPhraseSelf": "REMOVE MYSELF FROM ORG",
"users": "Users", "users": "Users",
"accessRoleMember": "Member", "accessRoleMember": "Member",
"accessRoleOwner": "Owner", "accessRoleOwner": "Owner",
@@ -531,6 +541,11 @@
"emailInvalid": "Invalid email address", "emailInvalid": "Invalid email address",
"inviteValidityDuration": "Please select a duration", "inviteValidityDuration": "Please select a duration",
"accessRoleSelectPlease": "Please select a role", "accessRoleSelectPlease": "Please select a role",
"removeOwnAdminRoleConfirmTitle": "Remove your administrator access?",
"removeOwnAdminRoleConfirmDescription": "You will no longer have administrator permissions in this organization after saving. Another administrator can restore access if needed.",
"removeOwnAdminRoleConfirmButton": "Remove My Administrator Access",
"removeOwnAdminRoleConfirmPhrase": "REMOVE MY ADMIN ACCESS",
"ownerMustRetainAdminRole": "The organization owner must keep at least one administrator role.",
"usernameRequired": "Username is required", "usernameRequired": "Username is required",
"idpSelectPlease": "Please select an identity provider", "idpSelectPlease": "Please select an identity provider",
"idpGenericOidc": "Generic OAuth2/OIDC provider.", "idpGenericOidc": "Generic OAuth2/OIDC provider.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Adding more than one target above will enable load balancing.", "targetNoOneDescription": "Adding more than one target above will enable load balancing.",
"targetsSubmit": "Save Targets", "targetsSubmit": "Save Targets",
"addTarget": "Add Target", "addTarget": "Add Target",
"proxyMultiSiteRoundRobinNodeHelp": "Round robin routing will not work between sites that are not connected to the same node, but failover will work.",
"targetErrorInvalidIp": "Invalid IP address", "targetErrorInvalidIp": "Invalid IP address",
"targetErrorInvalidIpDescription": "Please enter a valid IP address or hostname", "targetErrorInvalidIpDescription": "Please enter a valid IP address or hostname",
"targetErrorInvalidPort": "Invalid port", "targetErrorInvalidPort": "Invalid port",
@@ -2652,6 +2668,8 @@
"validPassword": "Valid Password", "validPassword": "Valid Password",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "View",
"configManaged": "Config Managed",
"connectedClient": "Connected Client", "connectedClient": "Connected Client",
"resourceBlocked": "Resource Blocked", "resourceBlocked": "Resource Blocked",
"droppedByRule": "Dropped by Rule", "droppedByRule": "Dropped by Rule",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Se ha producido un error al eliminar el enlace", "shareErrorDeleteMessage": "Se ha producido un error al eliminar el enlace",
"shareDeleted": "Enlace eliminado", "shareDeleted": "Enlace eliminado",
"shareDeletedDescription": "El enlace ha sido eliminado", "shareDeletedDescription": "El enlace ha sido eliminado",
"shareDelete": "Borrar Enlace Compartido",
"shareDeleteConfirm": "Confirmar Borrado del Enlace Compartido",
"shareQuestionRemove": "¿Está seguro de que desea borrar este enlace compartido?",
"shareMessageRemove": "Una vez borrado, el enlace dejará de funcionar y cualquier persona que lo use perderá acceso al recurso.",
"shareTokenDescription": "El token de acceso puede ser pasado de dos maneras: como parámetro de consulta o en las cabeceras de solicitud. Estos deben ser pasados del cliente en cada solicitud de acceso autenticado.", "shareTokenDescription": "El token de acceso puede ser pasado de dos maneras: como parámetro de consulta o en las cabeceras de solicitud. Estos deben ser pasados del cliente en cada solicitud de acceso autenticado.",
"accessToken": "Token de acceso", "accessToken": "Token de acceso",
"usageExamples": "Ejemplos de uso", "usageExamples": "Ejemplos de uso",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Una vez eliminado, este usuario ya no tendrá acceso a la organización. Siempre puede volver a invitarlos más tarde, pero tendrán que aceptar la invitación de nuevo.", "userMessageOrgRemove": "Una vez eliminado, este usuario ya no tendrá acceso a la organización. Siempre puede volver a invitarlos más tarde, pero tendrán que aceptar la invitación de nuevo.",
"userRemoveOrgConfirm": "Confirmar eliminar usuario", "userRemoveOrgConfirm": "Confirmar eliminar usuario",
"userRemoveOrg": "Eliminar usuario de la organización", "userRemoveOrg": "Eliminar usuario de la organización",
"userQuestionOrgRemoveSelf": "¿Está seguro de que desea eliminarse de esta organización?",
"userMessageOrgRemoveSelf": "Perderá acceso inmediatamente. Un administrador puede invitarlo de nuevo más tarde, pero necesitará aceptar una nueva invitación.",
"userRemoveOrgConfirmSelf": "Confirmar Eliminarme",
"userRemoveOrgSelf": "Eliminarse de la organización",
"userRemoveOrgSelfWarning": "Perderá acceso a esta organización inmediatamente.",
"userRemoveOrgConfirmPhraseSelf": "ELIMINARME DE LA ORGANIZACIÓN",
"users": "Usuarios", "users": "Usuarios",
"accessRoleMember": "Miembro", "accessRoleMember": "Miembro",
"accessRoleOwner": "Propietario", "accessRoleOwner": "Propietario",
@@ -531,6 +541,11 @@
"emailInvalid": "Dirección de correo inválida", "emailInvalid": "Dirección de correo inválida",
"inviteValidityDuration": "Por favor, seleccione una duración", "inviteValidityDuration": "Por favor, seleccione una duración",
"accessRoleSelectPlease": "Por favor, seleccione un rol", "accessRoleSelectPlease": "Por favor, seleccione un rol",
"removeOwnAdminRoleConfirmTitle": "¿Eliminar su acceso de administrador?",
"removeOwnAdminRoleConfirmDescription": "Ya no tendrá permisos de administrador en esta organización después de guardar. Otro administrador puede restaurar el acceso si es necesario.",
"removeOwnAdminRoleConfirmButton": "Eliminar Mi Acceso de Administrador",
"removeOwnAdminRoleConfirmPhrase": "ELIMINAR MI ACCESO DE ADMINISTRADOR",
"ownerMustRetainAdminRole": "El propietario de la organización debe mantener al menos un rol de administrador.",
"usernameRequired": "Nombre de usuario requerido", "usernameRequired": "Nombre de usuario requerido",
"idpSelectPlease": "Por favor, seleccione un proveedor de identidad", "idpSelectPlease": "Por favor, seleccione un proveedor de identidad",
"idpGenericOidc": "Proveedor OAuth2/OIDC genérico.", "idpGenericOidc": "Proveedor OAuth2/OIDC genérico.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Si se añade más de un objetivo anterior se activará el balance de carga.", "targetNoOneDescription": "Si se añade más de un objetivo anterior se activará el balance de carga.",
"targetsSubmit": "Guardar objetivos", "targetsSubmit": "Guardar objetivos",
"addTarget": "Añadir destino", "addTarget": "Añadir destino",
"proxyMultiSiteRoundRobinNodeHelp": "El enrutamiento de turnos no funcionará entre sitios que no están conectados al mismo nodo, pero el failover funcionará.",
"targetErrorInvalidIp": "Dirección IP inválida", "targetErrorInvalidIp": "Dirección IP inválida",
"targetErrorInvalidIpDescription": "Por favor, introduzca una dirección IP válida o nombre de host", "targetErrorInvalidIpDescription": "Por favor, introduzca una dirección IP válida o nombre de host",
"targetErrorInvalidPort": "Puerto inválido", "targetErrorInvalidPort": "Puerto inválido",
@@ -2652,6 +2668,8 @@
"validPassword": "Contraseña válida", "validPassword": "Contraseña válida",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "Ver",
"configManaged": "Configuración Gestionada",
"connectedClient": "Cliente conectado", "connectedClient": "Cliente conectado",
"resourceBlocked": "Recurso bloqueado", "resourceBlocked": "Recurso bloqueado",
"droppedByRule": "Soltado por regla", "droppedByRule": "Soltado por regla",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Une erreur s'est produite lors de la suppression du lien", "shareErrorDeleteMessage": "Une erreur s'est produite lors de la suppression du lien",
"shareDeleted": "Lien supprimé", "shareDeleted": "Lien supprimé",
"shareDeletedDescription": "Le lien a été supprimé", "shareDeletedDescription": "Le lien a été supprimé",
"shareDelete": "Supprimer le lien de partage",
"shareDeleteConfirm": "Confirmer la suppression du lien de partage",
"shareQuestionRemove": "Êtes-vous sûr de vouloir supprimer ce lien de partage ?",
"shareMessageRemove": "Une fois supprimé, le lien ne fonctionnera plus et toute personne l'utilisant perdra l'accès à la ressource.",
"shareTokenDescription": "Le jeton d'accès peut être passé de deux façons : en tant que paramètre de requête ou dans les en-têtes de la requête. Elles doivent être transmises par le client à chaque demande d'accès authentifié.", "shareTokenDescription": "Le jeton d'accès peut être passé de deux façons : en tant que paramètre de requête ou dans les en-têtes de la requête. Elles doivent être transmises par le client à chaque demande d'accès authentifié.",
"accessToken": "Jeton d'accès", "accessToken": "Jeton d'accès",
"usageExamples": "Exemples d'utilisation", "usageExamples": "Exemples d'utilisation",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Une fois retiré, cet utilisateur n'aura plus accès à l'organisation. Vous pouvez toujours le réinviter plus tard, mais il devra accepter l'invitation à nouveau.", "userMessageOrgRemove": "Une fois retiré, cet utilisateur n'aura plus accès à l'organisation. Vous pouvez toujours le réinviter plus tard, mais il devra accepter l'invitation à nouveau.",
"userRemoveOrgConfirm": "Confirmer la suppression de l'utilisateur", "userRemoveOrgConfirm": "Confirmer la suppression de l'utilisateur",
"userRemoveOrg": "Retirer l'utilisateur de l'organisation", "userRemoveOrg": "Retirer l'utilisateur de l'organisation",
"userQuestionOrgRemoveSelf": "Êtes-vous sûr de vouloir vous retirer de cette organisation ?",
"userMessageOrgRemoveSelf": "Vous perdrez immédiatement l'accès. Un administrateur pourra vous inviter à nouveau plus tard, mais vous devrez accepter une nouvelle invitation.",
"userRemoveOrgConfirmSelf": "Confirmer la suppression de moi-même",
"userRemoveOrgSelf": "Se retirer de l'organisation",
"userRemoveOrgSelfWarning": "Vous perdrez immédiatement l'accès à cette organisation.",
"userRemoveOrgConfirmPhraseSelf": "SUPPRIMER MOI-MÊME DE L'ORG",
"users": "Utilisateurs", "users": "Utilisateurs",
"accessRoleMember": "Membre", "accessRoleMember": "Membre",
"accessRoleOwner": "Propriétaire", "accessRoleOwner": "Propriétaire",
@@ -531,6 +541,11 @@
"emailInvalid": "Adresse e-mail invalide", "emailInvalid": "Adresse e-mail invalide",
"inviteValidityDuration": "Veuillez sélectionner une durée", "inviteValidityDuration": "Veuillez sélectionner une durée",
"accessRoleSelectPlease": "Veuillez sélectionner un rôle", "accessRoleSelectPlease": "Veuillez sélectionner un rôle",
"removeOwnAdminRoleConfirmTitle": "Retirer votre accès administrateur ?",
"removeOwnAdminRoleConfirmDescription": "Vous n'aurez plus de droits d'administrateur dans cette organisation après avoir enregistré. Un autre administrateur pourra restaurer cet accès si nécessaire.",
"removeOwnAdminRoleConfirmButton": "Retirer mon accès administrateur",
"removeOwnAdminRoleConfirmPhrase": "RETIRER MON ACCÈS ADMIN",
"ownerMustRetainAdminRole": "Le propriétaire de l'organisation doit conserver au moins un rôle d'administrateur.",
"usernameRequired": "Le nom d'utilisateur est requis", "usernameRequired": "Le nom d'utilisateur est requis",
"idpSelectPlease": "Veuillez sélectionner un fournisseur d'identité", "idpSelectPlease": "Veuillez sélectionner un fournisseur d'identité",
"idpGenericOidc": "Fournisseur OAuth2/OIDC générique.", "idpGenericOidc": "Fournisseur OAuth2/OIDC générique.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "L'ajout de plus d'une cible ci-dessus activera l'équilibrage de charge.", "targetNoOneDescription": "L'ajout de plus d'une cible ci-dessus activera l'équilibrage de charge.",
"targetsSubmit": "Enregistrer les cibles", "targetsSubmit": "Enregistrer les cibles",
"addTarget": "Ajouter une cible", "addTarget": "Ajouter une cible",
"proxyMultiSiteRoundRobinNodeHelp": "Le routage en tourniquet n'opérera pas entre des sites qui ne sont pas connectés au même nœud, mais le basculement fonctionnera.",
"targetErrorInvalidIp": "Adresse IP invalide", "targetErrorInvalidIp": "Adresse IP invalide",
"targetErrorInvalidIpDescription": "Veuillez entrer une adresse IP ou un nom d'hôte valide", "targetErrorInvalidIpDescription": "Veuillez entrer une adresse IP ou un nom d'hôte valide",
"targetErrorInvalidPort": "Port invalide", "targetErrorInvalidPort": "Port invalide",
@@ -2652,6 +2668,8 @@
"validPassword": "Mot de passe valide", "validPassword": "Mot de passe valide",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "Afficher",
"configManaged": "Configuration gérée",
"connectedClient": "Client connecté", "connectedClient": "Client connecté",
"resourceBlocked": "Ressource bloquée", "resourceBlocked": "Ressource bloquée",
"droppedByRule": "Abandonné par la règle", "droppedByRule": "Abandonné par la règle",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Si è verificato un errore durante l'eliminazione del link", "shareErrorDeleteMessage": "Si è verificato un errore durante l'eliminazione del link",
"shareDeleted": "Link eliminato", "shareDeleted": "Link eliminato",
"shareDeletedDescription": "Il link è stato eliminato", "shareDeletedDescription": "Il link è stato eliminato",
"shareDelete": "Elimina Link di Condivisione",
"shareDeleteConfirm": "Conferma Eliminazione Link di Condivisione",
"shareQuestionRemove": "Sei sicuro di voler eliminare questo link di condivisione?",
"shareMessageRemove": "Una volta eliminato, il link non funzionerà più e chiunque lo utilizzi perderà l'accesso alla risorsa.",
"shareTokenDescription": "Il token di accesso può essere passato in due modi: come parametro di interrogazione o nelle intestazioni della richiesta. Questi devono essere passati dal client su ogni richiesta di accesso autenticato.", "shareTokenDescription": "Il token di accesso può essere passato in due modi: come parametro di interrogazione o nelle intestazioni della richiesta. Questi devono essere passati dal client su ogni richiesta di accesso autenticato.",
"accessToken": "Token Di Accesso", "accessToken": "Token Di Accesso",
"usageExamples": "Esempi Di Utilizzo", "usageExamples": "Esempi Di Utilizzo",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Una volta rimosso questo utente non avrà più accesso all'organizzazione. Puoi sempre reinvitarlo in seguito, ma dovrà accettare nuovamente l'invito.", "userMessageOrgRemove": "Una volta rimosso questo utente non avrà più accesso all'organizzazione. Puoi sempre reinvitarlo in seguito, ma dovrà accettare nuovamente l'invito.",
"userRemoveOrgConfirm": "Conferma Rimozione Utente", "userRemoveOrgConfirm": "Conferma Rimozione Utente",
"userRemoveOrg": "Rimuovi Utente dall'Organizzazione", "userRemoveOrg": "Rimuovi Utente dall'Organizzazione",
"userQuestionOrgRemoveSelf": "Sei sicuro di voler rimuovere te stesso da questa organizzazione?",
"userMessageOrgRemoveSelf": "Perderai immediatamente l'accesso. Un amministratore può invitarti nuovamente in seguito, ma dovrai accettare un nuovo invito.",
"userRemoveOrgConfirmSelf": "Conferma Rimozione Me Stesso",
"userRemoveOrgSelf": "Rimuoviti dall'organizzazione",
"userRemoveOrgSelfWarning": "Perderai immediatamente l'accesso a questa organizzazione.",
"userRemoveOrgConfirmPhraseSelf": "RIMUOVITI DALL'ORGANIZZAZIONE",
"users": "Utenti", "users": "Utenti",
"accessRoleMember": "Membro", "accessRoleMember": "Membro",
"accessRoleOwner": "Proprietario", "accessRoleOwner": "Proprietario",
@@ -531,6 +541,11 @@
"emailInvalid": "Indirizzo email non valido", "emailInvalid": "Indirizzo email non valido",
"inviteValidityDuration": "Seleziona una durata", "inviteValidityDuration": "Seleziona una durata",
"accessRoleSelectPlease": "Seleziona un ruolo", "accessRoleSelectPlease": "Seleziona un ruolo",
"removeOwnAdminRoleConfirmTitle": "Rimuovere il tuo accesso amministrativo?",
"removeOwnAdminRoleConfirmDescription": "Non avrai più i permessi di amministratore in questa organizzazione dopo il salvataggio. Un altro amministratore può ripristinare l'accesso se necessario.",
"removeOwnAdminRoleConfirmButton": "Rimuovere il Mio Accesso Amministrativo",
"removeOwnAdminRoleConfirmPhrase": "RIMUOVERE IL MIO ACCESSO AMMINISTRATIVO",
"ownerMustRetainAdminRole": "Il proprietario dell'organizzazione deve mantenere almeno un ruolo di amministratore.",
"usernameRequired": "Username richiesto", "usernameRequired": "Username richiesto",
"idpSelectPlease": "Seleziona un provider di identità", "idpSelectPlease": "Seleziona un provider di identità",
"idpGenericOidc": "Provider OAuth2/OIDC generico.", "idpGenericOidc": "Provider OAuth2/OIDC generico.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "L'aggiunta di più di un target abiliterà il bilanciamento del carico.", "targetNoOneDescription": "L'aggiunta di più di un target abiliterà il bilanciamento del carico.",
"targetsSubmit": "Salva Target", "targetsSubmit": "Salva Target",
"addTarget": "Aggiungi Target", "addTarget": "Aggiungi Target",
"proxyMultiSiteRoundRobinNodeHelp": "Il routing round robin non funzionerà tra siti che non sono connessi allo stesso nodo, ma il failover funzionerà.",
"targetErrorInvalidIp": "Indirizzo IP non valido", "targetErrorInvalidIp": "Indirizzo IP non valido",
"targetErrorInvalidIpDescription": "Inserisci un indirizzo IP o un hostname valido", "targetErrorInvalidIpDescription": "Inserisci un indirizzo IP o un hostname valido",
"targetErrorInvalidPort": "Porta non valida", "targetErrorInvalidPort": "Porta non valida",
@@ -2652,6 +2668,8 @@
"validPassword": "Password Valida", "validPassword": "Password Valida",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "Visualizza",
"configManaged": "Gestione Configurazione",
"connectedClient": "Cliente Connesso", "connectedClient": "Cliente Connesso",
"resourceBlocked": "Risorsa Bloccata", "resourceBlocked": "Risorsa Bloccata",
"droppedByRule": "Eliminato dalla regola", "droppedByRule": "Eliminato dalla regola",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "링크 삭제 중 오류가 발생했습니다.", "shareErrorDeleteMessage": "링크 삭제 중 오류가 발생했습니다.",
"shareDeleted": "링크가 삭제되었습니다.", "shareDeleted": "링크가 삭제되었습니다.",
"shareDeletedDescription": "링크가 삭제되었습니다.", "shareDeletedDescription": "링크가 삭제되었습니다.",
"shareDelete": "공유 링크 삭제",
"shareDeleteConfirm": "공유 링크 삭제 확인",
"shareQuestionRemove": "이 공유 링크를 삭제하시겠습니까?",
"shareMessageRemove": "삭제되면 링크가 더 이상 작동하지 않으며, 이를 사용하는 모든 사용자는 자원에 대한 접근을 잃게 됩니다.",
"shareTokenDescription": "액세스 토큰은 쿼리 매개변수 또는 요청 헤더의 두 가지 방법으로 전달될 수 있습니다. 이는 인증된 액세스를 위해 클라이언트에서 모든 요청마다 전달되어야 합니다.", "shareTokenDescription": "액세스 토큰은 쿼리 매개변수 또는 요청 헤더의 두 가지 방법으로 전달될 수 있습니다. 이는 인증된 액세스를 위해 클라이언트에서 모든 요청마다 전달되어야 합니다.",
"accessToken": "액세스 토큰", "accessToken": "액세스 토큰",
"usageExamples": "사용 예", "usageExamples": "사용 예",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "이 사용자가 제거되면 더 이상 조직에 접근할 수 없습니다. 나중에 다시 초대할 수 있지만, 초대를 다시 수락해야 합니다.", "userMessageOrgRemove": "이 사용자가 제거되면 더 이상 조직에 접근할 수 없습니다. 나중에 다시 초대할 수 있지만, 초대를 다시 수락해야 합니다.",
"userRemoveOrgConfirm": "사용자 제거 확인", "userRemoveOrgConfirm": "사용자 제거 확인",
"userRemoveOrg": "조직에서 사용자 제거", "userRemoveOrg": "조직에서 사용자 제거",
"userQuestionOrgRemoveSelf": "이 조직에서 자신을 제거하시겠습니까?",
"userMessageOrgRemoveSelf": "귀하는 즉시 접근 권한을 잃게 됩니다. 관리자가 나중에 다시 초대할 수 있지만, 새 초대를 수락해야 합니다.",
"userRemoveOrgConfirmSelf": "내 제거 확인",
"userRemoveOrgSelf": "조직에서 자신을 제거하십시오",
"userRemoveOrgSelfWarning": "귀하는 이 조직에 대한 접근 권한을 즉시 상실합니다.",
"userRemoveOrgConfirmPhraseSelf": "조직에서 나를 제거",
"users": "사용자", "users": "사용자",
"accessRoleMember": "회원", "accessRoleMember": "회원",
"accessRoleOwner": "소유자", "accessRoleOwner": "소유자",
@@ -531,6 +541,11 @@
"emailInvalid": "유효하지 않은 이메일 주소입니다.", "emailInvalid": "유효하지 않은 이메일 주소입니다.",
"inviteValidityDuration": "지속 시간을 선택하십시오.", "inviteValidityDuration": "지속 시간을 선택하십시오.",
"accessRoleSelectPlease": "역할을 선택하세요", "accessRoleSelectPlease": "역할을 선택하세요",
"removeOwnAdminRoleConfirmTitle": "관리자 권한을 제거하시겠습니까?",
"removeOwnAdminRoleConfirmDescription": "저장 후 이 조직에 대한 관리자 권한이 없어집니다. 필요한 경우 다른 관리자가 접근 권한을 복구할 수 있습니다.",
"removeOwnAdminRoleConfirmButton": "내 관리자 권한 제거",
"removeOwnAdminRoleConfirmPhrase": "내 관리자 권한 제거",
"ownerMustRetainAdminRole": "조직 소유자는 최소한 하나의 관리자 역할을 유지해야 합니다.",
"usernameRequired": "사용자 이름은 필수입니다.", "usernameRequired": "사용자 이름은 필수입니다.",
"idpSelectPlease": "신원 제공자를 선택하십시오", "idpSelectPlease": "신원 제공자를 선택하십시오",
"idpGenericOidc": "일반 OAuth2/OIDC 공급자.", "idpGenericOidc": "일반 OAuth2/OIDC 공급자.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "위에 하나 이상의 대상을 추가하면 로드 밸런싱이 활성화됩니다.", "targetNoOneDescription": "위에 하나 이상의 대상을 추가하면 로드 밸런싱이 활성화됩니다.",
"targetsSubmit": "대상 저장", "targetsSubmit": "대상 저장",
"addTarget": "대상 추가", "addTarget": "대상 추가",
"proxyMultiSiteRoundRobinNodeHelp": "라운드 로빈 라우팅은 동일한 노드에 연결되지 않은 사이트 간에는 작동하지 않으나, 대체 라우팅은 작동합니다.",
"targetErrorInvalidIp": "유효하지 않은 IP 주소", "targetErrorInvalidIp": "유효하지 않은 IP 주소",
"targetErrorInvalidIpDescription": "유효한 IP 주소 또는 호스트 이름을 입력하세요.", "targetErrorInvalidIpDescription": "유효한 IP 주소 또는 호스트 이름을 입력하세요.",
"targetErrorInvalidPort": "유효하지 않은 포트", "targetErrorInvalidPort": "유효하지 않은 포트",
@@ -2652,6 +2668,8 @@
"validPassword": "유효한 비밀번호", "validPassword": "유효한 비밀번호",
"validEmail": "유효한 이메일", "validEmail": "유효한 이메일",
"validSSO": "유효한 SSO", "validSSO": "유효한 SSO",
"view": "보기",
"configManaged": "구성 관리됨",
"connectedClient": "연결된 클라이언트", "connectedClient": "연결된 클라이언트",
"resourceBlocked": "리소스 차단됨", "resourceBlocked": "리소스 차단됨",
"droppedByRule": "룰에 의해 드롭됨", "droppedByRule": "룰에 의해 드롭됨",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "En feil oppstod ved sletting av lenke", "shareErrorDeleteMessage": "En feil oppstod ved sletting av lenke",
"shareDeleted": "Lenke slettet", "shareDeleted": "Lenke slettet",
"shareDeletedDescription": "Lenken har blitt slettet", "shareDeletedDescription": "Lenken har blitt slettet",
"shareDelete": "Slett delingslenke",
"shareDeleteConfirm": "Bekreft sletting av delingslenke",
"shareQuestionRemove": "Er du sikker på at du vil slette denne delingslenken?",
"shareMessageRemove": "Når slettet, vil lenken ikke lenger fungere, og alle som bruker den vil miste tilgang til ressursen.",
"shareTokenDescription": "Adgangstoken kan sendes på to måter: som en spørringsparameter eller i forespørselsoverskriftene. Disse må sendes fra klienten på hver forespørsel om autentisert tilgang.", "shareTokenDescription": "Adgangstoken kan sendes på to måter: som en spørringsparameter eller i forespørselsoverskriftene. Disse må sendes fra klienten på hver forespørsel om autentisert tilgang.",
"accessToken": "Tilgangsnøkkel", "accessToken": "Tilgangsnøkkel",
"usageExamples": "Brukseksempler", "usageExamples": "Brukseksempler",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Når denne brukeren er fjernet, vil de ikke lenger ha tilgang til organisasjonen. Du kan alltid invitere dem på nytt senere, men de vil måtte godta invitasjonen på nytt.", "userMessageOrgRemove": "Når denne brukeren er fjernet, vil de ikke lenger ha tilgang til organisasjonen. Du kan alltid invitere dem på nytt senere, men de vil måtte godta invitasjonen på nytt.",
"userRemoveOrgConfirm": "Bekreft fjerning av bruker", "userRemoveOrgConfirm": "Bekreft fjerning av bruker",
"userRemoveOrg": "Fjern bruker fra organisasjon", "userRemoveOrg": "Fjern bruker fra organisasjon",
"userQuestionOrgRemoveSelf": "Er du sikker på at du vil fjerne deg selv fra denne organisasjonen?",
"userMessageOrgRemoveSelf": "Du vil miste tilgang umiddelbart. En administrator kan invitere deg igjen senere, men du må godta en ny invitasjon.",
"userRemoveOrgConfirmSelf": "Bekreft fjerning av meg selv",
"userRemoveOrgSelf": "Fjern deg selv fra organisasjonen",
"userRemoveOrgSelfWarning": "Du vil miste tilgangen til denne organisasjonen umiddelbart.",
"userRemoveOrgConfirmPhraseSelf": "FJERN MEG SELV FRA ORG",
"users": "Brukere", "users": "Brukere",
"accessRoleMember": "Medlem", "accessRoleMember": "Medlem",
"accessRoleOwner": "Eier", "accessRoleOwner": "Eier",
@@ -531,6 +541,11 @@
"emailInvalid": "Ugyldig e-postadresse", "emailInvalid": "Ugyldig e-postadresse",
"inviteValidityDuration": "Vennligst velg en varighet", "inviteValidityDuration": "Vennligst velg en varighet",
"accessRoleSelectPlease": "Vennligst velg en rolle", "accessRoleSelectPlease": "Vennligst velg en rolle",
"removeOwnAdminRoleConfirmTitle": "Fjern din administratoradgang?",
"removeOwnAdminRoleConfirmDescription": "Du vil ikke lenger ha administratorrettigheter i denne organisasjonen etter lagring. En annen administrator kan gjenopprette tilgang hvis nødvendig.",
"removeOwnAdminRoleConfirmButton": "Fjern min administratoradgang",
"removeOwnAdminRoleConfirmPhrase": "FJERN MIN ADMINISTRATORADGANG",
"ownerMustRetainAdminRole": "Organisasjonseier må beholde minst én administratorrolle.",
"usernameRequired": "Brukernavn er påkrevd", "usernameRequired": "Brukernavn er påkrevd",
"idpSelectPlease": "Vennligst velg en identitetsleverandør", "idpSelectPlease": "Vennligst velg en identitetsleverandør",
"idpGenericOidc": "Generisk OAuth2/OIDC-leverandør.", "idpGenericOidc": "Generisk OAuth2/OIDC-leverandør.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Å legge til mer enn ett mål ovenfor vil aktivere lastbalansering.", "targetNoOneDescription": "Å legge til mer enn ett mål ovenfor vil aktivere lastbalansering.",
"targetsSubmit": "Lagre mål", "targetsSubmit": "Lagre mål",
"addTarget": "Legg til mål", "addTarget": "Legg til mål",
"proxyMultiSiteRoundRobinNodeHelp": "Rundkjøringrutefordeling vil ikke fungere mellom steder som ikke er koblet til samme node, men failover vil fungere.",
"targetErrorInvalidIp": "Ugyldig IP-adresse", "targetErrorInvalidIp": "Ugyldig IP-adresse",
"targetErrorInvalidIpDescription": "Skriv inn en gyldig IP-adresse eller vertsnavn", "targetErrorInvalidIpDescription": "Skriv inn en gyldig IP-adresse eller vertsnavn",
"targetErrorInvalidPort": "Ugyldig port", "targetErrorInvalidPort": "Ugyldig port",
@@ -2652,6 +2668,8 @@
"validPassword": "Gyldig passord", "validPassword": "Gyldig passord",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "Vis",
"configManaged": "Konfigurasjon administrert",
"connectedClient": "Tilkoblet klient", "connectedClient": "Tilkoblet klient",
"resourceBlocked": "Ressurs blokkert", "resourceBlocked": "Ressurs blokkert",
"droppedByRule": "Legg i regelen", "droppedByRule": "Legg i regelen",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Fout opgetreden tijdens het verwijderen link", "shareErrorDeleteMessage": "Fout opgetreden tijdens het verwijderen link",
"shareDeleted": "Link verwijderd", "shareDeleted": "Link verwijderd",
"shareDeletedDescription": "De link is verwijderd", "shareDeletedDescription": "De link is verwijderd",
"shareDelete": "Verwijder Deel Link",
"shareDeleteConfirm": "Bevestig verwijdering van Deel Link",
"shareQuestionRemove": "Weet u zeker dat u deze deel link wilt verwijderen?",
"shareMessageRemove": "Zodra verwijderd, zal de link niet meer werken en zal iedereen die het gebruikt de toegang tot de bron verliezen.",
"shareTokenDescription": "De toegangstoken kan op twee manieren worden doorgegeven: als queryparameter of in de aanvraagheaders. Deze moeten worden doorgegeven van de client op elk verzoek voor geverifieerde toegang.", "shareTokenDescription": "De toegangstoken kan op twee manieren worden doorgegeven: als queryparameter of in de aanvraagheaders. Deze moeten worden doorgegeven van de client op elk verzoek voor geverifieerde toegang.",
"accessToken": "Toegangs-token", "accessToken": "Toegangs-token",
"usageExamples": "Voorbeelden van gebruik", "usageExamples": "Voorbeelden van gebruik",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Eenmaal verwijderd, heeft deze gebruiker geen toegang meer tot de organisatie. Je kunt ze later altijd opnieuw uitnodigen, maar ze zullen de uitnodiging opnieuw moeten accepteren.", "userMessageOrgRemove": "Eenmaal verwijderd, heeft deze gebruiker geen toegang meer tot de organisatie. Je kunt ze later altijd opnieuw uitnodigen, maar ze zullen de uitnodiging opnieuw moeten accepteren.",
"userRemoveOrgConfirm": "Bevestig verwijderen gebruiker", "userRemoveOrgConfirm": "Bevestig verwijderen gebruiker",
"userRemoveOrg": "Gebruiker uit organisatie verwijderen", "userRemoveOrg": "Gebruiker uit organisatie verwijderen",
"userQuestionOrgRemoveSelf": "Weet u zeker dat u zichzelf uit deze organisatie wilt verwijderen?",
"userMessageOrgRemoveSelf": "U verliest onmiddellijk toegang. Een beheerder kan u later opnieuw uitnodigen, maar u moet een nieuwe uitnodiging accepteren.",
"userRemoveOrgConfirmSelf": "Bevestig Verwijder Mijn Persoon",
"userRemoveOrgSelf": "Verwijder uzelf uit de organisatie",
"userRemoveOrgSelfWarning": "U verliest onmiddellijk toegang tot deze organisatie.",
"userRemoveOrgConfirmPhraseSelf": "VERWIJDER MIJ UIT ORGANISATIE",
"users": "Gebruikers", "users": "Gebruikers",
"accessRoleMember": "Lid", "accessRoleMember": "Lid",
"accessRoleOwner": "Eigenaar", "accessRoleOwner": "Eigenaar",
@@ -531,6 +541,11 @@
"emailInvalid": "Ongeldig e-mailadres", "emailInvalid": "Ongeldig e-mailadres",
"inviteValidityDuration": "Selecteer een tijdsduur", "inviteValidityDuration": "Selecteer een tijdsduur",
"accessRoleSelectPlease": "Selecteer een rol", "accessRoleSelectPlease": "Selecteer een rol",
"removeOwnAdminRoleConfirmTitle": "Uw beheerderstoegang verwijderen?",
"removeOwnAdminRoleConfirmDescription": "U zult na het opslaan geen beheerdersrechten meer hebben in deze organisatie. Een andere beheerder kan de toegang indien nodig herstellen.",
"removeOwnAdminRoleConfirmButton": "Verwijder Mijn Beheerderstoegang",
"removeOwnAdminRoleConfirmPhrase": "VERWIJDER MIJN BEHEERDERSTOEGANG",
"ownerMustRetainAdminRole": "De organisatie-eigenaar moet minstens één beheerdersrol behouden.",
"usernameRequired": "Gebruikersnaam is verplicht", "usernameRequired": "Gebruikersnaam is verplicht",
"idpSelectPlease": "Selecteer een identiteitsprovider", "idpSelectPlease": "Selecteer een identiteitsprovider",
"idpGenericOidc": "Algemene OAuth2/OIDC provider.", "idpGenericOidc": "Algemene OAuth2/OIDC provider.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Het toevoegen van meer dan één doel hierboven zal de load balancering mogelijk maken.", "targetNoOneDescription": "Het toevoegen van meer dan één doel hierboven zal de load balancering mogelijk maken.",
"targetsSubmit": "Doelstellingen opslaan", "targetsSubmit": "Doelstellingen opslaan",
"addTarget": "Doelwit toevoegen", "addTarget": "Doelwit toevoegen",
"proxyMultiSiteRoundRobinNodeHelp": "Round-robin routering werkt niet tussen locaties die niet met hetzelfde knooppunt zijn verbonden, maar failover werkt wel.",
"targetErrorInvalidIp": "Ongeldig IP-adres", "targetErrorInvalidIp": "Ongeldig IP-adres",
"targetErrorInvalidIpDescription": "Voer een geldig IP-adres of hostnaam in", "targetErrorInvalidIpDescription": "Voer een geldig IP-adres of hostnaam in",
"targetErrorInvalidPort": "Ongeldige poort", "targetErrorInvalidPort": "Ongeldige poort",
@@ -2652,6 +2668,8 @@
"validPassword": "Geldig wachtwoord", "validPassword": "Geldig wachtwoord",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "Bekijk",
"configManaged": "Configuratie Beheerd",
"connectedClient": "Verbonden Client", "connectedClient": "Verbonden Client",
"resourceBlocked": "Bron geblokkeerd", "resourceBlocked": "Bron geblokkeerd",
"droppedByRule": "Achtergelaten door regel", "droppedByRule": "Achtergelaten door regel",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Wystąpił błąd podczas usuwania linku", "shareErrorDeleteMessage": "Wystąpił błąd podczas usuwania linku",
"shareDeleted": "Link usunięty", "shareDeleted": "Link usunięty",
"shareDeletedDescription": "Link został usunięty", "shareDeletedDescription": "Link został usunięty",
"shareDelete": "Usuń link udostępniania",
"shareDeleteConfirm": "Potwierdź usunięcie linku udostępniania",
"shareQuestionRemove": "Czy na pewno chcesz usunąć ten link udostępniania?",
"shareMessageRemove": "Po usunięciu, link przestanie działać i wszyscy korzystający z niego stracą dostęp do zasobu.",
"shareTokenDescription": "Token dostępu może być przekazywany na dwa sposoby: jako parametr zapytania lub w nagłówkach żądania. Muszą być przekazywane z klienta na każde żądanie uwierzytelnionego dostępu.", "shareTokenDescription": "Token dostępu może być przekazywany na dwa sposoby: jako parametr zapytania lub w nagłówkach żądania. Muszą być przekazywane z klienta na każde żądanie uwierzytelnionego dostępu.",
"accessToken": "Token dostępu", "accessToken": "Token dostępu",
"usageExamples": "Przykłady użycia", "usageExamples": "Przykłady użycia",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Po usunięciu ten użytkownik nie będzie miał już dostępu do organizacji. Zawsze możesz ponownie go zaprosić później, ale będzie musiał ponownie zaakceptować zaproszenie.", "userMessageOrgRemove": "Po usunięciu ten użytkownik nie będzie miał już dostępu do organizacji. Zawsze możesz ponownie go zaprosić później, ale będzie musiał ponownie zaakceptować zaproszenie.",
"userRemoveOrgConfirm": "Potwierdź usunięcie użytkownika", "userRemoveOrgConfirm": "Potwierdź usunięcie użytkownika",
"userRemoveOrg": "Usuń użytkownika z organizacji", "userRemoveOrg": "Usuń użytkownika z organizacji",
"userQuestionOrgRemoveSelf": "Czy na pewno chcesz usunąć się z tej organizacji?",
"userMessageOrgRemoveSelf": "Stracisz dostęp natychmiastowo. Administrator może cię ponownie zaprosić, ale będziesz musiał przyjąć nowe zaproszenie.",
"userRemoveOrgConfirmSelf": "Potwierdź usunięcie siebie",
"userRemoveOrgSelf": "Usuń siebie z organizacji",
"userRemoveOrgSelfWarning": "Natychmiast stracisz dostęp do tej organizacji.",
"userRemoveOrgConfirmPhraseSelf": "USUŃ SIEBIE Z ORGANIZACJI",
"users": "Użytkownicy", "users": "Użytkownicy",
"accessRoleMember": "Członek", "accessRoleMember": "Członek",
"accessRoleOwner": "Właściciel", "accessRoleOwner": "Właściciel",
@@ -531,6 +541,11 @@
"emailInvalid": "Nieprawidłowy adres e-mail", "emailInvalid": "Nieprawidłowy adres e-mail",
"inviteValidityDuration": "Proszę wybrać okres ważności", "inviteValidityDuration": "Proszę wybrać okres ważności",
"accessRoleSelectPlease": "Proszę wybrać rolę", "accessRoleSelectPlease": "Proszę wybrać rolę",
"removeOwnAdminRoleConfirmTitle": "Usunąć dostęp administratora?",
"removeOwnAdminRoleConfirmDescription": "Po zapisaniu nie będziesz już posiadał uprawnień administratora w tej organizacji. Inny administrator może przywrócić dostęp, jeśli to konieczne.",
"removeOwnAdminRoleConfirmButton": "Usuń mój dostęp administratora",
"removeOwnAdminRoleConfirmPhrase": "USUŃ MÓJ DOSTĘP ADMINISTRATORA",
"ownerMustRetainAdminRole": "Właściciel organizacji musi zachować co najmniej jedną rolę administratora.",
"usernameRequired": "Nazwa użytkownika jest wymagana", "usernameRequired": "Nazwa użytkownika jest wymagana",
"idpSelectPlease": "Proszę wybrać dostawcę tożsamości", "idpSelectPlease": "Proszę wybrać dostawcę tożsamości",
"idpGenericOidc": "Ogólny dostawca OAuth2/OIDC.", "idpGenericOidc": "Ogólny dostawca OAuth2/OIDC.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Dodanie więcej niż jednego celu powyżej włączy równoważenie obciążenia.", "targetNoOneDescription": "Dodanie więcej niż jednego celu powyżej włączy równoważenie obciążenia.",
"targetsSubmit": "Zapisz cele", "targetsSubmit": "Zapisz cele",
"addTarget": "Dodaj cel", "addTarget": "Dodaj cel",
"proxyMultiSiteRoundRobinNodeHelp": "Trasowanie round-robin nie będzie działać między witrynami, które nie są połączone z tym samym węzłem, ale przełączanie awaryjne będzie działać.",
"targetErrorInvalidIp": "Nieprawidłowy adres IP", "targetErrorInvalidIp": "Nieprawidłowy adres IP",
"targetErrorInvalidIpDescription": "Wprowadź prawidłowy adres IP lub nazwę hosta", "targetErrorInvalidIpDescription": "Wprowadź prawidłowy adres IP lub nazwę hosta",
"targetErrorInvalidPort": "Nieprawidłowy port", "targetErrorInvalidPort": "Nieprawidłowy port",
@@ -2652,6 +2668,8 @@
"validPassword": "Prawidłowe hasło", "validPassword": "Prawidłowe hasło",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "Zobacz",
"configManaged": "Konfiguracja zarządzana",
"connectedClient": "Połączony Klient", "connectedClient": "Połączony Klient",
"resourceBlocked": "Zasób zablokowany", "resourceBlocked": "Zasób zablokowany",
"droppedByRule": "Upuszczone przez regułę", "droppedByRule": "Upuszczone przez regułę",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Ocorreu um erro ao apagar o link", "shareErrorDeleteMessage": "Ocorreu um erro ao apagar o link",
"shareDeleted": "Link excluído", "shareDeleted": "Link excluído",
"shareDeletedDescription": "O link foi eliminado", "shareDeletedDescription": "O link foi eliminado",
"shareDelete": "Excluir Link de Compartilhamento",
"shareDeleteConfirm": "Confirmar Exclusão de Link de Compartilhamento",
"shareQuestionRemove": "Tem certeza de que deseja excluir este link de compartilhamento?",
"shareMessageRemove": "Uma vez excluído, o link não funcionará mais e qualquer pessoa que o utilizar perderá o acesso ao recurso.",
"shareTokenDescription": "O token de acesso pode ser passado de duas maneiras: como um parâmetro de consulta ou nos cabeçalhos da solicitação. Estes devem ser passados do cliente em todas as solicitações para acesso autenticado.", "shareTokenDescription": "O token de acesso pode ser passado de duas maneiras: como um parâmetro de consulta ou nos cabeçalhos da solicitação. Estes devem ser passados do cliente em todas as solicitações para acesso autenticado.",
"accessToken": "Token de acesso", "accessToken": "Token de acesso",
"usageExamples": "Exemplos de uso", "usageExamples": "Exemplos de uso",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Uma vez removido, este utilizador não terá mais acesso à organização. Você sempre pode reconvidá-lo depois, mas eles precisarão aceitar o convite novamente.", "userMessageOrgRemove": "Uma vez removido, este utilizador não terá mais acesso à organização. Você sempre pode reconvidá-lo depois, mas eles precisarão aceitar o convite novamente.",
"userRemoveOrgConfirm": "Confirmar Remoção do Usuário", "userRemoveOrgConfirm": "Confirmar Remoção do Usuário",
"userRemoveOrg": "Remover Usuário da Organização", "userRemoveOrg": "Remover Usuário da Organização",
"userQuestionOrgRemoveSelf": "Tem certeza de que deseja se remover desta organização?",
"userMessageOrgRemoveSelf": "Você perderá o acesso imediatamente. Um administrador poderá convidá-lo novamente mais tarde, mas você precisará aceitar um novo convite.",
"userRemoveOrgConfirmSelf": "Confirmar a Remoção de Mim Mesmo",
"userRemoveOrgSelf": "Remover-se da organização",
"userRemoveOrgSelfWarning": "Você perderá o acesso a esta organização imediatamente.",
"userRemoveOrgConfirmPhraseSelf": "REMOVER-ME DA ORG",
"users": "Utilizadores", "users": "Utilizadores",
"accessRoleMember": "Membro", "accessRoleMember": "Membro",
"accessRoleOwner": "Proprietário", "accessRoleOwner": "Proprietário",
@@ -531,6 +541,11 @@
"emailInvalid": "Endereço de email inválido", "emailInvalid": "Endereço de email inválido",
"inviteValidityDuration": "Por favor, selecione uma duração", "inviteValidityDuration": "Por favor, selecione uma duração",
"accessRoleSelectPlease": "Por favor, selecione uma função", "accessRoleSelectPlease": "Por favor, selecione uma função",
"removeOwnAdminRoleConfirmTitle": "Remover seu acesso de administrador?",
"removeOwnAdminRoleConfirmDescription": "Você não terá mais permissões de administrador nesta organização após salvar. Outro administrador pode restaurar seu acesso, se necessário.",
"removeOwnAdminRoleConfirmButton": "Remover Meu Acesso de Administrador",
"removeOwnAdminRoleConfirmPhrase": "REMOVER MEU ACESSO DE ADMIN",
"ownerMustRetainAdminRole": "O proprietário da organização deve manter pelo menos um papel de administrador.",
"usernameRequired": "Nome de utilizador é obrigatório", "usernameRequired": "Nome de utilizador é obrigatório",
"idpSelectPlease": "Por favor, selecione um provedor de identidade", "idpSelectPlease": "Por favor, selecione um provedor de identidade",
"idpGenericOidc": "Provedor genérico OAuth2/OIDC.", "idpGenericOidc": "Provedor genérico OAuth2/OIDC.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Adicionar mais de um alvo acima habilitará o balanceamento de carga.", "targetNoOneDescription": "Adicionar mais de um alvo acima habilitará o balanceamento de carga.",
"targetsSubmit": "Guardar Alvos", "targetsSubmit": "Guardar Alvos",
"addTarget": "Adicionar Alvo", "addTarget": "Adicionar Alvo",
"proxyMultiSiteRoundRobinNodeHelp": "O roteamento round robin não funcionará entre sites que não estão conectados ao mesmo nó, mas o failover funcionará.",
"targetErrorInvalidIp": "Endereço IP inválido", "targetErrorInvalidIp": "Endereço IP inválido",
"targetErrorInvalidIpDescription": "Por favor, insira um endereço IP ou nome de host válido", "targetErrorInvalidIpDescription": "Por favor, insira um endereço IP ou nome de host válido",
"targetErrorInvalidPort": "Porta inválida", "targetErrorInvalidPort": "Porta inválida",
@@ -2652,6 +2668,8 @@
"validPassword": "Senha válida", "validPassword": "Senha válida",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "Visualizar",
"configManaged": "Configuração Gerenciada",
"connectedClient": "Cliente Conectado", "connectedClient": "Cliente Conectado",
"resourceBlocked": "Recurso bloqueado", "resourceBlocked": "Recurso bloqueado",
"droppedByRule": "Derrubado pela regra", "droppedByRule": "Derrubado pela regra",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Произошла ошибка при удалении ссылки", "shareErrorDeleteMessage": "Произошла ошибка при удалении ссылки",
"shareDeleted": "Ссылка удалена", "shareDeleted": "Ссылка удалена",
"shareDeletedDescription": "Ссылка была успешно удалена", "shareDeletedDescription": "Ссылка была успешно удалена",
"shareDelete": "Удалить общую ссылку",
"shareDeleteConfirm": "Подтвердите удаление общей ссылки",
"shareQuestionRemove": "Вы уверены, что хотите удалить эту общую ссылку?",
"shareMessageRemove": "После удаления ссылка перестанет работать, и все, кто ее использует, потеряют доступ к ресурсу.",
"shareTokenDescription": "Токен доступа может быть передан двумя способами: как параметр запроса или в заголовках запроса. Они должны быть переданы от клиента по каждому запросу для аутентифицированного доступа.", "shareTokenDescription": "Токен доступа может быть передан двумя способами: как параметр запроса или в заголовках запроса. Они должны быть переданы от клиента по каждому запросу для аутентифицированного доступа.",
"accessToken": "Токен доступа", "accessToken": "Токен доступа",
"usageExamples": "Примеры использования", "usageExamples": "Примеры использования",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "После удаления этот пользователь больше не будет иметь доступ к организации. Вы всегда можете пригласить его заново, но ему нужно будет снова принять приглашение.", "userMessageOrgRemove": "После удаления этот пользователь больше не будет иметь доступ к организации. Вы всегда можете пригласить его заново, но ему нужно будет снова принять приглашение.",
"userRemoveOrgConfirm": "Подтвердить удаление пользователя", "userRemoveOrgConfirm": "Подтвердить удаление пользователя",
"userRemoveOrg": "Удалить пользователя из организации", "userRemoveOrg": "Удалить пользователя из организации",
"userQuestionOrgRemoveSelf": "Вы уверены, что хотите удалить себя из этой организации?",
"userMessageOrgRemoveSelf": "Вы немедленно потеряете доступ. Администратор сможет снова пригласить вас позже, но вам нужно будет принять новое приглашение.",
"userRemoveOrgConfirmSelf": "Подтвердите удаление себя",
"userRemoveOrgSelf": "Удалите себя из организации",
"userRemoveOrgSelfWarning": "Вы немедленно потеряете доступ к этой организации.",
"userRemoveOrgConfirmPhraseSelf": "Удалить себя из организации",
"users": "Пользователи", "users": "Пользователи",
"accessRoleMember": "Участник", "accessRoleMember": "Участник",
"accessRoleOwner": "Владелец", "accessRoleOwner": "Владелец",
@@ -531,6 +541,11 @@
"emailInvalid": "Неверный адрес Email", "emailInvalid": "Неверный адрес Email",
"inviteValidityDuration": "Пожалуйста, выберите продолжительность", "inviteValidityDuration": "Пожалуйста, выберите продолжительность",
"accessRoleSelectPlease": "Пожалуйста, выберите роль", "accessRoleSelectPlease": "Пожалуйста, выберите роль",
"removeOwnAdminRoleConfirmTitle": "Удалить доступ администратора?",
"removeOwnAdminRoleConfirmDescription": "После сохранения у вас больше не будет прав администратора в этой организации. Другой администратор может восстановить доступ, если это необходимо.",
"removeOwnAdminRoleConfirmButton": "Удалить мой доступ администратора",
"removeOwnAdminRoleConfirmPhrase": "УДАЛИТЬ МОЙ ДОСТУП АДМИНИСТРАТОРА",
"ownerMustRetainAdminRole": "Владелец организации должен сохранить по крайней мере одну роль администратора.",
"usernameRequired": "Имя пользователя обязательно", "usernameRequired": "Имя пользователя обязательно",
"idpSelectPlease": "Пожалуйста, выберите Identity Provider", "idpSelectPlease": "Пожалуйста, выберите Identity Provider",
"idpGenericOidc": "Обычный OAuth2/OIDC provider.", "idpGenericOidc": "Обычный OAuth2/OIDC provider.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Добавление более одной цели выше включит балансировку нагрузки.", "targetNoOneDescription": "Добавление более одной цели выше включит балансировку нагрузки.",
"targetsSubmit": "Сохранить цели", "targetsSubmit": "Сохранить цели",
"addTarget": "Добавить цель", "addTarget": "Добавить цель",
"proxyMultiSiteRoundRobinNodeHelp": "Роутинг с балансировкой нагрузки не будет работать между сайтами, не подключенными к одному и тому же узлу, но подмена будет работать.",
"targetErrorInvalidIp": "Неверный IP-адрес", "targetErrorInvalidIp": "Неверный IP-адрес",
"targetErrorInvalidIpDescription": "Пожалуйста, введите действительный IP адрес или имя хоста", "targetErrorInvalidIpDescription": "Пожалуйста, введите действительный IP адрес или имя хоста",
"targetErrorInvalidPort": "Неверный порт", "targetErrorInvalidPort": "Неверный порт",
@@ -2652,6 +2668,8 @@
"validPassword": "Допустимый пароль", "validPassword": "Допустимый пароль",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "Просмотр",
"configManaged": "Конфигурация управляется",
"connectedClient": "Подключенный клиент", "connectedClient": "Подключенный клиент",
"resourceBlocked": "Ресурс заблокирован", "resourceBlocked": "Ресурс заблокирован",
"droppedByRule": "Отброшено по правилам", "droppedByRule": "Отброшено по правилам",

View File

@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "Bağlantı silinirken bir hata oluştu", "shareErrorDeleteMessage": "Bağlantı silinirken bir hata oluştu",
"shareDeleted": "Bağlantı silindi", "shareDeleted": "Bağlantı silindi",
"shareDeletedDescription": "Bağlantı silindi", "shareDeletedDescription": "Bağlantı silindi",
"shareDelete": "Paylaşım Bağlantısını Sil",
"shareDeleteConfirm": "Paylaşım Bağlantısının Silinmesini Onayla",
"shareQuestionRemove": "Bu paylaşım bağlantısını silmek istediğinizden emin misiniz?",
"shareMessageRemove": "Silindikten sonra, bağlantı artık çalışmayacak ve kullanan herkes kaynağa erişimini kaybedecek.",
"shareTokenDescription": "Erişim jetonunuz iki şekilde iletilebilir: sorgu parametresi olarak veya istek başlıklarında. Kimlik doğrulanmış erişim için her istekten müşteri tarafından iletilmelidir.", "shareTokenDescription": "Erişim jetonunuz iki şekilde iletilebilir: sorgu parametresi olarak veya istek başlıklarında. Kimlik doğrulanmış erişim için her istekten müşteri tarafından iletilmelidir.",
"accessToken": "Erişim Jetonu", "accessToken": "Erişim Jetonu",
"usageExamples": "Kullanım Örnekleri", "usageExamples": "Kullanım Örnekleri",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "Kaldırıldığında, bu kullanıcı organizasyona artık erişim sağlayamayacak. Kullanıcı tekrar davet edilebilir, ancak daveti kabul etmesi gerekecek.", "userMessageOrgRemove": "Kaldırıldığında, bu kullanıcı organizasyona artık erişim sağlayamayacak. Kullanıcı tekrar davet edilebilir, ancak daveti kabul etmesi gerekecek.",
"userRemoveOrgConfirm": "Kullanıcıyı Kaldırmayı Onayla", "userRemoveOrgConfirm": "Kullanıcıyı Kaldırmayı Onayla",
"userRemoveOrg": "Kullanıcıyı Organizasyondan Kaldır", "userRemoveOrg": "Kullanıcıyı Organizasyondan Kaldır",
"userQuestionOrgRemoveSelf": "Bu organizasyondan kendinizi kaldırmak istediğinizden emin misiniz?",
"userMessageOrgRemoveSelf": "Erişiminizi hemen kaybedeceksiniz. Bir yönetici daha sonra sizi tekrar davet edebilir, ancak yeni bir daveti kabul etmeniz gerekecek.",
"userRemoveOrgConfirmSelf": "Kendimi Kaldırmayı Onayla",
"userRemoveOrgSelf": "Kendinizi organizasyondan kaldırın",
"userRemoveOrgSelfWarning": "Bu organizasyona erişiminizi anında kaybedeceksiniz.",
"userRemoveOrgConfirmPhraseSelf": "KENDİMİ ORGANİZASYONDAN KALDIR",
"users": "Kullanıcılar", "users": "Kullanıcılar",
"accessRoleMember": "Üye", "accessRoleMember": "Üye",
"accessRoleOwner": "Sahip", "accessRoleOwner": "Sahip",
@@ -531,6 +541,11 @@
"emailInvalid": "Geçersiz e-posta adresi", "emailInvalid": "Geçersiz e-posta adresi",
"inviteValidityDuration": "Lütfen bir süre seçin", "inviteValidityDuration": "Lütfen bir süre seçin",
"accessRoleSelectPlease": "Lütfen bir rol seçin", "accessRoleSelectPlease": "Lütfen bir rol seçin",
"removeOwnAdminRoleConfirmTitle": "Yönetici erişiminizi kaldırmak istiyor musunuz?",
"removeOwnAdminRoleConfirmDescription": "Kaydettikten sonra, bu organizasyonda artık yönetici izinleriniz olmayacak. Gerekirse başka bir yönetici erişimi geri yükleyebilir.",
"removeOwnAdminRoleConfirmButton": "Yönetici Erişimi Kaldır",
"removeOwnAdminRoleConfirmPhrase": "YÖNETİCİ ERİŞİMİMİ KALDIR",
"ownerMustRetainAdminRole": "Organizasyon sahibi en az bir yönetici rolü bulundurmalıdır.",
"usernameRequired": "Kullanıcı adı gereklidir", "usernameRequired": "Kullanıcı adı gereklidir",
"idpSelectPlease": "Lütfen bir kimlik sağlayıcı seçin", "idpSelectPlease": "Lütfen bir kimlik sağlayıcı seçin",
"idpGenericOidc": "Genel OAuth2/OIDC sağlayıcısı.", "idpGenericOidc": "Genel OAuth2/OIDC sağlayıcısı.",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "Yukarıdaki birden fazla hedef ekleyerek yük dengeleme etkinleştirilecektir.", "targetNoOneDescription": "Yukarıdaki birden fazla hedef ekleyerek yük dengeleme etkinleştirilecektir.",
"targetsSubmit": "Hedefleri Kaydet", "targetsSubmit": "Hedefleri Kaydet",
"addTarget": "Hedef Ekle", "addTarget": "Hedef Ekle",
"proxyMultiSiteRoundRobinNodeHelp": "Round robin yönlendirme, aynı düğüme bağlı olmayan siteler arasında çalışmayacaktır, ancak failover çalışacaktır.",
"targetErrorInvalidIp": "Geçersiz IP adresi", "targetErrorInvalidIp": "Geçersiz IP adresi",
"targetErrorInvalidIpDescription": "Lütfen geçerli bir IP adresi veya host adı girin", "targetErrorInvalidIpDescription": "Lütfen geçerli bir IP adresi veya host adı girin",
"targetErrorInvalidPort": "Geçersiz port", "targetErrorInvalidPort": "Geçersiz port",
@@ -2652,6 +2668,8 @@
"validPassword": "Geçerli Şifre", "validPassword": "Geçerli Şifre",
"validEmail": "Geçerli E-posta", "validEmail": "Geçerli E-posta",
"validSSO": "Geçerli SSO", "validSSO": "Geçerli SSO",
"view": "Görüntüle",
"configManaged": "Yapılandırma Yönetildi",
"connectedClient": "Bağlı İstemci", "connectedClient": "Bağlı İstemci",
"resourceBlocked": "Kaynak Engellendi", "resourceBlocked": "Kaynak Engellendi",
"droppedByRule": "Kurallara Göre Çıkartıldı", "droppedByRule": "Kurallara Göre Çıkartıldı",

View File

@@ -32,7 +32,7 @@
"trialActive": "免费试用中", "trialActive": "免费试用中",
"trialExpired": "试用到期", "trialExpired": "试用到期",
"trialHasEnded": "您的试用已结束。", "trialHasEnded": "您的试用已结束。",
"trialDaysRemaining": "{count, plural, one {# day remaining} other {# days remaining}}", "trialDaysRemaining": "{count, plural, other {# 天剩余}}",
"trialDaysLeftShort": "试用期剩余 {days} 天", "trialDaysLeftShort": "试用期剩余 {days} 天",
"trialGoToBilling": "转到账单页面", "trialGoToBilling": "转到账单页面",
"subscriptionViolationViewBilling": "查看计费", "subscriptionViolationViewBilling": "查看计费",
@@ -156,6 +156,10 @@
"shareErrorDeleteMessage": "删除链接时出错", "shareErrorDeleteMessage": "删除链接时出错",
"shareDeleted": "链接已删除", "shareDeleted": "链接已删除",
"shareDeletedDescription": "链接已删除", "shareDeletedDescription": "链接已删除",
"shareDelete": "删除共享链接",
"shareDeleteConfirm": "确认删除共享链接",
"shareQuestionRemove": "您确定要删除这个共享链接吗?",
"shareMessageRemove": "删除后,该链接将不再可用,使用它的任何人将失去对资源的访问权限。",
"shareTokenDescription": "访问令牌可以通过两种方式传递:作为查询参数或请求标题。 每次验证访问请求都必须从客户端传递。", "shareTokenDescription": "访问令牌可以通过两种方式传递:作为查询参数或请求标题。 每次验证访问请求都必须从客户端传递。",
"accessToken": "访问令牌", "accessToken": "访问令牌",
"usageExamples": "用法示例", "usageExamples": "用法示例",
@@ -303,7 +307,7 @@
"accessUserManage": "管理用户", "accessUserManage": "管理用户",
"accessUsersDescription": "邀请和管理访问此组织的用户", "accessUsersDescription": "邀请和管理访问此组织的用户",
"accessUsersSearch": "搜索用户...", "accessUsersSearch": "搜索用户...",
"accessUsersRoleFilterCount": "{count, plural, one {# role} other {# roles}}", "accessUsersRoleFilterCount": "{count, plural, other {# 角色}}",
"accessUsersRoleFilterClear": "清除角色过滤器", "accessUsersRoleFilterClear": "清除角色过滤器",
"accessUserCreate": "创建用户", "accessUserCreate": "创建用户",
"accessUserRemove": "删除用户", "accessUserRemove": "删除用户",
@@ -523,6 +527,12 @@
"userMessageOrgRemove": "一旦删除,这个用户将不再能够访问组织。 你总是可以稍后重新邀请他们,但他们需要再次接受邀请。", "userMessageOrgRemove": "一旦删除,这个用户将不再能够访问组织。 你总是可以稍后重新邀请他们,但他们需要再次接受邀请。",
"userRemoveOrgConfirm": "确认删除用户", "userRemoveOrgConfirm": "确认删除用户",
"userRemoveOrg": "从组织中删除用户", "userRemoveOrg": "从组织中删除用户",
"userQuestionOrgRemoveSelf": "你确定要将自己从这个组织中移除吗?",
"userMessageOrgRemoveSelf": "你将立即失去访问权限。管理员稍后可以再次邀请你,但你需要接受新的邀请。",
"userRemoveOrgConfirmSelf": "确认删除我自己",
"userRemoveOrgSelf": "将自己从组织中移除",
"userRemoveOrgSelfWarning": "你将立即失去对此组织的访问权限。",
"userRemoveOrgConfirmPhraseSelf": "从组织中移除我自己",
"users": "用户", "users": "用户",
"accessRoleMember": "成员", "accessRoleMember": "成员",
"accessRoleOwner": "所有者", "accessRoleOwner": "所有者",
@@ -531,6 +541,11 @@
"emailInvalid": "无效的电子邮件地址", "emailInvalid": "无效的电子邮件地址",
"inviteValidityDuration": "请选择持续时间", "inviteValidityDuration": "请选择持续时间",
"accessRoleSelectPlease": "请选择一个角色", "accessRoleSelectPlease": "请选择一个角色",
"removeOwnAdminRoleConfirmTitle": "移除你的管理员权限?",
"removeOwnAdminRoleConfirmDescription": "保存后,你将不再拥有该组织的管理员权限。如果需要,其他管理员可以恢复访问。",
"removeOwnAdminRoleConfirmButton": "移除我的管理员访问权限",
"removeOwnAdminRoleConfirmPhrase": "移除我的管理员访问",
"ownerMustRetainAdminRole": "组织所有者必须保留至少一个管理员角色。",
"usernameRequired": "必须输入用户名", "usernameRequired": "必须输入用户名",
"idpSelectPlease": "请选择身份提供商", "idpSelectPlease": "请选择身份提供商",
"idpGenericOidc": "通用的 OAuth2/OIDC 提供商。", "idpGenericOidc": "通用的 OAuth2/OIDC 提供商。",
@@ -658,6 +673,7 @@
"targetNoOneDescription": "在上面添加多个目标将启用负载平衡。", "targetNoOneDescription": "在上面添加多个目标将启用负载平衡。",
"targetsSubmit": "保存目标", "targetsSubmit": "保存目标",
"addTarget": "添加目标", "addTarget": "添加目标",
"proxyMultiSiteRoundRobinNodeHelp": "轮询路由在未连接到相同节点的站点之间将不起作用,但故障转移会生效。",
"targetErrorInvalidIp": "无效的 IP 地址", "targetErrorInvalidIp": "无效的 IP 地址",
"targetErrorInvalidIpDescription": "请输入有效的IP地址或主机名", "targetErrorInvalidIpDescription": "请输入有效的IP地址或主机名",
"targetErrorInvalidPort": "无效的端口", "targetErrorInvalidPort": "无效的端口",
@@ -1499,7 +1515,7 @@
"alertingGraphCanvasTitle": "规则流程", "alertingGraphCanvasTitle": "规则流程",
"alertingGraphCanvasDescription": "源、触发器和操作的视觉概况。选择一个节点,在面板上进行编辑。", "alertingGraphCanvasDescription": "源、触发器和操作的视觉概况。选择一个节点,在面板上进行编辑。",
"alertingNodeNotConfigured": "尚未配置", "alertingNodeNotConfigured": "尚未配置",
"alertingNodeActionsCount": "{count, plural, one {# action} other {# actions}}", "alertingNodeActionsCount": "{count, plural, other {# 操作}}",
"alertingNodeRoleSource": "来源", "alertingNodeRoleSource": "来源",
"alertingNodeRoleTrigger": "触发", "alertingNodeRoleTrigger": "触发",
"alertingNodeRoleAction": "行为", "alertingNodeRoleAction": "行为",
@@ -2051,7 +2067,7 @@
"createInternalResourceDialogName": "名称", "createInternalResourceDialogName": "名称",
"createInternalResourceDialogSite": "站点", "createInternalResourceDialogSite": "站点",
"selectSite": "选择站点...", "selectSite": "选择站点...",
"multiSitesSelectorSitesCount": "{count, plural, one {# site} other {# sites}}", "multiSitesSelectorSitesCount": "{count, plural, other {# 个网站}}",
"noSitesFound": "未找到站点。", "noSitesFound": "未找到站点。",
"createInternalResourceDialogProtocol": "协议", "createInternalResourceDialogProtocol": "协议",
"createInternalResourceDialogTcp": "TCP", "createInternalResourceDialogTcp": "TCP",
@@ -2652,6 +2668,8 @@
"validPassword": "有效密码", "validPassword": "有效密码",
"validEmail": "Valid email", "validEmail": "Valid email",
"validSSO": "Valid SSO", "validSSO": "Valid SSO",
"view": "查看",
"configManaged": "配置已管理",
"connectedClient": "已连接客户端", "connectedClient": "已连接客户端",
"resourceBlocked": "资源被阻止", "resourceBlocked": "资源被阻止",
"droppedByRule": "被规则删除", "droppedByRule": "被规则删除",

View File

@@ -1227,7 +1227,11 @@ async function getDomainId(
return null; return null;
} }
const domainSelection = validDomains[0].domains; // Pick the most specific (longest baseDomain) valid domain so that, e.g.,
// *.test.dev.example.com is assigned to *.dev.example.com rather than *.example.com.
const domainSelection = validDomains.sort(
(a, b) => b.domains.baseDomain.length - a.domains.baseDomain.length
)[0].domains;
const baseDomain = domainSelection.baseDomain; const baseDomain = domainSelection.baseDomain;
// Wildcard full-domains are not allowed on namespace (provided/free) domains // Wildcard full-domains are not allowed on namespace (provided/free) domains

View File

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

View File

@@ -20,9 +20,7 @@ import {
} from "@server/db"; } from "@server/db";
import { and, eq, inArray, ne } from "drizzle-orm"; import { and, eq, inArray, ne } from "drizzle-orm";
import { import { deletePeer as newtDeletePeer } from "@server/routers/newt/peers";
deletePeer as newtDeletePeer
} from "@server/routers/newt/peers";
import { import {
initPeerAddHandshake, initPeerAddHandshake,
deletePeer as olmDeletePeer deletePeer as olmDeletePeer
@@ -33,7 +31,7 @@ import {
generateAliasConfig, generateAliasConfig,
generateRemoteSubnets, generateRemoteSubnets,
generateSubnetProxyTargetV2, generateSubnetProxyTargetV2,
parseEndpoint, parseEndpoint
} from "@server/lib/ip"; } from "@server/lib/ip";
import { import {
addPeerData, addPeerData,
@@ -51,10 +49,7 @@ export async function getClientSiteResourceAccess(
? await trx ? await trx
.select() .select()
.from(sites) .from(sites)
.innerJoin( .innerJoin(siteNetworks, eq(siteNetworks.siteId, sites.siteId))
siteNetworks,
eq(siteNetworks.siteId, sites.siteId)
)
.where(eq(siteNetworks.networkId, siteResource.networkId)) .where(eq(siteNetworks.networkId, siteResource.networkId))
.then((rows) => rows.map((row) => row.sites)) .then((rows) => rows.map((row) => row.sites))
: []; : [];
@@ -362,7 +357,8 @@ export async function rebuildClientAssociationsFromSiteResource(
.where(inArray(clients.clientId, existingClientSiteIds)) .where(inArray(clients.clientId, existingClientSiteIds))
: []; : [];
const otherResourceClientIds = clientsFromOtherResourcesBySite.get(siteId) ?? new Set<number>(); const otherResourceClientIds =
clientsFromOtherResourcesBySite.get(siteId) ?? new Set<number>();
logger.debug( logger.debug(
`rebuildClientAssociations: [rebuildClientAssociationsFromSiteResource] siteId=${siteId} otherResourceClientIds=[${[...otherResourceClientIds].join(", ")}] mergedAllClientIds=[${mergedAllClientIds.join(", ")}]` `rebuildClientAssociations: [rebuildClientAssociationsFromSiteResource] siteId=${siteId} otherResourceClientIds=[${[...otherResourceClientIds].join(", ")}] mergedAllClientIds=[${mergedAllClientIds.join(", ")}]`
@@ -709,7 +705,7 @@ export async function updateClientSiteDestinations(
sourcePort: destination.sourcePort, sourcePort: destination.sourcePort,
destinations: destination.destinations destinations: destination.destinations
}; };
logger.info( logger.debug(
`Payload for update-destinations: ${JSON.stringify(payload, null, 2)}` `Payload for update-destinations: ${JSON.stringify(payload, null, 2)}`
); );

View File

@@ -97,6 +97,13 @@ export class PrivateConfig {
); );
} }
process.env.BRANDING_HIDE_POWERED_BY =
this.rawPrivateConfig.branding?.hide_powered_by === true ||
this.rawPrivateConfig.branding?.resource_auth_page
?.hide_powered_by === true
? "true"
: "false";
process.env.LOGIN_PAGE_SUBTITLE_TEXT = process.env.LOGIN_PAGE_SUBTITLE_TEXT =
this.rawPrivateConfig.branding?.login_page?.subtitle_text || ""; this.rawPrivateConfig.branding?.login_page?.subtitle_text || "";

View File

@@ -141,6 +141,7 @@ export const privateConfigSchema = z
) )
.optional(), .optional(),
hide_auth_layout_footer: z.boolean().optional().default(false), hide_auth_layout_footer: z.boolean().optional().default(false),
hide_powered_by: z.boolean().optional(),
login_page: z login_page: z
.object({ .object({
subtitle_text: z.string().optional() subtitle_text: z.string().optional()

View File

@@ -98,15 +98,6 @@ export async function addUserRole(
); );
} }
if (existingUser[0].isOwner) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Cannot change the role of the owner of the organization"
)
);
}
const roleExists = await db const roleExists = await db
.select() .select()
.from(roles) .from(roles)

View File

@@ -98,11 +98,11 @@ export async function removeUserRole(
); );
} }
if (existingUser.isOwner) { if (existingUser.isOwner && role.isAdmin === true) {
return next( return next(
createHttpError( createHttpError(
HttpCode.FORBIDDEN, HttpCode.FORBIDDEN,
"Cannot change the roles of the owner of the organization" "Cannot remove the administrator role from the organization owner"
) )
); );
} }

View File

@@ -87,17 +87,8 @@ export async function setUserOrgRoles(
); );
} }
if (existingUser.isOwner) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Cannot change the roles of the owner of the organization"
)
);
}
const orgRoles = await db const orgRoles = await db
.select({ roleId: roles.roleId }) .select({ roleId: roles.roleId, isAdmin: roles.isAdmin })
.from(roles) .from(roles)
.where( .where(
and( and(
@@ -115,6 +106,18 @@ export async function setUserOrgRoles(
); );
} }
if (existingUser.isOwner) {
const hasAdminRole = orgRoles.some((r) => r.isAdmin === true);
if (!hasAdminRole) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"The organization owner must retain an administrator role"
)
);
}
}
let orgClientsToRebuild: Client[] = []; let orgClientsToRebuild: Client[] = [];
await db.transaction(async (trx) => { await db.transaction(async (trx) => {
await trx await trx

View File

@@ -27,11 +27,11 @@ export async function buildSiteConfigurationForOlmClient(
) { ) {
const siteConfigurations: { const siteConfigurations: {
siteId: number; siteId: number;
name?: string name?: string;
endpoint?: string endpoint?: string;
publicKey?: string publicKey?: string;
serverIP?: string | null serverIP?: string | null;
serverPort?: number | null serverPort?: number | null;
remoteSubnets?: string[]; remoteSubnets?: string[];
aliases: Alias[]; aliases: Alias[];
}[] = []; }[] = [];
@@ -79,7 +79,6 @@ export async function buildSiteConfigurationForOlmClient(
) )
); );
if (jitMode) { if (jitMode) {
// Add site configuration to the array // Add site configuration to the array
siteConfigurations.push({ siteConfigurations.push({
@@ -109,10 +108,9 @@ export async function buildSiteConfigurationForOlmClient(
continue; continue;
} }
if (!site.publicKey || site.publicKey == "") { // the site is not ready to accept new peers if (!site.publicKey || site.publicKey == "") {
logger.warn( // the site is not ready to accept new peers
`Site ${site.siteId} has no public key, skipping` logger.warn(`Site ${site.siteId} has no public key, skipping`);
);
continue; continue;
} }

View File

@@ -17,7 +17,7 @@ import { initPeerAddHandshake } from "./peers";
export const handleOlmServerInitAddPeerHandshake: MessageHandler = async ( export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
context context
) => { ) => {
logger.info("Handling register olm message!"); logger.info("Handle Olm Server Init Add Peer Handshake Message");
const { message, client: c, sendToClient } = context; const { message, client: c, sendToClient } = context;
const olm = c as Olm; const olm = c as Olm;

View File

@@ -9,16 +9,50 @@ import {
import { buildSiteConfigurationForOlmClient } from "./buildConfiguration"; import { buildSiteConfigurationForOlmClient } from "./buildConfiguration";
import { sendToClient } from "#dynamic/routers/ws"; import { sendToClient } from "#dynamic/routers/ws";
import logger from "@server/logger"; import logger from "@server/logger";
import { eq, inArray } from "drizzle-orm"; import { count, eq, inArray } from "drizzle-orm";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { canCompress } from "@server/lib/clientVersionChecks"; import { canCompress } from "@server/lib/clientVersionChecks";
import { build } from "@server/build";
export async function sendOlmSyncMessage(olm: Olm, client: Client) { export async function sendOlmSyncMessage(olm: Olm, client: Client) {
// Get all sites data
const sitesCountResult = await db
.select({ count: count() })
.from(sites)
.innerJoin(
clientSitesAssociationsCache,
eq(sites.siteId, clientSitesAssociationsCache.siteId)
)
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
// Extract the count value from the result array
const sitesCount =
sitesCountResult.length > 0 ? sitesCountResult[0].count : 0;
// Prepare an array to store site configurations
logger.debug(
`[handleOlmRegisterMessage] Found ${sitesCount} sites for client ${client.clientId}`,
{ orgId: client.orgId }
);
let jitMode = false;
if (sitesCount > 250 && build == "saas") {
// THIS IS THE MAX ON THE BUSINESS TIER
// we have too many sites
// If we have too many sites we need to drop into fully JIT mode by not sending any of the sites
logger.info(
`[handleOlmRegisterMessage] Too many sites (${sitesCount}), dropping into JIT mode`,
{ orgId: client.orgId }
);
jitMode = true;
}
// NOTE: WE ARE HARDCODING THE RELAY PARAMETER TO FALSE HERE BUT IN THE REGISTER MESSAGE ITS DEFINED BY THE CLIENT // NOTE: WE ARE HARDCODING THE RELAY PARAMETER TO FALSE HERE BUT IN THE REGISTER MESSAGE ITS DEFINED BY THE CLIENT
const siteConfigurations = await buildSiteConfigurationForOlmClient( const siteConfigurations = await buildSiteConfigurationForOlmClient(
client, client,
client.pubKey, client.pubKey,
false false,
jitMode
); );
// Get all exit nodes from sites where the client has peers // Get all exit nodes from sites where the client has peers
@@ -82,7 +116,6 @@ export async function sendOlmSyncMessage(olm: Olm, client: Client) {
exitNodes: exitNodesData exitNodes: exitNodesData
} }
}, },
{ {
compress: canCompress(olm.version, "olm") compress: canCompress(olm.version, "olm")
} }

View File

@@ -88,11 +88,11 @@ export async function addUserRoleLegacy(
); );
} }
if (existingUser.isOwner) { if (existingUser.isOwner && role.isAdmin !== true) {
return next( return next(
createHttpError( createHttpError(
HttpCode.FORBIDDEN, HttpCode.FORBIDDEN,
"Cannot change the role of the owner of the organization" "The organization owner must retain an administrator role"
) )
); );
} }

View File

@@ -47,10 +47,7 @@ export async function queryUser(orgId: string, userId: string) {
.from(userOrgRoles) .from(userOrgRoles)
.leftJoin(roles, eq(userOrgRoles.roleId, roles.roleId)) .leftJoin(roles, eq(userOrgRoles.roleId, roles.roleId))
.where( .where(
and( and(eq(userOrgRoles.userId, userId), eq(userOrgRoles.orgId, orgId))
eq(userOrgRoles.userId, userId),
eq(userOrgRoles.orgId, orgId)
)
); );
const isAdmin = roleRows.some((r) => r.isAdmin); const isAdmin = roleRows.some((r) => r.isAdmin);
@@ -61,7 +58,8 @@ export async function queryUser(orgId: string, userId: string) {
roleIds: roleRows.map((r) => r.roleId), roleIds: roleRows.map((r) => r.roleId),
roles: roleRows.map((r) => ({ roles: roleRows.map((r) => ({
roleId: r.roleId, roleId: r.roleId,
name: r.roleName ?? "" name: r.roleName ?? "",
isAdmin: r.isAdmin === true
})) }))
}; };
} }

View File

@@ -24,6 +24,7 @@ import m15 from "./scriptsPg/1.16.0";
import m16 from "./scriptsPg/1.17.0"; import m16 from "./scriptsPg/1.17.0";
import m17 from "./scriptsPg/1.18.0"; import m17 from "./scriptsPg/1.18.0";
import m18 from "./scriptsPg/1.18.3"; import m18 from "./scriptsPg/1.18.3";
import m19 from "./scriptsPg/1.18.4";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER // THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA // EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -47,7 +48,8 @@ const migrations = [
{ version: "1.16.0", run: m15 }, { version: "1.16.0", run: m15 },
{ version: "1.17.0", run: m16 }, { version: "1.17.0", run: m16 },
{ version: "1.18.0", run: m17 }, { version: "1.18.0", run: m17 },
{ version: "1.18.3", run: m18 } { version: "1.18.3", run: m18 },
{ version: "1.18.4", run: m19 }
// Add new migrations here as they are created // Add new migrations here as they are created
] as { ] as {
version: string; version: string;

View File

@@ -42,6 +42,7 @@ import m36 from "./scriptsSqlite/1.16.0";
import m37 from "./scriptsSqlite/1.17.0"; import m37 from "./scriptsSqlite/1.17.0";
import m38 from "./scriptsSqlite/1.18.0"; import m38 from "./scriptsSqlite/1.18.0";
import m39 from "./scriptsSqlite/1.18.3"; import m39 from "./scriptsSqlite/1.18.3";
import m40 from "./scriptsSqlite/1.18.4";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER // THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA // EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -81,7 +82,8 @@ const migrations = [
{ version: "1.16.0", run: m36 }, { version: "1.16.0", run: m36 },
{ version: "1.17.0", run: m37 }, { version: "1.17.0", run: m37 },
{ version: "1.18.0", run: m38 }, { version: "1.18.0", run: m38 },
{ version: "1.18.3", run: m39 } { version: "1.18.3", run: m39 },
{ version: "1.18.4", run: m40 }
// Add new migrations here as they are created // Add new migrations here as they are created
] as const; ] as const;

View File

@@ -0,0 +1,34 @@
import { db } from "@server/db/pg/driver";
import { sql } from "drizzle-orm";
const version = "1.18.4";
export default async function migration() {
console.log(`Running setup script ${version}...`);
try {
await db.execute(sql`BEGIN`);
await db.execute(sql`
ALTER TABLE "connectionAuditLog" ADD COLUMN "clientEndpoint" text;
`);
await db.execute(sql`
ALTER TABLE "eventStreamingDestinations" ADD COLUMN "lastError" text;
`);
await db.execute(sql`
ALTER TABLE "eventStreamingDestinations" ADD COLUMN "lastErrorAt" bigint;
`);
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`);
}

View File

@@ -0,0 +1,43 @@
import { APP_PATH } from "@server/lib/consts";
import Database from "better-sqlite3";
import path from "path";
const version = "1.18.4";
export default async function migration() {
console.log(`Running setup script ${version}...`);
const location = path.join(APP_PATH, "db", "db.sqlite");
const db = new Database(location);
try {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.prepare(
`
ALTER TABLE 'connectionAuditLog' ADD 'clientEndpoint' text;
`
).run();
db.prepare(
`
ALTER TABLE 'eventStreamingDestinations' ADD 'lastError' text;
`
).run();
db.prepare(
`
ALTER TABLE 'eventStreamingDestinations' ADD 'lastErrorAt' integer;
`
).run();
})();
db.pragma("foreign_keys = ON");
console.log("Migrated database");
} catch (e) {
console.log("Failed to migrate db:", e);
throw e;
}
console.log(`${version} migration complete`);
}

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import IdpTypeBadge from "@app/components/IdpTypeBadge"; import IdpTypeBadge from "@app/components/IdpTypeBadge";
import OrgRolesTagField from "@app/components/OrgRolesTagField"; import OrgRolesTagField from "@app/components/OrgRolesTagField";
import { import {
@@ -25,6 +26,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
import { userOrgUserContext } from "@app/hooks/useOrgUserContext"; import { userOrgUserContext } from "@app/hooks/useOrgUserContext";
import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { useUserContext } from "@app/hooks/useUserContext";
import { createApiClient, formatAxiosError } from "@app/lib/api"; import { createApiClient, formatAxiosError } from "@app/lib/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { build } from "@server/build"; import { build } from "@server/build";
@@ -32,7 +34,7 @@ import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { UserType } from "@server/types/UserTypes"; import { UserType } from "@server/types/UserTypes";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useActionState, useEffect } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
@@ -42,13 +44,15 @@ const accessControlsFormSchema = z.object({
roles: z.array( roles: z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
text: z.string() text: z.string(),
isAdmin: z.boolean().optional()
}) })
) )
}); });
export default function AccessControlsPage() { export default function AccessControlsPage() {
const { orgUser: user, updateOrgUser } = userOrgUserContext(); const { orgUser: user, updateOrgUser } = userOrgUserContext();
const { user: sessionUser } = useUserContext();
const { env } = useEnvContext(); const { env } = useEnvContext();
const api = createApiClient({ env }); const api = createApiClient({ env });
@@ -72,7 +76,8 @@ export default function AccessControlsPage() {
autoProvisioned: user.autoProvisioned || false, autoProvisioned: user.autoProvisioned || false,
roles: (user.roles ?? []).map((r) => ({ roles: (user.roles ?? []).map((r) => ({
id: r.roleId.toString(), id: r.roleId.toString(),
text: r.name text: r.name,
isAdmin: r.isAdmin === true
})) }))
} }
}); });
@@ -84,7 +89,8 @@ export default function AccessControlsPage() {
"roles", "roles",
(user.roles ?? []).map((r) => ({ (user.roles ?? []).map((r) => ({
id: r.roleId.toString(), id: r.roleId.toString(),
text: r.name text: r.name,
isAdmin: r.isAdmin === true
})) }))
); );
form.setValue("autoProvisioned", user.autoProvisioned || false); form.setValue("autoProvisioned", user.autoProvisioned || false);
@@ -95,11 +101,11 @@ export default function AccessControlsPage() {
? t("singleRolePerUserPlanNotice") ? t("singleRolePerUserPlanNotice")
: t("singleRolePerUserEditionNotice"); : t("singleRolePerUserEditionNotice");
const [, action, isSubmitting] = useActionState(onSubmit, null); const [isSaving, setIsSaving] = useState(false);
async function onSubmit() { const [confirmRemoveOwnAdminOpen, setConfirmRemoveOwnAdminOpen] =
const isValid = await form.trigger(); useState(false);
if (!isValid) return;
async function executeSave() {
const values = form.getValues(); const values = form.getValues();
if (values.roles.length === 0) { if (values.roles.length === 0) {
@@ -111,6 +117,7 @@ export default function AccessControlsPage() {
return; return;
} }
setIsSaving(true);
try { try {
const roleIds = values.roles.map((r) => parseInt(r.id, 10)); const roleIds = values.roles.map((r) => parseInt(r.id, 10));
const updateRoleRequest = supportsMultipleRolesPerUser const updateRoleRequest = supportsMultipleRolesPerUser
@@ -130,7 +137,8 @@ export default function AccessControlsPage() {
roleIds, roleIds,
roles: values.roles.map((r) => ({ roles: values.roles.map((r) => ({
roleId: parseInt(r.id, 10), roleId: parseInt(r.id, 10),
name: r.text name: r.text,
isAdmin: r.isAdmin === true
})), })),
autoProvisioned: values.autoProvisioned autoProvisioned: values.autoProvisioned
}); });
@@ -149,11 +157,61 @@ export default function AccessControlsPage() {
t("accessRoleErrorAddDescription") t("accessRoleErrorAddDescription")
) )
}); });
} finally {
setIsSaving(false);
} }
} }
async function handleAccessControlsSubmit(e: React.FormEvent) {
e.preventDefault();
const isValid = await form.trigger();
if (!isValid) return;
const values = form.getValues();
if (values.roles.length === 0) {
toast({
variant: "destructive",
title: t("accessRoleErrorAdd"),
description: t("accessRoleSelectPlease")
});
return;
}
const willHaveAdminRole = values.roles.some(
(r) => r.isAdmin === true
);
const isRemovingOwnAdmin =
sessionUser.userId === user.userId &&
user.isAdmin &&
!willHaveAdminRole;
if (isRemovingOwnAdmin) {
setConfirmRemoveOwnAdminOpen(true);
return;
}
await executeSave();
}
return ( return (
<SettingsContainer> <SettingsContainer>
<ConfirmDeleteDialog
open={confirmRemoveOwnAdminOpen}
setOpen={setConfirmRemoveOwnAdminOpen}
title={t("removeOwnAdminRoleConfirmTitle")}
dialog={
<div className="space-y-2">
<p>{t("removeOwnAdminRoleConfirmDescription")}</p>
</div>
}
buttonText={t("removeOwnAdminRoleConfirmButton")}
string={t("removeOwnAdminRoleConfirmPhrase")}
onConfirm={executeSave}
/>
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle> <SettingsSectionTitle>
@@ -168,7 +226,7 @@ export default function AccessControlsPage() {
<SettingsSectionForm> <SettingsSectionForm>
<Form {...form}> <Form {...form}>
<form <form
action={action} onSubmit={(e) => void handleAccessControlsSubmit(e)}
className="space-y-4" className="space-y-4"
id="access-controls-form" id="access-controls-form"
> >
@@ -237,8 +295,8 @@ export default function AccessControlsPage() {
<SettingsSectionFooter> <SettingsSectionFooter>
<Button <Button
type="submit" type="submit"
loading={isSubmitting} loading={isSaving}
disabled={isSubmitting} disabled={isSaving}
form="access-controls-form" form="access-controls-form"
> >
{t("accessControlsSubmit")} {t("accessControlsSubmit")}

View File

@@ -84,6 +84,7 @@ import {
AlertTriangle, AlertTriangle,
CircleCheck, CircleCheck,
CircleX, CircleX,
ExternalLink,
Info, Info,
Plus, Plus,
Settings Settings
@@ -961,13 +962,18 @@ function ProxyResourceTargetsForm({
{build === "saas" && {build === "saas" &&
targets.length > 1 && targets.length > 1 &&
new Set(targets.map((t) => t.siteId)).size > 1 && ( new Set(targets.map((t) => t.siteId)).size > 1 && (
<p className="text-sm text-muted-foreground mt-3 flex items-start gap-1.5"> <p className="text-sm text-muted-foreground mt-3">
<AlertTriangle className="h-4 w-4 shrink-0 mt-0.5" /> {t("proxyMultiSiteRoundRobinNodeHelp")}{" "}
<span> <a
Round robin routing will not work between href="https://docs.pangolin.net/manage/resources/public/targets#distributing-sites-load-across-servers"
sites that are not connected to the same target="_blank"
node, but failover will work. rel="noopener noreferrer"
</span> className="text-primary hover:underline inline-flex items-center gap-1"
>
{t("learnMore")}
<ExternalLink className="size-3.5 shrink-0" />
</a>
.
</p> </p>
)} )}
</SettingsSectionBody> </SettingsSectionBody>

View File

@@ -82,8 +82,8 @@ import { AxiosResponse } from "axios";
import { import {
CircleCheck, CircleCheck,
CircleX, CircleX,
ExternalLink,
Info, Info,
InfoIcon,
Plus, Plus,
Settings, Settings,
SquareArrowOutUpRight SquareArrowOutUpRight
@@ -1425,16 +1425,22 @@ export default function Page() {
</Button> </Button>
</div> </div>
)} )}
{build === "enterprise" && {build === "saas" &&
targets.length > 1 && targets.length > 1 &&
new Set(targets.map((t) => t.siteId)).size > 1 && ( new Set(targets.map((t) => t.siteId)).size >
<p className="text-sm text-muted-foreground mt-3 flex items-start gap-1.5"> 1 && (
<InfoIcon className="h-4 w-4 shrink-0 mt-0.5" /> <p className="text-sm text-muted-foreground mt-3">
<span> {t("proxyMultiSiteRoundRobinNodeHelp")}{" "}
Round robin routing will not work between <a
sites that are not connected to the same href="https://docs.pangolin.net/manage/resources/public/targets#distributing-sites-load-across-servers"
node, but failover will work. target="_blank"
</span> rel="noopener noreferrer"
className="text-primary hover:underline inline-flex items-center gap-1"
>
{t("learnMore")}
<ExternalLink className="size-3.5 shrink-0" />
</a>
.
</p> </p>
)} )}
</SettingsSectionBody> </SettingsSectionBody>

View File

@@ -13,6 +13,8 @@ import DomainCertForm from "@app/components/DomainCertForm";
import { build } from "@server/build"; import { build } from "@server/build";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { Lock } from "lucide-react";
import { Badge } from "@app/components/ui/badge";
interface DomainPageClientProps { interface DomainPageClientProps {
initialDomain: GetDomainResponse; initialDomain: GetDomainResponse;
@@ -49,7 +51,22 @@ export default function DomainPageClient({
<> <>
<div className="flex justify-between"> <div className="flex justify-between">
<SettingsSectionTitle <SettingsSectionTitle
title={domain.baseDomain} title={
<span className="flex items-center gap-2">
{domain.baseDomain}
{domain.configManaged && (
<Badge
variant="secondary"
className="flex items-center gap-1 text-sm font-normal"
>
<Lock className="h-3 w-3" />
{t("configManaged", {
fallback: "Config Managed"
})}
</Badge>
)}
</span>
}
description={t("domainSettingDescription")} description={t("domainSettingDescription")}
/> />
{env.flags.usePangolinDns && domain.failed ? ( {env.flags.usePangolinDns && domain.failed ? (

View File

@@ -16,6 +16,7 @@ import { formatAxiosError } from "@app/lib/api";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { Badge } from "@app/components/ui/badge"; import { Badge } from "@app/components/ui/badge";
import { Lock } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import CreateDomainForm from "@app/components/CreateDomainForm"; import CreateDomainForm from "@app/components/CreateDomainForm";
import { useToast } from "@app/hooks/useToast"; import { useToast } from "@app/hooks/useToast";
@@ -72,7 +73,11 @@ export default function DomainsTable({ domains, orgId }: Props) {
const { org } = useOrgContext(); const { org } = useOrgContext();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { data: rawDomains, isRefetching, refetch } = useQuery({ const {
data: rawDomains,
isRefetching,
refetch
} = useQuery({
...orgQueries.domains({ orgId }), ...orgQueries.domains({ orgId }),
initialData: domains as any, initialData: domains as any,
refetchInterval: durationToMs(10, "seconds") refetchInterval: durationToMs(10, "seconds")
@@ -80,12 +85,15 @@ export default function DomainsTable({ domains, orgId }: Props) {
const tableData = useMemo( const tableData = useMemo(
() => () =>
(rawDomains ?? []).map((d) => ({ (rawDomains ?? []).map(
(d) =>
({
...d, ...d,
baseDomain: toUnicode(d.baseDomain), baseDomain: toUnicode(d.baseDomain),
type: d.type ?? "", type: d.type ?? "",
errorMessage: d.errorMessage ?? null errorMessage: d.errorMessage ?? null
} as DomainRow)), }) as DomainRow
),
[rawDomains] [rawDomains]
); );
@@ -198,12 +206,17 @@ export default function DomainsTable({ domains, orgId }: Props) {
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Badge variant="red" className="cursor-help"> <Badge
variant="red"
className="cursor-help"
>
{t("failed", { fallback: "Failed" })} {t("failed", { fallback: "Failed" })}
</Badge> </Badge>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-xs"> <TooltipContent className="max-w-xs">
<p className="break-words">{errorMessage}</p> <p className="break-words">
{errorMessage}
</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@@ -220,12 +233,17 @@ export default function DomainsTable({ domains, orgId }: Props) {
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Badge variant="yellow" className="cursor-help"> <Badge
variant="yellow"
className="cursor-help"
>
{t("pending")} {t("pending")}
</Badge> </Badge>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-xs"> <TooltipContent className="max-w-xs">
<p className="break-words">{errorMessage}</p> <p className="break-words">
{errorMessage}
</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@@ -253,6 +271,25 @@ export default function DomainsTable({ domains, orgId }: Props) {
<ArrowUpDown className="ml-2 h-4 w-4" /> <ArrowUpDown className="ml-2 h-4 w-4" />
</Button> </Button>
); );
},
cell: ({ row }) => {
const domain = row.original;
return (
<span className="flex items-center gap-2">
{domain.baseDomain}
{domain.configManaged && (
<Badge
variant="secondary"
className="flex items-center gap-1 text-xs font-normal"
>
<Lock className="h-3 w-3" />
{t("configManaged", {
fallback: "Config Managed"
})}
</Badge>
)}
</span>
);
} }
}, },
...(env.env.flags.usePangolinDns ? [typeColumn] : []), ...(env.env.flags.usePangolinDns ? [typeColumn] : []),
@@ -283,6 +320,7 @@ export default function DomainsTable({ domains, orgId }: Props) {
{t("viewSettings")} {t("viewSettings")}
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
{!domain.configManaged && (
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
setSelectedDomain(domain); setSelectedDomain(domain);
@@ -293,6 +331,7 @@ export default function DomainsTable({ domains, orgId }: Props) {
{t("delete")} {t("delete")}
</span> </span>
</DropdownMenuItem> </DropdownMenuItem>
)}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
{domain.failed && ( {domain.failed && (
@@ -315,7 +354,9 @@ export default function DomainsTable({ domains, orgId }: Props) {
href={`/${orgId}/settings/domains/${domain.domainId}`} href={`/${orgId}/settings/domains/${domain.domainId}`}
> >
<Button variant={"outline"}> <Button variant={"outline"}>
{t("edit")} {domain.configManaged
? t("view", { fallback: "View" })
: t("edit")}
<ArrowRight className="ml-2 w-4 h-4" /> <ArrowRight className="ml-2 w-4 h-4" />
</Button> </Button>
</Link> </Link>

View File

@@ -99,7 +99,7 @@ export default function InviteStatusCard({
router.push(redirectUrl); router.push(redirectUrl);
} else if (!user && type === "not_logged_in") { } else if (!user && type === "not_logged_in") {
const redirectUrl = email const redirectUrl = email
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${email}` ? `/auth/login?redirect=/invite?token=${tokenParam}&user=${email}`
: `/auth/login?redirect=/invite?token=${tokenParam}`; : `/auth/login?redirect=/invite?token=${tokenParam}`;
router.push(redirectUrl); router.push(redirectUrl);
} else { } else {
@@ -113,7 +113,7 @@ export default function InviteStatusCard({
async function goToLogin() { async function goToLogin() {
await api.post("/auth/logout", {}); await api.post("/auth/logout", {});
const redirectUrl = email const redirectUrl = email
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${email}` ? `/auth/login?redirect=/invite?token=${tokenParam}&user=${email}`
: `/auth/login?redirect=/invite?token=${tokenParam}`; : `/auth/login?redirect=/invite?token=${tokenParam}`;
router.push(redirectUrl); router.push(redirectUrl);
} }

View File

@@ -16,6 +16,7 @@ import Link from "next/link";
import { replacePlaceholder } from "@app/lib/replacePlaceholder"; import { replacePlaceholder } from "@app/lib/replacePlaceholder";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import { pullEnv } from "@app/lib/pullEnv"; import { pullEnv } from "@app/lib/pullEnv";
import { build } from "@server/build";
type OrgLoginPageProps = { type OrgLoginPageProps = {
loginPage: LoadLoginPageResponse | undefined; loginPage: LoadLoginPageResponse | undefined;
@@ -52,6 +53,7 @@ export default async function OrgLoginPage({
const t = await getTranslations(); const t = await getTranslations();
return ( return (
<div> <div>
{build !== "enterprise" || !env.branding.hidePoweredBy ? (
<div className="text-center mb-2"> <div className="text-center mb-2">
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{t("poweredBy")}{" "} {t("poweredBy")}{" "}
@@ -65,6 +67,7 @@ export default async function OrgLoginPage({
</Link> </Link>
</span> </span>
</div> </div>
) : null}
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader> <CardHeader>
{branding?.logoUrl && ( {branding?.logoUrl && (

View File

@@ -375,7 +375,8 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
{!accessDenied ? ( {!accessDenied ? (
<div> <div>
{isUnlocked() && build === "enterprise" ? ( {isUnlocked() && build === "enterprise" ? (
!env.branding.resourceAuthPage?.hidePoweredBy && ( !env.branding.resourceAuthPage?.hidePoweredBy &&
!env.branding.hidePoweredBy && (
<div className="text-center mb-2"> <div className="text-center mb-2">
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{t("poweredBy")}{" "} {t("poweredBy")}{" "}

View File

@@ -61,6 +61,8 @@ export default function ShareLinksTable({
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedLink, setSelectedLink] = useState<ShareLinkRow | null>(null);
const [rows, setRows] = useState<ShareLinkRow[]>(shareLinks); const [rows, setRows] = useState<ShareLinkRow[]>(shareLinks);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
@@ -92,6 +94,7 @@ export default function ShareLinksTable({
title: t("shareErrorDelete"), title: t("shareErrorDelete"),
description: formatAxiosError(e, t("shareErrorDeleteMessage")) description: formatAxiosError(e, t("shareErrorDeleteMessage"))
}); });
throw e;
}); });
const newRows = rows.filter((r) => r.accessTokenId !== id); const newRows = rows.filter((r) => r.accessTokenId !== id);
@@ -293,9 +296,10 @@ export default function ShareLinksTable({
{/* </DropdownMenu> */} {/* </DropdownMenu> */}
<Button <Button
variant={"outline"} variant={"outline"}
onClick={() => onClick={() => {
deleteSharelink(row.original.accessTokenId) setSelectedLink(resourceRow);
} setIsDeleteModalOpen(true);
}}
> >
{t("delete")} {t("delete")}
</Button> </Button>
@@ -307,6 +311,30 @@ export default function ShareLinksTable({
return ( return (
<> <>
{selectedLink && (
<ConfirmDeleteDialog
open={isDeleteModalOpen}
setOpen={(val) => {
setIsDeleteModalOpen(val);
if (!val) setSelectedLink(null);
}}
dialog={
<div className="space-y-2">
<p>{t("shareQuestionRemove")}</p>
<p>{t("shareMessageRemove")}</p>
</div>
}
buttonText={t("shareDeleteConfirm")}
onConfirm={async () =>
deleteSharelink(selectedLink.accessTokenId)
}
string={
selectedLink.title || selectedLink.resourceName
}
title={t("shareDelete")}
/>
)}
<CreateShareLinkForm <CreateShareLinkForm
open={isCreateModalOpen} open={isCreateModalOpen}
setOpen={setIsCreateModalOpen} setOpen={setIsCreateModalOpen}

View File

@@ -99,6 +99,14 @@ export default function UsersTable({
]; ];
}, [searchParams.toString()]); }, [searchParams.toString()]);
const isRemovingSelf = useMemo(() => {
if (!selectedUser || !user) return false;
return (
`${selectedUser.username}-${selectedUser.idpId}` ===
`${user.username}-${user.idpId}`
);
}, [selectedUser, user]);
function handleFilterChange( function handleFilterChange(
column: string, column: string,
value: string | undefined | null value: string | undefined | null
@@ -223,10 +231,7 @@ export default function UsersTable({
header: () => <span className="p-3"></span>, header: () => <span className="p-3"></span>,
cell: ({ row }) => { cell: ({ row }) => {
const userRow = row.original; const userRow = row.original;
const isCurrentUser = const canRemoveFromOrg = !userRow.isOwner;
`${userRow.username}-${userRow.idpId}` ===
`${user?.username}-${user?.idpId}`;
const isDisabled = userRow.isOwner || isCurrentUser;
return ( return (
<div className="flex items-center justify-end"> <div className="flex items-center justify-end">
<div> <div>
@@ -235,7 +240,6 @@ export default function UsersTable({
<Button <Button
variant="ghost" variant="ghost"
className="h-8 w-8 p-0" className="h-8 w-8 p-0"
disabled={isDisabled}
> >
<span className="sr-only"> <span className="sr-only">
{t("openMenu")} {t("openMenu")}
@@ -247,16 +251,12 @@ export default function UsersTable({
<Link <Link
href={`/${org?.org.orgId}/settings/access/users/${userRow.id}`} href={`/${org?.org.orgId}/settings/access/users/${userRow.id}`}
className="block w-full" className="block w-full"
aria-disabled={isDisabled}
onClick={(e) =>
isDisabled && e.preventDefault()
}
> >
<DropdownMenuItem disabled={isDisabled}> <DropdownMenuItem>
{t("accessUserManage")} {t("accessUserManage")}
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
{!isDisabled && ( {canRemoveFromOrg && (
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
setIsDeleteModalOpen(true); setIsDeleteModalOpen(true);
@@ -271,16 +271,6 @@ export default function UsersTable({
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
{isDisabled ? (
<Button
variant={"outline"}
className="ml-2"
disabled
>
{t("manage")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
) : (
<Link <Link
href={`/${org?.org.orgId}/settings/access/users/${userRow.id}`} href={`/${org?.org.orgId}/settings/access/users/${userRow.id}`}
> >
@@ -289,7 +279,6 @@ export default function UsersTable({
<ArrowRight className="ml-2 w-4 h-4" /> <ArrowRight className="ml-2 w-4 h-4" />
</Button> </Button>
</Link> </Link>
)}
</div> </div>
); );
} }
@@ -359,14 +348,35 @@ export default function UsersTable({
}} }}
dialog={ dialog={
<div className="space-y-2"> <div className="space-y-2">
<p>{t("userQuestionOrgRemove")}</p> <p>
<p>{t("userMessageOrgRemove")}</p> {t(
isRemovingSelf
? "userQuestionOrgRemoveSelf"
: "userQuestionOrgRemove"
)}
</p>
<p>
{t(
isRemovingSelf
? "userMessageOrgRemoveSelf"
: "userMessageOrgRemove"
)}
</p>
</div> </div>
} }
buttonText={t("userRemoveOrgConfirm")} buttonText={t(
isRemovingSelf
? "userRemoveOrgConfirmSelf"
: "userRemoveOrgConfirm"
)}
warningText={
isRemovingSelf ? t("userRemoveOrgSelfWarning") : undefined
}
onConfirm={async () => startTransition(removeUser)} onConfirm={async () => startTransition(removeUser)}
string={ string={
selectedUser isRemovingSelf
? t("userRemoveOrgConfirmPhraseSelf")
: selectedUser
? getUserDisplayName({ ? getUserDisplayName({
email: selectedUser.email, email: selectedUser.email,
name: selectedUser.name, name: selectedUser.name,
@@ -374,7 +384,9 @@ export default function UsersTable({
}) })
: "" : ""
} }
title={t("userRemoveOrg")} title={t(
isRemovingSelf ? "userRemoveOrgSelf" : "userRemoveOrg"
)}
/> />
<ControlledDataTable <ControlledDataTable

View File

@@ -11,7 +11,7 @@ import { cn } from "@app/lib/cn";
import { CheckIcon } from "lucide-react"; import { CheckIcon } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
export type TagValue = { text: string; id: string }; export type TagValue = { text: string; id: string; isAdmin?: boolean };
export type MultiSelectTagsProps<T extends TagValue> = { export type MultiSelectTagsProps<T extends TagValue> = {
emptyPlaceholder?: string; emptyPlaceholder?: string;

View File

@@ -6,7 +6,7 @@ import { useDebounce } from "use-debounce";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { MultiSelectTagInput } from "./multi-select/multi-select-tag-input"; import { MultiSelectTagInput } from "./multi-select/multi-select-tag-input";
export type SelectedRole = { id: string; text: string }; export type SelectedRole = { id: string; text: string; isAdmin?: boolean };
export type RolesSelectorProps = { export type RolesSelectorProps = {
orgId: string; orgId: string;

View File

@@ -81,6 +81,8 @@ export function pullEnv(): Env {
process.env.BRANDING_HIDE_AUTH_LAYOUT_FOOTER === "true" process.env.BRANDING_HIDE_AUTH_LAYOUT_FOOTER === "true"
? true ? true
: false, : false,
hidePoweredBy:
process.env.BRANDING_HIDE_POWERED_BY === "true" ? true : false,
logo: { logo: {
lightPath: process.env.BRANDING_LOGO_LIGHT_PATH as string, lightPath: process.env.BRANDING_LOGO_LIGHT_PATH as string,
darkPath: process.env.BRANDING_LOGO_DARK_PATH as string, darkPath: process.env.BRANDING_LOGO_DARK_PATH as string,

View File

@@ -41,6 +41,7 @@ export type Env = {
appName?: string; appName?: string;
background_image_path?: string; background_image_path?: string;
hideAuthLayoutFooter?: boolean; hideAuthLayoutFooter?: boolean;
hidePoweredBy?: boolean;
logo?: { logo?: {
lightPath?: string; lightPath?: string;
darkPath?: string; darkPath?: string;