Compare commits

..

3 Commits

Author SHA1 Message Date
Owen Schwartz
784588cebc Merge pull request #3350 from fosrl/dev
Make sure the rebuild actually executes
2026-06-26 09:28:12 -04:00
Owen
2e628fe0e4 Make sure the rebuild actually executes 2026-06-26 09:26:43 -04:00
Owen Schwartz
7590e8d8a1 Merge pull request #3345 from fosrl/dev
Show utility subnet on org
2026-06-25 13:05:32 -07:00
16 changed files with 386 additions and 813 deletions

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Сайтът е обновен", "siteUpdated": "Сайтът е обновен",
"siteUpdatedDescription": "Сайтът е актуализиран.", "siteUpdatedDescription": "Сайтът е актуализиран.",
"siteGeneralDescription": "Конфигурирайте общи настройки за този сайт", "siteGeneralDescription": "Конфигурирайте общи настройки за този сайт",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Конфигурирайте настройките на сайта", "siteSettingDescription": "Конфигурирайте настройките на сайта",
"siteResourcesTab": "Ресурси", "siteResourcesTab": "Ресурси",
"siteResourcesNoneOnSite": "Този сайт все още няма публични или частни ресурси.", "siteResourcesNoneOnSite": "Този сайт все още няма публични или частни ресурси.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Отдалечени възли", "sidebarRemoteExitNodes": "Отдалечени възли",
"remoteExitNodeId": "ID.", "remoteExitNodeId": "ID.",
"remoteExitNodeSecretKey": "Секретен ключ.", "remoteExitNodeSecretKey": "Секретен ключ.",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Създаване на отдалечен възел.", "title": "Създаване на отдалечен възел.",
"description": "Създайте нов самохостнал отдалечен ретранслатор и прокси сървърен възел.", "description": "Създайте нов самохостнал отдалечен ретранслатор и прокси сървърен възел.",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC доставчик", "idpGoogleDescription": "Google OAuth2/OIDC доставчик",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC доставчик", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC доставчик",
"subnet": "Подмрежа", "subnet": "Подмрежа",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Подмрежата за конфигурацията на мрежата на тази организация.", "subnetDescription": "Подмрежата за конфигурацията на мрежата на тази организация.",
"customDomain": "Персонализиран домейн.", "customDomain": "Персонализиран домейн.",
"authPage": "Страници за автентификация.", "authPage": "Страници за автентификация.",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----НАЧАЛО НА OPENSSH ЧАСТЕН КЛЮЧ-----", "sshPrivateKeyPlaceholder": "-----НАЧАЛО НА OPENSSH ЧАСТЕН КЛЮЧ-----",
"sshPrivateKeyRequired": "Изисква се частен ключ", "sshPrivateKeyRequired": "Изисква се частен ключ",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Въведете вашата VNC парола за свързване",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Парола (по избор)", "vncPasswordOptional": "Парола (по избор)",
"vncNoResourceTarget": "Не е налична цел за ресурса", "vncNoResourceTarget": "Не е налична цел за ресурса",
"vncFailedToLoadNovnc": "Неуспешно зареждане на noVNC", "vncFailedToLoadNovnc": "Неуспешно зареждане на noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Lokalita upravena", "siteUpdated": "Lokalita upravena",
"siteUpdatedDescription": "Lokalita byla upravena.", "siteUpdatedDescription": "Lokalita byla upravena.",
"siteGeneralDescription": "Upravte obecná nastavení pro tuto lokalitu", "siteGeneralDescription": "Upravte obecná nastavení pro tuto lokalitu",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Konfigurace nastavení na webu", "siteSettingDescription": "Konfigurace nastavení na webu",
"siteResourcesTab": "Zdroje", "siteResourcesTab": "Zdroje",
"siteResourcesNoneOnSite": "Tento web zatím nemá veřejné ani soukromé zdroje.", "siteResourcesNoneOnSite": "Tento web zatím nemá veřejné ani soukromé zdroje.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Vzdálené uzly", "sidebarRemoteExitNodes": "Vzdálené uzly",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Tajný klíč", "remoteExitNodeSecretKey": "Tajný klíč",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Vytvořit vzdálený uzel", "title": "Vytvořit vzdálený uzel",
"description": "Vytvořte nový vlastní hostovaný vzdálený relační a proxy server uzel", "description": "Vytvořte nový vlastní hostovaný vzdálený relační a proxy server uzel",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Poskytovatel Google OAuth2/OIDC", "idpGoogleDescription": "Poskytovatel Google OAuth2/OIDC",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Podsíť", "subnet": "Podsíť",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Podsíť pro konfiguraci sítě této organizace.", "subnetDescription": "Podsíť pro konfiguraci sítě této organizace.",
"customDomain": "Vlastní doména", "customDomain": "Vlastní doména",
"authPage": "Autentizační stránky", "authPage": "Autentizační stránky",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----ZAČÁTEK SOUKROMÉHO KLÍČE OPENSSH-----", "sshPrivateKeyPlaceholder": "-----ZAČÁTEK SOUKROMÉHO KLÍČE OPENSSH-----",
"sshPrivateKeyRequired": "Je vyžadován soukromý klíč", "sshPrivateKeyRequired": "Je vyžadován soukromý klíč",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Zadejte své heslo VNC pro připojení",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Heslo (nepovinné)", "vncPasswordOptional": "Heslo (nepovinné)",
"vncNoResourceTarget": "Není k dispozici žádný cíl zdroje", "vncNoResourceTarget": "Není k dispozici žádný cíl zdroje",
"vncFailedToLoadNovnc": "Nepodařilo se načíst noVNC", "vncFailedToLoadNovnc": "Nepodařilo se načíst noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Site opdateret", "siteUpdated": "Site opdateret",
"siteUpdatedDescription": "Sitet er blevet opdateret.", "siteUpdatedDescription": "Sitet er blevet opdateret.",
"siteGeneralDescription": "Konfigurer de generelle indstillinger for dette site", "siteGeneralDescription": "Konfigurer de generelle indstillinger for dette site",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Konfigurer indstillingerne for sitet", "siteSettingDescription": "Konfigurer indstillingerne for sitet",
"siteResourcesTab": "Ressourcer", "siteResourcesTab": "Ressourcer",
"siteResourcesNoneOnSite": "Dette site har endnu ingen offentlige eller private ressourcer.", "siteResourcesNoneOnSite": "Dette site har endnu ingen offentlige eller private ressourcer.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Eksterne noder", "sidebarRemoteExitNodes": "Eksterne noder",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Sikkerhedsnøgle", "remoteExitNodeSecretKey": "Sikkerhedsnøgle",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Opret ekstern node", "title": "Opret ekstern node",
"description": "Opret en ny selvhostet ekstern relay- og proxyservernode", "description": "Opret en ny selvhostet ekstern relay- og proxyservernode",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC udbyder", "idpGoogleDescription": "Google OAuth2/OIDC udbyder",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC leverandør", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC leverandør",
"subnet": "Subnet", "subnet": "Subnet",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Subnettet for denne organisations netværkskonfiguration.", "subnetDescription": "Subnettet for denne organisations netværkskonfiguration.",
"customDomain": "Brugerdefineret domæne", "customDomain": "Brugerdefineret domæne",
"authPage": "Autentiseringssider", "authPage": "Autentiseringssider",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGYNN OPENSSH PRIVAT NØGLE-----", "sshPrivateKeyPlaceholder": "-----BEGYNN OPENSSH PRIVAT NØGLE-----",
"sshPrivateKeyRequired": "Privat nøgle er påkrævet", "sshPrivateKeyRequired": "Privat nøgle er påkrævet",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Indtast VNC-adgangskoden for at oprette forbindelse til",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Adgangskode (valgfrit)", "vncPasswordOptional": "Adgangskode (valgfrit)",
"vncNoResourceTarget": "Intet ressourcemål tilgængeligt", "vncNoResourceTarget": "Intet ressourcemål tilgængeligt",
"vncFailedToLoadNovnc": "Kunne ikke indlæse noVNC", "vncFailedToLoadNovnc": "Kunne ikke indlæse noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Standort aktualisiert", "siteUpdated": "Standort aktualisiert",
"siteUpdatedDescription": "Der Standort wurde aktualisiert.", "siteUpdatedDescription": "Der Standort wurde aktualisiert.",
"siteGeneralDescription": "Allgemeine Einstellungen für diesen Standort konfigurieren", "siteGeneralDescription": "Allgemeine Einstellungen für diesen Standort konfigurieren",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Standorteinstellungen konfigurieren", "siteSettingDescription": "Standorteinstellungen konfigurieren",
"siteResourcesTab": "Ressourcen", "siteResourcesTab": "Ressourcen",
"siteResourcesNoneOnSite": "Dieser Standort hat noch keine öffentlichen oder privaten Ressourcen", "siteResourcesNoneOnSite": "Dieser Standort hat noch keine öffentlichen oder privaten Ressourcen",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Entfernte Knoten", "sidebarRemoteExitNodes": "Entfernte Knoten",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Geheimnis", "remoteExitNodeSecretKey": "Geheimnis",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Erstelle Remote Node", "title": "Erstelle Remote Node",
"description": "Erstelle einen neues selbst gehostetes Relay und ihre Proxyserver Nodes", "description": "Erstelle einen neues selbst gehostetes Relay und ihre Proxyserver Nodes",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC Provider", "idpGoogleDescription": "Google OAuth2/OIDC Provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Subnetz", "subnet": "Subnetz",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Das Subnetz für die Netzwerkkonfiguration dieser Organisation.", "subnetDescription": "Das Subnetz für die Netzwerkkonfiguration dieser Organisation.",
"customDomain": "Eigene Domain", "customDomain": "Eigene Domain",
"authPage": "Authentifizierungs-Seiten", "authPage": "Authentifizierungs-Seiten",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----", "sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Privater Schlüssel ist erforderlich", "sshPrivateKeyRequired": "Privater Schlüssel ist erforderlich",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Geben Sie Ihr VNC-Passwort ein, um sich zu verbinden",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Passwort (optional)", "vncPasswordOptional": "Passwort (optional)",
"vncNoResourceTarget": "Kein Ressourcen-Ziel verfügbar", "vncNoResourceTarget": "Kein Ressourcen-Ziel verfügbar",
"vncFailedToLoadNovnc": "Fehler beim Laden von noVNC", "vncFailedToLoadNovnc": "Fehler beim Laden von noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Sitio actualizado", "siteUpdated": "Sitio actualizado",
"siteUpdatedDescription": "El sitio ha sido actualizado.", "siteUpdatedDescription": "El sitio ha sido actualizado.",
"siteGeneralDescription": "Configurar la configuración general de este sitio", "siteGeneralDescription": "Configurar la configuración general de este sitio",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Configurar los ajustes en el sitio", "siteSettingDescription": "Configurar los ajustes en el sitio",
"siteResourcesTab": "Recursos", "siteResourcesTab": "Recursos",
"siteResourcesNoneOnSite": "Este sitio aún no tiene recursos públicos o privados.", "siteResourcesNoneOnSite": "Este sitio aún no tiene recursos públicos o privados.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Nodos remotos", "sidebarRemoteExitNodes": "Nodos remotos",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Secreto", "remoteExitNodeSecretKey": "Secreto",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Crear nodo remoto", "title": "Crear nodo remoto",
"description": "Crea un nuevo nodo de retransmisión y proxy server autogestionado", "description": "Crea un nuevo nodo de retransmisión y proxy server autogestionado",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Proveedor OAuth2/OIDC de Google", "idpGoogleDescription": "Proveedor OAuth2/OIDC de Google",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Subred", "subnet": "Subred",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "La subred para la configuración de red de esta organización.", "subnetDescription": "La subred para la configuración de red de esta organización.",
"customDomain": "Dominio personalizado", "customDomain": "Dominio personalizado",
"authPage": "Páginas de autenticación", "authPage": "Páginas de autenticación",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----COMIENZO DE LA CLAVE PRIVADA OPENSSH-----", "sshPrivateKeyPlaceholder": "-----COMIENZO DE LA CLAVE PRIVADA OPENSSH-----",
"sshPrivateKeyRequired": "Se requiere clave privada", "sshPrivateKeyRequired": "Se requiere clave privada",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Introduce tu contraseña VNC para conectar",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Contraseña (opcional)", "vncPasswordOptional": "Contraseña (opcional)",
"vncNoResourceTarget": "No hay objetivo de recurso disponible", "vncNoResourceTarget": "No hay objetivo de recurso disponible",
"vncFailedToLoadNovnc": "Error al cargar noVNC", "vncFailedToLoadNovnc": "Error al cargar noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Nœud mis à jour", "siteUpdated": "Nœud mis à jour",
"siteUpdatedDescription": "Le nœud a été mis à jour.", "siteUpdatedDescription": "Le nœud a été mis à jour.",
"siteGeneralDescription": "Configurer les paramètres par défaut de ce nœud", "siteGeneralDescription": "Configurer les paramètres par défaut de ce nœud",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Configurer les paramètres du site", "siteSettingDescription": "Configurer les paramètres du site",
"siteResourcesTab": "Ressources", "siteResourcesTab": "Ressources",
"siteResourcesNoneOnSite": "Ce site n'a pas encore de ressources publiques ou privées.", "siteResourcesNoneOnSite": "Ce site n'a pas encore de ressources publiques ou privées.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Nœuds distants", "sidebarRemoteExitNodes": "Nœuds distants",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Clé secrète", "remoteExitNodeSecretKey": "Clé secrète",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Créer un nœud distant", "title": "Créer un nœud distant",
"description": "Créez un nouveau nœud de relais et de serveur proxy distant auto-hébergé", "description": "Créez un nouveau nœud de relais et de serveur proxy distant auto-hébergé",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Fournisseur Google OAuth2/OIDC", "idpGoogleDescription": "Fournisseur Google OAuth2/OIDC",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Sous-réseau", "subnet": "Sous-réseau",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Le sous-réseau de la configuration réseau de cette organisation.", "subnetDescription": "Le sous-réseau de la configuration réseau de cette organisation.",
"customDomain": "Domaine personnalisé", "customDomain": "Domaine personnalisé",
"authPage": "Pages d'authentification", "authPage": "Pages d'authentification",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----", "sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Une clé privée est requise", "sshPrivateKeyRequired": "Une clé privée est requise",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Entrez votre mot de passe VNC pour vous connecter",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Mot de passe (facultatif)", "vncPasswordOptional": "Mot de passe (facultatif)",
"vncNoResourceTarget": "Aucune cible de ressource disponible", "vncNoResourceTarget": "Aucune cible de ressource disponible",
"vncFailedToLoadNovnc": "Échec du chargement de noVNC", "vncFailedToLoadNovnc": "Échec du chargement de noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Sito aggiornato", "siteUpdated": "Sito aggiornato",
"siteUpdatedDescription": "Il sito è stato aggiornato.", "siteUpdatedDescription": "Il sito è stato aggiornato.",
"siteGeneralDescription": "Configura le impostazioni generali per questo sito", "siteGeneralDescription": "Configura le impostazioni generali per questo sito",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Configura le impostazioni del sito", "siteSettingDescription": "Configura le impostazioni del sito",
"siteResourcesTab": "Risorse", "siteResourcesTab": "Risorse",
"siteResourcesNoneOnSite": "Questo sito non ha ancora risorse pubbliche o private.", "siteResourcesNoneOnSite": "Questo sito non ha ancora risorse pubbliche o private.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Nodi Remoti", "sidebarRemoteExitNodes": "Nodi Remoti",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Segreto", "remoteExitNodeSecretKey": "Segreto",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Crea Nodo Remoto", "title": "Crea Nodo Remoto",
"description": "Crea un nuovo nodo server proxy e relay remoto ospitato in proprio", "description": "Crea un nuovo nodo server proxy e relay remoto ospitato in proprio",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC provider", "idpGoogleDescription": "Google OAuth2/OIDC provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Sottorete", "subnet": "Sottorete",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "La sottorete per la configurazione di rete di questa organizzazione.", "subnetDescription": "La sottorete per la configurazione di rete di questa organizzazione.",
"customDomain": "Dominio Personalizzato", "customDomain": "Dominio Personalizzato",
"authPage": "Pagine di Autenticazione", "authPage": "Pagine di Autenticazione",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----", "sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "È richiesta una chiave privata", "sshPrivateKeyRequired": "È richiesta una chiave privata",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Inserisci la tua password VNC per connetterti",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Password (opzionale)", "vncPasswordOptional": "Password (opzionale)",
"vncNoResourceTarget": "Nessun bersaglio di risorsa disponibile", "vncNoResourceTarget": "Nessun bersaglio di risorsa disponibile",
"vncFailedToLoadNovnc": "Impossibile caricare noVNC", "vncFailedToLoadNovnc": "Impossibile caricare noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "사이트가 업데이트되었습니다", "siteUpdated": "사이트가 업데이트되었습니다",
"siteUpdatedDescription": "사이트가 업데이트되었습니다.", "siteUpdatedDescription": "사이트가 업데이트되었습니다.",
"siteGeneralDescription": "이 사이트에 대한 일반 설정을 구성하세요.", "siteGeneralDescription": "이 사이트에 대한 일반 설정을 구성하세요.",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "사이트에서 설정을 구성하세요.", "siteSettingDescription": "사이트에서 설정을 구성하세요.",
"siteResourcesTab": "리소스", "siteResourcesTab": "리소스",
"siteResourcesNoneOnSite": "이 사이트에는 아직 공용 또는 개인 리소스가 없습니다.", "siteResourcesNoneOnSite": "이 사이트에는 아직 공용 또는 개인 리소스가 없습니다.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "원격 노드", "sidebarRemoteExitNodes": "원격 노드",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "비밀", "remoteExitNodeSecretKey": "비밀",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "원격 노드 생성", "title": "원격 노드 생성",
"description": "새로운 자체 호스팅 원격 중계 및 프록시 서버 노드를 생성하십시오.", "description": "새로운 자체 호스팅 원격 중계 및 프록시 서버 노드를 생성하십시오.",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC 공급자", "idpGoogleDescription": "Google OAuth2/OIDC 공급자",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC 공급자", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC 공급자",
"subnet": "서브넷", "subnet": "서브넷",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "이 조직의 네트워크 구성에 대한 서브넷입니다.", "subnetDescription": "이 조직의 네트워크 구성에 대한 서브넷입니다.",
"customDomain": "사용자 정의 도메인", "customDomain": "사용자 정의 도메인",
"authPage": "인증 페이지", "authPage": "인증 페이지",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----", "sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "프라이빗 키가 필요합니다", "sshPrivateKeyRequired": "프라이빗 키가 필요합니다",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "연결하려면 VNC 비밀번호를 입력하세요",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "비밀번호 (선택 사항)", "vncPasswordOptional": "비밀번호 (선택 사항)",
"vncNoResourceTarget": "사용할 수 있는 리소스 대상이 없습니다", "vncNoResourceTarget": "사용할 수 있는 리소스 대상이 없습니다",
"vncFailedToLoadNovnc": "noVNC 로드를 실패했습니다", "vncFailedToLoadNovnc": "noVNC 로드를 실패했습니다",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Område oppdatert", "siteUpdated": "Område oppdatert",
"siteUpdatedDescription": "Området har blitt oppdatert.", "siteUpdatedDescription": "Området har blitt oppdatert.",
"siteGeneralDescription": "Konfigurer de generelle innstillingene for dette området", "siteGeneralDescription": "Konfigurer de generelle innstillingene for dette området",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Konfigurere innstillingene på nettstedet", "siteSettingDescription": "Konfigurere innstillingene på nettstedet",
"siteResourcesTab": "Ressurser", "siteResourcesTab": "Ressurser",
"siteResourcesNoneOnSite": "Dette nettstedet har ingen offentlige eller private ressurser enda.", "siteResourcesNoneOnSite": "Dette nettstedet har ingen offentlige eller private ressurser enda.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Eksterne Noder", "sidebarRemoteExitNodes": "Eksterne Noder",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Sikkerhetsnøkkel", "remoteExitNodeSecretKey": "Sikkerhetsnøkkel",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Opprett ekstern node", "title": "Opprett ekstern node",
"description": "Opprett en ny egendrift ekstern relé- og proxyservernode", "description": "Opprett en ny egendrift ekstern relé- og proxyservernode",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC leverandør", "idpGoogleDescription": "Google OAuth2/OIDC leverandør",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Subnett", "subnet": "Subnett",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Undernettverket for denne organisasjonens nettverkskonfigurasjon.", "subnetDescription": "Undernettverket for denne organisasjonens nettverkskonfigurasjon.",
"customDomain": "Egendefinert domene", "customDomain": "Egendefinert domene",
"authPage": "Autentiseringssider", "authPage": "Autentiseringssider",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGYNN OPENSSH PRIVAT NØKKEL-----", "sshPrivateKeyPlaceholder": "-----BEGYNN OPENSSH PRIVAT NØKKEL-----",
"sshPrivateKeyRequired": "Privat nøkkel er påkrevd", "sshPrivateKeyRequired": "Privat nøkkel er påkrevd",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Skriv inn VNC-passordet for å koble til",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Passord (valgfritt)", "vncPasswordOptional": "Passord (valgfritt)",
"vncNoResourceTarget": "Ingen ressursemål tilgjengelig", "vncNoResourceTarget": "Ingen ressursemål tilgjengelig",
"vncFailedToLoadNovnc": "Klarte ikke å laste noVNC", "vncFailedToLoadNovnc": "Klarte ikke å laste noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Site bijgewerkt", "siteUpdated": "Site bijgewerkt",
"siteUpdatedDescription": "De site is bijgewerkt.", "siteUpdatedDescription": "De site is bijgewerkt.",
"siteGeneralDescription": "Algemene instellingen voor deze site configureren", "siteGeneralDescription": "Algemene instellingen voor deze site configureren",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Configureer de instellingen van de site", "siteSettingDescription": "Configureer de instellingen van de site",
"siteResourcesTab": "Bronnen", "siteResourcesTab": "Bronnen",
"siteResourcesNoneOnSite": "Deze site heeft nog geen openbare of privébronnen.", "siteResourcesNoneOnSite": "Deze site heeft nog geen openbare of privébronnen.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Externe knooppunten", "sidebarRemoteExitNodes": "Externe knooppunten",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Geheim", "remoteExitNodeSecretKey": "Geheim",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Externe knoop aanmaken", "title": "Externe knoop aanmaken",
"description": "Maak een nieuwe zelf-gehoste externe relais- en proxyservermodule", "description": "Maak een nieuwe zelf-gehoste externe relais- en proxyservermodule",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC provider", "idpGoogleDescription": "Google OAuth2/OIDC provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Subnet", "subnet": "Subnet",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Het subnet van de netwerkconfiguratie van deze organisatie.", "subnetDescription": "Het subnet van de netwerkconfiguratie van deze organisatie.",
"customDomain": "Aangepast domein", "customDomain": "Aangepast domein",
"authPage": "Authenticatiepagina's", "authPage": "Authenticatiepagina's",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----", "sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Privésleutel is vereist", "sshPrivateKeyRequired": "Privésleutel is vereist",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Voer uw VNC-wachtwoord in om verbinding te maken",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Wachtwoord (optioneel)", "vncPasswordOptional": "Wachtwoord (optioneel)",
"vncNoResourceTarget": "Geen bron doelwit beschikbaar", "vncNoResourceTarget": "Geen bron doelwit beschikbaar",
"vncFailedToLoadNovnc": "Laden van noVNC mislukt", "vncFailedToLoadNovnc": "Laden van noVNC mislukt",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Strona zaktualizowana", "siteUpdated": "Strona zaktualizowana",
"siteUpdatedDescription": "Strona została zaktualizowana.", "siteUpdatedDescription": "Strona została zaktualizowana.",
"siteGeneralDescription": "Skonfiguruj ustawienia ogólne dla tej witryny", "siteGeneralDescription": "Skonfiguruj ustawienia ogólne dla tej witryny",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Skonfiguruj ustawienia na stronie", "siteSettingDescription": "Skonfiguruj ustawienia na stronie",
"siteResourcesTab": "Zasoby", "siteResourcesTab": "Zasoby",
"siteResourcesNoneOnSite": "Ta strona nie ma jeszcze żadnych zasobów publicznych ani prywatnych.", "siteResourcesNoneOnSite": "Ta strona nie ma jeszcze żadnych zasobów publicznych ani prywatnych.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Zdalne węzły", "sidebarRemoteExitNodes": "Zdalne węzły",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Sekret", "remoteExitNodeSecretKey": "Sekret",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Utwórz zdalny węzeł", "title": "Utwórz zdalny węzeł",
"description": "Utwórz nowy, samodzielnie hostowany węzeł przekaźnika zdalnego i serwera proxy", "description": "Utwórz nowy, samodzielnie hostowany węzeł przekaźnika zdalnego i serwera proxy",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Dostawca Google OAuth2/OIDC", "idpGoogleDescription": "Dostawca Google OAuth2/OIDC",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Podsieć", "subnet": "Podsieć",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Podsieć dla konfiguracji sieci tej organizacji.", "subnetDescription": "Podsieć dla konfiguracji sieci tej organizacji.",
"customDomain": "Niestandardowa domena", "customDomain": "Niestandardowa domena",
"authPage": "Strony uwierzytelniania", "authPage": "Strony uwierzytelniania",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----", "sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Wymagany jest klucz prywatny", "sshPrivateKeyRequired": "Wymagany jest klucz prywatny",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Wprowadź hasło VNC, aby się połączyć",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Hasło (opcjonalne)", "vncPasswordOptional": "Hasło (opcjonalne)",
"vncNoResourceTarget": "Brak dostępnego celu zasobu", "vncNoResourceTarget": "Brak dostępnego celu zasobu",
"vncFailedToLoadNovnc": "Błąd ładowania noVNC", "vncFailedToLoadNovnc": "Błąd ładowania noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Site atualizado", "siteUpdated": "Site atualizado",
"siteUpdatedDescription": "O site foi atualizado.", "siteUpdatedDescription": "O site foi atualizado.",
"siteGeneralDescription": "Configurar as configurações gerais para este site", "siteGeneralDescription": "Configurar as configurações gerais para este site",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Configurar as configurações no site", "siteSettingDescription": "Configurar as configurações no site",
"siteResourcesTab": "Recursos", "siteResourcesTab": "Recursos",
"siteResourcesNoneOnSite": "Este site ainda não possui recursos públicos ou privados.", "siteResourcesNoneOnSite": "Este site ainda não possui recursos públicos ou privados.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Nós remotos", "sidebarRemoteExitNodes": "Nós remotos",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Chave Secreta", "remoteExitNodeSecretKey": "Chave Secreta",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Criar Nó Remoto", "title": "Criar Nó Remoto",
"description": "Crie um novo nó de retransmissão e proxy servidor auto-hospedado", "description": "Crie um novo nó de retransmissão e proxy servidor auto-hospedado",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Provedor Google OAuth2/OIDC", "idpGoogleDescription": "Provedor Google OAuth2/OIDC",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Sub-rede", "subnet": "Sub-rede",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "A sub-rede para a configuração de rede dessa organização.", "subnetDescription": "A sub-rede para a configuração de rede dessa organização.",
"customDomain": "Domínio Personalizado", "customDomain": "Domínio Personalizado",
"authPage": "Páginas de Autenticação", "authPage": "Páginas de Autenticação",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----", "sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Chave privada é necessária", "sshPrivateKeyRequired": "Chave privada é necessária",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Digite sua senha VNC para conectar",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Senha (opcional)", "vncPasswordOptional": "Senha (opcional)",
"vncNoResourceTarget": "Nenhum alvo de recurso disponível", "vncNoResourceTarget": "Nenhum alvo de recurso disponível",
"vncFailedToLoadNovnc": "Falha ao carregar noVNC", "vncFailedToLoadNovnc": "Falha ao carregar noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Сайт обновлён", "siteUpdated": "Сайт обновлён",
"siteUpdatedDescription": "Сайт был успешно обновлён.", "siteUpdatedDescription": "Сайт был успешно обновлён.",
"siteGeneralDescription": "Настройте общие параметры для этого сайта", "siteGeneralDescription": "Настройте общие параметры для этого сайта",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Настройка параметров на сайте", "siteSettingDescription": "Настройка параметров на сайте",
"siteResourcesTab": "Ресурсы", "siteResourcesTab": "Ресурсы",
"siteResourcesNoneOnSite": "На этом сайте пока нет публичных или частных ресурсов.", "siteResourcesNoneOnSite": "На этом сайте пока нет публичных или частных ресурсов.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Удаленные узлы", "sidebarRemoteExitNodes": "Удаленные узлы",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "Секретный ключ", "remoteExitNodeSecretKey": "Секретный ключ",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Создать удалённый узел", "title": "Создать удалённый узел",
"description": "Создайте новый самостоятельный удалённый ретранслятор и узел прокси-сервера", "description": "Создайте новый самостоятельный удалённый ретранслятор и узел прокси-сервера",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC провайдер", "idpGoogleDescription": "Google OAuth2/OIDC провайдер",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Подсеть", "subnet": "Подсеть",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Подсеть для конфигурации сети этой организации.", "subnetDescription": "Подсеть для конфигурации сети этой организации.",
"customDomain": "Пользовательский домен", "customDomain": "Пользовательский домен",
"authPage": "Страницы аутентификации", "authPage": "Страницы аутентификации",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----НАЧАЛО ЛИЧНОГО КЛЮЧА OPENSSH-----", "sshPrivateKeyPlaceholder": "-----НАЧАЛО ЛИЧНОГО КЛЮЧА OPENSSH-----",
"sshPrivateKeyRequired": "Требуется личный ключ", "sshPrivateKeyRequired": "Требуется личный ключ",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Введите пароль VNC для подключения",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Пароль (необязательно)", "vncPasswordOptional": "Пароль (необязательно)",
"vncNoResourceTarget": "Отсутствует целевой ресурс", "vncNoResourceTarget": "Отсутствует целевой ресурс",
"vncFailedToLoadNovnc": "Не удалось загрузить noVNC", "vncFailedToLoadNovnc": "Не удалось загрузить noVNC",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "Site güncellendi", "siteUpdated": "Site güncellendi",
"siteUpdatedDescription": "Site güncellendi.", "siteUpdatedDescription": "Site güncellendi.",
"siteGeneralDescription": "Bu site için genel ayarları yapılandırın", "siteGeneralDescription": "Bu site için genel ayarları yapılandırın",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Sitenizdeki ayarları yapılandırın", "siteSettingDescription": "Sitenizdeki ayarları yapılandırın",
"siteResourcesTab": "Kaynaklar", "siteResourcesTab": "Kaynaklar",
"siteResourcesNoneOnSite": "Bu sitede henüz genel veya özel kaynak yok.", "siteResourcesNoneOnSite": "Bu sitede henüz genel veya özel kaynak yok.",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "Uzak Düğümler", "sidebarRemoteExitNodes": "Uzak Düğümler",
"remoteExitNodeId": "Kimlik", "remoteExitNodeId": "Kimlik",
"remoteExitNodeSecretKey": "Gizli", "remoteExitNodeSecretKey": "Gizli",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "Uzak Düğüm Oluştur", "title": "Uzak Düğüm Oluştur",
"description": "Yeni bir kendine misafir uzaktan ileti ve ara sunucu düğümü oluşturun", "description": "Yeni bir kendine misafir uzaktan ileti ve ara sunucu düğümü oluşturun",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC sağlayıcısı", "idpGoogleDescription": "Google OAuth2/OIDC sağlayıcısı",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC sağlayıcısı", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC sağlayıcısı",
"subnet": "Alt ağ", "subnet": "Alt ağ",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "Bu organizasyonun ağ yapılandırması için alt ağ.", "subnetDescription": "Bu organizasyonun ağ yapılandırması için alt ağ.",
"customDomain": "Özel Alan", "customDomain": "Özel Alan",
"authPage": "Kimlik Sayfaları", "authPage": "Kimlik Sayfaları",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BAŞLANGIÇ OPENSSH ÖZEL ANAHTARI-----", "sshPrivateKeyPlaceholder": "-----BAŞLANGIÇ OPENSSH ÖZEL ANAHTARI-----",
"sshPrivateKeyRequired": "Özel anahtar gereklidir", "sshPrivateKeyRequired": "Özel anahtar gereklidir",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "Bağlanmak için VNC parolanızı girin",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "Parola (isteğe bağlı)", "vncPasswordOptional": "Parola (isteğe bağlı)",
"vncNoResourceTarget": "Kaynak hedefi mevcut değil", "vncNoResourceTarget": "Kaynak hedefi mevcut değil",
"vncFailedToLoadNovnc": "NoVNC yüklenemedi", "vncFailedToLoadNovnc": "NoVNC yüklenemedi",

View File

@@ -123,16 +123,6 @@
"siteUpdated": "站点已更新", "siteUpdated": "站点已更新",
"siteUpdatedDescription": "网站已更新。", "siteUpdatedDescription": "网站已更新。",
"siteGeneralDescription": "配置此站点的常规设置", "siteGeneralDescription": "配置此站点的常规设置",
"siteRestartTitle": "Restart Site",
"siteRestartDescription": "Restart the WireGuard tunnel for this site. This will briefly interrupt connectivity.",
"siteRestartBody": "Use this if the site tunnel is not functioning correctly and you want to force a reconnect without restarting the host.",
"siteRestartButton": "Restart Site",
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted",
"siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "配置站点设置", "siteSettingDescription": "配置站点设置",
"siteResourcesTab": "资源", "siteResourcesTab": "资源",
"siteResourcesNoneOnSite": "此站点尚无公开或私人资源。", "siteResourcesNoneOnSite": "此站点尚无公开或私人资源。",
@@ -2388,21 +2378,6 @@
"sidebarRemoteExitNodes": "远程节点", "sidebarRemoteExitNodes": "远程节点",
"remoteExitNodeId": "ID", "remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "密钥", "remoteExitNodeSecretKey": "密钥",
"remoteExitNodeNetworkingTitle": "Network Settings",
"remoteExitNodeNetworkingDescription": "Configure how this remote exit node routes traffic and which sites prefer to connect through it. Advanced features to be used with backhaul networking configurations.",
"remoteExitNodeNetworkingSave": "Save Settings",
"remoteExitNodeNetworkingSaveSuccessTitle": "Network settings saved",
"remoteExitNodeNetworkingSaveSuccessDescription": "Network settings have been updated successfully.",
"remoteExitNodeNetworkingSaveError": "Failed to save network settings",
"remoteExitNodeNetworkingSubnetsTitle": "Remote Subnets",
"remoteExitNodeNetworkingSubnetsDescription": "Define the CIDR ranges that this remote exit node will route traffic to. Type a valid CIDR (e.g. <code>10.0.0.0/8</code>) and press Enter to add.",
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
"remoteExitNodeCreate": { "remoteExitNodeCreate": {
"title": "创建远程节点", "title": "创建远程节点",
"description": "创建一个新的自托管远程中继和代理服务器节点", "description": "创建一个新的自托管远程中继和代理服务器节点",
@@ -2581,7 +2556,6 @@
"idpGoogleDescription": "Google OAuth2/OIDC 提供商", "idpGoogleDescription": "Google OAuth2/OIDC 提供商",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "子网", "subnet": "子网",
"utilitySubnet": "Utility Subnet",
"subnetDescription": "此组织网络配置的子网。", "subnetDescription": "此组织网络配置的子网。",
"customDomain": "自定义域", "customDomain": "自定义域",
"authPage": "身份验证页面", "authPage": "身份验证页面",
@@ -3602,8 +3576,7 @@
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----", "sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "需要私钥", "sshPrivateKeyRequired": "需要私钥",
"vncTitle": "VNC", "vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC credentials to connect", "vncSignInDescription": "输入您的 VNC 密码以连接",
"vncUsernameOptional": "Username (optional)",
"vncPasswordOptional": "密码 (可选)", "vncPasswordOptional": "密码 (可选)",
"vncNoResourceTarget": "没有可用的资源目标", "vncNoResourceTarget": "没有可用的资源目标",
"vncFailedToLoadNovnc": "加载 noVNC 失败", "vncFailedToLoadNovnc": "加载 noVNC 失败",

View File

@@ -29,7 +29,7 @@ type ClientRow = typeof clients.$inferSelect;
function runQueuedClientAssociationRebuilds( function runQueuedClientAssociationRebuilds(
userId: string, userId: string,
queuedClients: ClientRow[] queuedClients: ClientRow[]
): void { ) {
if (queuedClients.length === 0) { if (queuedClients.length === 0) {
return; return;
} }
@@ -39,425 +39,403 @@ function runQueuedClientAssociationRebuilds(
uniqueClientsById.set(client.clientId, client); uniqueClientsById.set(client.clientId, client);
} }
void (async () => { for (const client of uniqueClientsById.values()) {
for (const client of uniqueClientsById.values()) { rebuildClientAssociationsFromClient(client).catch((error) => {
try { logger.error(
await rebuildClientAssociationsFromClient(client); `Error rebuilding client associations for client ${client.clientId} (user ${userId}): ${String(
} catch (error) { error
logger.error( )}`
`Failed rebuilding associations for client ${client.clientId} (user ${userId}): ${String(error)}` );
); });
} }
}
logger.debug( logger.debug(
`Queued association rebuild completed for ${uniqueClientsById.size} client(s) (user ${userId})` `Queued association rebuild completed for ${uniqueClientsById.size} client(s) (user ${userId})`
); );
})();
} }
export async function calculateUserClientsForOrgs( export async function calculateUserClientsForOrgs(
userId: string userId: string
): Promise<void> { ): Promise<void> {
const trx = primaryDb; const trx = primaryDb;
const queuedAssociationRebuilds: ClientRow[] = []; const queuedAssociationRebuilds: ClientRow[] = [];
const orgCache = new Map<string, typeof orgs.$inferSelect | null>();
const adminRoleCache = new Map<string, typeof roles.$inferSelect | null>();
const exitNodesCache = new Map<
string,
Awaited<ReturnType<typeof listExitNodes>>
>();
const isOrgLicensedCache = new Map<string, boolean>();
const existingClientCache = new Map<
string,
typeof clients.$inferSelect | null
>();
const roleClientAccessCache = new Map<string, boolean>();
const userClientAccessCache = new Map<string, boolean>();
const execute = async (transaction: Transaction | typeof db) => { const getOrgOlmKey = (orgId: string, olmId: string) => `${orgId}:${olmId}`;
const orgCache = new Map<string, typeof orgs.$inferSelect | null>(); const getRoleClientKey = (roleId: number, clientId: number) =>
const adminRoleCache = new Map< `${roleId}:${clientId}`;
string, const getUserClientKey = (cachedUserId: string, clientId: number) =>
typeof roles.$inferSelect | null `${cachedUserId}:${clientId}`;
>();
const exitNodesCache = new Map<
string,
Awaited<ReturnType<typeof listExitNodes>>
>();
const isOrgLicensedCache = new Map<string, boolean>();
const existingClientCache = new Map<
string,
typeof clients.$inferSelect | null
>();
const roleClientAccessCache = new Map<string, boolean>();
const userClientAccessCache = new Map<string, boolean>();
const getOrgOlmKey = (orgId: string, olmId: string) => const getOrg = async (orgId: string) => {
`${orgId}:${olmId}`; if (orgCache.has(orgId)) {
const getRoleClientKey = (roleId: number, clientId: number) => return orgCache.get(orgId) ?? null;
`${roleId}:${clientId}`;
const getUserClientKey = (cachedUserId: string, clientId: number) =>
`${cachedUserId}:${clientId}`;
const getOrg = async (orgId: string) => {
if (orgCache.has(orgId)) {
return orgCache.get(orgId) ?? null;
}
const [org] = await transaction
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
orgCache.set(orgId, org ?? null);
return org ?? null;
};
const getAdminRole = async (orgId: string) => {
if (adminRoleCache.has(orgId)) {
return adminRoleCache.get(orgId) ?? null;
}
const [adminRole] = await transaction
.select()
.from(roles)
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
.limit(1);
adminRoleCache.set(orgId, adminRole ?? null);
return adminRole ?? null;
};
const getExitNodes = async (orgId: string) => {
if (exitNodesCache.has(orgId)) {
return exitNodesCache.get(orgId)!;
}
const exitNodes = await listExitNodes(orgId);
exitNodesCache.set(orgId, exitNodes);
return exitNodes;
};
const getIsOrgLicensed = async (orgId: string) => {
if (isOrgLicensedCache.has(orgId)) {
return isOrgLicensedCache.get(orgId)!;
}
const isOrgLicensed = await isLicensedOrSubscribed(
orgId,
tierMatrix.deviceApprovals
);
isOrgLicensedCache.set(orgId, isOrgLicensed);
return isOrgLicensed;
};
const getExistingClient = async (orgId: string, olmId: string) => {
const key = getOrgOlmKey(orgId, olmId);
if (existingClientCache.has(key)) {
return existingClientCache.get(key) ?? null;
}
const [existingClient] = await transaction
.select()
.from(clients)
.where(
and(
eq(clients.userId, userId),
eq(clients.orgId, orgId),
eq(clients.olmId, olmId)
)
)
.limit(1);
existingClientCache.set(key, existingClient ?? null);
return existingClient ?? null;
};
const hasRoleClientAccess = async (
roleId: number,
clientId: number
) => {
const key = getRoleClientKey(roleId, clientId);
if (roleClientAccessCache.has(key)) {
return roleClientAccessCache.get(key)!;
}
const [existingRoleClient] = await transaction
.select()
.from(roleClients)
.where(
and(
eq(roleClients.roleId, roleId),
eq(roleClients.clientId, clientId)
)
)
.limit(1);
const hasAccess = Boolean(existingRoleClient);
roleClientAccessCache.set(key, hasAccess);
return hasAccess;
};
const hasUserClientAccess = async (
cachedUserId: string,
clientId: number
) => {
const key = getUserClientKey(cachedUserId, clientId);
if (userClientAccessCache.has(key)) {
return userClientAccessCache.get(key)!;
}
const [existingUserClient] = await transaction
.select()
.from(userClients)
.where(
and(
eq(userClients.userId, cachedUserId),
eq(userClients.clientId, clientId)
)
)
.limit(1);
const hasAccess = Boolean(existingUserClient);
userClientAccessCache.set(key, hasAccess);
return hasAccess;
};
// Get all OLMs for this user
const userOlms = await transaction
.select()
.from(olms)
.where(eq(olms.userId, userId));
if (userOlms.length === 0) {
// No OLMs for this user, but we should still clean up any orphaned clients
await cleanupOrphanedClients(
userId,
transaction,
[],
queuedAssociationRebuilds
);
return;
} }
// Get all user orgs with all roles (for org list and role-based logic) const [org] = await trx
const userOrgRoleRows = await transaction
.select() .select()
.from(userOrgs) .from(orgs)
.innerJoin( .where(eq(orgs.orgId, orgId));
userOrgRoles, orgCache.set(orgId, org ?? null);
return org ?? null;
};
const getAdminRole = async (orgId: string) => {
if (adminRoleCache.has(orgId)) {
return adminRoleCache.get(orgId) ?? null;
}
const [adminRole] = await trx
.select()
.from(roles)
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
.limit(1);
adminRoleCache.set(orgId, adminRole ?? null);
return adminRole ?? null;
};
const getExitNodes = async (orgId: string) => {
if (exitNodesCache.has(orgId)) {
return exitNodesCache.get(orgId)!;
}
const exitNodes = await listExitNodes(orgId);
exitNodesCache.set(orgId, exitNodes);
return exitNodes;
};
const getIsOrgLicensed = async (orgId: string) => {
if (isOrgLicensedCache.has(orgId)) {
return isOrgLicensedCache.get(orgId)!;
}
const isOrgLicensed = await isLicensedOrSubscribed(
orgId,
tierMatrix.deviceApprovals
);
isOrgLicensedCache.set(orgId, isOrgLicensed);
return isOrgLicensed;
};
const getExistingClient = async (orgId: string, olmId: string) => {
const key = getOrgOlmKey(orgId, olmId);
if (existingClientCache.has(key)) {
return existingClientCache.get(key) ?? null;
}
const [existingClient] = await trx
.select()
.from(clients)
.where(
and( and(
eq(userOrgs.userId, userOrgRoles.userId), eq(clients.userId, userId),
eq(userOrgs.orgId, userOrgRoles.orgId) eq(clients.orgId, orgId),
eq(clients.olmId, olmId)
) )
) )
.innerJoin(roles, eq(userOrgRoles.roleId, roles.roleId)) .limit(1);
.where(eq(userOrgs.userId, userId));
const userOrgIds = [ existingClientCache.set(key, existingClient ?? null);
...new Set(userOrgRoleRows.map((r) => r.userOrgs.orgId))
]; return existingClient ?? null;
const orgIdToRoleRows = new Map< };
string,
(typeof userOrgRoleRows)[0][] const hasRoleClientAccess = async (roleId: number, clientId: number) => {
>(); const key = getRoleClientKey(roleId, clientId);
for (const r of userOrgRoleRows) { if (roleClientAccessCache.has(key)) {
const list = orgIdToRoleRows.get(r.userOrgs.orgId) ?? []; return roleClientAccessCache.get(key)!;
list.push(r);
orgIdToRoleRows.set(r.userOrgs.orgId, list);
}
const orgRequiresDeviceApprovalRole = new Map<string, boolean>();
for (const [orgId, roleRowsForOrg] of orgIdToRoleRows.entries()) {
orgRequiresDeviceApprovalRole.set(
orgId,
roleRowsForOrg.some((r) => r.roles.requireDeviceApproval)
);
} }
// For each OLM, ensure there's a client in each org the user is in const [existingRoleClient] = await trx
for (const olm of userOlms) { .select()
for (const orgId of orgIdToRoleRows.keys()) { .from(roleClients)
const roleRowsForOrg = orgIdToRoleRows.get(orgId)!; .where(
const userOrg = roleRowsForOrg[0].userOrgs; and(
eq(roleClients.roleId, roleId),
eq(roleClients.clientId, clientId)
)
)
.limit(1);
const org = await getOrg(orgId); const hasAccess = Boolean(existingRoleClient);
roleClientAccessCache.set(key, hasAccess);
if (!org) { return hasAccess;
logger.warn( };
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): org not found`
);
continue;
}
if (!org.subnet) { const hasUserClientAccess = async (
logger.warn( cachedUserId: string,
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): org has no subnet configured` clientId: number
); ) => {
continue; const key = getUserClientKey(cachedUserId, clientId);
} if (userClientAccessCache.has(key)) {
return userClientAccessCache.get(key)!;
// Get admin role for this org (needed for access grants)
const adminRole = await getAdminRole(orgId);
if (!adminRole) {
logger.warn(
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): no admin role found`
);
continue;
}
// Check if a client already exists for this OLM+user+org combination
const existingClient = await getExistingClient(
orgId,
olm.olmId
);
if (existingClient) {
// Ensure admin role has access to the client
const hasRoleAccess = await hasRoleClientAccess(
adminRole.roleId,
existingClient.clientId
);
if (!hasRoleAccess) {
await transaction.insert(roleClients).values({
roleId: adminRole.roleId,
clientId: existingClient.clientId
});
roleClientAccessCache.set(
getRoleClientKey(
adminRole.roleId,
existingClient.clientId
),
true
);
logger.debug(
`Granted admin role access to existing client ${existingClient.clientId} for OLM ${olm.olmId} in org ${orgId} (user ${userId})`
);
}
// Ensure user has access to the client
const hasUserAccess = await hasUserClientAccess(
userId,
existingClient.clientId
);
if (!hasUserAccess) {
await transaction.insert(userClients).values({
userId,
clientId: existingClient.clientId
});
userClientAccessCache.set(
getUserClientKey(userId, existingClient.clientId),
true
);
logger.debug(
`Granted user access to existing client ${existingClient.clientId} for OLM ${olm.olmId} in org ${orgId} (user ${userId})`
);
}
logger.debug(
`Client already exists for OLM ${olm.olmId} in org ${orgId} (user ${userId}), skipping creation`
);
continue;
}
// Get exit nodes for this org
const exitNodesList = await getExitNodes(orgId);
if (exitNodesList.length === 0) {
logger.warn(
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): no exit nodes found`
);
continue;
}
const randomExitNode =
exitNodesList[
Math.floor(Math.random() * exitNodesList.length)
];
// Get next available subnet
const { value: newSubnet, release: releaseSubnetLock } =
await getNextAvailableClientSubnet(orgId, transaction);
const subnet = newSubnet.split("/")[0];
const updatedSubnet = `${subnet}/${org.subnet.split("/")[1]}`;
const niceId = await getUniqueClientName(orgId);
const isOrgLicensed = await getIsOrgLicensed(userOrg.orgId);
const requireApproval =
build !== "oss" &&
isOrgLicensed &&
orgRequiresDeviceApprovalRole.get(orgId) === true;
const newClientData: InferInsertModel<typeof clients> = {
userId,
orgId: userOrg.orgId,
exitNodeId: randomExitNode.exitNodeId,
name: olm.name || "User Client",
subnet: updatedSubnet,
olmId: olm.olmId,
type: "olm",
niceId,
approvalState: requireApproval ? "pending" : null
};
// Create the client
const [newClient] = await transaction
.insert(clients)
.values(newClientData)
.returning();
await releaseSubnetLock();
existingClientCache.set(
getOrgOlmKey(orgId, olm.olmId),
newClient
);
// create approval request
if (requireApproval) {
await transaction
.insert(approvals)
.values({
timestamp: Math.floor(new Date().getTime() / 1000),
orgId: userOrg.orgId,
clientId: newClient.clientId,
userId,
type: "user_device"
})
.returning();
}
queuedAssociationRebuilds.push(newClient);
// Grant admin role access to the client
await transaction.insert(roleClients).values({
roleId: adminRole.roleId,
clientId: newClient.clientId
});
roleClientAccessCache.set(
getRoleClientKey(adminRole.roleId, newClient.clientId),
true
);
// Grant user access to the client
await transaction.insert(userClients).values({
userId,
clientId: newClient.clientId
});
userClientAccessCache.set(
getUserClientKey(userId, newClient.clientId),
true
);
logger.debug(
`Created client for OLM ${olm.olmId} in org ${orgId} (user ${userId}) with access granted to admin role and user`
);
}
} }
// Clean up clients in orgs the user is no longer in const [existingUserClient] = await trx
.select()
.from(userClients)
.where(
and(
eq(userClients.userId, cachedUserId),
eq(userClients.clientId, clientId)
)
)
.limit(1);
const hasAccess = Boolean(existingUserClient);
userClientAccessCache.set(key, hasAccess);
return hasAccess;
};
// Get all OLMs for this user
const userOlms = await trx
.select()
.from(olms)
.where(eq(olms.userId, userId));
if (userOlms.length === 0) {
// No OLMs for this user, but we should still clean up any orphaned clients
await cleanupOrphanedClients( await cleanupOrphanedClients(
userId, userId,
transaction, trx,
userOrgIds, [],
queuedAssociationRebuilds queuedAssociationRebuilds
); );
}; return;
}
// Get all user orgs with all roles (for org list and role-based logic)
const userOrgRoleRows = await trx
.select()
.from(userOrgs)
.innerJoin(
userOrgRoles,
and(
eq(userOrgs.userId, userOrgRoles.userId),
eq(userOrgs.orgId, userOrgRoles.orgId)
)
)
.innerJoin(roles, eq(userOrgRoles.roleId, roles.roleId))
.where(eq(userOrgs.userId, userId));
const userOrgIds = [
...new Set(userOrgRoleRows.map((r) => r.userOrgs.orgId))
];
const orgIdToRoleRows = new Map<string, (typeof userOrgRoleRows)[0][]>();
for (const r of userOrgRoleRows) {
const list = orgIdToRoleRows.get(r.userOrgs.orgId) ?? [];
list.push(r);
orgIdToRoleRows.set(r.userOrgs.orgId, list);
}
const orgRequiresDeviceApprovalRole = new Map<string, boolean>();
for (const [orgId, roleRowsForOrg] of orgIdToRoleRows.entries()) {
orgRequiresDeviceApprovalRole.set(
orgId,
roleRowsForOrg.some((r) => r.roles.requireDeviceApproval)
);
}
// For each OLM, ensure there's a client in each org the user is in
for (const olm of userOlms) {
for (const orgId of orgIdToRoleRows.keys()) {
const roleRowsForOrg = orgIdToRoleRows.get(orgId)!;
const userOrg = roleRowsForOrg[0].userOrgs;
const org = await getOrg(orgId);
if (!org) {
logger.warn(
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): org not found`
);
continue;
}
if (!org.subnet) {
logger.warn(
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): org has no subnet configured`
);
continue;
}
// Get admin role for this org (needed for access grants)
const adminRole = await getAdminRole(orgId);
if (!adminRole) {
logger.warn(
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): no admin role found`
);
continue;
}
// Check if a client already exists for this OLM+user+org combination
const existingClient = await getExistingClient(orgId, olm.olmId);
if (existingClient) {
// Ensure admin role has access to the client
const hasRoleAccess = await hasRoleClientAccess(
adminRole.roleId,
existingClient.clientId
);
if (!hasRoleAccess) {
await trx.insert(roleClients).values({
roleId: adminRole.roleId,
clientId: existingClient.clientId
});
roleClientAccessCache.set(
getRoleClientKey(
adminRole.roleId,
existingClient.clientId
),
true
);
logger.debug(
`Granted admin role access to existing client ${existingClient.clientId} for OLM ${olm.olmId} in org ${orgId} (user ${userId})`
);
}
// Ensure user has access to the client
const hasUserAccess = await hasUserClientAccess(
userId,
existingClient.clientId
);
if (!hasUserAccess) {
await trx.insert(userClients).values({
userId,
clientId: existingClient.clientId
});
userClientAccessCache.set(
getUserClientKey(userId, existingClient.clientId),
true
);
logger.debug(
`Granted user access to existing client ${existingClient.clientId} for OLM ${olm.olmId} in org ${orgId} (user ${userId})`
);
}
logger.debug(
`Client already exists for OLM ${olm.olmId} in org ${orgId} (user ${userId}), skipping creation`
);
continue;
}
// Get exit nodes for this org
const exitNodesList = await getExitNodes(orgId);
if (exitNodesList.length === 0) {
logger.warn(
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): no exit nodes found`
);
continue;
}
const randomExitNode =
exitNodesList[Math.floor(Math.random() * exitNodesList.length)];
// Get next available subnet
const { value: newSubnet, release: releaseSubnetLock } =
await getNextAvailableClientSubnet(orgId, trx);
const subnet = newSubnet.split("/")[0];
const updatedSubnet = `${subnet}/${org.subnet.split("/")[1]}`;
const niceId = await getUniqueClientName(orgId);
const isOrgLicensed = await getIsOrgLicensed(userOrg.orgId);
const requireApproval =
build !== "oss" &&
isOrgLicensed &&
orgRequiresDeviceApprovalRole.get(orgId) === true;
const newClientData: InferInsertModel<typeof clients> = {
userId,
orgId: userOrg.orgId,
exitNodeId: randomExitNode.exitNodeId,
name: olm.name || "User Client",
subnet: updatedSubnet,
olmId: olm.olmId,
type: "olm",
niceId,
approvalState: requireApproval ? "pending" : null
};
// Create the client
const [newClient] = await trx
.insert(clients)
.values(newClientData)
.returning();
await releaseSubnetLock();
existingClientCache.set(getOrgOlmKey(orgId, olm.olmId), newClient);
// create approval request
if (requireApproval) {
await trx
.insert(approvals)
.values({
timestamp: Math.floor(new Date().getTime() / 1000),
orgId: userOrg.orgId,
clientId: newClient.clientId,
userId,
type: "user_device"
})
.returning();
}
queuedAssociationRebuilds.push(newClient);
// Grant admin role access to the client
await trx.insert(roleClients).values({
roleId: adminRole.roleId,
clientId: newClient.clientId
});
roleClientAccessCache.set(
getRoleClientKey(adminRole.roleId, newClient.clientId),
true
);
// Grant user access to the client
await trx.insert(userClients).values({
userId,
clientId: newClient.clientId
});
userClientAccessCache.set(
getUserClientKey(userId, newClient.clientId),
true
);
logger.debug(
`Created client for OLM ${olm.olmId} in org ${orgId} (user ${userId}) with access granted to admin role and user`
);
}
}
// Clean up clients in orgs the user is no longer in
await cleanupOrphanedClients(
userId,
trx,
userOrgIds,
queuedAssociationRebuilds
);
runQueuedClientAssociationRebuilds(userId, queuedAssociationRebuilds); runQueuedClientAssociationRebuilds(userId, queuedAssociationRebuilds);
} }
@@ -496,7 +474,7 @@ async function cleanupOrphanedClients(
) )
.returning(); .returning();
// Queue deleted clients for post-transaction association cleanup. // Queue deleted clients for post-trx association cleanup.
for (const deletedClient of deletedClients) { for (const deletedClient of deletedClients) {
queuedAssociationRebuilds.push(deletedClient); queuedAssociationRebuilds.push(deletedClient);