From 37830d211d86bf45222642de10b52a88d33b60b9 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 8 Dec 2025 10:23:11 -0500 Subject: [PATCH 01/25] use static.pangolin.net --- src/app/[orgId]/settings/clients/machine/create/page.tsx | 2 +- src/app/[orgId]/settings/sites/create/page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/[orgId]/settings/clients/machine/create/page.tsx b/src/app/[orgId]/settings/clients/machine/create/page.tsx index efad8ffc..05ace912 100644 --- a/src/app/[orgId]/settings/clients/machine/create/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/create/page.tsx @@ -136,7 +136,7 @@ export default function Page() { All: [ { title: t("install"), - command: `curl -fsSL https://pangolin.net/get-olm.sh | bash` + command: `curl -fsSL https://static.pangolin.net/get-olm.sh | bash` }, { title: t("run"), diff --git a/src/app/[orgId]/settings/sites/create/page.tsx b/src/app/[orgId]/settings/sites/create/page.tsx index be3d5e68..f4ea4f05 100644 --- a/src/app/[orgId]/settings/sites/create/page.tsx +++ b/src/app/[orgId]/settings/sites/create/page.tsx @@ -233,7 +233,7 @@ export default function Page() { All: [ { title: t("install"), - command: `curl -fsSL https://pangolin.net/get-newt.sh | bash` + command: `curl -fsSL https://static.pangolin.net/get-newt.sh | bash` }, { title: t("run"), From f9b15b9156f1536c5fec8afd26c271f67acd83b2 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 8 Dec 2025 10:31:53 -0500 Subject: [PATCH 02/25] add color to health check --- .../settings/resources/proxy/[niceId]/proxy/page.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx index 476ac6ab..00f487ea 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx @@ -931,9 +931,10 @@ export default function ReverseProxyTargets(props: { openHealthCheckDialog(row.original) } > - -
- {getStatusIcon(status)} +
+ {getStatusText(status)}
From 0234234108374edada78ac1fb86d32c41453a565 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 8 Dec 2025 10:38:29 -0500 Subject: [PATCH 03/25] fix settings footer buttons break point on mobile --- .../[remoteExitNodeId]/credentials/page.tsx | 40 ++++++++-------- .../machine/[niceId]/credentials/page.tsx | 48 ++++++++----------- .../sites/[niceId]/credentials/page.tsx | 40 ++++++++-------- src/components/Settings.tsx | 2 +- 4 files changed, 60 insertions(+), 70 deletions(-) diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx index 0cc84b27..4f66b7f8 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx @@ -198,27 +198,25 @@ export default function CredentialsPage() { {build !== "oss" && ( -
- - -
+ +
)} diff --git a/src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx index 881e6384..e75aa3eb 100644 --- a/src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx @@ -182,27 +182,25 @@ export default function CredentialsPage() { {build !== "oss" && ( -
- - -
+ +
)} @@ -229,9 +227,7 @@ export default function CredentialsPage() { )}

- {t( - "clientRegenerateAndDisconnectWarning" - )} + {t("clientRegenerateAndDisconnectWarning")}

) : ( @@ -241,9 +237,7 @@ export default function CredentialsPage() { "clientRegenerateCredentialsConfirmation" )}

-

- {t("clientRegenerateCredentialsWarning")} -

+

{t("clientRegenerateCredentialsWarning")}

)}
diff --git a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx index b2b526ab..6258cd69 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/credentials/page.tsx @@ -265,27 +265,25 @@ export default function CredentialsPage() { {build !== "oss" && ( -
- - -
+ +
)} diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 78f20b0a..194d8b46 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -66,7 +66,7 @@ export function SettingsSectionFooter({ children: React.ReactNode; }) { return ( -
+
{children}
); From 05daedc6ad5531161c826e62d92a22f1cb0801e3 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:21 -0500 Subject: [PATCH 04/25] New translations en-us.json (French) --- messages/fr-FR.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 6b489450..63e2b5d8 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -181,7 +181,7 @@ "baseDomain": "Domaine racine", "subdomnainDescription": "Le sous-domaine où la ressource sera accessible.", "resourceRawSettings": "Paramètres TCP/UDP", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Configurer comment la ressource sera accédée via TCP/UDP", "protocol": "Protocole", "protocolSelect": "Choisir un protocole", "resourcePortNumber": "Numéro de port", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "Les paramètres du proxy ont été mis à jour avec succès", "proxyErrorUpdate": "Échec de la mise à jour des paramètres du proxy", "proxyErrorUpdateDescription": "Une erreur s'est produite lors de la mise à jour des paramètres du proxy", - "targetAddr": "Host", + "targetAddr": "Hôte", "targetPort": "Port", "targetProtocol": "Protocole", "targetTlsSettings": "Configuration sécurisée de connexion", @@ -1322,7 +1322,7 @@ "dismissAll": "Tout cacher", "pangolinUpdateAvailable": "Mise à jour disponible", "pangolinUpdateAvailableInfo": "La version {version} est prête à être installée", - "pangolinUpdateAvailableReleaseNotes": "Voir les notes de version", + "pangolinUpdateAvailableReleaseNotes": "Voir les notes de publication", "newtUpdateAvailable": "Mise à jour disponible", "newtUpdateAvailableInfo": "Une nouvelle version de Newt est disponible. Veuillez mettre à jour vers la dernière version pour une meilleure expérience.", "domainPickerEnterDomain": "Domaine", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 jours", "logRetention90Days": "90 jours", "logRetentionForever": "Pour toujours", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Fin de l'année suivante", "actionLogsDescription": "Voir l'historique des actions effectuées dans cette organisation", "accessLogsDescription": "Voir les demandes d'authentification d'accès aux ressources de cette organisation", "licenseRequiredToUse": "Une licence Entreprise est nécessaire pour utiliser cette fonctionnalité.", From 79958be3804d2b444f83d3ea84dda9783c589ab9 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:22 -0500 Subject: [PATCH 05/25] New translations en-us.json (Spanish) --- messages/es-ES.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index 239a83c0..1ff02bca 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -181,7 +181,7 @@ "baseDomain": "Dominio base", "subdomnainDescription": "El subdominio al que el recurso será accesible.", "resourceRawSettings": "Configuración TCP/UDP", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Configurar cómo se accederá al recurso a través de TCP/UDP", "protocol": "Protocolo", "protocolSelect": "Seleccionar un protocolo", "resourcePortNumber": "Número de puerto", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "La configuración del proxy se ha actualizado correctamente", "proxyErrorUpdate": "Error al actualizar la configuración del proxy", "proxyErrorUpdateDescription": "Se ha producido un error al actualizar la configuración del proxy", - "targetAddr": "Host", + "targetAddr": "Anfitrión", "targetPort": "Puerto", "targetProtocol": "Protocolo", "targetTlsSettings": "Configuración de conexión segura", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "Actualizaciones de producto", "productUpdateEmpty": "Sin actualizaciones", "dismissAll": "Descartar todo", - "pangolinUpdateAvailable": "Nueva versión disponible", + "pangolinUpdateAvailable": "Actualización disponible", "pangolinUpdateAvailableInfo": "La versión {version} está lista para instalar", - "pangolinUpdateAvailableReleaseNotes": "Ver notas del lanzamiento", + "pangolinUpdateAvailableReleaseNotes": "Ver notas de lanzamiento", "newtUpdateAvailable": "Nueva actualización disponible", "newtUpdateAvailableInfo": "Hay una nueva versión de Newt disponible. Actualice a la última versión para la mejor experiencia.", "domainPickerEnterDomain": "Dominio", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 días", "logRetention90Days": "90 días", "logRetentionForever": "Para siempre", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Fin del año siguiente", "actionLogsDescription": "Ver un historial de acciones realizadas en esta organización", "accessLogsDescription": "Ver solicitudes de acceso a los recursos de esta organización", "licenseRequiredToUse": "Se requiere una licencia Enterprise para utilizar esta función.", @@ -2270,5 +2270,5 @@ "remoteExitNodeRegenerateAndDisconnectWarning": "Esto regenerará las credenciales y desconectará inmediatamente el nodo de salida remoto. El nodo de salida remoto tendrá que reiniciarse con las nuevas credenciales.", "remoteExitNodeRegenerateCredentialsConfirmation": "¿Estás seguro de que quieres regenerar las credenciales para este nodo de salida remoto?", "remoteExitNodeRegenerateCredentialsWarning": "Esto regenerará las credenciales. El nodo de salida remoto permanecerá conectado hasta que lo reinicie manualmente y utilice las nuevas credenciales.", - "agent": "Agent" + "agent": "Agente" } From c51a1c9c4de06045b4b5b25185085b83aaa0b4f3 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:25 -0500 Subject: [PATCH 06/25] New translations en-us.json (Bulgarian) --- messages/bg-BG.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 1bc2272c..5deb59e3 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -181,7 +181,7 @@ "baseDomain": "Базов домейн", "subdomnainDescription": "Поддомейнът, където ресурсът ще бъде достъпен.", "resourceRawSettings": "TCP/UDP настройки", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Конфигурирайте как ресурсът ще бъде достъпен чрез TCP/UDP", "protocol": "Протокол", "protocolSelect": "Изберете протокол", "resourcePortNumber": "Номер на порт", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "Настройките за прокси бяха успешно актуализирани", "proxyErrorUpdate": "Неуспешно актуализиране на настройки на прокси", "proxyErrorUpdateDescription": "Възникна грешка при актуализиране на настройки на прокси", - "targetAddr": "Host", + "targetAddr": "Хост", "targetPort": "Порт", "targetProtocol": "Протокол", "targetTlsSettings": "Конфигурация на защитена връзка", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "Актуализации на продукта", "productUpdateEmpty": "Няма актуализации", "dismissAll": "Отхвърляне на всички", - "pangolinUpdateAvailable": "Налична е нова версия", + "pangolinUpdateAvailable": "Актуализация е налична", "pangolinUpdateAvailableInfo": "Версия {version} е готова за инсталиране", - "pangolinUpdateAvailableReleaseNotes": "Преглед на бележките за издание", + "pangolinUpdateAvailableReleaseNotes": "Преглед на бележките за изданието", "newtUpdateAvailable": "Ново обновление", "newtUpdateAvailableInfo": "Нова версия на Newt е налична. Моля, обновете до последната версия за най-добро изживяване.", "domainPickerEnterDomain": "Домейн", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 дни", "logRetention90Days": "90 дни", "logRetentionForever": "Завинаги", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Край на следващата година", "actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация", "accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация", "licenseRequiredToUse": "Необходим е лиценз Enterprise, за да се използва тази функция.", @@ -2270,5 +2270,5 @@ "remoteExitNodeRegenerateAndDisconnectWarning": "Това ще генерира нови удостоверителни данни и незабавно ще прекъсне връзката на отдалечения възел. Отдалеченият възел ще трябва да се рестартира с новите удостоверителни данни.", "remoteExitNodeRegenerateCredentialsConfirmation": "Сигурни ли сте, че искате да генерирате новите удостоверителни данни за този отдалечен възел?", "remoteExitNodeRegenerateCredentialsWarning": "Това ще генерира нови удостоверителни данни. Отдалеченият възел ще остане свързан, докато не го рестартирате ръчно и използвате новите удостоверителни данни.", - "agent": "Agent" + "agent": "Агент" } From 64e5cc172ddabdf02fad7f93083bc7196cb30399 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:27 -0500 Subject: [PATCH 07/25] New translations en-us.json (Czech) --- messages/cs-CZ.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 6436503f..be3d2232 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -181,7 +181,7 @@ "baseDomain": "Základní doména", "subdomnainDescription": "Subdoména, kde bude zdroj přístupný.", "resourceRawSettings": "Nastavení TCP/UDP", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Nakonfigurujte, jak bude dokument přístupný přes TCP/UDP", "protocol": "Protokol", "protocolSelect": "Vybrat protokol", "resourcePortNumber": "Číslo portu", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "Nastavení proxy bylo úspěšně aktualizováno", "proxyErrorUpdate": "Aktualizace nastavení proxy se nezdařila", "proxyErrorUpdateDescription": "Došlo k chybě při aktualizaci nastavení proxy", - "targetAddr": "Host", + "targetAddr": "Hostitel", "targetPort": "Přístav", "targetProtocol": "Protokol", "targetTlsSettings": "Nastavení bezpečného připojení", @@ -1320,7 +1320,7 @@ "productUpdateTitle": "Aktualizace produktu", "productUpdateEmpty": "Žádné aktualizace", "dismissAll": "Odmítnout vše", - "pangolinUpdateAvailable": "K dispozici je nová verze", + "pangolinUpdateAvailable": "Dostupná aktualizace", "pangolinUpdateAvailableInfo": "Verze {version} je připravena k instalaci", "pangolinUpdateAvailableReleaseNotes": "Zobrazit poznámky k vydání", "newtUpdateAvailable": "Dostupná aktualizace", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 dní", "logRetention90Days": "90 dní", "logRetentionForever": "Navždy", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Konec následujícího roku", "actionLogsDescription": "Zobrazit historii akcí provedených v této organizaci", "accessLogsDescription": "Zobrazit žádosti o ověření přístupu pro zdroje v této organizaci", "licenseRequiredToUse": "Pro použití této funkce je vyžadována licence pro podnikání.", From 080e2f0a3a9446f3e8a1a8c98a7318e1448dd40f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:28 -0500 Subject: [PATCH 08/25] New translations en-us.json (German) --- messages/de-DE.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 3eba335f..333c7052 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -181,7 +181,7 @@ "baseDomain": "Basis-Domain", "subdomnainDescription": "Die Subdomäne, auf die die Ressource zugegriffen werden soll.", "resourceRawSettings": "TCP/UDP Einstellungen", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Konfigurieren, wie auf die Ressource über TCP/UDP zugegriffen wird", "protocol": "Protokoll", "protocolSelect": "Wählen Sie ein Protokoll", "resourcePortNumber": "Portnummer", @@ -1102,7 +1102,7 @@ "actionDeleteIdpOrg": "IDP-Organisationsrichtlinie löschen", "actionListIdpOrgs": "IDP-Organisationen auflisten", "actionUpdateIdpOrg": "IDP-Organisation aktualisieren", - "actionCreateClient": "Client anlegen", + "actionCreateClient": "Endgerät anlegen", "actionDeleteClient": "Client löschen", "actionUpdateClient": "Client aktualisieren", "actionListClients": "Clients auflisten", @@ -1320,7 +1320,7 @@ "productUpdateTitle": "Produkt-Updates", "productUpdateEmpty": "Keine Updates", "dismissAll": "Alle verwerfen", - "pangolinUpdateAvailable": "Neue Version verfügbar", + "pangolinUpdateAvailable": "Update verfügbar", "pangolinUpdateAvailableInfo": "Version {version} ist bereit zur Installation", "pangolinUpdateAvailableReleaseNotes": "Versionshinweise anzeigen", "newtUpdateAvailable": "Update verfügbar", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 Tage", "logRetention90Days": "90 Tage", "logRetentionForever": "Für immer", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Ende des folgenden Jahres", "actionLogsDescription": "Verlauf der in dieser Organisation durchgeführten Aktionen anzeigen", "accessLogsDescription": "Zugriffsauth-Anfragen für Ressourcen in dieser Organisation anzeigen", "licenseRequiredToUse": "Um diese Funktion nutzen zu können, ist eine Enterprise-Lizenz erforderlich.", From 7229bfa51b323f924f43d1f00077dabb128803b1 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:31 -0500 Subject: [PATCH 09/25] New translations en-us.json (Italian) --- messages/it-IT.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index 1468acd5..441c9676 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -181,7 +181,7 @@ "baseDomain": "Dominio Base", "subdomnainDescription": "Il sottodominio in cui la risorsa sarà accessibile.", "resourceRawSettings": "Impostazioni TCP/UDP", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Configura come accedere alla risorsa tramite TCP/UDP", "protocol": "Protocollo", "protocolSelect": "Seleziona un protocollo", "resourcePortNumber": "Numero Porta", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "Aggiornamenti Prodotto", "productUpdateEmpty": "Nessun aggiornamento", "dismissAll": "Ignora tutto", - "pangolinUpdateAvailable": "Nuova versione disponibile", + "pangolinUpdateAvailable": "Aggiornamento Disponibile", "pangolinUpdateAvailableInfo": "La versione {version} è pronta per l'installazione", - "pangolinUpdateAvailableReleaseNotes": "Visualizza note di rilascio", + "pangolinUpdateAvailableReleaseNotes": "Visualizza Note Di Rilascio", "newtUpdateAvailable": "Aggiornamento Disponibile", "newtUpdateAvailableInfo": "È disponibile una nuova versione di Newt. Si prega di aggiornare all'ultima versione per la migliore esperienza.", "domainPickerEnterDomain": "Dominio", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 giorni", "logRetention90Days": "90 giorni", "logRetentionForever": "Per Sempre", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Fine dell'anno successivo", "actionLogsDescription": "Visualizza una cronologia delle azioni eseguite in questa organizzazione", "accessLogsDescription": "Visualizza le richieste di autenticazione di accesso per le risorse in questa organizzazione", "licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza Enterprise.", @@ -2270,5 +2270,5 @@ "remoteExitNodeRegenerateAndDisconnectWarning": "Questo rigenererà le credenziali e disconnetterà immediatamente il nodo di uscita remoto. Il nodo di uscita remoto dovrà essere riavviato con le nuove credenziali.", "remoteExitNodeRegenerateCredentialsConfirmation": "Sei sicuro di voler rigenerare le credenziali per questo nodo di uscita remoto?", "remoteExitNodeRegenerateCredentialsWarning": "Questo rigenererà le credenziali. Il nodo di uscita remoto rimarrà connesso finché non lo riavvierai manualmente e userai le nuove credenziali.", - "agent": "Agent" + "agent": "Agente" } From c2a32a50cd4436df2126cd30dcb231ee7912df5b Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:32 -0500 Subject: [PATCH 10/25] New translations en-us.json (Korean) --- messages/ko-KR.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index a479eb49..82a12e4e 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -181,7 +181,7 @@ "baseDomain": "기본 도메인", "subdomnainDescription": "리소스에 접근할 수 있는 하위 도메인입니다.", "resourceRawSettings": "TCP/UDP 설정", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "TCP/UDP를 통해 리소스에 접근하는 방법을 구성하세요.", "protocol": "프로토콜", "protocolSelect": "프로토콜 선택", "resourcePortNumber": "포트 번호", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "프록시 설정이 성공적으로 업데이트되었습니다", "proxyErrorUpdate": "프록시 설정 업데이트에 실패했습니다.", "proxyErrorUpdateDescription": "프록시 설정을 업데이트하는 동안 오류가 발생했습니다", - "targetAddr": "Host", + "targetAddr": "호스트", "targetPort": "포트", "targetProtocol": "프로토콜", "targetTlsSettings": "보안 연결 구성", @@ -1320,7 +1320,7 @@ "productUpdateTitle": "제품 업데이트", "productUpdateEmpty": "업데이트 없음", "dismissAll": "모두 해제", - "pangolinUpdateAvailable": "새 버전 사용 가능", + "pangolinUpdateAvailable": "업데이트 가능", "pangolinUpdateAvailableInfo": "버전 {version}을(를) 설치할 준비가 되었습니다", "pangolinUpdateAvailableReleaseNotes": "릴리스 노트 보기", "newtUpdateAvailable": "업데이트 가능", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 일", "logRetention90Days": "90 일", "logRetentionForever": "영구", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "다음 연도 말", "actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다", "accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다", "licenseRequiredToUse": "이 기능을 사용하려면 Enterprise 라이선스가 필요합니다.", @@ -2270,5 +2270,5 @@ "remoteExitNodeRegenerateAndDisconnectWarning": "이 과정은 자격 증명을 다시 생성하고 원격 종료 노드와의 연결을 즉시 해제합니다. 원격 종료 노드는 새 자격 증명으로 다시 시작되어야 합니다.", "remoteExitNodeRegenerateCredentialsConfirmation": "이 원격 종료 노드에 대한 자격 증명을 다시 생성하시겠습니까?", "remoteExitNodeRegenerateCredentialsWarning": "이 과정은 자격 증명을 다시 생성합니다. 수동으로 다시 시작하고 새 자격 증명을 사용하기 전까지 원격 종료 노드는 연결된 상태로 유지됩니다.", - "agent": "Agent" + "agent": "에이전트" } From f239c4370eeeb62526ad8ce91617e4204e11605a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:34 -0500 Subject: [PATCH 11/25] New translations en-us.json (Dutch) --- messages/nl-NL.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index b98d8dc3..c235676a 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -181,7 +181,7 @@ "baseDomain": "Basis domein", "subdomnainDescription": "Het subdomein waar de bron toegankelijk zal zijn.", "resourceRawSettings": "TCP/UDP instellingen", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Stel in hoe de bron wordt benaderd via TCP/UDP", "protocol": "Protocol", "protocolSelect": "Selecteer een protocol", "resourcePortNumber": "Nummer van poort", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "Proxyinstellingen zijn succesvol bijgewerkt", "proxyErrorUpdate": "Bijwerken van proxy-instellingen mislukt", "proxyErrorUpdateDescription": "Fout opgetreden tijdens het bijwerken van de proxy-instellingen", - "targetAddr": "Host", + "targetAddr": "Hostnaam", "targetPort": "Poort", "targetProtocol": "Protocol", "targetTlsSettings": "HTTPS & TLS instellingen", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "Update Producten", "productUpdateEmpty": "Geen updates", "dismissAll": "Alles afwijzen", - "pangolinUpdateAvailable": "Nieuwe versie beschikbaar", + "pangolinUpdateAvailable": "Update beschikbaar", "pangolinUpdateAvailableInfo": "Versie {version} is klaar om te installeren", - "pangolinUpdateAvailableReleaseNotes": "Bekijk release notities", + "pangolinUpdateAvailableReleaseNotes": "Uitgaveopmerkingen bekijken", "newtUpdateAvailable": "Update beschikbaar", "newtUpdateAvailableInfo": "Er is een nieuwe versie van Newt beschikbaar. Update naar de nieuwste versie voor de beste ervaring.", "domainPickerEnterDomain": "Domein", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 dagen", "logRetention90Days": "90 dagen", "logRetentionForever": "Voor altijd", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Einde van volgend jaar", "actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie", "accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken", "licenseRequiredToUse": "Een Enterprise-licentie is vereist om deze functie te gebruiken.", From 17ee51249ca2d95b6c1293dc77ebc05396f894b2 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:36 -0500 Subject: [PATCH 12/25] New translations en-us.json (Polish) --- messages/pl-PL.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 8a0ec10b..99817d14 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -181,7 +181,7 @@ "baseDomain": "Bazowa domena", "subdomnainDescription": "Poddomena, w której zasób będzie dostępny.", "resourceRawSettings": "Ustawienia TCP/UDP", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Skonfiguruj jak zasób będzie dostępny przez TCP/UDP", "protocol": "Protokół", "protocolSelect": "Wybierz protokół", "resourcePortNumber": "Numer portu", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "Aktualizacje produktu", "productUpdateEmpty": "Brak aktualizacji", "dismissAll": "Zamknij wszystkie", - "pangolinUpdateAvailable": "Dostępna jest nowa wersja", + "pangolinUpdateAvailable": "Dostępna aktualizacja", "pangolinUpdateAvailableInfo": "Wersja {version} jest gotowa do zainstalowania", - "pangolinUpdateAvailableReleaseNotes": "Zobacz notatki o wydaniu", + "pangolinUpdateAvailableReleaseNotes": "Zobacz informacje o wydaniu", "newtUpdateAvailable": "Dostępna aktualizacja", "newtUpdateAvailableInfo": "Nowa wersja Newt jest dostępna. Prosimy o aktualizację do najnowszej wersji dla najlepszej pracy.", "domainPickerEnterDomain": "Domena", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 dni", "logRetention90Days": "90 dni", "logRetentionForever": "Na zawsze", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Koniec następnego roku", "actionLogsDescription": "Zobacz historię działań wykonywanych w tej organizacji", "accessLogsDescription": "Wyświetl prośby o autoryzację dostępu do zasobów w tej organizacji", "licenseRequiredToUse": "Licencja Enterprise jest wymagana do korzystania z tej funkcji.", From 4fee65e5a416bd63dad63cc9764a275481730f16 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:37 -0500 Subject: [PATCH 13/25] New translations en-us.json (Portuguese) --- messages/pt-PT.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 1421b27c..41ae04b0 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -181,7 +181,7 @@ "baseDomain": "Domínio Base", "subdomnainDescription": "O subdomínio onde o recurso será acessível.", "resourceRawSettings": "Configurações TCP/UDP", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Configurar como o recurso será acessado sobre TCP/UDP", "protocol": "Protocolo", "protocolSelect": "Selecione um protocolo", "resourcePortNumber": "Número da Porta", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "Configurações de proxy atualizadas com sucesso", "proxyErrorUpdate": "Falha ao atualizar configurações de proxy", "proxyErrorUpdateDescription": "Ocorreu um erro ao atualizar as configurações de proxy", - "targetAddr": "Host", + "targetAddr": "Servidor", "targetPort": "Porta", "targetProtocol": "Protocolo", "targetTlsSettings": "Configuração de conexão segura", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "Atualizações de Produto", "productUpdateEmpty": "Não há atualizações", "dismissAll": "Recusar tudo", - "pangolinUpdateAvailable": "Nova versão disponível", + "pangolinUpdateAvailable": "Atualização disponível", "pangolinUpdateAvailableInfo": "A versão {version} está pronta para ser instalada", - "pangolinUpdateAvailableReleaseNotes": "Ver notas de lançamento", + "pangolinUpdateAvailableReleaseNotes": "Ver notas de versão", "newtUpdateAvailable": "Nova Atualização Disponível", "newtUpdateAvailableInfo": "Uma nova versão do Newt está disponível. Atualize para a versão mais recente para uma melhor experiência.", "domainPickerEnterDomain": "Domínio", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 dias", "logRetention90Days": "90 dias", "logRetentionForever": "Permanentemente", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Fim do ano seguinte", "actionLogsDescription": "Visualizar histórico de ações realizadas nesta organização", "accessLogsDescription": "Ver solicitações de autenticação de recursos nesta organização", "licenseRequiredToUse": "É necessária uma licença empresarial para usar esse recurso.", @@ -2270,5 +2270,5 @@ "remoteExitNodeRegenerateAndDisconnectWarning": "Isto irá regenerar as credenciais e desconectar imediatamente o nó de saída remota. O nó de saída remota precisará ser reiniciado com as novas credenciais.", "remoteExitNodeRegenerateCredentialsConfirmation": "Você tem certeza que deseja regenerar as credenciais para este nó de saída remota?", "remoteExitNodeRegenerateCredentialsWarning": "Isto irá regenerar as credenciais. O nó de saída remota permanecerá conectado até que você o reinicie manualmente e use as novas credenciais.", - "agent": "Agent" + "agent": "Representante" } From 4084c85c000f1e6de4316dc4f80f0eb9167f098e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:39 -0500 Subject: [PATCH 14/25] New translations en-us.json (Russian) --- messages/ru-RU.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 53583ceb..d687b783 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -181,7 +181,7 @@ "baseDomain": "Базовый домен", "subdomnainDescription": "Поддомен, в котором ресурс будет доступен.", "resourceRawSettings": "Настройки TCP/UDP", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Настройка доступа к ресурсу по TCP/UDP", "protocol": "Протокол", "protocolSelect": "Выберите протокол", "resourcePortNumber": "Номер порта", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "Настройки прокси успешно обновлены", "proxyErrorUpdate": "Не удалось обновить настройки прокси", "proxyErrorUpdateDescription": "Произошла ошибка при обновлении настроек прокси", - "targetAddr": "Host", + "targetAddr": "Хост", "targetPort": "Порт", "targetProtocol": "Протокол", "targetTlsSettings": "Конфигурация безопасного соединения", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "Обновления продуктов", "productUpdateEmpty": "Нет обновлений", "dismissAll": "Отклонить все", - "pangolinUpdateAvailable": "Доступна новая версия", + "pangolinUpdateAvailable": "Доступно обновление", "pangolinUpdateAvailableInfo": "Версия {version} готова к установке", - "pangolinUpdateAvailableReleaseNotes": "Просмотреть заметки о выпуске", + "pangolinUpdateAvailableReleaseNotes": "Просмотреть примечания к выпуску", "newtUpdateAvailable": "Доступно обновление", "newtUpdateAvailableInfo": "Доступна новая версия Newt. Пожалуйста, обновитесь до последней версии для лучшего опыта.", "domainPickerEnterDomain": "Домен", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 дней", "logRetention90Days": "90 дней", "logRetentionForever": "Всегда", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Конец следующего года", "actionLogsDescription": "Просмотр истории действий, выполненных в этой организации", "accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации", "licenseRequiredToUse": "Для использования этой функции требуется лицензия предприятия.", @@ -2270,5 +2270,5 @@ "remoteExitNodeRegenerateAndDisconnectWarning": "Это позволит восстановить учётные данные и немедленно отключить удаленный узел выхода. Удаленный узел выхода должен быть перезапущен с новыми учетными данными.", "remoteExitNodeRegenerateCredentialsConfirmation": "Вы уверены, что хотите восстановить учетные данные для этого удаленного выхода узла?", "remoteExitNodeRegenerateCredentialsWarning": "Это позволит восстановить учетные данные. Удалённый узел останется подключенным, пока вы не перезапустите его вручную и воспользуетесь новыми учетными данными.", - "agent": "Agent" + "agent": "Агент" } From 2eb440d019bc317afba2eca669447f1a8c744dac Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:40 -0500 Subject: [PATCH 15/25] New translations en-us.json (Turkish) --- messages/tr-TR.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index b31a1948..7119808a 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -181,7 +181,7 @@ "baseDomain": "Temel Alan Adı", "subdomnainDescription": "Kaynağınızın erişilebileceği alt alan adı.", "resourceRawSettings": "TCP/UDP Ayarları", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Kaynaklara TCP/UDP üzerinden nasıl erişileceğini yapılandırın", "protocol": "Protokol", "protocolSelect": "Bir protokol seçin", "resourcePortNumber": "Port Numarası", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "Ürün Güncellemeleri", "productUpdateEmpty": "Güncelleme yok", "dismissAll": "Hepsini Kapat", - "pangolinUpdateAvailable": "Yeni sürüm mevcut", + "pangolinUpdateAvailable": "Güncelleme Mevcut", "pangolinUpdateAvailableInfo": "Sürüm {version} yüklenmeye hazır", - "pangolinUpdateAvailableReleaseNotes": "Sürüm notlarını görüntüleyin", + "pangolinUpdateAvailableReleaseNotes": "Yayın Notlarını Görüntüle", "newtUpdateAvailable": "Güncelleme Mevcut", "newtUpdateAvailableInfo": "Newt'in yeni bir versiyonu mevcut. En iyi deneyim için lütfen en son sürüme güncelleyin.", "domainPickerEnterDomain": "Alan Adı", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 gün", "logRetention90Days": "90 gün", "logRetentionForever": "Sonsuza kadar", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Bir sonraki yılın sonu", "actionLogsDescription": "Bu organizasyondaki eylemler geçmişini görüntüleyin", "accessLogsDescription": "Bu organizasyondaki kaynaklar için erişim kimlik doğrulama isteklerini görüntüleyin", "licenseRequiredToUse": "Bu özelliği kullanmak için bir kurumsal lisans gereklidir.", @@ -2270,5 +2270,5 @@ "remoteExitNodeRegenerateAndDisconnectWarning": "Bu, kimlik bilgilerini yeniden oluşturacak ve hemen uzak çıkış düğümünün bağlantısını kesecek. Uzak çıkış düğümü, yeni kimlik bilgileri ile yeniden başlatılmalıdır.", "remoteExitNodeRegenerateCredentialsConfirmation": "Bu uzak çıkış düğümü için kimlik bilgilerini yeniden oluşturmak istediğinizden emin misiniz?", "remoteExitNodeRegenerateCredentialsWarning": "Bu, kimlik bilgilerini yeniden oluşturacak. Uzak çıkış düğümü, manuel olarak yeniden başlatılana ve yeni kimlik bilgiler kullanılana kadar bağlı kalacak.", - "agent": "Agent" + "agent": "Aracı" } From d06cd9b5be22dad49ad6adff842e4c2091c96451 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:42 -0500 Subject: [PATCH 16/25] New translations en-us.json (Chinese Simplified) --- messages/zh-CN.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index e304c9bf..b8e1f2b1 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -181,7 +181,7 @@ "baseDomain": "根域名", "subdomnainDescription": "可访问资源的子域。", "resourceRawSettings": "TCP/UDP 设置", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "配置如何通过 TCP/UDP 访问资源", "protocol": "协议", "protocolSelect": "选择协议", "resourcePortNumber": "端口号", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "已成功更新代理设置", "proxyErrorUpdate": "更新代理设置失败", "proxyErrorUpdateDescription": "更新代理设置时出错", - "targetAddr": "Host", + "targetAddr": "主机", "targetPort": "端口", "targetProtocol": "协议", "targetTlsSettings": "安全连接配置", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "产品更新", "productUpdateEmpty": "无更新", "dismissAll": "关闭所有", - "pangolinUpdateAvailable": "新版本可用", + "pangolinUpdateAvailable": "可用更新", "pangolinUpdateAvailableInfo": "版本 {version} 已准备就绪", - "pangolinUpdateAvailableReleaseNotes": "查看发布笔记", + "pangolinUpdateAvailableReleaseNotes": "查看发布说明", "newtUpdateAvailable": "更新可用", "newtUpdateAvailableInfo": "新版本的 Newt 已可用。请更新到最新版本以获得最佳体验。", "domainPickerEnterDomain": "域名", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 天", "logRetention90Days": "90 天", "logRetentionForever": "永远的", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "下一年结束", "actionLogsDescription": "查看此机构执行的操作历史", "accessLogsDescription": "查看此机构资源的访问认证请求", "licenseRequiredToUse": "需要企业许可证才能使用此功能。", @@ -2270,5 +2270,5 @@ "remoteExitNodeRegenerateAndDisconnectWarning": "这将重新生成凭据并立即断开远程退出节点。远程退出节点将需要用新的凭据重启。", "remoteExitNodeRegenerateCredentialsConfirmation": "您确定要重新生成此远程退出节点的凭据吗?", "remoteExitNodeRegenerateCredentialsWarning": "这将重新生成凭据。远程退出节点将保持连接,直到您手动重启它并使用新凭据。", - "agent": "Agent" + "agent": "代理" } From d1c98cf650ceca57e796d2a14a1648f3a3a0e47d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Mon, 8 Dec 2025 11:56:43 -0500 Subject: [PATCH 17/25] New translations en-us.json (Norwegian Bokmal) --- messages/nb-NO.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 057949a3..ec7553b6 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -181,7 +181,7 @@ "baseDomain": "Grunndomene", "subdomnainDescription": "Underdomenet hvor ressursen vil være tilgjengelig.", "resourceRawSettings": "TCP/UDP-innstillinger", - "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", + "resourceRawSettingsDescription": "Konfigurer hvordan ressursen vil bli tilgjengelig over TCP/UDP", "protocol": "Protokoll", "protocolSelect": "Velg en protokoll", "resourcePortNumber": "Portnummer", @@ -499,7 +499,7 @@ "proxyUpdatedDescription": "Proxy innstillinger har blitt oppdatert", "proxyErrorUpdate": "En feil oppsto under oppdatering av proxyinnstillinger", "proxyErrorUpdateDescription": "En feil oppsto under oppdatering av proxyinnstillinger", - "targetAddr": "Host", + "targetAddr": "Vert", "targetPort": "Port", "targetProtocol": "Protokoll", "targetTlsSettings": "Sikker tilkoblings-konfigurasjon", @@ -1320,9 +1320,9 @@ "productUpdateTitle": "Oppdateringer om produktet", "productUpdateEmpty": "Ingen oppdateringer", "dismissAll": "Avvis alle", - "pangolinUpdateAvailable": "Ny versjon tilgjengelig", + "pangolinUpdateAvailable": "Oppdatering tilgjengelig", "pangolinUpdateAvailableInfo": "Versjon {version} er klar til å installere", - "pangolinUpdateAvailableReleaseNotes": "Se utgivelsnotater", + "pangolinUpdateAvailableReleaseNotes": "Se utgivelsesnotater", "newtUpdateAvailable": "Oppdatering tilgjengelig", "newtUpdateAvailableInfo": "En ny versjon av Newt er tilgjengelig. Vennligst oppdater til den nyeste versjonen for den beste opplevelsen.", "domainPickerEnterDomain": "Domene", @@ -2108,7 +2108,7 @@ "logRetention30Days": "30 dager", "logRetention90Days": "90 dager", "logRetentionForever": "Alltid", - "logRetentionEndOfFollowingYear": "End of following year", + "logRetentionEndOfFollowingYear": "Slutt på neste år", "actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen", "accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen", "licenseRequiredToUse": "En Enterprise lisens er påkrevd for å bruke denne funksjonen.", From 1aeb31be04f12ebf677a7a5f4dfe26f8a99baaa4 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 8 Dec 2025 14:12:10 -0500 Subject: [PATCH 18/25] remove file --- statement-breakpoint | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 statement-breakpoint diff --git a/statement-breakpoint b/statement-breakpoint deleted file mode 100644 index e69de29b..00000000 From ede51bebb587c16e1a912959877619e8455afba3 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 8 Dec 2025 19:51:32 -0500 Subject: [PATCH 19/25] use semver to compare versions in product updates --- src/components/ProductUpdates.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/ProductUpdates.tsx b/src/components/ProductUpdates.tsx index f0857160..dae64480 100644 --- a/src/components/ProductUpdates.tsx +++ b/src/components/ProductUpdates.tsx @@ -20,6 +20,7 @@ import { import { useTranslations } from "next-intl"; import { Transition } from "@headlessui/react"; import * as React from "react"; +import { gt, valid } from "semver"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { Button } from "./ui/button"; import { Badge } from "./ui/badge"; @@ -72,11 +73,15 @@ export default function ProductUpdates({ if (!data) return null; + const latestVersion = data?.latestVersion?.data?.pangolin.latestVersion; + const currentVersion = env.app.version; + const showNewVersionPopup = Boolean( - data?.latestVersion?.data && - ignoredVersionUpdate !== - data.latestVersion.data?.pangolin.latestVersion && - env.app.version !== data.latestVersion.data?.pangolin.latestVersion + latestVersion && + valid(latestVersion) && + valid(currentVersion) && + ignoredVersionUpdate !== latestVersion && + gt(latestVersion, currentVersion) ); const filteredUpdates = data.updates.filter( From 048ce850a832aa9b242162bd739e7505e0fa0afe Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 8 Dec 2025 21:12:19 -0500 Subject: [PATCH 20/25] get coutry using maxmind and clear stale device codes --- server/routers/auth/startDeviceWebAuth.ts | 24 +++++++++++------------ server/setup/clearStaleData.ts | 10 +++++++++- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/server/routers/auth/startDeviceWebAuth.ts b/server/routers/auth/startDeviceWebAuth.ts index 925df67f..85fb5262 100644 --- a/server/routers/auth/startDeviceWebAuth.ts +++ b/server/routers/auth/startDeviceWebAuth.ts @@ -13,10 +13,12 @@ import { maxmindLookup } from "@server/db/maxmind"; import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; -const bodySchema = z.object({ - deviceName: z.string().optional(), - applicationName: z.string().min(1, "Application name is required") -}).strict(); +const bodySchema = z + .object({ + deviceName: z.string().optional(), + applicationName: z.string().min(1, "Application name is required") + }) + .strict(); export type StartDeviceWebAuthBody = z.infer; @@ -34,14 +36,12 @@ function generateDeviceCode(): string { // Helper function to hash device code before storing in database function hashDeviceCode(code: string): string { - return encodeHexLowerCase( - sha256(new TextEncoder().encode(code)) - ); + return encodeHexLowerCase(sha256(new TextEncoder().encode(code))); } // Helper function to extract IP from request function extractIpFromRequest(req: Request): string | undefined { - const ip = req.ip || req.socket.remoteAddress; + const ip = req.ip; if (!ip) { return undefined; } @@ -75,10 +75,10 @@ async function getCityFromIp(ip: string): Promise { return undefined; } - // MaxMind CountryResponse doesn't include city by default - // If city data is available, it would be in result.city?.names?.en - // But since we're using CountryResponse type, we'll just return undefined - // The user said "don't do this if not easy", so we'll skip city for now + if (result.country) { + return result.country.names?.en || result.country.iso_code; + } + return undefined; } catch (error) { logger.debug("Failed to get city from IP", error); diff --git a/server/setup/clearStaleData.ts b/server/setup/clearStaleData.ts index 2e54656c..8c7e85f0 100644 --- a/server/setup/clearStaleData.ts +++ b/server/setup/clearStaleData.ts @@ -1,5 +1,5 @@ import { build } from "@server/build"; -import { db, sessionTransferToken } from "@server/db"; +import { db, deviceWebAuthCodes, sessionTransferToken } from "@server/db"; import { emailVerificationCodes, newtSessions, @@ -89,4 +89,12 @@ export async function clearStaleData() { logger.warn("Error clearing expired sessionTransferToken:", e); } } + + try { + await db + .delete(deviceWebAuthCodes) + .where(lt(deviceWebAuthCodes.expiresAt, new Date().getTime())); + } catch (e) { + logger.warn("Error clearing expired deviceWebAuthCodes:", e); + } } From e011580b96f569b747fc1ec0212206d0a8f9bcc6 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 8 Dec 2025 15:15:42 -0500 Subject: [PATCH 21/25] Update and add server version --- package-lock.json | 42 ++++++++++++++++--- .../private/lib/traefik/getTraefikConfig.ts | 2 +- server/routers/newt/getNewtToken.ts | 6 ++- server/routers/olm/getOlmToken.ts | 5 ++- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index bcda398c..de277ff9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1555,6 +1555,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -3993,6 +3994,7 @@ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -8116,6 +8118,7 @@ "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -8322,6 +8325,7 @@ "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8332,6 +8336,7 @@ "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.25.0" }, @@ -9919,6 +9924,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.12.tgz", "integrity": "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.12" }, @@ -10024,6 +10030,7 @@ "integrity": "sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -10385,6 +10392,7 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -10478,6 +10486,7 @@ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -10513,6 +10522,7 @@ "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -10546,6 +10556,7 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -10556,6 +10567,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -10631,8 +10643,7 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/@types/webpack": { "version": "5.28.5", @@ -10716,6 +10727,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz", "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", @@ -11360,6 +11372,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -11844,6 +11857,7 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -11899,6 +11913,7 @@ "integrity": "sha512-mXpa5jnIKKHeoGzBrUJrc65cXFKcILGZpU3FXR0pradUEm9MA7UZz02qfEejaMcm9iXrSOCenwwYMJ/tZ1y5Ig==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -12031,6 +12046,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -13027,6 +13043,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -13477,7 +13494,6 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -14620,6 +14636,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -14716,6 +14733,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -14893,6 +14911,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -15207,6 +15226,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -17845,7 +17865,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -17856,7 +17875,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -17979,6 +17997,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.5.7", "@swc/helpers": "0.5.15", @@ -20100,6 +20119,7 @@ "version": "4.0.3", "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -20991,6 +21011,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -21173,6 +21194,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -21650,6 +21672,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -21680,6 +21703,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -22455,6 +22479,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz", "integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -23029,6 +23054,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -24161,7 +24187,8 @@ "version": "4.1.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -24711,6 +24738,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -25252,6 +25280,7 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", "license": "MIT", + "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -25561,6 +25590,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index 2d0e3ddb..8060ccad 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -189,7 +189,7 @@ export async function getTraefikConfig( ); if (!validation.isValid) { - logger.error( + logger.debug( `Invalid path rewrite configuration for resource ${resourceId}: ${validation.error}` ); return; diff --git a/server/routers/newt/getNewtToken.ts b/server/routers/newt/getNewtToken.ts index 3bf45dcf..63797358 100644 --- a/server/routers/newt/getNewtToken.ts +++ b/server/routers/newt/getNewtToken.ts @@ -15,6 +15,7 @@ import { import { verifyPassword } from "@server/auth/password"; import logger from "@server/logger"; import config from "@server/lib/config"; +import { APP_VERSION } from "@server/lib/consts"; export const newtGetTokenBodySchema = z.object({ newtId: z.string(), @@ -94,9 +95,10 @@ export async function getNewtToken( const resToken = generateSessionToken(); await createNewtSession(resToken, existingNewt.newtId); - return response<{ token: string }>(res, { + return response<{ token: string; serverVersion: string }>(res, { data: { - token: resToken + token: resToken, + serverVersion: APP_VERSION }, success: true, error: false, diff --git a/server/routers/olm/getOlmToken.ts b/server/routers/olm/getOlmToken.ts index 9cd77c89..3852b00e 100644 --- a/server/routers/olm/getOlmToken.ts +++ b/server/routers/olm/getOlmToken.ts @@ -22,6 +22,7 @@ import { import { verifyPassword } from "@server/auth/password"; import logger from "@server/logger"; import config from "@server/lib/config"; +import { APP_VERSION } from "@server/lib/consts"; export const olmGetTokenBodySchema = z.object({ olmId: z.string(), @@ -205,10 +206,12 @@ export async function getOlmToken( return response<{ token: string; exitNodes: { publicKey: string; endpoint: string }[]; + serverVersion: string; }>(res, { data: { token: resToken, - exitNodes: exitNodesHpData + exitNodes: exitNodesHpData, + serverVersion: APP_VERSION }, success: true, error: false, From 0a9b19ecfc3a3347c57959298ec6e28b10213b07 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 8 Dec 2025 21:25:46 -0500 Subject: [PATCH 22/25] Try to fix deadlocks again Fixes FOU-284 --- .../routers/gerbil/receiveBandwidth.ts | 13 - server/routers/gerbil/receiveBandwidth.ts | 394 ++++++++++-------- 2 files changed, 225 insertions(+), 182 deletions(-) delete mode 100644 server/private/routers/gerbil/receiveBandwidth.ts diff --git a/server/private/routers/gerbil/receiveBandwidth.ts b/server/private/routers/gerbil/receiveBandwidth.ts deleted file mode 100644 index de0b2d2b..00000000 --- a/server/private/routers/gerbil/receiveBandwidth.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * This file is part of a proprietary work. - * - * Copyright (c) 2025 Fossorial, Inc. - * All rights reserved. - * - * This file is licensed under the Fossorial Commercial License. - * You may not use this file except in compliance with the License. - * Unauthorized use, copying, modification, or distribution is strictly prohibited. - * - * This file is not licensed under the AGPLv3. - */ - diff --git a/server/routers/gerbil/receiveBandwidth.ts b/server/routers/gerbil/receiveBandwidth.ts index ffbd05c1..297e7c02 100644 --- a/server/routers/gerbil/receiveBandwidth.ts +++ b/server/routers/gerbil/receiveBandwidth.ts @@ -14,12 +14,55 @@ import { build } from "@server/build"; // Track sites that are already offline to avoid unnecessary queries const offlineSites = new Set(); +// Retry configuration for deadlock handling +const MAX_RETRIES = 3; +const BASE_DELAY_MS = 50; + interface PeerBandwidth { publicKey: string; bytesIn: number; bytesOut: number; } +/** + * Check if an error is a deadlock error + */ +function isDeadlockError(error: any): boolean { + return ( + error?.code === "40P01" || + error?.cause?.code === "40P01" || + (error?.message && error.message.includes("deadlock")) + ); +} + +/** + * Execute a function with retry logic for deadlock handling + */ +async function withDeadlockRetry( + operation: () => Promise, + context: string +): Promise { + let attempt = 0; + while (true) { + try { + return await operation(); + } catch (error: any) { + if (isDeadlockError(error) && attempt < MAX_RETRIES) { + attempt++; + const baseDelay = Math.pow(2, attempt - 1) * BASE_DELAY_MS; + const jitter = Math.random() * baseDelay; + const delay = baseDelay + jitter; + logger.warn( + `Deadlock detected in ${context}, retrying attempt ${attempt}/${MAX_RETRIES} after ${delay.toFixed(0)}ms` + ); + await new Promise((resolve) => setTimeout(resolve, delay)); + continue; + } + throw error; + } + } +} + export const receiveBandwidth = async ( req: Request, res: Response, @@ -60,201 +103,208 @@ export async function updateSiteBandwidth( const currentTime = new Date(); const oneMinuteAgo = new Date(currentTime.getTime() - 60000); // 1 minute ago - // logger.debug(`Received data: ${JSON.stringify(bandwidthData)}`); + // Sort bandwidth data by publicKey to ensure consistent lock ordering across all instances + // This is critical for preventing deadlocks when multiple instances update the same sites + const sortedBandwidthData = [...bandwidthData].sort((a, b) => + a.publicKey.localeCompare(b.publicKey) + ); - await db.transaction(async (trx) => { - // First, handle sites that are actively reporting bandwidth - const activePeers = bandwidthData.filter((peer) => peer.bytesIn > 0); // Bytesout will have data as it tries to send keep alive messages + // First, handle sites that are actively reporting bandwidth + const activePeers = sortedBandwidthData.filter((peer) => peer.bytesIn > 0); - if (activePeers.length > 0) { - // Remove any active peers from offline tracking since they're sending data - activePeers.forEach((peer) => offlineSites.delete(peer.publicKey)); + // Aggregate usage data by organization (collected outside transaction) + const orgUsageMap = new Map(); + const orgUptimeMap = new Map(); - // Aggregate usage data by organization - const orgUsageMap = new Map(); - const orgUptimeMap = new Map(); + if (activePeers.length > 0) { + // Remove any active peers from offline tracking since they're sending data + activePeers.forEach((peer) => offlineSites.delete(peer.publicKey)); - // Update all active sites with bandwidth data and get the site data in one operation - const updatedSites = []; - for (const peer of activePeers) { - const [updatedSite] = await trx - .update(sites) - .set({ - megabytesOut: sql`${sites.megabytesOut} + ${peer.bytesIn}`, - megabytesIn: sql`${sites.megabytesIn} + ${peer.bytesOut}`, - lastBandwidthUpdate: currentTime.toISOString(), - online: true - }) - .where(eq(sites.pubKey, peer.publicKey)) - .returning({ - online: sites.online, - orgId: sites.orgId, - siteId: sites.siteId, - lastBandwidthUpdate: sites.lastBandwidthUpdate - }); + // Update each active site individually with retry logic + // This reduces transaction scope and allows retries per-site + for (const peer of activePeers) { + try { + const updatedSite = await withDeadlockRetry(async () => { + const [result] = await db + .update(sites) + .set({ + megabytesOut: sql`${sites.megabytesOut} + ${peer.bytesIn}`, + megabytesIn: sql`${sites.megabytesIn} + ${peer.bytesOut}`, + lastBandwidthUpdate: currentTime.toISOString(), + online: true + }) + .where(eq(sites.pubKey, peer.publicKey)) + .returning({ + online: sites.online, + orgId: sites.orgId, + siteId: sites.siteId, + lastBandwidthUpdate: sites.lastBandwidthUpdate + }); + return result; + }, `update active site ${peer.publicKey}`); if (updatedSite) { if (exitNodeId) { - if ( - await checkExitNodeOrg( - exitNodeId, - updatedSite.orgId, - trx - ) - ) { - // not allowed + const notAllowed = await checkExitNodeOrg( + exitNodeId, + updatedSite.orgId + ); + if (notAllowed) { logger.warn( `Exit node ${exitNodeId} is not allowed for org ${updatedSite.orgId}` ); - // THIS SHOULD TRIGGER THE TRANSACTION TO FAIL? - throw new Error("Exit node not allowed"); + // Skip this site but continue processing others + continue; } } - updatedSites.push({ ...updatedSite, peer }); - } - } - - // Calculate org usage aggregations using the updated site data - for (const { peer, ...site } of updatedSites) { - // Aggregate bandwidth usage for the org - const totalBandwidth = peer.bytesIn + peer.bytesOut; - const currentOrgUsage = orgUsageMap.get(site.orgId) || 0; - orgUsageMap.set(site.orgId, currentOrgUsage + totalBandwidth); - - // Add 10 seconds of uptime for each active site - const currentOrgUptime = orgUptimeMap.get(site.orgId) || 0; - orgUptimeMap.set(site.orgId, currentOrgUptime + 10 / 60); // Store in minutes and jut add 10 seconds - } - - if (calcUsageAndLimits) { - // REMOTE EXIT NODES DO NOT COUNT TOWARDS USAGE - // Process all usage updates sequentially by organization to reduce deadlock risk - const allOrgIds = new Set([...orgUsageMap.keys(), ...orgUptimeMap.keys()]); - - for (const orgId of allOrgIds) { - try { - // Process bandwidth usage for this org - const totalBandwidth = orgUsageMap.get(orgId); - if (totalBandwidth) { - const bandwidthUsage = await usageService.add( - orgId, - FeatureId.EGRESS_DATA_MB, - totalBandwidth, - trx - ); - if (bandwidthUsage) { - usageService - .checkLimitSet( - orgId, - true, - FeatureId.EGRESS_DATA_MB, - bandwidthUsage, - trx - ) - .catch((error: any) => { - logger.error( - `Error checking bandwidth limits for org ${orgId}:`, - error - ); - }); - } - } - - // Process uptime usage for this org - const totalUptime = orgUptimeMap.get(orgId); - if (totalUptime) { - const uptimeUsage = await usageService.add( - orgId, - FeatureId.SITE_UPTIME, - totalUptime, - trx - ); - if (uptimeUsage) { - usageService - .checkLimitSet( - orgId, - true, - FeatureId.SITE_UPTIME, - uptimeUsage, - trx - ) - .catch((error: any) => { - logger.error( - `Error checking uptime limits for org ${orgId}:`, - error - ); - }); - } - } - } catch (error) { - logger.error( - `Error processing usage for org ${orgId}:`, - error - ); - // Don't break the loop, continue with other orgs - } + // Aggregate bandwidth usage for the org + const totalBandwidth = peer.bytesIn + peer.bytesOut; + const currentOrgUsage = orgUsageMap.get(updatedSite.orgId) || 0; + orgUsageMap.set(updatedSite.orgId, currentOrgUsage + totalBandwidth); + + // Add 10 seconds of uptime for each active site + const currentOrgUptime = orgUptimeMap.get(updatedSite.orgId) || 0; + orgUptimeMap.set(updatedSite.orgId, currentOrgUptime + 10 / 60); } + } catch (error) { + logger.error( + `Failed to update bandwidth for site ${peer.publicKey}:`, + error + ); + // Continue with other sites } } + } - // Handle sites that reported zero bandwidth but need online status updated - const zeroBandwidthPeers = bandwidthData.filter( - (peer) => peer.bytesIn === 0 && !offlineSites.has(peer.publicKey) // Bytesout will have data as it tries to send keep alive messages - ); + // Process usage updates outside of site update transactions + // This separates the concerns and reduces lock contention + if (calcUsageAndLimits && (orgUsageMap.size > 0 || orgUptimeMap.size > 0)) { + // Sort org IDs to ensure consistent lock ordering + const allOrgIds = [...new Set([...orgUsageMap.keys(), ...orgUptimeMap.keys()])].sort(); - if (zeroBandwidthPeers.length > 0) { - const zeroBandwidthSites = await trx - .select() - .from(sites) - .where( - inArray( - sites.pubKey, - zeroBandwidthPeers.map((p) => p.publicKey) - ) - ); - - for (const site of zeroBandwidthSites) { - let newOnlineStatus = site.online; - - // Check if site should go offline based on last bandwidth update WITH DATA - if (site.lastBandwidthUpdate) { - const lastUpdateWithData = new Date( - site.lastBandwidthUpdate + for (const orgId of allOrgIds) { + try { + // Process bandwidth usage for this org + const totalBandwidth = orgUsageMap.get(orgId); + if (totalBandwidth) { + const bandwidthUsage = await usageService.add( + orgId, + FeatureId.EGRESS_DATA_MB, + totalBandwidth ); - if (lastUpdateWithData < oneMinuteAgo) { - newOnlineStatus = false; + if (bandwidthUsage) { + // Fire and forget - don't block on limit checking + usageService + .checkLimitSet( + orgId, + true, + FeatureId.EGRESS_DATA_MB, + bandwidthUsage + ) + .catch((error: any) => { + logger.error( + `Error checking bandwidth limits for org ${orgId}:`, + error + ); + }); } - } else { - // No previous data update recorded, set to offline - newOnlineStatus = false; } - // Always update lastBandwidthUpdate to show this instance is receiving reports - // Only update online status if it changed - if (site.online !== newOnlineStatus) { - const [updatedSite] = await trx - .update(sites) - .set({ - online: newOnlineStatus - }) - .where(eq(sites.siteId, site.siteId)) - .returning(); + // Process uptime usage for this org + const totalUptime = orgUptimeMap.get(orgId); + if (totalUptime) { + const uptimeUsage = await usageService.add( + orgId, + FeatureId.SITE_UPTIME, + totalUptime + ); + if (uptimeUsage) { + // Fire and forget - don't block on limit checking + usageService + .checkLimitSet( + orgId, + true, + FeatureId.SITE_UPTIME, + uptimeUsage + ) + .catch((error: any) => { + logger.error( + `Error checking uptime limits for org ${orgId}:`, + error + ); + }); + } + } + } catch (error) { + logger.error( + `Error processing usage for org ${orgId}:`, + error + ); + // Continue with other orgs + } + } + } + + // Handle sites that reported zero bandwidth but need online status updated + const zeroBandwidthPeers = sortedBandwidthData.filter( + (peer) => peer.bytesIn === 0 && !offlineSites.has(peer.publicKey) + ); + + if (zeroBandwidthPeers.length > 0) { + // Fetch all zero bandwidth sites in one query + const zeroBandwidthSites = await db + .select() + .from(sites) + .where( + inArray( + sites.pubKey, + zeroBandwidthPeers.map((p) => p.publicKey) + ) + ); + + // Sort by siteId to ensure consistent lock ordering + const sortedZeroBandwidthSites = zeroBandwidthSites.sort( + (a, b) => a.siteId - b.siteId + ); + + for (const site of sortedZeroBandwidthSites) { + let newOnlineStatus = site.online; + + // Check if site should go offline based on last bandwidth update WITH DATA + if (site.lastBandwidthUpdate) { + const lastUpdateWithData = new Date(site.lastBandwidthUpdate); + if (lastUpdateWithData < oneMinuteAgo) { + newOnlineStatus = false; + } + } else { + // No previous data update recorded, set to offline + newOnlineStatus = false; + } + + // Only update online status if it changed + if (site.online !== newOnlineStatus) { + try { + const updatedSite = await withDeadlockRetry(async () => { + const [result] = await db + .update(sites) + .set({ + online: newOnlineStatus + }) + .where(eq(sites.siteId, site.siteId)) + .returning(); + return result; + }, `update offline status for site ${site.siteId}`); if (updatedSite && exitNodeId) { - if ( - await checkExitNodeOrg( - exitNodeId, - updatedSite.orgId, - trx - ) - ) { - // not allowed + const notAllowed = await checkExitNodeOrg( + exitNodeId, + updatedSite.orgId + ); + if (notAllowed) { logger.warn( `Exit node ${exitNodeId} is not allowed for org ${updatedSite.orgId}` ); - // THIS SHOULD TRIGGER THE TRANSACTION TO FAIL? - throw new Error("Exit node not allowed"); } } @@ -262,8 +312,14 @@ export async function updateSiteBandwidth( if (!newOnlineStatus && site.pubKey) { offlineSites.add(site.pubKey); } + } catch (error) { + logger.error( + `Failed to update offline status for site ${site.siteId}:`, + error + ); + // Continue with other sites } } } - }); + } } From a306aa971b49516222bc3fef6eb84cd3a500a929 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 8 Dec 2025 21:37:17 -0500 Subject: [PATCH 23/25] Pick client endpoint as part of the transation --- server/lib/calculateUserClientsForOrgs.ts | 2 +- server/lib/ip.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/server/lib/calculateUserClientsForOrgs.ts b/server/lib/calculateUserClientsForOrgs.ts index f7666a36..9cdae547 100644 --- a/server/lib/calculateUserClientsForOrgs.ts +++ b/server/lib/calculateUserClientsForOrgs.ts @@ -166,7 +166,7 @@ export async function calculateUserClientsForOrgs( ]; // Get next available subnet - const newSubnet = await getNextAvailableClientSubnet(orgId); + const newSubnet = await getNextAvailableClientSubnet(orgId, transaction); if (!newSubnet) { logger.warn( `Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): no available subnet found` diff --git a/server/lib/ip.ts b/server/lib/ip.ts index f9b3cb61..d367a018 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -244,7 +244,8 @@ export function isIpInCidr(ip: string, cidr: string): boolean { } export async function getNextAvailableClientSubnet( - orgId: string + orgId: string, + transaction: Transaction | typeof db = db ): Promise { const [org] = await db.select().from(orgs).where(eq(orgs.orgId, orgId)); From 887af85db185c9a05d775185ff01fddf2c6662e6 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 8 Dec 2025 22:06:37 -0500 Subject: [PATCH 24/25] Fix removing remote subnet on remove site resource --- server/lib/rebuildClientAssociations.ts | 72 ++++++++++++++++++++++++- server/routers/client/targets.ts | 2 +- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/server/lib/rebuildClientAssociations.ts b/server/lib/rebuildClientAssociations.ts index 134cbc06..9095b9bc 100644 --- a/server/lib/rebuildClientAssociations.ts +++ b/server/lib/rebuildClientAssociations.ts @@ -701,11 +701,45 @@ async function handleSubnetProxyTargetUpdates( } for (const client of removedClients) { + // Check if this client still has access to another resource on this site with the same destination + const destinationStillInUse = await trx + .select() + .from(siteResources) + .innerJoin( + clientSiteResourcesAssociationsCache, + eq( + clientSiteResourcesAssociationsCache.siteResourceId, + siteResources.siteResourceId + ) + ) + .where( + and( + eq( + clientSiteResourcesAssociationsCache.clientId, + client.clientId + ), + eq(siteResources.siteId, siteResource.siteId), + eq( + siteResources.destination, + siteResource.destination + ), + ne( + siteResources.siteResourceId, + siteResource.siteResourceId + ) + ) + ); + + // Only remove remote subnet if no other resource uses the same destination + const remoteSubnetsToRemove = destinationStillInUse.length > 0 + ? [] + : generateRemoteSubnets([siteResource]); + olmJobs.push( removePeerData( client.clientId, siteResource.siteId, - generateRemoteSubnets([siteResource]), + remoteSubnetsToRemove, generateAliasConfig([siteResource]) ) ); @@ -1213,12 +1247,46 @@ async function handleMessagesForClientResources( } try { + // Check if this client still has access to another resource on this site with the same destination + const destinationStillInUse = await trx + .select() + .from(siteResources) + .innerJoin( + clientSiteResourcesAssociationsCache, + eq( + clientSiteResourcesAssociationsCache.siteResourceId, + siteResources.siteResourceId + ) + ) + .where( + and( + eq( + clientSiteResourcesAssociationsCache.clientId, + client.clientId + ), + eq(siteResources.siteId, resource.siteId), + eq( + siteResources.destination, + resource.destination + ), + ne( + siteResources.siteResourceId, + resource.siteResourceId + ) + ) + ); + + // Only remove remote subnet if no other resource uses the same destination + const remoteSubnetsToRemove = destinationStillInUse.length > 0 + ? [] + : generateRemoteSubnets([resource]); + // Remove peer data from olm olmJobs.push( removePeerData( client.clientId, resource.siteId, - generateRemoteSubnets([resource]), + remoteSubnetsToRemove, generateAliasConfig([resource]) ) ); diff --git a/server/routers/client/targets.ts b/server/routers/client/targets.ts index c9bb910b..9887a454 100644 --- a/server/routers/client/targets.ts +++ b/server/routers/client/targets.ts @@ -1,5 +1,5 @@ import { sendToClient } from "#dynamic/routers/ws"; -import { db, olms } from "@server/db"; +import { db, olms, Transaction } from "@server/db"; import { Alias, SubnetProxyTarget } from "@server/lib/ip"; import logger from "@server/logger"; import { eq } from "drizzle-orm"; From 18498a32ce51f9c7ff8e07d725a39081dc883626 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 8 Dec 2025 22:07:17 -0500 Subject: [PATCH 25/25] Quite log messages --- server/private/routers/ws/ws.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/private/routers/ws/ws.ts b/server/private/routers/ws/ws.ts index 41c400cd..f3f4c8fd 100644 --- a/server/private/routers/ws/ws.ts +++ b/server/private/routers/ws/ws.ts @@ -55,9 +55,9 @@ const processMessage = async ( try { const message: WSMessage = JSON.parse(data.toString()); - logger.debug( - `Processing message from ${clientType.toUpperCase()} ID: ${clientId}, type: ${message.type}` - ); + // logger.debug( + // `Processing message from ${clientType.toUpperCase()} ID: ${clientId}, type: ${message.type}` + // ); if (!message.type || typeof message.type !== "string") { throw new Error("Invalid message format: missing or invalid type");