mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-05 19:59:43 +00:00
Compare commits
28 Commits
f8591f27c5
...
1.19.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
784588cebc | ||
|
|
2e628fe0e4 | ||
|
|
7590e8d8a1 | ||
|
|
053ff1e799 | ||
|
|
c5ffca499e | ||
|
|
9ae54f445d | ||
|
|
8183d19400 | ||
|
|
7425daad3f | ||
|
|
39d61a35eb | ||
|
|
f3fe11c136 | ||
|
|
66b1b385a3 | ||
|
|
822a07d48e | ||
|
|
3c13b1ea15 | ||
|
|
fee635b861 | ||
|
|
7e4dea918a | ||
|
|
56187d61d5 | ||
|
|
36460d4cc0 | ||
|
|
88a9b92dc3 | ||
|
|
dd26518d6f | ||
|
|
60339706bb | ||
|
|
4b1b3d3d5b | ||
|
|
cf21bacd9c | ||
|
|
80d257b94b | ||
|
|
e0d0c5dcbf | ||
|
|
0f02d1bc02 | ||
|
|
a48032adb3 | ||
|
|
bb7729df00 | ||
|
|
e104489257 |
@@ -66,9 +66,15 @@
|
||||
"local": "Локална",
|
||||
"edit": "Редактиране",
|
||||
"siteConfirmDelete": "Потвърждение на изтриване на сайта",
|
||||
"siteConfirmDeleteAndResources": "Потвърдете изтриването на сайта и ресурсите",
|
||||
"siteDelete": "Изтриване на сайта",
|
||||
"siteDeleteAndResources": "Изтриване на сайта и ресурсите",
|
||||
"siteMessageRemove": "След премахване, сайтът вече няма да бъде достъпен. Всички цели, свързани със сайта, също ще бъдат премахнати.",
|
||||
"siteMessageRemoveAndResources": "Това ще изтрие окончателно всички публични и частни ресурси, свързани с този сайт, дори ако ресурсът е асоцииран и с други сайтове.",
|
||||
"siteQuestionRemove": "Сигурни ли сте, че искате да премахнете сайта от организацията?",
|
||||
"siteQuestionRemoveAndResources": "Наистина ли желаете да изтриете този сайт и всички свързани ресурси?",
|
||||
"sitesTableDeleteSite": "Изтриване на сайта",
|
||||
"sitesTableDeleteSiteAndResources": "Изтриване на сайта и ресурсите",
|
||||
"siteManageSites": "Управление на сайтове",
|
||||
"siteDescription": "Създайте и управлявайте сайтове, за да осигурите свързаност със частни мрежи",
|
||||
"sitesBannerTitle": "Свържете се с мрежа.",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "CIDR диапазонът на ресурса в мрежата на сайта.",
|
||||
"createInternalResourceDialogAlias": "Псевдоним",
|
||||
"createInternalResourceDialogAliasDescription": "По избор вътрешен DNS псевдоним за този ресурс.",
|
||||
"internalResourceAliasLocalWarning": "Синоними с окончание .local могат да причинят проблеми с резолюцията поради mDNS в някои мрежи.",
|
||||
"internalResourceDownstreamSchemeRequired": "Методът е задължителен за HTTP ресурси",
|
||||
"internalResourceHttpPortRequired": "Портът към целта е задължителен за HTTP ресурси",
|
||||
"siteConfiguration": "Конфигурация",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "Липсва идентификатор на организация или домейн",
|
||||
"loadingDNSRecords": "Зареждане на DNS записи...",
|
||||
"olmUpdateAvailableInfo": "Налична е актуализирана версия на Olm. Моля, актуализирайте до най-новата версия за най-добро преживяване.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "На разположение е обновена версия. Моля, обновете до най-новата версия за най-добър опит.",
|
||||
"client": "Клиент",
|
||||
"proxyProtocol": "Настройки на прокси протокол",
|
||||
"proxyProtocolDescription": "Конфигурирайте Proxy Protocol, за да запазите IP адресите на клиентите за TCP услуги.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Místní",
|
||||
"edit": "Upravit",
|
||||
"siteConfirmDelete": "Potvrdit odstranění lokality",
|
||||
"siteConfirmDeleteAndResources": "Potvrdit odstranění lokality a zdrojů",
|
||||
"siteDelete": "Odstranění lokality",
|
||||
"siteDeleteAndResources": "Odstranit lokalitu a zdroje",
|
||||
"siteMessageRemove": "Po odstranění webu již nebude přístupný. Všechny cíle spojené s webem budou také odstraněny.",
|
||||
"siteMessageRemoveAndResources": "Toto trvale odstraní všechny veřejné a soukromé zdroje spojené s touto lokalitou, i když je zdroj také přiřazen k jiným lokalitám.",
|
||||
"siteQuestionRemove": "Jste si jisti, že chcete odstranit tuto stránku z organizace?",
|
||||
"siteQuestionRemoveAndResources": "Opravdu chcete odstranit tuto lokalitu a všechny přidružené zdroje?",
|
||||
"sitesTableDeleteSite": "Odstranění lokality",
|
||||
"sitesTableDeleteSiteAndResources": "Odstranit lokalitu a zdroje",
|
||||
"siteManageSites": "Správa lokalit",
|
||||
"siteDescription": "Vytvořte a spravujte stránky pro povolení připojení k soukromým sítím",
|
||||
"sitesBannerTitle": "Připojit jakoukoli síť",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "Rozsah zdrojů CIDR v síti webu.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Volitelný interní DNS alias pro tento dokument.",
|
||||
"internalResourceAliasLocalWarning": "Aliasy končící na .local mohou způsobit problémy s vyřešením díky mDNS v některých sítích.",
|
||||
"internalResourceDownstreamSchemeRequired": "HTTP metoda je vyžadována pro HTTP zdroje",
|
||||
"internalResourceHttpPortRequired": "Přípoječný port je nutný pro HTTP zdroj",
|
||||
"siteConfiguration": "Konfigurace",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "Chybí ID organizace nebo domény",
|
||||
"loadingDNSRecords": "Načítání DNS záznamů...",
|
||||
"olmUpdateAvailableInfo": "Je k dispozici aktualizovaná verze Olm. Pro nejlepší zážitek prosím aktualizujte na nejnovější verzi.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "Je k dispozici aktualizovaná verze. Aktualizujte prosím na nejnovější verzi pro nejlepší zážitek.",
|
||||
"client": "Zákazník",
|
||||
"proxyProtocol": "Nastavení proxy protokolu",
|
||||
"proxyProtocolDescription": "Konfigurace Proxy protokolu pro zachování klientských IP adres pro služby TCP.",
|
||||
|
||||
3608
messages/da-DK.json
Normal file
3608
messages/da-DK.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -66,9 +66,15 @@
|
||||
"local": "Lokal",
|
||||
"edit": "Bearbeiten",
|
||||
"siteConfirmDelete": "Löschen des Standorts bestätigen",
|
||||
"siteConfirmDeleteAndResources": "Löschen von Standort und Ressourcen bestätigen",
|
||||
"siteDelete": "Standort löschen",
|
||||
"siteDeleteAndResources": "Standort und Ressourcen löschen",
|
||||
"siteMessageRemove": "Sobald der Standort entfernt ist, wird er nicht mehr zugänglich sein. Alle mit dem Standort verbundenen Ziele werden ebenfalls entfernt.",
|
||||
"siteMessageRemoveAndResources": "Dies wird dauerhaft alle öffentlichen und privaten Ressourcen, die mit diesem Standort verknüpft sind, löschen, selbst wenn eine Ressource auch mit anderen Standorten verbunden ist.",
|
||||
"siteQuestionRemove": "Sind Sie sicher, dass Sie den Standort aus der Organisation entfernen möchten?",
|
||||
"siteQuestionRemoveAndResources": "Sind Sie sicher, dass Sie diesen Standort und alle zugehörigen Ressourcen löschen möchten?",
|
||||
"sitesTableDeleteSite": "Standort löschen",
|
||||
"sitesTableDeleteSiteAndResources": "Standort und Ressourcen löschen",
|
||||
"siteManageSites": "Standorte verwalten",
|
||||
"siteDescription": "Erstellen und Verwalten von Standorten, um die Verbindung zu privaten Netzwerken zu ermöglichen",
|
||||
"sitesBannerTitle": "Verbinde ein beliebiges Netzwerk",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "Der CIDR-Bereich der Ressource im Netzwerk der Website.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Ein optionaler interner DNS-Alias für diese Ressource.",
|
||||
"internalResourceAliasLocalWarning": "Aliasse, die auf .local enden, können aufgrund von mDNS in einigen Netzwerken zu Auflösungsproblemen führen.",
|
||||
"internalResourceDownstreamSchemeRequired": "Schema ist für HTTP-Ressourcen erforderlich",
|
||||
"internalResourceHttpPortRequired": "Zielport ist für HTTP-Ressourcen erforderlich",
|
||||
"siteConfiguration": "Konfiguration",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "Organisation oder Domänen-ID fehlt",
|
||||
"loadingDNSRecords": "Lade DNS-Einträge...",
|
||||
"olmUpdateAvailableInfo": "Eine aktualisierte Version von Olm ist verfügbar. Bitte aktualisieren Sie auf die neueste Version für die beste Erfahrung.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "Eine aktualisierte Version ist verfügbar. Bitte aktualisieren Sie auf die neueste Version für das beste Erlebnis.",
|
||||
"client": "Client",
|
||||
"proxyProtocol": "Proxy-Protokoll-Einstellungen",
|
||||
"proxyProtocolDescription": "Konfigurieren Sie das Proxy-Protokoll, um die IP-Adressen des Clients für TCP-Dienste zu erhalten.",
|
||||
|
||||
@@ -2556,6 +2556,7 @@
|
||||
"idpGoogleDescription": "Google OAuth2/OIDC provider",
|
||||
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
|
||||
"subnet": "Subnet",
|
||||
"utilitySubnet": "Utility Subnet",
|
||||
"subnetDescription": "The subnet for this organization's network configuration.",
|
||||
"customDomain": "Custom Domain",
|
||||
"authPage": "Authentication Pages",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Local",
|
||||
"edit": "Editar",
|
||||
"siteConfirmDelete": "Confirmar Borrar Sitio",
|
||||
"siteConfirmDeleteAndResources": "Confirmar eliminación del sitio y recursos",
|
||||
"siteDelete": "Eliminar sitio",
|
||||
"siteDeleteAndResources": "Eliminar sitio y recursos",
|
||||
"siteMessageRemove": "Una vez eliminado, el sitio ya no será accesible. Todos los objetivos asociados con el sitio también serán eliminados.",
|
||||
"siteMessageRemoveAndResources": "Esto eliminará permanentemente todos los recursos públicos y privados vinculados a este sitio, incluso si un recurso también está asociado con otros sitios.",
|
||||
"siteQuestionRemove": "¿Está seguro que desea eliminar el sitio de la organización?",
|
||||
"siteQuestionRemoveAndResources": "¿Está seguro de que desea eliminar este sitio y todos los recursos asociados?",
|
||||
"sitesTableDeleteSite": "Eliminar sitio",
|
||||
"sitesTableDeleteSiteAndResources": "Eliminar sitio y recursos",
|
||||
"siteManageSites": "Administrar Sitios",
|
||||
"siteDescription": "Crear y administrar sitios para permitir la conectividad a redes privadas",
|
||||
"sitesBannerTitle": "Conectar cualquier red",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "El rango CIDR del recurso en la red del sitio.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Un alias DNS interno opcional para este recurso.",
|
||||
"internalResourceAliasLocalWarning": "Los alias que terminan en .local pueden causar problemas de resolución debido a mDNS en algunas redes.",
|
||||
"internalResourceDownstreamSchemeRequired": "Se requiere el método para recursos HTTP",
|
||||
"internalResourceHttpPortRequired": "Se requiere el puerto de destino para recursos HTTP",
|
||||
"siteConfiguration": "Configuración",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "Falta el ID de organización o dominio",
|
||||
"loadingDNSRecords": "Cargando registros DNS...",
|
||||
"olmUpdateAvailableInfo": "Una versión actualizada de Olm está disponible. Por favor, actualice a la última versión para obtener la mejor experiencia.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "Hay una versión actualizada disponible. Actualice a la última versión para obtener la mejor experiencia.",
|
||||
"client": "Cliente",
|
||||
"proxyProtocol": "Configuración del Protocolo Proxy",
|
||||
"proxyProtocolDescription": "Configurar el protocolo de proxy para preservar las direcciones IP del cliente para los servicios TCP.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Locale",
|
||||
"edit": "Modifier",
|
||||
"siteConfirmDelete": "Confirmer la suppression du nœud",
|
||||
"siteConfirmDeleteAndResources": "Confirmer la suppression du site et des ressources",
|
||||
"siteDelete": "Supprimer le nœud",
|
||||
"siteDeleteAndResources": "Supprimer le site et les ressources",
|
||||
"siteMessageRemove": "Une fois supprimé, le nœud ne sera plus accessible. Toutes les cibles associées au nœud seront également supprimées.",
|
||||
"siteMessageRemoveAndResources": "Cela supprimera définitivement toutes les ressources publiques et privées liées à ce site, même si une ressource est également associée à d'autres sites.",
|
||||
"siteQuestionRemove": "Êtes-vous sûr de vouloir supprimer ce nœud de l'organisation ?",
|
||||
"siteQuestionRemoveAndResources": "Êtes-vous sûr de vouloir supprimer ce site et toutes les ressources associées?",
|
||||
"sitesTableDeleteSite": "Supprimer le site",
|
||||
"sitesTableDeleteSiteAndResources": "Supprimer le site et les ressources",
|
||||
"siteManageSites": "Gérer les nœuds",
|
||||
"siteDescription": "Créer et gérer des sites pour activer la connectivité aux réseaux privés",
|
||||
"sitesBannerTitle": "Se connecter à n'importe quel réseau",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "La gamme CIDR de la ressource sur le réseau du site.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Un alias DNS interne optionnel pour cette ressource.",
|
||||
"internalResourceAliasLocalWarning": "Les alias se terminant par .local peuvent causer des problèmes de résolution dus au mDNS sur certains réseaux.",
|
||||
"internalResourceDownstreamSchemeRequired": "Un schéma est requis pour les ressources HTTP",
|
||||
"internalResourceHttpPortRequired": "Le port de destination est requis pour les ressources HTTP",
|
||||
"siteConfiguration": "Configuration",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "L'organisation ou l'identifiant de domaine est manquant",
|
||||
"loadingDNSRecords": "Chargement des enregistrements DNS...",
|
||||
"olmUpdateAvailableInfo": "Une version mise à jour de Olm est disponible. Veuillez mettre à jour vers la dernière version pour la meilleure expérience.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "Une version mise à jour est disponible. Veuillez mettre à jour vers la dernière version pour une meilleure expérience.",
|
||||
"client": "Client",
|
||||
"proxyProtocol": "Paramètres du protocole proxy",
|
||||
"proxyProtocolDescription": "Configurer le protocole Proxy pour préserver les adresses IP du client pour les services TCP.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Locale",
|
||||
"edit": "Modifica",
|
||||
"siteConfirmDelete": "Conferma Eliminazione Sito",
|
||||
"siteConfirmDeleteAndResources": "Conferma Eliminazione Sito e Risorse",
|
||||
"siteDelete": "Elimina Sito",
|
||||
"siteDeleteAndResources": "Elimina Sito e Risorse",
|
||||
"siteMessageRemove": "Una volta rimosso il sito non sarà più accessibile. Tutti gli oggetti associati al sito verranno rimossi.",
|
||||
"siteMessageRemoveAndResources": "Questo eliminerà permanentemente tutte le risorse pubbliche e private collegate a questo sito, anche se una risorsa è anche associata ad altri siti.",
|
||||
"siteQuestionRemove": "Sei sicuro di voler rimuovere il sito dall'organizzazione?",
|
||||
"siteQuestionRemoveAndResources": "Sei sicuro di voler eliminare questo sito e tutte le risorse associate?",
|
||||
"sitesTableDeleteSite": "Elimina Sito",
|
||||
"sitesTableDeleteSiteAndResources": "Elimina Sito e Risorse",
|
||||
"siteManageSites": "Gestisci Siti",
|
||||
"siteDescription": "Creare e gestire siti per abilitare la connettività a reti private",
|
||||
"sitesBannerTitle": "Connetti Qualsiasi Rete",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "La gamma CIDR della risorsa sulla rete del sito.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Un alias DNS interno opzionale per questa risorsa.",
|
||||
"internalResourceAliasLocalWarning": "Gli alias che terminano in .local possono causare problemi di risoluzione a causa di mDNS su alcune reti.",
|
||||
"internalResourceDownstreamSchemeRequired": "Il metodo è richiesto per risorse HTTP",
|
||||
"internalResourceHttpPortRequired": "Porta di destinazione richiesta per risorse HTTP",
|
||||
"siteConfiguration": "Configurazione",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "Manca l'ID dell'organizzazione o del dominio",
|
||||
"loadingDNSRecords": "Caricamento record DNS...",
|
||||
"olmUpdateAvailableInfo": "È disponibile una versione aggiornata di Olm. Si prega di aggiornare all'ultima versione per la migliore esperienza.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "È disponibile una versione aggiornata. Si prega di aggiornare all'ultima versione per la migliore esperienza.",
|
||||
"client": "Client",
|
||||
"proxyProtocol": "Impostazioni Protocollo Proxy",
|
||||
"proxyProtocolDescription": "Configurare il protocollo proxy per preservare gli indirizzi IP client per i servizi TCP.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "로컬",
|
||||
"edit": "편집",
|
||||
"siteConfirmDelete": "사이트 삭제 확인",
|
||||
"siteConfirmDeleteAndResources": "사이트 및 리소스 삭제 확인",
|
||||
"siteDelete": "사이트 삭제",
|
||||
"siteDeleteAndResources": "사이트 및 리소스 삭제",
|
||||
"siteMessageRemove": "삭제되면 사이트에 더 이상 액세스할 수 없습니다. 사이트와 연결된 모든 대상도 삭제됩니다.",
|
||||
"siteMessageRemoveAndResources": "이 사이트와 연결된 모든 공용 및 개인 리소스는 다른 사이트에도 연결되어 있더라도 영구적으로 삭제됩니다.",
|
||||
"siteQuestionRemove": "조직에서 사이트를 제거하시겠습니까?",
|
||||
"siteQuestionRemoveAndResources": "이 사이트와 모든 관련 리소스를 삭제하시겠습니까?",
|
||||
"sitesTableDeleteSite": "사이트 삭제",
|
||||
"sitesTableDeleteSiteAndResources": "사이트 및 리소스 삭제",
|
||||
"siteManageSites": "사이트 관리",
|
||||
"siteDescription": "프라이빗 네트워크로의 연결을 활성화하려면 사이트를 생성하고 관리하세요.",
|
||||
"sitesBannerTitle": "모든 네트워크 연결",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "사이트 네트워크의 자원 IP 주소입니다.",
|
||||
"createInternalResourceDialogAlias": "별칭",
|
||||
"createInternalResourceDialogAliasDescription": "이 리소스에 대한 선택적 내부 DNS 별칭입니다.",
|
||||
"internalResourceAliasLocalWarning": ".local로 끝나는 별칭은 일부 네트워크에서 mDNS로 인해 해결 문제가 발생할 수 있습니다.",
|
||||
"internalResourceDownstreamSchemeRequired": "HTTP 리소스에 스킴이 필요합니다",
|
||||
"internalResourceHttpPortRequired": "HTTP 리소스에 목적지 포트가 필요합니다",
|
||||
"siteConfiguration": "설정",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "조직 ID 또는 도메인 ID가 누락되었습니다",
|
||||
"loadingDNSRecords": "DNS 레코드를 로드하는 중...",
|
||||
"olmUpdateAvailableInfo": "올름의 새 버전이 이용 가능합니다. 최상의 경험을 위해 최신 버전으로 업데이트하세요.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "업데이트된 버전이 있습니다. 최상의 경험을 위해 최신 버전으로 업데이트하세요.",
|
||||
"client": "클라이언트",
|
||||
"proxyProtocol": "프록시 프로토콜 설정",
|
||||
"proxyProtocolDescription": "TCP 서비스에 대한 클라이언트 IP 주소를 유지하도록 프록시 프로토콜을 구성하세요.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Lokal",
|
||||
"edit": "Rediger",
|
||||
"siteConfirmDelete": "Bekreft Sletting av Område",
|
||||
"siteConfirmDeleteAndResources": "Bekreft sletting av nettsted og ressurser",
|
||||
"siteDelete": "Slett Område",
|
||||
"siteDeleteAndResources": "Slett nettsted og ressurser",
|
||||
"siteMessageRemove": "Når nettstedet er fjernet, vil det ikke lenger være tilgjengelig. Alle målene for nettstedet vil også bli fjernet.",
|
||||
"siteMessageRemoveAndResources": "Dette vil permanent slette alle offentlige og private ressurser tilknyttet dette nettstedet, selv om en ressurs også er tilknyttet andre nettsteder.",
|
||||
"siteQuestionRemove": "Er du sikker på at du vil fjerne nettstedet fra organisasjonen?",
|
||||
"siteQuestionRemoveAndResources": "Er du sikker på at du vil slette dette nettstedet og alle tilknyttede ressurser?",
|
||||
"sitesTableDeleteSite": "Slett nettsted",
|
||||
"sitesTableDeleteSiteAndResources": "Slett nettsted og ressurser",
|
||||
"siteManageSites": "Administrer Områder",
|
||||
"siteDescription": "Opprette og administrere nettsteder for å aktivere tilkobling til private nettverk",
|
||||
"sitesBannerTitle": "Koble til alle nettverk",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "CIDR-rekkevidden til ressursen på nettstedets nettverk.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Et valgfritt internt DNS-alias for denne ressursen.",
|
||||
"internalResourceAliasLocalWarning": "Alias som slutter på .local kan forårsake oppløsningsproblemer på grunn av mDNS på enkelte nettverk.",
|
||||
"internalResourceDownstreamSchemeRequired": "Skjema er påkrevd for HTTP-ressurser",
|
||||
"internalResourceHttpPortRequired": "Destinasjonsport er nødvendig for HTTP-ressurser",
|
||||
"siteConfiguration": "Konfigurasjon",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "ID for organisasjon eller domene mangler",
|
||||
"loadingDNSRecords": "Laster DNS-poster...",
|
||||
"olmUpdateAvailableInfo": "En oppdatert versjon av Olm er tilgjengelig. Oppdater til den nyeste versjonen for å få den beste opplevelsen.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "En oppdatert versjon er tilgjengelig. Vennligst oppdater til den nyeste versjonen for den beste opplevelsen.",
|
||||
"client": "Klient",
|
||||
"proxyProtocol": "Protokoll innstillinger for Protokoll",
|
||||
"proxyProtocolDescription": "Konfigurer Proxy-protokoll for å bevare klientens IP-adresser til TCP-tjenester.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Lokaal",
|
||||
"edit": "Bewerken",
|
||||
"siteConfirmDelete": "Verwijderen van site bevestigen",
|
||||
"siteConfirmDeleteAndResources": "Bevestig Verwijderen van Site en Bronnen",
|
||||
"siteDelete": "Site verwijderen",
|
||||
"siteDeleteAndResources": "Site en Bronnen verwijderen",
|
||||
"siteMessageRemove": "Eenmaal verwijderd zal de site niet langer toegankelijk zijn. Alle aan de site gekoppelde doelen zullen ook worden verwijderd.",
|
||||
"siteMessageRemoveAndResources": "Dit zal permanent alle publieke en private resources gekoppeld aan deze site verwijderen, zelfs als een resource ook aan andere sites is gekoppeld.",
|
||||
"siteQuestionRemove": "Weet u zeker dat u de site wilt verwijderen uit de organisatie?",
|
||||
"siteQuestionRemoveAndResources": "Weet u zeker dat u deze site en alle gekoppelde resources wilt verwijderen?",
|
||||
"sitesTableDeleteSite": "Site verwijderen",
|
||||
"sitesTableDeleteSiteAndResources": "Site en Bronnen verwijderen",
|
||||
"siteManageSites": "Sites beheren",
|
||||
"siteDescription": "Maak en beheer sites om verbinding met privénetwerken in te schakelen",
|
||||
"sitesBannerTitle": "Verbind elk netwerk",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "Het CIDR-bereik van het document op het netwerk van de site.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Een optionele interne DNS-alias voor dit document.",
|
||||
"internalResourceAliasLocalWarning": "Aliassen die eindigen op .local kunnen resolutieproblemen veroorzaken vanwege mDNS op sommige netwerken.",
|
||||
"internalResourceDownstreamSchemeRequired": "Schema is vereist voor HTTP-bronnen",
|
||||
"internalResourceHttpPortRequired": "Bestemmingspoort is vereist voor HTTP-bronnen",
|
||||
"siteConfiguration": "Configuratie",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "Organisatie of domein ID ontbreekt",
|
||||
"loadingDNSRecords": "DNS-records laden...",
|
||||
"olmUpdateAvailableInfo": "Er is een bijgewerkte versie van Olm beschikbaar. Update alstublieft naar de nieuwste versie voor de beste ervaring.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "Er is een bijgewerkte versie beschikbaar. Update naar de nieuwste versie voor de beste ervaring.",
|
||||
"client": "Klant",
|
||||
"proxyProtocol": "Proxy Protocol Instellingen",
|
||||
"proxyProtocolDescription": "Proxyprotocol configureren om de IP-adressen van de client voor TCP-diensten te bewaren.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Lokalny",
|
||||
"edit": "Edytuj",
|
||||
"siteConfirmDelete": "Potwierdź usunięcie witryny",
|
||||
"siteConfirmDeleteAndResources": "Potwierdź usunięcie witryny i zasobów",
|
||||
"siteDelete": "Usuń witrynę",
|
||||
"siteDeleteAndResources": "Usuń witrynę i zasoby",
|
||||
"siteMessageRemove": "Po usunięciu witryna nie będzie już dostępna. Wszystkie cele związane z witryną zostaną również usunięte.",
|
||||
"siteMessageRemoveAndResources": "To spowoduje trwałe usunięcie wszystkich zasobów publicznych i prywatnych powiązanych z tą witryną, nawet jeśli zasób jest także powiązany z innymi witrynami.",
|
||||
"siteQuestionRemove": "Czy na pewno chcesz usunąć witrynę z organizacji?",
|
||||
"siteQuestionRemoveAndResources": "Czy na pewno chcesz usunąć tę witrynę i wszystkie powiązane zasoby?",
|
||||
"sitesTableDeleteSite": "Usuń witrynę",
|
||||
"sitesTableDeleteSiteAndResources": "Usuń witrynę i zasoby",
|
||||
"siteManageSites": "Zarządzaj stronami",
|
||||
"siteDescription": "Tworzenie stron i zarządzanie nimi, aby włączyć połączenia z prywatnymi sieciami",
|
||||
"sitesBannerTitle": "Połącz dowolną sieć",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "Zakres CIDR zasobu w sieci witryny.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Opcjonalny wewnętrzny alias DNS dla tego zasobu.",
|
||||
"internalResourceAliasLocalWarning": "Alias kończący się na .local może powodować problemy z rozpoznawaniem z powodu mDNS w niektórych sieciach.",
|
||||
"internalResourceDownstreamSchemeRequired": "Schemat jest wymagany dla zasobów HTTP",
|
||||
"internalResourceHttpPortRequired": "Port docelowy jest wymagany dla zasobów HTTP",
|
||||
"siteConfiguration": "Konfiguracja",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "Brakuje identyfikatora organizacji lub domeny",
|
||||
"loadingDNSRecords": "Ładowanie rekordów DNS...",
|
||||
"olmUpdateAvailableInfo": "Dostępna jest zaktualizowana wersja Olm. Zaktualizuj do najnowszej wersji, aby uzyskać najlepsze doświadczenia.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "Dostępna jest zaktualizowana wersja. Zaktualizuj do najnowszej wersji, aby uzyskać najlepsze wrażenia z użytkowania.",
|
||||
"client": "Klient",
|
||||
"proxyProtocol": "Ustawienia protokołu proxy",
|
||||
"proxyProtocolDescription": "Skonfiguruj protokół Proxy aby zachować adresy IP klienta dla usług TCP.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Localização",
|
||||
"edit": "Alterar",
|
||||
"siteConfirmDelete": "Confirmar que pretende apagar o site",
|
||||
"siteConfirmDeleteAndResources": "Confirmar Exclusão do Site e Recursos",
|
||||
"siteDelete": "Excluir site",
|
||||
"siteDeleteAndResources": "Excluir Site e Recursos",
|
||||
"siteMessageRemove": "Uma vez removido, o site não estará mais acessível. Todas as metas associadas ao site também serão removidas.",
|
||||
"siteMessageRemoveAndResources": "Isso excluirá permanentemente todos os recursos públicos e privados vinculados a este site, mesmo que um recurso também esteja associado a outros sites.",
|
||||
"siteQuestionRemove": "Você tem certeza que deseja remover este site da organização?",
|
||||
"siteQuestionRemoveAndResources": "Tem certeza de que deseja excluir este site e todos os recursos associados?",
|
||||
"sitesTableDeleteSite": "Excluir Site",
|
||||
"sitesTableDeleteSiteAndResources": "Excluir Site e Recursos",
|
||||
"siteManageSites": "Gerir sites",
|
||||
"siteDescription": "Criar e gerenciar sites para ativar a conectividade a redes privadas",
|
||||
"sitesBannerTitle": "Conectar a Qualquer Rede",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "A faixa CIDR do recurso na rede do site.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Um alias de DNS interno opcional para este recurso.",
|
||||
"internalResourceAliasLocalWarning": "Os aliases terminando em .local podem causar problemas de resolução devido ao mDNS em algumas redes.",
|
||||
"internalResourceDownstreamSchemeRequired": "Esquema é obrigatório para recursos HTTP",
|
||||
"internalResourceHttpPortRequired": "Porta de destino é obrigatória para recursos HTTP",
|
||||
"siteConfiguration": "Configuração",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "ID da organização ou domínio está faltando",
|
||||
"loadingDNSRecords": "Carregando registros DNS...",
|
||||
"olmUpdateAvailableInfo": "Uma versão atualizada do Olm está disponível. Atualize para a versão mais recente para ter a melhor experiência.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "Uma versão atualizada está disponível. Por favor, atualize para a versão mais recente para uma melhor experiência.",
|
||||
"client": "Cliente",
|
||||
"proxyProtocol": "Configurações de Protocolo Proxy",
|
||||
"proxyProtocolDescription": "Configurar o protocolo proxy para preservar endereços IP do cliente para serviços TCP.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Локальный",
|
||||
"edit": "Редактировать",
|
||||
"siteConfirmDelete": "Подтвердить удаление сайта",
|
||||
"siteConfirmDeleteAndResources": "Подтвердите удаление сайта и ресурсов",
|
||||
"siteDelete": "Удалить сайт",
|
||||
"siteDeleteAndResources": "Удалить сайт и ресурсы",
|
||||
"siteMessageRemove": "После удаления сайт больше не будет доступен. Все цели, связанные с сайтом, также будут удалены.",
|
||||
"siteMessageRemoveAndResources": "Это навсегда удалит все общественные и частные ресурсы, связанные с этим сайтом, даже если ресурс также связан с другими сайтами.",
|
||||
"siteQuestionRemove": "Вы уверены, что хотите удалить сайт из организации?",
|
||||
"siteQuestionRemoveAndResources": "Вы уверены, что хотите удалить этот сайт и все связанные с ним ресурсы?",
|
||||
"sitesTableDeleteSite": "Удалить сайт",
|
||||
"sitesTableDeleteSiteAndResources": "Удалить сайт и ресурсы",
|
||||
"siteManageSites": "Управление сайтами",
|
||||
"siteDescription": "Создание и управление сайтами, чтобы включить подключение к приватным сетям",
|
||||
"sitesBannerTitle": "Подключить любую сеть",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "Диапазон CIDR ресурса в сети сайта.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Дополнительный внутренний DNS псевдоним для этого ресурса.",
|
||||
"internalResourceAliasLocalWarning": "Псевдонимы, оканчивающиеся на .local, могут вызывать проблемы с разрешением из-за mDNS в некоторых сетях.",
|
||||
"internalResourceDownstreamSchemeRequired": "Схема обязательна для HTTP ресурсов",
|
||||
"internalResourceHttpPortRequired": "Порт назначения обязателен для HTTP ресурсов",
|
||||
"siteConfiguration": "Конфигурация",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "Отсутствует организация или ID домена",
|
||||
"loadingDNSRecords": "Загрузка записей DNS...",
|
||||
"olmUpdateAvailableInfo": "Доступна обновленная версия Олма. Пожалуйста, обновитесь до последней версии.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "Доступна обновленная версия. Пожалуйста, обновитесь до последней версии для получения лучшего опыта.",
|
||||
"client": "Клиент",
|
||||
"proxyProtocol": "Настройки протокола прокси",
|
||||
"proxyProtocolDescription": "Настроить Прокси-протокол для сохранения IP-адресов клиента для служб TCP.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "Yerel",
|
||||
"edit": "Düzenle",
|
||||
"siteConfirmDelete": "Site Silmeyi Onayla",
|
||||
"siteConfirmDeleteAndResources": "Site ve Kaynakları Silmeyi Onayla",
|
||||
"siteDelete": "Siteyi Sil",
|
||||
"siteDeleteAndResources": "Site ve Kaynakları Sil",
|
||||
"siteMessageRemove": "Kaldırıldıktan sonra site artık erişilebilir olmayacaktır. Siteyle ilişkilendirilmiş tüm hedefler de kaldırılacaktır.",
|
||||
"siteMessageRemoveAndResources": "Bu işlem, diğer sitelerle de ilişkilendirilmiş olsa bile, bu siteye bağlı tüm genel ve özel kaynakları kalıcı olarak silecektir.",
|
||||
"siteQuestionRemove": "Siteyi organizasyondan kaldırmak istediğinizden emin misiniz?",
|
||||
"siteQuestionRemoveAndResources": "Bu siteyi ve tüm ilişkili kaynakları silmek istediğinizden emin misiniz?",
|
||||
"sitesTableDeleteSite": "Siteyi Sil",
|
||||
"sitesTableDeleteSiteAndResources": "Site ve Kaynakları Sil",
|
||||
"siteManageSites": "Siteleri Yönet",
|
||||
"siteDescription": "Özel ağlara erişimi etkinleştirmek için siteler oluşturun ve yönetin",
|
||||
"sitesBannerTitle": "Herhangi Bir Ağa Bağlan",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "Site ağındaki kaynağın CIDR aralığı.",
|
||||
"createInternalResourceDialogAlias": "Takma Ad",
|
||||
"createInternalResourceDialogAliasDescription": "Bu kaynak için isteğe bağlı dahili DNS takma adı.",
|
||||
"internalResourceAliasLocalWarning": "Bazı ağlarda mDNS nedeniyle .local ile biten takma adlar çözümleme sorunlarına neden olabilir.",
|
||||
"internalResourceDownstreamSchemeRequired": "HTTP kaynakları için şema gereklidir",
|
||||
"internalResourceHttpPortRequired": "HTTP kaynakları için hedef bağlantı noktası gereklidir",
|
||||
"siteConfiguration": "Yapılandırma",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "Organizasyon veya Alan Adı Kimliği eksik",
|
||||
"loadingDNSRecords": "DNS kayıtları yükleniyor...",
|
||||
"olmUpdateAvailableInfo": "Olm'nin güncellenmiş bir sürümü mevcut. En iyi deneyim için lütfen en son sürüme güncelleyin.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "Güncellenmiş bir sürüm mevcut. En iyi deneyim için lütfen en son sürüme güncelleyin.",
|
||||
"client": "İstemci",
|
||||
"proxyProtocol": "Proxy Protokol Ayarları",
|
||||
"proxyProtocolDescription": "TCP hizmetleri için istemci IP adreslerini korumak amacıyla Proxy Protokolünü yapılandırın.",
|
||||
|
||||
@@ -66,9 +66,15 @@
|
||||
"local": "本地的",
|
||||
"edit": "编辑",
|
||||
"siteConfirmDelete": "确认删除节点",
|
||||
"siteConfirmDeleteAndResources": "确认删除站点及资源",
|
||||
"siteDelete": "删除节点",
|
||||
"siteDeleteAndResources": "删除站点及资源",
|
||||
"siteMessageRemove": "一旦移除,节点将无法访问。与节点相关的所有目标也将被移除。",
|
||||
"siteMessageRemoveAndResources": "这将永久删除与该站点关联的所有公共和私人资源,即使资源也与其他站点相关联。",
|
||||
"siteQuestionRemove": "您确定要从组织中删除该节点吗?",
|
||||
"siteQuestionRemoveAndResources": "您确定要删除此站点及所有关联资源吗?",
|
||||
"sitesTableDeleteSite": "删除站点",
|
||||
"sitesTableDeleteSiteAndResources": "删除站点及资源",
|
||||
"siteManageSites": "管理站点",
|
||||
"siteDescription": "创建和管理站点,启用与私人网络的连接",
|
||||
"sitesBannerTitle": "连接任何网络",
|
||||
@@ -2338,6 +2344,7 @@
|
||||
"createInternalResourceDialogDestinationCidrDescription": "站点网络上资源的 CIDR 范围。",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "此资源可选的内部DNS别名。",
|
||||
"internalResourceAliasLocalWarning": "以 .local 结尾的别名可能会因某些网络上的 mDNS 而导致解析问题。",
|
||||
"internalResourceDownstreamSchemeRequired": "HTTP 资源需要方案",
|
||||
"internalResourceHttpPortRequired": "HTTP 资源需要目的端口",
|
||||
"siteConfiguration": "配置",
|
||||
@@ -2967,7 +2974,7 @@
|
||||
"orgOrDomainIdMissing": "缺少机构或域 ID",
|
||||
"loadingDNSRecords": "正在载入DNS记录...",
|
||||
"olmUpdateAvailableInfo": "有最新版本的 Olm 可用。请更新到最新版本以获取最佳体验。",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "有新版本可用。请更新到最新版本以获得最佳体验。",
|
||||
"client": "客户端:",
|
||||
"proxyProtocol": "代理协议设置",
|
||||
"proxyProtocolDescription": "配置代理协议以保留TCP服务的客户端 IP 地址。",
|
||||
|
||||
@@ -795,10 +795,13 @@ export const COUNTRIES = [
|
||||
name: "Serbia",
|
||||
code: "RS"
|
||||
},
|
||||
{
|
||||
name: "Serbia and Montenegro",
|
||||
code: "CS"
|
||||
},
|
||||
// Removed as this is a deprecated ISO country code, not supported anymore
|
||||
// Also the individual flags for Serbia & Montenegro are already included in the list
|
||||
// more details: https://en.wikipedia.org/wiki/ISO_3166-2:CS
|
||||
// {
|
||||
// name: "Serbia and Montenegro",
|
||||
// code: "CS"
|
||||
// },
|
||||
{
|
||||
name: "Seychelles",
|
||||
code: "SC"
|
||||
|
||||
@@ -29,7 +29,7 @@ type ClientRow = typeof clients.$inferSelect;
|
||||
function runQueuedClientAssociationRebuilds(
|
||||
userId: string,
|
||||
queuedClients: ClientRow[]
|
||||
): void {
|
||||
) {
|
||||
if (queuedClients.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -39,425 +39,403 @@ function runQueuedClientAssociationRebuilds(
|
||||
uniqueClientsById.set(client.clientId, client);
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
for (const client of uniqueClientsById.values()) {
|
||||
try {
|
||||
await rebuildClientAssociationsFromClient(client);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed rebuilding associations for client ${client.clientId} (user ${userId}): ${String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const client of uniqueClientsById.values()) {
|
||||
rebuildClientAssociationsFromClient(client).catch((error) => {
|
||||
logger.error(
|
||||
`Error rebuilding client associations for client ${client.clientId} (user ${userId}): ${String(
|
||||
error
|
||||
)}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Queued association rebuild completed for ${uniqueClientsById.size} client(s) (user ${userId})`
|
||||
);
|
||||
})();
|
||||
logger.debug(
|
||||
`Queued association rebuild completed for ${uniqueClientsById.size} client(s) (user ${userId})`
|
||||
);
|
||||
}
|
||||
|
||||
export async function calculateUserClientsForOrgs(
|
||||
userId: string
|
||||
): Promise<void> {
|
||||
const trx = primaryDb;
|
||||
|
||||
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 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 getOrgOlmKey = (orgId: string, olmId: string) => `${orgId}:${olmId}`;
|
||||
const getRoleClientKey = (roleId: number, clientId: number) =>
|
||||
`${roleId}:${clientId}`;
|
||||
const getUserClientKey = (cachedUserId: string, clientId: number) =>
|
||||
`${cachedUserId}:${clientId}`;
|
||||
|
||||
const getOrgOlmKey = (orgId: string, olmId: string) =>
|
||||
`${orgId}:${olmId}`;
|
||||
const getRoleClientKey = (roleId: number, clientId: number) =>
|
||||
`${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;
|
||||
const getOrg = async (orgId: string) => {
|
||||
if (orgCache.has(orgId)) {
|
||||
return orgCache.get(orgId) ?? null;
|
||||
}
|
||||
|
||||
// Get all user orgs with all roles (for org list and role-based logic)
|
||||
const userOrgRoleRows = await transaction
|
||||
const [org] = await trx
|
||||
.select()
|
||||
.from(userOrgs)
|
||||
.innerJoin(
|
||||
userOrgRoles,
|
||||
.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 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(
|
||||
eq(userOrgs.userId, userOrgRoles.userId),
|
||||
eq(userOrgs.orgId, userOrgRoles.orgId)
|
||||
eq(clients.userId, userId),
|
||||
eq(clients.orgId, orgId),
|
||||
eq(clients.olmId, olmId)
|
||||
)
|
||||
)
|
||||
.innerJoin(roles, eq(userOrgRoles.roleId, roles.roleId))
|
||||
.where(eq(userOrgs.userId, userId));
|
||||
.limit(1);
|
||||
|
||||
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)
|
||||
);
|
||||
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)!;
|
||||
}
|
||||
|
||||
// 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 [existingRoleClient] = await trx
|
||||
.select()
|
||||
.from(roleClients)
|
||||
.where(
|
||||
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) {
|
||||
logger.warn(
|
||||
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): org not found`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
return hasAccess;
|
||||
};
|
||||
|
||||
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 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`
|
||||
);
|
||||
}
|
||||
const hasUserClientAccess = async (
|
||||
cachedUserId: string,
|
||||
clientId: number
|
||||
) => {
|
||||
const key = getUserClientKey(cachedUserId, clientId);
|
||||
if (userClientAccessCache.has(key)) {
|
||||
return userClientAccessCache.get(key)!;
|
||||
}
|
||||
|
||||
// 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(
|
||||
userId,
|
||||
transaction,
|
||||
userOrgIds,
|
||||
trx,
|
||||
[],
|
||||
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);
|
||||
}
|
||||
@@ -496,7 +474,7 @@ async function cleanupOrphanedClients(
|
||||
)
|
||||
.returning();
|
||||
|
||||
// Queue deleted clients for post-transaction association cleanup.
|
||||
// Queue deleted clients for post-trx association cleanup.
|
||||
for (const deletedClient of deletedClients) {
|
||||
queuedAssociationRebuilds.push(deletedClient);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import logger from "@server/logger";
|
||||
import stoi from "@server/lib/stoi";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { getCountryCodeForIp } from "@server/lib/geoip";
|
||||
|
||||
const getSiteSchema = z.strictObject({
|
||||
siteId: z
|
||||
@@ -47,6 +48,7 @@ type SiteQueryRow = NonNullable<Awaited<ReturnType<typeof query>>>;
|
||||
export type GetSiteResponse = SiteQueryRow["sites"] & {
|
||||
newtId: string | null;
|
||||
newtVersion: string | null;
|
||||
countryCode: string | null;
|
||||
};
|
||||
|
||||
registry.registerPath({
|
||||
@@ -134,7 +136,10 @@ export async function getSite(
|
||||
const data: GetSiteResponse = {
|
||||
...site.sites,
|
||||
newtId: site.newt ? site.newt.newtId : null,
|
||||
newtVersion: site.newt?.version ?? null
|
||||
newtVersion: site.newt?.version ?? null,
|
||||
countryCode: site.sites.endpoint
|
||||
? ((await getCountryCodeForIp(site.sites.endpoint)) ?? null)
|
||||
: null
|
||||
};
|
||||
|
||||
return response<GetSiteResponse>(res, {
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function OrgInfoCard({}: OrgInfoCardProps) {
|
||||
return (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
<InfoSections cols={3}>
|
||||
<InfoSections cols={4}>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t("name")}</InfoSectionTitle>
|
||||
<InfoSectionContent>{org.org.name}</InfoSectionContent>
|
||||
@@ -34,6 +34,14 @@ export default function OrgInfoCard({}: OrgInfoCardProps) {
|
||||
{org.org.subnet || t("none")}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("utilitySubnet")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{org.org.utilitySubnet || t("none")}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
</InfoSections>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
InfoSectionTitle
|
||||
} from "@app/components/InfoSection";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
|
||||
|
||||
type SiteInfoCardProps = {};
|
||||
|
||||
@@ -52,7 +53,11 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t("publicIpEndpoint")}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{formatPublicEndpoint(site.endpoint)}
|
||||
{formatPublicEndpoint(site.endpoint)}
|
||||
<span className="text-lg">
|
||||
{site.countryCode &&
|
||||
countryCodeToFlagEmoji(site.countryCode)}
|
||||
</span>
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
) : null;
|
||||
|
||||
@@ -74,6 +74,7 @@ import {
|
||||
sortPolicyRulesForResourceOverlay,
|
||||
type PolicyAccessRule
|
||||
} from "./policy-access-rule-utils";
|
||||
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
|
||||
|
||||
export type PolicyAccessRulesTableProps = {
|
||||
rules: PolicyAccessRule[];
|
||||
@@ -490,8 +491,17 @@ export function PolicyAccessRulesTable({
|
||||
{
|
||||
accessorKey: "value",
|
||||
header: () => <span className="p-3">{t("value")}</span>,
|
||||
cell: ({ row }) =>
|
||||
row.original.match === "COUNTRY" ? (
|
||||
cell: ({ row }) => {
|
||||
let selectedCountry: (typeof COUNTRIES)[number] | undefined;
|
||||
if (
|
||||
row.original.match === "COUNTRY" &&
|
||||
row.original.value
|
||||
) {
|
||||
selectedCountry = COUNTRIES.find(
|
||||
(c) => c.code === row.original.value
|
||||
);
|
||||
}
|
||||
return row.original.match === "COUNTRY" ? (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
@@ -502,15 +512,22 @@ export function PolicyAccessRulesTable({
|
||||
}
|
||||
className="w-full min-w-0 justify-between"
|
||||
>
|
||||
{row.original.value
|
||||
? COUNTRIES.find(
|
||||
(c) =>
|
||||
c.code === row.original.value
|
||||
)?.name +
|
||||
" (" +
|
||||
row.original.value +
|
||||
")"
|
||||
: t("selectCountry")}
|
||||
{selectedCountry ? (
|
||||
<>
|
||||
<span>
|
||||
{selectedCountry.code === "ALL"
|
||||
? "🌍"
|
||||
: countryCodeToFlagEmoji(
|
||||
selectedCountry.code
|
||||
)}
|
||||
|
||||
{selectedCountry.name} (
|
||||
{selectedCountry.code})
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
t("selectCountry")
|
||||
)}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@@ -540,6 +557,13 @@ export function PolicyAccessRulesTable({
|
||||
<Check
|
||||
className={`mr-2 h-4 w-4 ${row.original.value === country.code ? "opacity-100" : "opacity-0"}`}
|
||||
/>
|
||||
<span>
|
||||
{country.code === "ALL"
|
||||
? "🌍"
|
||||
: countryCodeToFlagEmoji(
|
||||
country.code
|
||||
)}
|
||||
</span>
|
||||
{country.name} (
|
||||
{country.code})
|
||||
</CommandItem>
|
||||
@@ -767,7 +791,8 @@ export function PolicyAccessRulesTable({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "enabled",
|
||||
|
||||
Reference in New Issue
Block a user