mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-18 19:46:39 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1580b7abff |
@@ -41,7 +41,7 @@
|
|||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Pangolin is an open-source, identity-based remote access platform built on WireGuard® that enables secure connectivity to infrastructure anywhere. It combines reverse-proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to private resources with NAT traversal, all with granular access control.
|
Pangolin is an open-source, identity-based remote access platform built on WireGuard® that enables secure, seamless connectivity to private and public resources. Pangolin combines reverse proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to any private resources with NAT traversal, all with granular access controls.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -63,26 +63,11 @@ Pangolin is an open-source, identity-based remote access platform built on WireG
|
|||||||
|
|
||||||
Pangolin's site connectors provide gateways into networks so you can access any networked resources. Sites use outbound tunnels and intelligent NAT traversal to make networks behind restrictive firewalls available for authorized access without public IPs or open ports. Easily deploy a site as a binary or container on any platform.
|
Pangolin's site connectors provide gateways into networks so you can access any networked resources. Sites use outbound tunnels and intelligent NAT traversal to make networks behind restrictive firewalls available for authorized access without public IPs or open ports. Easily deploy a site as a binary or container on any platform.
|
||||||
|
|
||||||
* Lightweight user-space connector runs anywhere
|
|
||||||
* Punches through any firewall
|
|
||||||
* Doesn't require open ports or a public IP
|
|
||||||
* Strict network segmentation
|
|
||||||
* WireGuard-based
|
|
||||||
* Get alerts when a device or network resource goes down
|
|
||||||
|
|
||||||
<img src="public/screenshots/sites.png" alt="Sites" width="100%" />
|
<img src="public/screenshots/sites.png" alt="Sites" width="100%" />
|
||||||
|
|
||||||
### Browser-based reverse proxy access
|
### Browser-based reverse proxy access
|
||||||
|
|
||||||
Expose HTTPS web applications and connect to VNC, RDP, and SSH entirely in the browser through identity and context-aware tunneled reverse proxies. Users access resources with authentication and granular access control without installing a client. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet.
|
Expose web applications through identity and context-aware tunneled reverse proxies. Users access applications through any web browser with authentication and granular access control without installing a client. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet.
|
||||||
|
|
||||||
* Expose a web panel anywhere
|
|
||||||
* Access via any web browser
|
|
||||||
* Single sign-on across all resources
|
|
||||||
* HTTPS resources
|
|
||||||
* Remote desktop in the browser with VNC and RDP
|
|
||||||
* In-browser SSH terminal with privileged access management (PAM)
|
|
||||||
* PIN codes, passcodes, email OTP, geoblocking, allow-lists, and more
|
|
||||||
|
|
||||||
<img src="public/clip.gif" alt="Reverse proxy access" width="100%" />
|
<img src="public/clip.gif" alt="Reverse proxy access" width="100%" />
|
||||||
|
|
||||||
@@ -90,35 +75,14 @@ Expose HTTPS web applications and connect to VNC, RDP, and SSH entirely in the b
|
|||||||
|
|
||||||
Access private resources like SSH servers, databases, RDP, and entire network ranges through Pangolin clients. Intelligent NAT traversal enables connections even through restrictive firewalls, while DNS aliases provide friendly names and fast connections to resources across all your sites. Add redundancy by routing traffic through multiple connectors in your network.
|
Access private resources like SSH servers, databases, RDP, and entire network ranges through Pangolin clients. Intelligent NAT traversal enables connections even through restrictive firewalls, while DNS aliases provide friendly names and fast connections to resources across all your sites. Add redundancy by routing traffic through multiple connectors in your network.
|
||||||
|
|
||||||
* Peer-to-peer with intelligent NAT traversal
|
|
||||||
* Hosts/IPs and port ranges
|
|
||||||
* Network ranges/CIDRs
|
|
||||||
* Friendly DNS aliases for network addresses
|
|
||||||
* Privileged access management (PAM) with SSH resources
|
|
||||||
* Private HTTPS resources only accessible on the private network
|
|
||||||
|
|
||||||
<img src="public/screenshots/private-resources.png" alt="Private resources" width="100%" />
|
<img src="public/screenshots/private-resources.png" alt="Private resources" width="100%" />
|
||||||
|
|
||||||
### Give users and roles access to resources
|
### Give users and roles access to resources
|
||||||
|
|
||||||
Use Pangolin's built-in users or bring your own identity provider and set up role-based access control (RBAC). Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications, services, and routes you explicitly define.
|
Use Pangolin's built in users or bring your own identity provider and set up role based access control (RBAC). Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications, services, and routes you explicitly define.
|
||||||
|
|
||||||
* Bring your existing identity provider (IdP) or use Pangolin identities
|
|
||||||
* Sync users and roles from your IdP
|
|
||||||
* User- and role-based access control
|
|
||||||
* Full network audit and access logs
|
|
||||||
|
|
||||||
<img src="public/screenshots/users.png" alt="Users from identity provider with roles" width="100%" />
|
<img src="public/screenshots/users.png" alt="Users from identity provider with roles" width="100%" />
|
||||||
|
|
||||||
### Find and launch resources from a personalized home page
|
|
||||||
|
|
||||||
Give users a landing page to quickly find and open the resources they can access. Resources are grouped by site or label, searchable, and filterable, with grid or list views. Saved views capture filters, grouping, and layout as personal or organization-wide defaults.
|
|
||||||
|
|
||||||
* Single place for admins and non-admins to see accessible resources
|
|
||||||
* Create reusable views for common access patterns
|
|
||||||
|
|
||||||
<img src="public/screenshots/resource-launcher.png" alt="Resource Launcher" width="100%" />
|
|
||||||
|
|
||||||
## Download Clients
|
## Download Clients
|
||||||
|
|
||||||
Download the Pangolin client for your platform:
|
Download the Pangolin client for your platform:
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ services:
|
|||||||
- 80:80 # Port for traefik because of the network_mode
|
- 80:80 # Port for traefik because of the network_mode
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:v3.7
|
image: traefik:v3.6
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: service:gerbil # Ports appear on the gerbil service
|
network_mode: service:gerbil # Ports appear on the gerbil service
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ services:
|
|||||||
- 80:80{{end}}
|
- 80:80{{end}}
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
image: docker.io/traefik:v3.7
|
image: docker.io/traefik:v3.6
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
{{if .InstallGerbil}}network_mode: service:gerbil # Ports appear on the gerbil service{{end}}{{if not .InstallGerbil}}
|
{{if .InstallGerbil}}network_mode: service:gerbil # Ports appear on the gerbil service{{end}}{{if not .InstallGerbil}}
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ var redisFlag *bool
|
|||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
crowdsecFlag := flag.Bool("crowdsec", false, "Enable the CrowdSec installation prompt")
|
crowdsecFlag := flag.Bool("crowdsec", false, "Enable the CrowdSec installation prompt")
|
||||||
redisFlag = flag.Bool("redis", false, "Install Redis as caching solution. Required for HA. Not required for the Enterprise version.")
|
redisFlag = flag.Bool("redis", false, "Install Redis as cacheing solution. Required for HA. Not required for the Enterprise version.")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// print a banner about prerequisites - opening port 80, 443, 51820, and 21820 on the VPS and firewall and pointing your domain to the VPS IP with a records. Docs are at http://localhost:3000/Getting%20Started/dns-networking
|
// print a banner about prerequisites - opening port 80, 443, 51820, and 21820 on the VPS and firewall and pointing your domain to the VPS IP with a records. Docs are at http://localhost:3000/Getting%20Started/dns-networking
|
||||||
|
|||||||
+5
-20
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Осигуряване",
|
"provisioningManage": "Осигуряване",
|
||||||
"provisioningDescription": "Управление на ключовете за осигуряване и преглед на чаканещите сайтове за одобрение.",
|
"provisioningDescription": "Управление на ключовете за осигуряване и преглед на чаканещите сайтове за одобрение.",
|
||||||
"pendingSites": "Чаканещи сайтове",
|
"pendingSites": "Чаканещи сайтове",
|
||||||
"siteApproveSuccess": "Сайтът и свързаните ресурси са одобрени успешно",
|
"siteApproveSuccess": "Сайтът е одобрен успешно",
|
||||||
"siteApproveError": "Грешка при одобряването на сайта",
|
"siteApproveError": "Грешка при одобряването на сайта",
|
||||||
"siteReject": "Отказ на сайт",
|
|
||||||
"siteQuestionReject": "Сигурни ли сте, че искате да откажете този сайт?",
|
|
||||||
"siteMessageReject": "Това ще изтрие окончателно сайта и всички свързани ресурси, които все още са на изчакване.",
|
|
||||||
"siteConfirmReject": "Потвърдете отказ на сайт",
|
|
||||||
"siteRejectSuccess": "Сайтът беше успешно отхвърлен",
|
|
||||||
"siteRejectError": "Грешка при отхвърляне на сайта",
|
|
||||||
"provisioningKeys": "Ключове за осигуряване",
|
"provisioningKeys": "Ключове за осигуряване",
|
||||||
"searchProvisioningKeys": "Търсене на ключове за осигуряване...",
|
"searchProvisioningKeys": "Търсене на ключове за осигуряване...",
|
||||||
"provisioningKeysAdd": "Генериране на ключ за осигуряване",
|
"provisioningKeysAdd": "Генериране на ключ за осигуряване",
|
||||||
@@ -472,8 +466,8 @@
|
|||||||
"provisioningKeysSave": "Запазете ключа за осигуряване",
|
"provisioningKeysSave": "Запазете ключа за осигуряване",
|
||||||
"provisioningKeysSaveDescription": "Ще можете да видите това само веднъж. Копирайте го на сигурно място.",
|
"provisioningKeysSaveDescription": "Ще можете да видите това само веднъж. Копирайте го на сигурно място.",
|
||||||
"provisioningKeysErrorCreate": "Грешка при създаване на ключ за осигуряване",
|
"provisioningKeysErrorCreate": "Грешка при създаване на ключ за осигуряване",
|
||||||
"provisioningKeysList": "Нов ключ за разпределяне",
|
"provisioningKeysList": "Нов ключ за осигуряване",
|
||||||
"provisioningKeysMaxBatchSize": "Максимален размер на пакета",
|
"provisioningKeysMaxBatchSize": "Максимален размер на пакет",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Неограничен размер на партида (без лимит)",
|
"provisioningKeysUnlimitedBatchSize": "Неограничен размер на партида (без лимит)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Неограничено",
|
"provisioningKeysMaxBatchUnlimited": "Неограничено",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Въведете валиден максимален размер на партида (1–1,000,000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Въведете валиден максимален размер на партида (1–1,000,000).",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "Никога",
|
"provisioningKeysNeverUsed": "Никога",
|
||||||
"provisioningKeysEdit": "Редактиране на ключ за осигуряване",
|
"provisioningKeysEdit": "Редактиране на ключ за осигуряване",
|
||||||
"provisioningKeysEditDescription": "Актуализирайте максималния размер на партида и времето на изтичане за този ключ.",
|
"provisioningKeysEditDescription": "Актуализирайте максималния размер на партида и времето на изтичане за този ключ.",
|
||||||
"provisioningKeysApproveNewSites": "Одобряване на нови сайтове",
|
"provisioningKeysApproveNewSites": "Одобрете нови сайтове",
|
||||||
"provisioningKeysApproveNewSitesDescription": "Автоматично одобряване на сайтове, които се регистрират с този ключ.",
|
"provisioningKeysApproveNewSitesDescription": "Автоматично одобряване на сайтове, които се регистрират с този ключ.",
|
||||||
"provisioningKeysUpdateError": "Грешка при актуализирането на ключа за осигуряване",
|
"provisioningKeysUpdateError": "Грешка при актуализирането на ключа за осигуряване",
|
||||||
"provisioningKeysUpdated": "Ключът за осигуряване е актуализиран",
|
"provisioningKeysUpdated": "Ключът за осигуряване е актуализиран",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Създаване на Организационна Потребител",
|
"createOrgUser": "Създаване на Организационна Потребител",
|
||||||
"actionUpdateOrg": "Актуализиране на организацията",
|
"actionUpdateOrg": "Актуализиране на организацията",
|
||||||
"actionRemoveInvitation": "Премахване на поканата.",
|
"actionRemoveInvitation": "Премахване на поканата.",
|
||||||
"actionRemoveUserRole": "Премахване на роля на потребител",
|
|
||||||
"actionUpdateUser": "Актуализиране на потребител",
|
"actionUpdateUser": "Актуализиране на потребител",
|
||||||
"actionGetUser": "Получаване на потребител",
|
"actionGetUser": "Получаване на потребител",
|
||||||
"actionGetOrgUser": "Вземете потребител на организация",
|
"actionGetOrgUser": "Вземете потребител на организация",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Въведете конфигурационния токен от сървърната конзола.",
|
"setupTokenDescription": "Въведете конфигурационния токен от сървърната конзола.",
|
||||||
"setupTokenRequired": "Необходим е конфигурационен токен",
|
"setupTokenRequired": "Необходим е конфигурационен токен",
|
||||||
"actionUpdateSite": "Актуализиране на сайт",
|
"actionUpdateSite": "Актуализиране на сайт",
|
||||||
"actionApproveSite": "Одобряване на сайт",
|
|
||||||
"actionRejectSite": "Отказ на сайт",
|
|
||||||
"actionResetSiteBandwidth": "Нулиране на честотната лента на организацията",
|
"actionResetSiteBandwidth": "Нулиране на честотната лента на организацията",
|
||||||
"actionListSiteRoles": "Изброяване на позволените роли за сайта",
|
"actionListSiteRoles": "Изброяване на позволените роли за сайта",
|
||||||
"actionCreateResource": "Създаване на ресурс",
|
"actionCreateResource": "Създаване на ресурс",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Списък с покани",
|
"actionListInvitations": "Списък с покани",
|
||||||
"actionExportLogs": "Експортиране на дневници",
|
"actionExportLogs": "Експортиране на дневници",
|
||||||
"actionViewLogs": "Преглед на дневници",
|
"actionViewLogs": "Преглед на дневници",
|
||||||
"actionCreateSiteProvisioningKey": "Създаване на ключ за предоставяне на сайт",
|
|
||||||
"actionListSiteProvisioningKeys": "Списък на ключовете за предоставяне на сайт",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Актуализиране на ключ за предоставяне на сайт",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Изтриване на ключ за предоставяне на сайт",
|
|
||||||
"noneSelected": "Нищо не е избрано",
|
"noneSelected": "Нищо не е избрано",
|
||||||
"orgNotFound2": "Няма намерени организации.",
|
"orgNotFound2": "Няма намерени организации.",
|
||||||
"search": "Търси…",
|
"search": "Търси…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Въведете кода от приложението за удостоверяване или един от вашите резервни кодове за еднократна употреба.",
|
"otpAuthDescription": "Въведете кода от приложението за удостоверяване или един от вашите резервни кодове за еднократна употреба.",
|
||||||
"otpAuthSubmit": "Изпрати код",
|
"otpAuthSubmit": "Изпрати код",
|
||||||
"idpContinue": "Или продължете със",
|
"idpContinue": "Или продължете със",
|
||||||
"idpLastUsed": "Последно използвано",
|
|
||||||
"otpAuthBack": "Назад към парола",
|
"otpAuthBack": "Назад към парола",
|
||||||
"navbar": "Навигационно меню",
|
"navbar": "Навигационно меню",
|
||||||
"navbarDescription": "Главно навигационно меню за приложението",
|
"navbarDescription": "Главно навигационно меню за приложението",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Качването неуспешно",
|
"rdpUploadFailed": "Качването неуспешно",
|
||||||
"rdpUnicodeKeyboardMode": "Режим на unicode клавиатура",
|
"rdpUnicodeKeyboardMode": "Режим на unicode клавиатура",
|
||||||
"sessionToolbarShow": "Показване на лентата с инструменти",
|
"sessionToolbarShow": "Показване на лентата с инструменти",
|
||||||
"sessionToolbarHide": "Скриване на лентата с инструменти",
|
"sessionToolbarHide": "Скриване на лентата с инструменти"
|
||||||
"actionUpdateSiteApprovals": "Обновяване на одобренията на сайта"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-19
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Zajištění",
|
"provisioningManage": "Zajištění",
|
||||||
"provisioningDescription": "Spravovat klíče pro nastavení a zkontrolovat čekající stránky čekající na schválení.",
|
"provisioningDescription": "Spravovat klíče pro nastavení a zkontrolovat čekající stránky čekající na schválení.",
|
||||||
"pendingSites": "Nevyřízené weby",
|
"pendingSites": "Nevyřízené weby",
|
||||||
"siteApproveSuccess": "Stránka a přidružené zdroje byly úspěšně schváleny",
|
"siteApproveSuccess": "Web byl úspěšně schválen",
|
||||||
"siteApproveError": "Chyba při schvalování webu",
|
"siteApproveError": "Chyba při schvalování webu",
|
||||||
"siteReject": "Odmítnout stránku",
|
|
||||||
"siteQuestionReject": "Opravdu chcete tuto stránku odmítnout?",
|
|
||||||
"siteMessageReject": "Toto trvale odstraní stránku a všechny přidružené zdroje, které jsou stále nevyřízené.",
|
|
||||||
"siteConfirmReject": "Potvrdit odmítnutí stránky",
|
|
||||||
"siteRejectSuccess": "Stránka byla úspěšně odmítnuta",
|
|
||||||
"siteRejectError": "Chyba při odmítání stránky",
|
|
||||||
"provisioningKeys": "Poskytovací klíče",
|
"provisioningKeys": "Poskytovací klíče",
|
||||||
"searchProvisioningKeys": "Hledat klíče k zajišťování...",
|
"searchProvisioningKeys": "Hledat klíče k zajišťování...",
|
||||||
"provisioningKeysAdd": "Generovat zajišťovací klíč",
|
"provisioningKeysAdd": "Generovat zajišťovací klíč",
|
||||||
@@ -472,12 +466,12 @@
|
|||||||
"provisioningKeysSave": "Uložit konfigurační klíč",
|
"provisioningKeysSave": "Uložit konfigurační klíč",
|
||||||
"provisioningKeysSaveDescription": "Toto můžete vidět pouze jednou. Zkopírujte ho na bezpečné místo.",
|
"provisioningKeysSaveDescription": "Toto můžete vidět pouze jednou. Zkopírujte ho na bezpečné místo.",
|
||||||
"provisioningKeysErrorCreate": "Chyba při vytváření doplňovacího klíče",
|
"provisioningKeysErrorCreate": "Chyba při vytváření doplňovacího klíče",
|
||||||
"provisioningKeysList": "Nový provisioning klíč",
|
"provisioningKeysList": "Nový klíč pro poskytování informací",
|
||||||
"provisioningKeysMaxBatchSize": "Maximální velikost dávky",
|
"provisioningKeysMaxBatchSize": "Maximální velikost dávky",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Neomezená velikost šarže (bez omezení)",
|
"provisioningKeysUnlimitedBatchSize": "Neomezená velikost šarže (bez omezení)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Bez omezení",
|
"provisioningKeysMaxBatchUnlimited": "Bez omezení",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Zadejte platnou maximální velikost šarže (1–1,000,000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Zadejte platnou maximální velikost šarže (1–1,000,000).",
|
||||||
"provisioningKeysValidUntil": "Platná do",
|
"provisioningKeysValidUntil": "Platné do",
|
||||||
"provisioningKeysValidUntilHint": "Ponechte prázdné, pokud vyprší platnost.",
|
"provisioningKeysValidUntilHint": "Ponechte prázdné, pokud vyprší platnost.",
|
||||||
"provisioningKeysValidUntilInvalid": "Zadejte platné datum a čas.",
|
"provisioningKeysValidUntilInvalid": "Zadejte platné datum a čas.",
|
||||||
"provisioningKeysNumUsed": "Časy použití",
|
"provisioningKeysNumUsed": "Časy použití",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Vytvořit Org uživatele",
|
"createOrgUser": "Vytvořit Org uživatele",
|
||||||
"actionUpdateOrg": "Aktualizovat organizaci",
|
"actionUpdateOrg": "Aktualizovat organizaci",
|
||||||
"actionRemoveInvitation": "Odstranit pozvání",
|
"actionRemoveInvitation": "Odstranit pozvání",
|
||||||
"actionRemoveUserRole": "Odstranit uživatelskou roli",
|
|
||||||
"actionUpdateUser": "Aktualizovat uživatele",
|
"actionUpdateUser": "Aktualizovat uživatele",
|
||||||
"actionGetUser": "Získat uživatele",
|
"actionGetUser": "Získat uživatele",
|
||||||
"actionGetOrgUser": "Získat uživatele organizace",
|
"actionGetOrgUser": "Získat uživatele organizace",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Zadejte nastavovací token z konzole serveru.",
|
"setupTokenDescription": "Zadejte nastavovací token z konzole serveru.",
|
||||||
"setupTokenRequired": "Je vyžadován token nastavení",
|
"setupTokenRequired": "Je vyžadován token nastavení",
|
||||||
"actionUpdateSite": "Aktualizovat stránku",
|
"actionUpdateSite": "Aktualizovat stránku",
|
||||||
"actionApproveSite": "Schválit stránku",
|
|
||||||
"actionRejectSite": "Odmítnout stránku",
|
|
||||||
"actionResetSiteBandwidth": "Resetovat šířku pásma organizace",
|
"actionResetSiteBandwidth": "Resetovat šířku pásma organizace",
|
||||||
"actionListSiteRoles": "Seznam povolených rolí webu",
|
"actionListSiteRoles": "Seznam povolených rolí webu",
|
||||||
"actionCreateResource": "Vytvořit zdroj",
|
"actionCreateResource": "Vytvořit zdroj",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Seznam pozvánek",
|
"actionListInvitations": "Seznam pozvánek",
|
||||||
"actionExportLogs": "Exportovat protokoly",
|
"actionExportLogs": "Exportovat protokoly",
|
||||||
"actionViewLogs": "Zobrazit logy",
|
"actionViewLogs": "Zobrazit logy",
|
||||||
"actionCreateSiteProvisioningKey": "Vytvořit klíč pro zřízení webu",
|
|
||||||
"actionListSiteProvisioningKeys": "Seznam klíčů pro zřízení webu",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Aktualizovat klíč pro zřízení webu",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Smazat klíč pro zřízení webu",
|
|
||||||
"noneSelected": "Není vybráno",
|
"noneSelected": "Není vybráno",
|
||||||
"orgNotFound2": "Nebyly nalezeny žádné organizace.",
|
"orgNotFound2": "Nebyly nalezeny žádné organizace.",
|
||||||
"search": "Vyhledávání…",
|
"search": "Vyhledávání…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Zadejte kód z vaší autentizační aplikace nebo jeden z vlastních záložních kódů.",
|
"otpAuthDescription": "Zadejte kód z vaší autentizační aplikace nebo jeden z vlastních záložních kódů.",
|
||||||
"otpAuthSubmit": "Odeslat kód",
|
"otpAuthSubmit": "Odeslat kód",
|
||||||
"idpContinue": "Nebo pokračovat s",
|
"idpContinue": "Nebo pokračovat s",
|
||||||
"idpLastUsed": "Naposled použito",
|
|
||||||
"otpAuthBack": "Zpět na heslo",
|
"otpAuthBack": "Zpět na heslo",
|
||||||
"navbar": "Navigation Menu",
|
"navbar": "Navigation Menu",
|
||||||
"navbarDescription": "Hlavní navigační menu aplikace",
|
"navbarDescription": "Hlavní navigační menu aplikace",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Nahrání selhalo",
|
"rdpUploadFailed": "Nahrání selhalo",
|
||||||
"rdpUnicodeKeyboardMode": "Režim Unicode klávesnice",
|
"rdpUnicodeKeyboardMode": "Režim Unicode klávesnice",
|
||||||
"sessionToolbarShow": "Zobrazit panel nástrojů",
|
"sessionToolbarShow": "Zobrazit panel nástrojů",
|
||||||
"sessionToolbarHide": "Skrýt panel nástrojů",
|
"sessionToolbarHide": "Skrýt panel nástrojů"
|
||||||
"actionUpdateSiteApprovals": "Aktualizovat schválení webu"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-18
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Provisionering",
|
"provisioningManage": "Provisionering",
|
||||||
"provisioningDescription": "Administrer provisioneringsnøgler og gennemgå ventende sites som venter på godkendelse.",
|
"provisioningDescription": "Administrer provisioneringsnøgler og gennemgå ventende sites som venter på godkendelse.",
|
||||||
"pendingSites": "Ventende sites",
|
"pendingSites": "Ventende sites",
|
||||||
"siteApproveSuccess": "Site og tilknyttede ressourcer godkendt med succes",
|
"siteApproveSuccess": "Med succes godkendelse af site",
|
||||||
"siteApproveError": "Fejl ved godkendelse af side",
|
"siteApproveError": "Fejl ved godkendelse af side",
|
||||||
"siteReject": "Afvis Site",
|
|
||||||
"siteQuestionReject": "Er du sikker på, at du vil afvise dette site?",
|
|
||||||
"siteMessageReject": "Dette vil permanent slette sitet og enhver tilknyttet ressource, der stadig er i afventning.",
|
|
||||||
"siteConfirmReject": "Bekræft Afvis Site",
|
|
||||||
"siteRejectSuccess": "Site afvist med succes",
|
|
||||||
"siteRejectError": "Fejl ved afvisning af sitet",
|
|
||||||
"provisioningKeys": "Provisioneringsnøgler",
|
"provisioningKeys": "Provisioneringsnøgler",
|
||||||
"searchProvisioningKeys": "Søg varer i provisioneringsnøgler...",
|
"searchProvisioningKeys": "Søg varer i provisioneringsnøgler...",
|
||||||
"provisioningKeysAdd": "Generer provisioneringsnøgle",
|
"provisioningKeysAdd": "Generer provisioneringsnøgle",
|
||||||
@@ -473,7 +467,7 @@
|
|||||||
"provisioningKeysSaveDescription": "Du kan kun se denne én gang. Kopiér det til et sikkert sted.",
|
"provisioningKeysSaveDescription": "Du kan kun se denne én gang. Kopiér det til et sikkert sted.",
|
||||||
"provisioningKeysErrorCreate": "Fejl under oprettelse af provisioneringsnøgle",
|
"provisioningKeysErrorCreate": "Fejl under oprettelse af provisioneringsnøgle",
|
||||||
"provisioningKeysList": "Ny provisioneringsnøgle",
|
"provisioningKeysList": "Ny provisioneringsnøgle",
|
||||||
"provisioningKeysMaxBatchSize": "Maksimum Batch Størrelse",
|
"provisioningKeysMaxBatchSize": "Maks størrelse på bunt",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Ubegrænset mengde bunt (ingen begrænsning)",
|
"provisioningKeysUnlimitedBatchSize": "Ubegrænset mengde bunt (ingen begrænsning)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Ubegrænset",
|
"provisioningKeysMaxBatchUnlimited": "Ubegrænset",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Angiv en gyldig sjakkstørrelse (1–1 000.000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Angiv en gyldig sjakkstørrelse (1–1 000.000).",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Opret organisationsbruger",
|
"createOrgUser": "Opret organisationsbruger",
|
||||||
"actionUpdateOrg": "Opdater organisation",
|
"actionUpdateOrg": "Opdater organisation",
|
||||||
"actionRemoveInvitation": "Fjern invitation",
|
"actionRemoveInvitation": "Fjern invitation",
|
||||||
"actionRemoveUserRole": "Fjern Brugers Rolle",
|
|
||||||
"actionUpdateUser": "Opdater bruger",
|
"actionUpdateUser": "Opdater bruger",
|
||||||
"actionGetUser": "Hent bruger",
|
"actionGetUser": "Hent bruger",
|
||||||
"actionGetOrgUser": "Hent organisationsbruger",
|
"actionGetOrgUser": "Hent organisationsbruger",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Indtast opsætningstoken fra serverkonsollen.",
|
"setupTokenDescription": "Indtast opsætningstoken fra serverkonsollen.",
|
||||||
"setupTokenRequired": "Opsætningstoken er nødvendig",
|
"setupTokenRequired": "Opsætningstoken er nødvendig",
|
||||||
"actionUpdateSite": "Opdater site",
|
"actionUpdateSite": "Opdater site",
|
||||||
"actionApproveSite": "Godkend Site",
|
|
||||||
"actionRejectSite": "Afvis Site",
|
|
||||||
"actionResetSiteBandwidth": "Nulstil organisationsbåndbredde",
|
"actionResetSiteBandwidth": "Nulstil organisationsbåndbredde",
|
||||||
"actionListSiteRoles": "Vis tilladte områderoller",
|
"actionListSiteRoles": "Vis tilladte områderoller",
|
||||||
"actionCreateResource": "Opret ressource",
|
"actionCreateResource": "Opret ressource",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Vis invitationer",
|
"actionListInvitations": "Vis invitationer",
|
||||||
"actionExportLogs": "Eksportér logs",
|
"actionExportLogs": "Eksportér logs",
|
||||||
"actionViewLogs": "Vis logs",
|
"actionViewLogs": "Vis logs",
|
||||||
"actionCreateSiteProvisioningKey": "Opret provisioneringsnøgle til site",
|
|
||||||
"actionListSiteProvisioningKeys": "Vis provisioneringsnøgler til site",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Opdater provisioneringsnøgle til site",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Slet provisioneringsnøgle til site",
|
|
||||||
"noneSelected": "Ingen valgt",
|
"noneSelected": "Ingen valgt",
|
||||||
"orgNotFound2": "Ingen organisationer fundet.",
|
"orgNotFound2": "Ingen organisationer fundet.",
|
||||||
"search": "Søg…",
|
"search": "Søg…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Indtast koden fra autentiseringsappen din eller en af dine engangs reservekoder.",
|
"otpAuthDescription": "Indtast koden fra autentiseringsappen din eller en af dine engangs reservekoder.",
|
||||||
"otpAuthSubmit": "Send kode",
|
"otpAuthSubmit": "Send kode",
|
||||||
"idpContinue": "Eller fortsæt med",
|
"idpContinue": "Eller fortsæt med",
|
||||||
"idpLastUsed": "Sidst brugt",
|
|
||||||
"otpAuthBack": "Tilbage til adgangskode",
|
"otpAuthBack": "Tilbage til adgangskode",
|
||||||
"navbar": "Navigationsmenu",
|
"navbar": "Navigationsmenu",
|
||||||
"navbarDescription": "Hovednavigasjonsmeny for applikationen",
|
"navbarDescription": "Hovednavigasjonsmeny for applikationen",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Uploaden mislykkedes",
|
"rdpUploadFailed": "Uploaden mislykkedes",
|
||||||
"rdpUnicodeKeyboardMode": "Unicode tastaturtilstand",
|
"rdpUnicodeKeyboardMode": "Unicode tastaturtilstand",
|
||||||
"sessionToolbarShow": "Vis værktøjslinje",
|
"sessionToolbarShow": "Vis værktøjslinje",
|
||||||
"sessionToolbarHide": "Skjul værktøjslinje",
|
"sessionToolbarHide": "Skjul værktøjslinje"
|
||||||
"actionUpdateSiteApprovals": "Opdater godkendelser på site"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-19
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Bereitstellung",
|
"provisioningManage": "Bereitstellung",
|
||||||
"provisioningDescription": "Bereitstellungsschlüssel verwalten und ausstehende Standorte prüfen, die noch auf Genehmigung warten.",
|
"provisioningDescription": "Bereitstellungsschlüssel verwalten und ausstehende Standorte prüfen, die noch auf Genehmigung warten.",
|
||||||
"pendingSites": "Ausstehende Standorte",
|
"pendingSites": "Ausstehende Standorte",
|
||||||
"siteApproveSuccess": "Site und zugehörige Ressourcen erfolgreich genehmigt",
|
"siteApproveSuccess": "Standort erfolgreich freigegeben",
|
||||||
"siteApproveError": "Fehler beim Genehmigen des Standorts",
|
"siteApproveError": "Fehler beim Genehmigen des Standorts",
|
||||||
"siteReject": "Site ablehnen",
|
|
||||||
"siteQuestionReject": "Sind Sie sicher, dass Sie diese Site ablehnen möchten?",
|
|
||||||
"siteMessageReject": "Dies wird die Site und alle damit verbundenen, noch ausstehenden Ressourcen dauerhaft löschen.",
|
|
||||||
"siteConfirmReject": "Ablehnung der Site bestätigen",
|
|
||||||
"siteRejectSuccess": "Site erfolgreich abgelehnt",
|
|
||||||
"siteRejectError": "Fehler beim Ablehnen der Site",
|
|
||||||
"provisioningKeys": "Bereitstellungsschlüssel",
|
"provisioningKeys": "Bereitstellungsschlüssel",
|
||||||
"searchProvisioningKeys": "Bereitstellungsschlüssel suchen...",
|
"searchProvisioningKeys": "Bereitstellungsschlüssel suchen...",
|
||||||
"provisioningKeysAdd": "Bereitstellungsschlüssel generieren",
|
"provisioningKeysAdd": "Bereitstellungsschlüssel generieren",
|
||||||
@@ -473,7 +467,7 @@
|
|||||||
"provisioningKeysSaveDescription": "Sie können dies nur einmal sehen. Kopieren Sie es an einen sicheren Ort.",
|
"provisioningKeysSaveDescription": "Sie können dies nur einmal sehen. Kopieren Sie es an einen sicheren Ort.",
|
||||||
"provisioningKeysErrorCreate": "Fehler beim Erstellen des Bereitstellungsschlüssels",
|
"provisioningKeysErrorCreate": "Fehler beim Erstellen des Bereitstellungsschlüssels",
|
||||||
"provisioningKeysList": "Neuer Bereitstellungsschlüssel",
|
"provisioningKeysList": "Neuer Bereitstellungsschlüssel",
|
||||||
"provisioningKeysMaxBatchSize": "Maximale Batch-Größe",
|
"provisioningKeysMaxBatchSize": "Max. Batch-Größe",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Unbegrenzte Batch-Größe (kein Limit)",
|
"provisioningKeysUnlimitedBatchSize": "Unbegrenzte Batch-Größe (kein Limit)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Unbegrenzt",
|
"provisioningKeysMaxBatchUnlimited": "Unbegrenzt",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Geben Sie eine gültige maximale Batchgröße ein (1–1.000.000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Geben Sie eine gültige maximale Batchgröße ein (1–1.000.000).",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "Nie",
|
"provisioningKeysNeverUsed": "Nie",
|
||||||
"provisioningKeysEdit": "Bereitstellungsschlüssel bearbeiten",
|
"provisioningKeysEdit": "Bereitstellungsschlüssel bearbeiten",
|
||||||
"provisioningKeysEditDescription": "Aktualisieren Sie die maximale Batch-Größe und Ablaufzeit für diesen Schlüssel.",
|
"provisioningKeysEditDescription": "Aktualisieren Sie die maximale Batch-Größe und Ablaufzeit für diesen Schlüssel.",
|
||||||
"provisioningKeysApproveNewSites": "Neue Sites genehmigen",
|
"provisioningKeysApproveNewSites": "Neuen Standort genehmigen",
|
||||||
"provisioningKeysApproveNewSitesDescription": "Sites, die sich mit diesem Schlüssel registrieren, automatisch freigeben.",
|
"provisioningKeysApproveNewSitesDescription": "Sites, die sich mit diesem Schlüssel registrieren, automatisch freigeben.",
|
||||||
"provisioningKeysUpdateError": "Fehler beim Aktualisieren des Bereitstellungsschlüssels",
|
"provisioningKeysUpdateError": "Fehler beim Aktualisieren des Bereitstellungsschlüssels",
|
||||||
"provisioningKeysUpdated": "Bereitstellungsschlüssel aktualisiert",
|
"provisioningKeysUpdated": "Bereitstellungsschlüssel aktualisiert",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Org Benutzer erstellen",
|
"createOrgUser": "Org Benutzer erstellen",
|
||||||
"actionUpdateOrg": "Organisation aktualisieren",
|
"actionUpdateOrg": "Organisation aktualisieren",
|
||||||
"actionRemoveInvitation": "Einladung entfernen",
|
"actionRemoveInvitation": "Einladung entfernen",
|
||||||
"actionRemoveUserRole": "Benutzerrolle entfernen",
|
|
||||||
"actionUpdateUser": "Benutzer aktualisieren",
|
"actionUpdateUser": "Benutzer aktualisieren",
|
||||||
"actionGetUser": "Benutzer abrufen",
|
"actionGetUser": "Benutzer abrufen",
|
||||||
"actionGetOrgUser": "Organisationsbenutzer abrufen",
|
"actionGetOrgUser": "Organisationsbenutzer abrufen",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Geben Sie das Setup-Token von der Serverkonsole ein.",
|
"setupTokenDescription": "Geben Sie das Setup-Token von der Serverkonsole ein.",
|
||||||
"setupTokenRequired": "Setup-Token ist erforderlich",
|
"setupTokenRequired": "Setup-Token ist erforderlich",
|
||||||
"actionUpdateSite": "Standorte aktualisieren",
|
"actionUpdateSite": "Standorte aktualisieren",
|
||||||
"actionApproveSite": "Site genehmigen",
|
|
||||||
"actionRejectSite": "Site ablehnen",
|
|
||||||
"actionResetSiteBandwidth": "Organisations-Bandbreite zurücksetzen",
|
"actionResetSiteBandwidth": "Organisations-Bandbreite zurücksetzen",
|
||||||
"actionListSiteRoles": "Erlaubte Standort-Rollen auflisten",
|
"actionListSiteRoles": "Erlaubte Standort-Rollen auflisten",
|
||||||
"actionCreateResource": "Ressource erstellen",
|
"actionCreateResource": "Ressource erstellen",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Einladungen auflisten",
|
"actionListInvitations": "Einladungen auflisten",
|
||||||
"actionExportLogs": "Logs exportieren",
|
"actionExportLogs": "Logs exportieren",
|
||||||
"actionViewLogs": "Logs anzeigen",
|
"actionViewLogs": "Logs anzeigen",
|
||||||
"actionCreateSiteProvisioningKey": "Bereitstellungsschlüssel für Standort erstellen",
|
|
||||||
"actionListSiteProvisioningKeys": "Liste der Bereitstellungsschlüssel für Standorte",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Bereitstellungsschlüssel für Standort aktualisieren",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Bereitstellungsschlüssel für Standort löschen",
|
|
||||||
"noneSelected": "Keine ausgewählt",
|
"noneSelected": "Keine ausgewählt",
|
||||||
"orgNotFound2": "Keine Organisationen gefunden.",
|
"orgNotFound2": "Keine Organisationen gefunden.",
|
||||||
"search": "Suche…",
|
"search": "Suche…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Geben Sie den Code aus Ihrer Authenticator-App oder einen Ihrer einmaligen Backup-Codes ein.",
|
"otpAuthDescription": "Geben Sie den Code aus Ihrer Authenticator-App oder einen Ihrer einmaligen Backup-Codes ein.",
|
||||||
"otpAuthSubmit": "Code absenden",
|
"otpAuthSubmit": "Code absenden",
|
||||||
"idpContinue": "Oder weiter mit",
|
"idpContinue": "Oder weiter mit",
|
||||||
"idpLastUsed": "Zuletzt verwendet",
|
|
||||||
"otpAuthBack": "Zurück zum Passwort",
|
"otpAuthBack": "Zurück zum Passwort",
|
||||||
"navbar": "Navigationsmenü",
|
"navbar": "Navigationsmenü",
|
||||||
"navbarDescription": "Hauptnavigationsmenü für die Anwendung",
|
"navbarDescription": "Hauptnavigationsmenü für die Anwendung",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Upload fehlgeschlagen",
|
"rdpUploadFailed": "Upload fehlgeschlagen",
|
||||||
"rdpUnicodeKeyboardMode": "Unicode-Tastaturmodus",
|
"rdpUnicodeKeyboardMode": "Unicode-Tastaturmodus",
|
||||||
"sessionToolbarShow": "Werkzeugleiste zeigen",
|
"sessionToolbarShow": "Werkzeugleiste zeigen",
|
||||||
"sessionToolbarHide": "Werkzeugleiste ausblenden",
|
"sessionToolbarHide": "Werkzeugleiste ausblenden"
|
||||||
"actionUpdateSiteApprovals": "Standortgenehmigungen aktualisieren"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-29
@@ -178,7 +178,7 @@
|
|||||||
"shareDeleteConfirm": "Confirm Delete Shareable Link",
|
"shareDeleteConfirm": "Confirm Delete Shareable Link",
|
||||||
"shareQuestionRemove": "Are you sure you want to delete this share link?",
|
"shareQuestionRemove": "Are you sure you want to delete this share link?",
|
||||||
"shareMessageRemove": "Once deleted, the link will no longer work and anyone using it will lose access to the resource.",
|
"shareMessageRemove": "Once deleted, the link will no longer work and anyone using it will lose access to the resource.",
|
||||||
"shareTokenDescription": "The access token can be passed as a query parameter or in request headers. By default it must be sent on every request. If session persistence is enabled, the first request exchanges it for a session cookie.",
|
"shareTokenDescription": "The access token can be passed in two ways: as a query parameter or in the request headers. These must be passed from the client on every request for authenticated access.",
|
||||||
"accessToken": "Access Token",
|
"accessToken": "Access Token",
|
||||||
"usageExamples": "Usage Examples",
|
"usageExamples": "Usage Examples",
|
||||||
"tokenId": "Token ID",
|
"tokenId": "Token ID",
|
||||||
@@ -196,14 +196,8 @@
|
|||||||
"shareTitleOptional": "Title (optional)",
|
"shareTitleOptional": "Title (optional)",
|
||||||
"sharePathOptional": "Path (optional)",
|
"sharePathOptional": "Path (optional)",
|
||||||
"sharePathDescription": "The link will redirect users to this path after authentication.",
|
"sharePathDescription": "The link will redirect users to this path after authentication.",
|
||||||
"shareAssociateUserOptional": "Associate User (optional)",
|
|
||||||
"shareAssociateUserDescription": "When set, requests using this link are attributed to the user in access logs and identity headers. The link is removed if the user leaves the organization.",
|
|
||||||
"userSelect": "Select user",
|
|
||||||
"usersNotFound": "No users found",
|
|
||||||
"expireIn": "Expire In",
|
"expireIn": "Expire In",
|
||||||
"neverExpire": "Never expire",
|
"neverExpire": "Never expire",
|
||||||
"sharePersistSession": "Persist session after first use",
|
|
||||||
"sharePersistSessionDescription": "When enabled, the first request with this token via a query param or header sets a session cookie so later requests do not need the token. Leave off for API clients that should send the token on every request.",
|
|
||||||
"shareExpireDescription": "Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource.",
|
"shareExpireDescription": "Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource.",
|
||||||
"shareSeeOnce": "You will only be able to see this link once. Make sure to copy it.",
|
"shareSeeOnce": "You will only be able to see this link once. Make sure to copy it.",
|
||||||
"shareAccessHint": "Anyone with this link can access the resource. Share it with care.",
|
"shareAccessHint": "Anyone with this link can access the resource. Share it with care.",
|
||||||
@@ -455,14 +449,8 @@
|
|||||||
"provisioningManage": "Provisioning",
|
"provisioningManage": "Provisioning",
|
||||||
"provisioningDescription": "Manage provisioning keys and review pending sites awaiting approval.",
|
"provisioningDescription": "Manage provisioning keys and review pending sites awaiting approval.",
|
||||||
"pendingSites": "Pending Sites",
|
"pendingSites": "Pending Sites",
|
||||||
"siteApproveSuccess": "Site and associated resources approved successfully",
|
"siteApproveSuccess": "Site approved successfully",
|
||||||
"siteApproveError": "Error approving site",
|
"siteApproveError": "Error approving site",
|
||||||
"siteReject": "Reject Site",
|
|
||||||
"siteQuestionReject": "Are you sure you want to reject this site?",
|
|
||||||
"siteMessageReject": "This will permanently delete the site and any associated resources that are still pending.",
|
|
||||||
"siteConfirmReject": "Confirm Reject Site",
|
|
||||||
"siteRejectSuccess": "Site rejected successfully",
|
|
||||||
"siteRejectError": "Error rejecting site",
|
|
||||||
"provisioningKeys": "Provisioning Keys",
|
"provisioningKeys": "Provisioning Keys",
|
||||||
"searchProvisioningKeys": "Search provisioning keys...",
|
"searchProvisioningKeys": "Search provisioning keys...",
|
||||||
"provisioningKeysAdd": "Generate Provisioning Key",
|
"provisioningKeysAdd": "Generate Provisioning Key",
|
||||||
@@ -478,12 +466,12 @@
|
|||||||
"provisioningKeysSave": "Save the provisioning key",
|
"provisioningKeysSave": "Save the provisioning key",
|
||||||
"provisioningKeysSaveDescription": "You will only be able to see this once. Copy it to a secure place.",
|
"provisioningKeysSaveDescription": "You will only be able to see this once. Copy it to a secure place.",
|
||||||
"provisioningKeysErrorCreate": "Error creating provisioning key",
|
"provisioningKeysErrorCreate": "Error creating provisioning key",
|
||||||
"provisioningKeysList": "New Provisioning Key",
|
"provisioningKeysList": "New provisioning key",
|
||||||
"provisioningKeysMaxBatchSize": "Max Batch Size",
|
"provisioningKeysMaxBatchSize": "Max batch size",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Unlimited batch size (no limit)",
|
"provisioningKeysUnlimitedBatchSize": "Unlimited batch size (no limit)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Unlimited",
|
"provisioningKeysMaxBatchUnlimited": "Unlimited",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Enter a valid max batch size (1–1,000,000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Enter a valid max batch size (1–1,000,000).",
|
||||||
"provisioningKeysValidUntil": "Valid Until",
|
"provisioningKeysValidUntil": "Valid until",
|
||||||
"provisioningKeysValidUntilHint": "Leave empty for no expiration.",
|
"provisioningKeysValidUntilHint": "Leave empty for no expiration.",
|
||||||
"provisioningKeysValidUntilInvalid": "Enter a valid date and time.",
|
"provisioningKeysValidUntilInvalid": "Enter a valid date and time.",
|
||||||
"provisioningKeysNumUsed": "Times used",
|
"provisioningKeysNumUsed": "Times used",
|
||||||
@@ -492,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "Never",
|
"provisioningKeysNeverUsed": "Never",
|
||||||
"provisioningKeysEdit": "Edit Provisioning Key",
|
"provisioningKeysEdit": "Edit Provisioning Key",
|
||||||
"provisioningKeysEditDescription": "Update the max batch size and expiration time for this key.",
|
"provisioningKeysEditDescription": "Update the max batch size and expiration time for this key.",
|
||||||
"provisioningKeysApproveNewSites": "Approve New Sites",
|
"provisioningKeysApproveNewSites": "Approve new sites",
|
||||||
"provisioningKeysApproveNewSitesDescription": "Automatically approve sites that register with this key.",
|
"provisioningKeysApproveNewSitesDescription": "Automatically approve sites that register with this key.",
|
||||||
"provisioningKeysUpdateError": "Error updating provisioning key",
|
"provisioningKeysUpdateError": "Error updating provisioning key",
|
||||||
"provisioningKeysUpdated": "Provisioning key updated",
|
"provisioningKeysUpdated": "Provisioning key updated",
|
||||||
@@ -1432,8 +1420,6 @@
|
|||||||
"setupTokenDescription": "Enter the setup token from the server console.",
|
"setupTokenDescription": "Enter the setup token from the server console.",
|
||||||
"setupTokenRequired": "Setup token is required",
|
"setupTokenRequired": "Setup token is required",
|
||||||
"actionUpdateSite": "Update Site",
|
"actionUpdateSite": "Update Site",
|
||||||
"actionApproveSite": "Approve Site",
|
|
||||||
"actionRejectSite": "Reject Site",
|
|
||||||
"actionResetSiteBandwidth": "Reset Organization Bandwidth",
|
"actionResetSiteBandwidth": "Reset Organization Bandwidth",
|
||||||
"actionListSiteRoles": "List Allowed Site Roles",
|
"actionListSiteRoles": "List Allowed Site Roles",
|
||||||
"actionCreateResource": "Create Resource",
|
"actionCreateResource": "Create Resource",
|
||||||
@@ -1517,10 +1503,6 @@
|
|||||||
"actionListInvitations": "List Invitations",
|
"actionListInvitations": "List Invitations",
|
||||||
"actionExportLogs": "Export Logs",
|
"actionExportLogs": "Export Logs",
|
||||||
"actionViewLogs": "View Logs",
|
"actionViewLogs": "View Logs",
|
||||||
"actionCreateSiteProvisioningKey": "Create Site Provisioning Key",
|
|
||||||
"actionListSiteProvisioningKeys": "List Site Provisioning Keys",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Update Site Provisioning Key",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Delete Site Provisioning Key",
|
|
||||||
"noneSelected": "None selected",
|
"noneSelected": "None selected",
|
||||||
"orgNotFound2": "No organizations found.",
|
"orgNotFound2": "No organizations found.",
|
||||||
"search": "Search…",
|
"search": "Search…",
|
||||||
@@ -1535,7 +1517,6 @@
|
|||||||
"otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.",
|
"otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.",
|
||||||
"otpAuthSubmit": "Submit Code",
|
"otpAuthSubmit": "Submit Code",
|
||||||
"idpContinue": "Or continue with",
|
"idpContinue": "Or continue with",
|
||||||
"idpLastUsed": "Last Used",
|
|
||||||
"otpAuthBack": "Back to Password",
|
"otpAuthBack": "Back to Password",
|
||||||
"navbar": "Navigation Menu",
|
"navbar": "Navigation Menu",
|
||||||
"navbarDescription": "Main navigation menu for the application",
|
"navbarDescription": "Main navigation menu for the application",
|
||||||
@@ -3095,8 +3076,8 @@
|
|||||||
"sourceAddress": "Source Address",
|
"sourceAddress": "Source Address",
|
||||||
"destinationAddress": "Destination Address",
|
"destinationAddress": "Destination Address",
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
"licenseRequiredToUse": "An <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> license or <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> is required to use this feature. <bookADemoLink>Book a free demo or POC trial to learn more.</bookADemoLink>",
|
"licenseRequiredToUse": "An <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> license or <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> is required to use this feature. <bookADemoLink>Book a free demo or POC trial to learn more</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "The <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is required to use this feature. This feature is also available in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Book a free demo or POC trial to learn more.</bookADemoLink>",
|
"ossEnterpriseEditionRequired": "The <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is required to use this feature. This feature is also available in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Book a free demo or POC trial to learn more</bookADemoLink>.",
|
||||||
"certResolver": "Certificate Resolver",
|
"certResolver": "Certificate Resolver",
|
||||||
"certResolverDescription": "Select the certificate resolver to use for this resource.",
|
"certResolverDescription": "Select the certificate resolver to use for this resource.",
|
||||||
"selectCertResolver": "Select Certificate Resolver",
|
"selectCertResolver": "Select Certificate Resolver",
|
||||||
@@ -3821,6 +3802,5 @@
|
|||||||
"rdpUploadFailed": "Upload failed",
|
"rdpUploadFailed": "Upload failed",
|
||||||
"rdpUnicodeKeyboardMode": "Unicode keyboard mode",
|
"rdpUnicodeKeyboardMode": "Unicode keyboard mode",
|
||||||
"sessionToolbarShow": "Show toolbar",
|
"sessionToolbarShow": "Show toolbar",
|
||||||
"sessionToolbarHide": "Hide toolbar",
|
"sessionToolbarHide": "Hide toolbar"
|
||||||
"actionUpdateSiteApprovals": "Update Site Approvals"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-21
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Aprovisionamiento",
|
"provisioningManage": "Aprovisionamiento",
|
||||||
"provisioningDescription": "Administrar las claves de aprovisionamiento y revisar los sitios pendientes de aprobación.",
|
"provisioningDescription": "Administrar las claves de aprovisionamiento y revisar los sitios pendientes de aprobación.",
|
||||||
"pendingSites": "Sitios pendientes",
|
"pendingSites": "Sitios pendientes",
|
||||||
"siteApproveSuccess": "Sitio y recursos asociados aprobados correctamente",
|
"siteApproveSuccess": "Sitio aprobado con éxito",
|
||||||
"siteApproveError": "Error al aprobar el sitio",
|
"siteApproveError": "Error al aprobar el sitio",
|
||||||
"siteReject": "Rechazar Sitio",
|
|
||||||
"siteQuestionReject": "¿Está seguro de que desea rechazar este sitio?",
|
|
||||||
"siteMessageReject": "Esto eliminará permanentemente el sitio y cualquier recurso asociado que aún esté pendiente.",
|
|
||||||
"siteConfirmReject": "Confirmar Rechazo del Sitio",
|
|
||||||
"siteRejectSuccess": "Sitio rechazado correctamente",
|
|
||||||
"siteRejectError": "Error al rechazar el sitio",
|
|
||||||
"provisioningKeys": "Claves de aprovisionamiento",
|
"provisioningKeys": "Claves de aprovisionamiento",
|
||||||
"searchProvisioningKeys": "Buscar claves de suministro...",
|
"searchProvisioningKeys": "Buscar claves de suministro...",
|
||||||
"provisioningKeysAdd": "Generar clave de aprovisionamiento",
|
"provisioningKeysAdd": "Generar clave de aprovisionamiento",
|
||||||
@@ -472,12 +466,12 @@
|
|||||||
"provisioningKeysSave": "Guardar la clave de aprovisionamiento",
|
"provisioningKeysSave": "Guardar la clave de aprovisionamiento",
|
||||||
"provisioningKeysSaveDescription": "Sólo podrás verlo una vez. Copítalo a un lugar seguro.",
|
"provisioningKeysSaveDescription": "Sólo podrás verlo una vez. Copítalo a un lugar seguro.",
|
||||||
"provisioningKeysErrorCreate": "Error al crear la clave de provisioning",
|
"provisioningKeysErrorCreate": "Error al crear la clave de provisioning",
|
||||||
"provisioningKeysList": "Nueva Clave de Provisión",
|
"provisioningKeysList": "Nueva clave de aprovisionamiento",
|
||||||
"provisioningKeysMaxBatchSize": "Tamaño Máximo del Lote",
|
"provisioningKeysMaxBatchSize": "Tamaño máximo de lote",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Tamaño ilimitado del lote (sin límite)",
|
"provisioningKeysUnlimitedBatchSize": "Tamaño ilimitado del lote (sin límite)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Ilimitado",
|
"provisioningKeysMaxBatchUnlimited": "Ilimitado",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Introduzca un tamaño máximo de lote válido (1–1,000,000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Introduzca un tamaño máximo de lote válido (1–1,000,000).",
|
||||||
"provisioningKeysValidUntil": "Válido Hasta",
|
"provisioningKeysValidUntil": "Válido hasta",
|
||||||
"provisioningKeysValidUntilHint": "Dejar vacío para no expirar.",
|
"provisioningKeysValidUntilHint": "Dejar vacío para no expirar.",
|
||||||
"provisioningKeysValidUntilInvalid": "Introduzca una fecha y hora válidas.",
|
"provisioningKeysValidUntilInvalid": "Introduzca una fecha y hora válidas.",
|
||||||
"provisioningKeysNumUsed": "Tiempos usados",
|
"provisioningKeysNumUsed": "Tiempos usados",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "Nunca",
|
"provisioningKeysNeverUsed": "Nunca",
|
||||||
"provisioningKeysEdit": "Editar clave de aprovisionamiento",
|
"provisioningKeysEdit": "Editar clave de aprovisionamiento",
|
||||||
"provisioningKeysEditDescription": "Actualizar el tamaño máximo de lote y el tiempo de caducidad para esta clave.",
|
"provisioningKeysEditDescription": "Actualizar el tamaño máximo de lote y el tiempo de caducidad para esta clave.",
|
||||||
"provisioningKeysApproveNewSites": "Aprobar Nuevos Sitios",
|
"provisioningKeysApproveNewSites": "Aprobar nuevos sitios",
|
||||||
"provisioningKeysApproveNewSitesDescription": "Aprobar automáticamente los sitios que se registran con esta clave.",
|
"provisioningKeysApproveNewSitesDescription": "Aprobar automáticamente los sitios que se registran con esta clave.",
|
||||||
"provisioningKeysUpdateError": "Error al actualizar la clave de aprovisionamiento",
|
"provisioningKeysUpdateError": "Error al actualizar la clave de aprovisionamiento",
|
||||||
"provisioningKeysUpdated": "Clave de aprovisionamiento actualizada",
|
"provisioningKeysUpdated": "Clave de aprovisionamiento actualizada",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Crear usuario Org",
|
"createOrgUser": "Crear usuario Org",
|
||||||
"actionUpdateOrg": "Actualizar organización",
|
"actionUpdateOrg": "Actualizar organización",
|
||||||
"actionRemoveInvitation": "Eliminar invitación",
|
"actionRemoveInvitation": "Eliminar invitación",
|
||||||
"actionRemoveUserRole": "Quitar Rol de Usuario",
|
|
||||||
"actionUpdateUser": "Actualizar usuario",
|
"actionUpdateUser": "Actualizar usuario",
|
||||||
"actionGetUser": "Obtener usuario",
|
"actionGetUser": "Obtener usuario",
|
||||||
"actionGetOrgUser": "Obtener usuario de la organización",
|
"actionGetOrgUser": "Obtener usuario de la organización",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Ingrese el token de configuración desde la consola del servidor.",
|
"setupTokenDescription": "Ingrese el token de configuración desde la consola del servidor.",
|
||||||
"setupTokenRequired": "Se requiere el token de configuración",
|
"setupTokenRequired": "Se requiere el token de configuración",
|
||||||
"actionUpdateSite": "Actualizar sitio",
|
"actionUpdateSite": "Actualizar sitio",
|
||||||
"actionApproveSite": "Aprobar Sitio",
|
|
||||||
"actionRejectSite": "Rechazar Sitio",
|
|
||||||
"actionResetSiteBandwidth": "Restablecer ancho de banda de la organización",
|
"actionResetSiteBandwidth": "Restablecer ancho de banda de la organización",
|
||||||
"actionListSiteRoles": "Lista de roles permitidos del sitio",
|
"actionListSiteRoles": "Lista de roles permitidos del sitio",
|
||||||
"actionCreateResource": "Crear Recurso",
|
"actionCreateResource": "Crear Recurso",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Listar invitaciones",
|
"actionListInvitations": "Listar invitaciones",
|
||||||
"actionExportLogs": "Exportar registros",
|
"actionExportLogs": "Exportar registros",
|
||||||
"actionViewLogs": "Ver registros",
|
"actionViewLogs": "Ver registros",
|
||||||
"actionCreateSiteProvisioningKey": "Crear clave de aprovisionamiento del sitio",
|
|
||||||
"actionListSiteProvisioningKeys": "Listado de claves de aprovisionamiento del sitio",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Actualizar clave de aprovisionamiento del sitio",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Eliminar clave de aprovisionamiento del sitio",
|
|
||||||
"noneSelected": "Ninguno seleccionado",
|
"noneSelected": "Ninguno seleccionado",
|
||||||
"orgNotFound2": "No se encontraron organizaciones.",
|
"orgNotFound2": "No se encontraron organizaciones.",
|
||||||
"search": "Buscar…",
|
"search": "Buscar…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Introduzca el código de su aplicación de autenticación o uno de sus códigos de copia de seguridad de un solo uso.",
|
"otpAuthDescription": "Introduzca el código de su aplicación de autenticación o uno de sus códigos de copia de seguridad de un solo uso.",
|
||||||
"otpAuthSubmit": "Enviar código",
|
"otpAuthSubmit": "Enviar código",
|
||||||
"idpContinue": "O continuar con",
|
"idpContinue": "O continuar con",
|
||||||
"idpLastUsed": "Último Uso",
|
|
||||||
"otpAuthBack": "Volver a la contraseña",
|
"otpAuthBack": "Volver a la contraseña",
|
||||||
"navbar": "Menú de navegación",
|
"navbar": "Menú de navegación",
|
||||||
"navbarDescription": "Menú de navegación principal para la aplicación",
|
"navbarDescription": "Menú de navegación principal para la aplicación",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Error de subida",
|
"rdpUploadFailed": "Error de subida",
|
||||||
"rdpUnicodeKeyboardMode": "Modo teclado Unicode",
|
"rdpUnicodeKeyboardMode": "Modo teclado Unicode",
|
||||||
"sessionToolbarShow": "Mostrar barra de herramientas",
|
"sessionToolbarShow": "Mostrar barra de herramientas",
|
||||||
"sessionToolbarHide": "Ocultar barra de herramientas",
|
"sessionToolbarHide": "Ocultar barra de herramientas"
|
||||||
"actionUpdateSiteApprovals": "Actualizar aprobaciones del sitio"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-19
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Mise en place",
|
"provisioningManage": "Mise en place",
|
||||||
"provisioningDescription": "Gérer les clés de provisioning et examiner les sites en attente d'approbation.",
|
"provisioningDescription": "Gérer les clés de provisioning et examiner les sites en attente d'approbation.",
|
||||||
"pendingSites": "Sites en attente",
|
"pendingSites": "Sites en attente",
|
||||||
"siteApproveSuccess": "Site et ressources associées approuvés avec succès",
|
"siteApproveSuccess": "Site approuvé avec succès",
|
||||||
"siteApproveError": "Erreur lors de l'approbation du site",
|
"siteApproveError": "Erreur lors de l'approbation du site",
|
||||||
"siteReject": "Rejeter le site",
|
|
||||||
"siteQuestionReject": "Êtes-vous sûr de vouloir rejeter ce site ?",
|
|
||||||
"siteMessageReject": "Cela supprimera définitivement le site et toutes les ressources associées qui sont encore en attente.",
|
|
||||||
"siteConfirmReject": "Confirmer le rejet du site",
|
|
||||||
"siteRejectSuccess": "Site rejeté avec succès",
|
|
||||||
"siteRejectError": "Erreur lors du rejet du site",
|
|
||||||
"provisioningKeys": "Clés de provisionnement",
|
"provisioningKeys": "Clés de provisionnement",
|
||||||
"searchProvisioningKeys": "Recherche des clés de provision...",
|
"searchProvisioningKeys": "Recherche des clés de provision...",
|
||||||
"provisioningKeysAdd": "Générer une clé de provisioning",
|
"provisioningKeysAdd": "Générer une clé de provisioning",
|
||||||
@@ -472,12 +466,12 @@
|
|||||||
"provisioningKeysSave": "Enregistrer la clé de provisioning",
|
"provisioningKeysSave": "Enregistrer la clé de provisioning",
|
||||||
"provisioningKeysSaveDescription": "Vous ne pourrez voir cela qu'une seule fois. Copiez-le dans un endroit sécurisé.",
|
"provisioningKeysSaveDescription": "Vous ne pourrez voir cela qu'une seule fois. Copiez-le dans un endroit sécurisé.",
|
||||||
"provisioningKeysErrorCreate": "Erreur lors de la création de la clé de provisioning",
|
"provisioningKeysErrorCreate": "Erreur lors de la création de la clé de provisioning",
|
||||||
"provisioningKeysList": "Nouvelle clé d'approvisionnement",
|
"provisioningKeysList": "Nouvelle clé de provisioning",
|
||||||
"provisioningKeysMaxBatchSize": "Taille maximale du lot",
|
"provisioningKeysMaxBatchSize": "Taille maximale du lot",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Taille de lot illimitée (sans limite)",
|
"provisioningKeysUnlimitedBatchSize": "Taille de lot illimitée (sans limite)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Illimité",
|
"provisioningKeysMaxBatchUnlimited": "Illimité",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Entrez une taille de lot maximale valide (1–1 000 000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Entrez une taille de lot maximale valide (1–1 000 000).",
|
||||||
"provisioningKeysValidUntil": "Valable jusqu'à",
|
"provisioningKeysValidUntil": "Valable jusqu'au",
|
||||||
"provisioningKeysValidUntilHint": "Laisser vide pour ne pas expirer.",
|
"provisioningKeysValidUntilHint": "Laisser vide pour ne pas expirer.",
|
||||||
"provisioningKeysValidUntilInvalid": "Entrez une date et une heure valides.",
|
"provisioningKeysValidUntilInvalid": "Entrez une date et une heure valides.",
|
||||||
"provisioningKeysNumUsed": "Nombre de fois utilisées",
|
"provisioningKeysNumUsed": "Nombre de fois utilisées",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Créer un utilisateur Org",
|
"createOrgUser": "Créer un utilisateur Org",
|
||||||
"actionUpdateOrg": "Mettre à jour l'organisation",
|
"actionUpdateOrg": "Mettre à jour l'organisation",
|
||||||
"actionRemoveInvitation": "Supprimer l'invitation",
|
"actionRemoveInvitation": "Supprimer l'invitation",
|
||||||
"actionRemoveUserRole": "Supprimer le rôle d'utilisateur",
|
|
||||||
"actionUpdateUser": "Mettre à jour l'utilisateur",
|
"actionUpdateUser": "Mettre à jour l'utilisateur",
|
||||||
"actionGetUser": "Obtenir l'utilisateur",
|
"actionGetUser": "Obtenir l'utilisateur",
|
||||||
"actionGetOrgUser": "Obtenir l'utilisateur de l'organisation",
|
"actionGetOrgUser": "Obtenir l'utilisateur de l'organisation",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Entrez le jeton de configuration depuis la console du serveur.",
|
"setupTokenDescription": "Entrez le jeton de configuration depuis la console du serveur.",
|
||||||
"setupTokenRequired": "Le jeton de configuration est requis.",
|
"setupTokenRequired": "Le jeton de configuration est requis.",
|
||||||
"actionUpdateSite": "Mettre à jour un site",
|
"actionUpdateSite": "Mettre à jour un site",
|
||||||
"actionApproveSite": "Approuver le site",
|
|
||||||
"actionRejectSite": "Rejeter le site",
|
|
||||||
"actionResetSiteBandwidth": "Réinitialiser la bande passante de l'organisation",
|
"actionResetSiteBandwidth": "Réinitialiser la bande passante de l'organisation",
|
||||||
"actionListSiteRoles": "Lister les rôles autorisés du site",
|
"actionListSiteRoles": "Lister les rôles autorisés du site",
|
||||||
"actionCreateResource": "Créer une ressource",
|
"actionCreateResource": "Créer une ressource",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Lister les invitations",
|
"actionListInvitations": "Lister les invitations",
|
||||||
"actionExportLogs": "Exporter les journaux",
|
"actionExportLogs": "Exporter les journaux",
|
||||||
"actionViewLogs": "Voir les logs",
|
"actionViewLogs": "Voir les logs",
|
||||||
"actionCreateSiteProvisioningKey": "Créer une clé de provisionnement de site",
|
|
||||||
"actionListSiteProvisioningKeys": "Lister les clés de provisionnement de site",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Mettre à jour la clé de provisionnement de site",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Supprimer la clé de provisionnement de site",
|
|
||||||
"noneSelected": "Aucune sélection",
|
"noneSelected": "Aucune sélection",
|
||||||
"orgNotFound2": "Aucune organisation trouvée.",
|
"orgNotFound2": "Aucune organisation trouvée.",
|
||||||
"search": "Rechercher…",
|
"search": "Rechercher…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Entrez le code de votre application d'authentification ou l'un de vos codes de secours à usage unique.",
|
"otpAuthDescription": "Entrez le code de votre application d'authentification ou l'un de vos codes de secours à usage unique.",
|
||||||
"otpAuthSubmit": "Soumettre le code",
|
"otpAuthSubmit": "Soumettre le code",
|
||||||
"idpContinue": "Ou continuer avec",
|
"idpContinue": "Ou continuer avec",
|
||||||
"idpLastUsed": "Dernière utilisation",
|
|
||||||
"otpAuthBack": "Retour au mot de passe",
|
"otpAuthBack": "Retour au mot de passe",
|
||||||
"navbar": "Menu de navigation",
|
"navbar": "Menu de navigation",
|
||||||
"navbarDescription": "Menu de navigation principal de l'application",
|
"navbarDescription": "Menu de navigation principal de l'application",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Échec du téléchargement",
|
"rdpUploadFailed": "Échec du téléchargement",
|
||||||
"rdpUnicodeKeyboardMode": "Mode clavier Unicode",
|
"rdpUnicodeKeyboardMode": "Mode clavier Unicode",
|
||||||
"sessionToolbarShow": "Afficher la barre d'outils",
|
"sessionToolbarShow": "Afficher la barre d'outils",
|
||||||
"sessionToolbarHide": "Masquer la barre d'outils",
|
"sessionToolbarHide": "Masquer la barre d'outils"
|
||||||
"actionUpdateSiteApprovals": "Mettre à jour les approbations de site"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-21
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Accantonamento",
|
"provisioningManage": "Accantonamento",
|
||||||
"provisioningDescription": "Gestire le chiavi di provisioning e rivedere i siti in attesa di approvazione.",
|
"provisioningDescription": "Gestire le chiavi di provisioning e rivedere i siti in attesa di approvazione.",
|
||||||
"pendingSites": "Siti In Attesa",
|
"pendingSites": "Siti In Attesa",
|
||||||
"siteApproveSuccess": "Sito e risorse associate approvate con successo",
|
"siteApproveSuccess": "Sito approvato con successo",
|
||||||
"siteApproveError": "Errore nell'approvazione del sito",
|
"siteApproveError": "Errore nell'approvazione del sito",
|
||||||
"siteReject": "Rifiuta Sito",
|
|
||||||
"siteQuestionReject": "Sei sicuro di voler rifiutare questo sito?",
|
|
||||||
"siteMessageReject": "Questo eliminerà permanentemente il sito e tutte le risorse associate ancora in sospeso.",
|
|
||||||
"siteConfirmReject": "Conferma Rifiuto Sito",
|
|
||||||
"siteRejectSuccess": "Sito rifiutato con successo",
|
|
||||||
"siteRejectError": "Errore nel rifiutare il sito",
|
|
||||||
"provisioningKeys": "Chiavi Di Provvedimento",
|
"provisioningKeys": "Chiavi Di Provvedimento",
|
||||||
"searchProvisioningKeys": "Cerca le chiavi di provisioning...",
|
"searchProvisioningKeys": "Cerca le chiavi di provisioning...",
|
||||||
"provisioningKeysAdd": "Genera Chiave di provisioning",
|
"provisioningKeysAdd": "Genera Chiave di provisioning",
|
||||||
@@ -472,12 +466,12 @@
|
|||||||
"provisioningKeysSave": "Salva la chiave di provisioning",
|
"provisioningKeysSave": "Salva la chiave di provisioning",
|
||||||
"provisioningKeysSaveDescription": "Sarai in grado di vedere solo una volta. Copiarlo in un posto sicuro.",
|
"provisioningKeysSaveDescription": "Sarai in grado di vedere solo una volta. Copiarlo in un posto sicuro.",
|
||||||
"provisioningKeysErrorCreate": "Errore nella creazione della chiave di provisioning",
|
"provisioningKeysErrorCreate": "Errore nella creazione della chiave di provisioning",
|
||||||
"provisioningKeysList": "Nuova Chiave di Provisioning",
|
"provisioningKeysList": "Nuova chiave di provisioning",
|
||||||
"provisioningKeysMaxBatchSize": "Dimensione Massima Batch",
|
"provisioningKeysMaxBatchSize": "Dimensione massima batch",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Dimensione illimitata del batch (nessun limite)",
|
"provisioningKeysUnlimitedBatchSize": "Dimensione illimitata del batch (nessun limite)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Illimitato",
|
"provisioningKeysMaxBatchUnlimited": "Illimitato",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Inserisci una dimensione massima valida del batch (1–1.000.000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Inserisci una dimensione massima valida del batch (1–1.000.000).",
|
||||||
"provisioningKeysValidUntil": "Valido Fino a",
|
"provisioningKeysValidUntil": "Valido fino al",
|
||||||
"provisioningKeysValidUntilHint": "Lasciare vuoto per nessuna scadenza.",
|
"provisioningKeysValidUntilHint": "Lasciare vuoto per nessuna scadenza.",
|
||||||
"provisioningKeysValidUntilInvalid": "Inserisci una data e ora valide.",
|
"provisioningKeysValidUntilInvalid": "Inserisci una data e ora valide.",
|
||||||
"provisioningKeysNumUsed": "Volte usate",
|
"provisioningKeysNumUsed": "Volte usate",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "Mai",
|
"provisioningKeysNeverUsed": "Mai",
|
||||||
"provisioningKeysEdit": "Modifica Chiave di provisioning",
|
"provisioningKeysEdit": "Modifica Chiave di provisioning",
|
||||||
"provisioningKeysEditDescription": "Aggiorna la dimensione massima del batch e il tempo di scadenza per questa chiave.",
|
"provisioningKeysEditDescription": "Aggiorna la dimensione massima del batch e il tempo di scadenza per questa chiave.",
|
||||||
"provisioningKeysApproveNewSites": "Approva Nuovi Siti",
|
"provisioningKeysApproveNewSites": "Approva nuovi siti",
|
||||||
"provisioningKeysApproveNewSitesDescription": "Approvare automaticamente i siti che si registrano con questa chiave.",
|
"provisioningKeysApproveNewSitesDescription": "Approvare automaticamente i siti che si registrano con questa chiave.",
|
||||||
"provisioningKeysUpdateError": "Errore nell'aggiornamento della chiave di provisioning",
|
"provisioningKeysUpdateError": "Errore nell'aggiornamento della chiave di provisioning",
|
||||||
"provisioningKeysUpdated": "Chiave di provisioning aggiornata",
|
"provisioningKeysUpdated": "Chiave di provisioning aggiornata",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Crea Utente Org",
|
"createOrgUser": "Crea Utente Org",
|
||||||
"actionUpdateOrg": "Aggiorna Organizzazione",
|
"actionUpdateOrg": "Aggiorna Organizzazione",
|
||||||
"actionRemoveInvitation": "Rimuovi Invito",
|
"actionRemoveInvitation": "Rimuovi Invito",
|
||||||
"actionRemoveUserRole": "Rimuovi Ruolo Utente",
|
|
||||||
"actionUpdateUser": "Aggiorna Utente",
|
"actionUpdateUser": "Aggiorna Utente",
|
||||||
"actionGetUser": "Ottieni Utente",
|
"actionGetUser": "Ottieni Utente",
|
||||||
"actionGetOrgUser": "Ottieni Utente Organizzazione",
|
"actionGetOrgUser": "Ottieni Utente Organizzazione",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Inserisci il token di configurazione dalla console del server.",
|
"setupTokenDescription": "Inserisci il token di configurazione dalla console del server.",
|
||||||
"setupTokenRequired": "Il token di configurazione è richiesto",
|
"setupTokenRequired": "Il token di configurazione è richiesto",
|
||||||
"actionUpdateSite": "Aggiorna Sito",
|
"actionUpdateSite": "Aggiorna Sito",
|
||||||
"actionApproveSite": "Approva Sito",
|
|
||||||
"actionRejectSite": "Rifiuta Sito",
|
|
||||||
"actionResetSiteBandwidth": "Reimposta Larghezza Banda Dell'Organizzazione",
|
"actionResetSiteBandwidth": "Reimposta Larghezza Banda Dell'Organizzazione",
|
||||||
"actionListSiteRoles": "Elenca Ruoli Sito Consentiti",
|
"actionListSiteRoles": "Elenca Ruoli Sito Consentiti",
|
||||||
"actionCreateResource": "Crea Risorsa",
|
"actionCreateResource": "Crea Risorsa",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Elenco Inviti",
|
"actionListInvitations": "Elenco Inviti",
|
||||||
"actionExportLogs": "Esporta Log",
|
"actionExportLogs": "Esporta Log",
|
||||||
"actionViewLogs": "Visualizza Log",
|
"actionViewLogs": "Visualizza Log",
|
||||||
"actionCreateSiteProvisioningKey": "Crea Chiave di Provisioning del Sito",
|
|
||||||
"actionListSiteProvisioningKeys": "Elenca Chiavi di Provisioning del Sito",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Aggiorna Chiave di Provisioning del Sito",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Elimina Chiave di Provisioning del Sito",
|
|
||||||
"noneSelected": "Nessuna selezione",
|
"noneSelected": "Nessuna selezione",
|
||||||
"orgNotFound2": "Nessuna organizzazione trovata.",
|
"orgNotFound2": "Nessuna organizzazione trovata.",
|
||||||
"search": "Cerca…",
|
"search": "Cerca…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Inserisci il codice dalla tua app di autenticazione o uno dei tuoi codici di backup monouso.",
|
"otpAuthDescription": "Inserisci il codice dalla tua app di autenticazione o uno dei tuoi codici di backup monouso.",
|
||||||
"otpAuthSubmit": "Invia Codice",
|
"otpAuthSubmit": "Invia Codice",
|
||||||
"idpContinue": "O continua con",
|
"idpContinue": "O continua con",
|
||||||
"idpLastUsed": "Ultimo Utilizzo",
|
|
||||||
"otpAuthBack": "Torna alla Password",
|
"otpAuthBack": "Torna alla Password",
|
||||||
"navbar": "Menu di Navigazione",
|
"navbar": "Menu di Navigazione",
|
||||||
"navbarDescription": "Menu di navigazione principale dell'applicazione",
|
"navbarDescription": "Menu di navigazione principale dell'applicazione",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Caricamento fallito",
|
"rdpUploadFailed": "Caricamento fallito",
|
||||||
"rdpUnicodeKeyboardMode": "Modalità tastiera Unicode",
|
"rdpUnicodeKeyboardMode": "Modalità tastiera Unicode",
|
||||||
"sessionToolbarShow": "Mostra barra degli strumenti",
|
"sessionToolbarShow": "Mostra barra degli strumenti",
|
||||||
"sessionToolbarHide": "Nascondi barra degli strumenti",
|
"sessionToolbarHide": "Nascondi barra degli strumenti"
|
||||||
"actionUpdateSiteApprovals": "Aggiorna Approvazioni del Sito"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-18
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "프로비저닝",
|
"provisioningManage": "프로비저닝",
|
||||||
"provisioningDescription": "프로비저닝 키를 관리하고 승인을 기다리는 사이트를 검토합니다.",
|
"provisioningDescription": "프로비저닝 키를 관리하고 승인을 기다리는 사이트를 검토합니다.",
|
||||||
"pendingSites": "대기중인 사이트",
|
"pendingSites": "대기중인 사이트",
|
||||||
"siteApproveSuccess": "사이트 및 관련 리소스가 성공적으로 승인되었습니다",
|
"siteApproveSuccess": "사이트가 성공적으로 승인되었습니다",
|
||||||
"siteApproveError": "사이트 승인 오류",
|
"siteApproveError": "사이트 승인 오류",
|
||||||
"siteReject": "사이트 거부",
|
|
||||||
"siteQuestionReject": "이 사이트를 거부하시겠습니까?",
|
|
||||||
"siteMessageReject": "이렇게 하면 펜딩 중인 사이트 및 관련 리소스가 영구적으로 삭제됩니다.",
|
|
||||||
"siteConfirmReject": "사이트 거부 확인",
|
|
||||||
"siteRejectSuccess": "사이트가 성공적으로 거부되었습니다",
|
|
||||||
"siteRejectError": "사이트 거부 오류",
|
|
||||||
"provisioningKeys": "프로비저닝 키",
|
"provisioningKeys": "프로비저닝 키",
|
||||||
"searchProvisioningKeys": "프로비저닝 키 검색...",
|
"searchProvisioningKeys": "프로비저닝 키 검색...",
|
||||||
"provisioningKeysAdd": "프로비저닝 키 생성",
|
"provisioningKeysAdd": "프로비저닝 키 생성",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "절대",
|
"provisioningKeysNeverUsed": "절대",
|
||||||
"provisioningKeysEdit": "프로비저닝 키 수정",
|
"provisioningKeysEdit": "프로비저닝 키 수정",
|
||||||
"provisioningKeysEditDescription": "이 키의 최대 배치 크기 및 만료 시간을 업데이트하세요.",
|
"provisioningKeysEditDescription": "이 키의 최대 배치 크기 및 만료 시간을 업데이트하세요.",
|
||||||
"provisioningKeysApproveNewSites": "새 사이트 승인",
|
"provisioningKeysApproveNewSites": "새로운 사이트 승인",
|
||||||
"provisioningKeysApproveNewSitesDescription": "이 키를 등록하는 사이트를 자동으로 승인합니다.",
|
"provisioningKeysApproveNewSitesDescription": "이 키를 등록하는 사이트를 자동으로 승인합니다.",
|
||||||
"provisioningKeysUpdateError": "프로비저닝 키 업데이트 오류",
|
"provisioningKeysUpdateError": "프로비저닝 키 업데이트 오류",
|
||||||
"provisioningKeysUpdated": "프로비저닝 키가 업데이트되었습니다",
|
"provisioningKeysUpdated": "프로비저닝 키가 업데이트되었습니다",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "조직 사용자 생성",
|
"createOrgUser": "조직 사용자 생성",
|
||||||
"actionUpdateOrg": "조직 업데이트",
|
"actionUpdateOrg": "조직 업데이트",
|
||||||
"actionRemoveInvitation": "초대 제거",
|
"actionRemoveInvitation": "초대 제거",
|
||||||
"actionRemoveUserRole": "사용자 역할 제거",
|
|
||||||
"actionUpdateUser": "사용자 업데이트",
|
"actionUpdateUser": "사용자 업데이트",
|
||||||
"actionGetUser": "사용자 조회",
|
"actionGetUser": "사용자 조회",
|
||||||
"actionGetOrgUser": "조직 사용자 가져오기",
|
"actionGetOrgUser": "조직 사용자 가져오기",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "서버 콘솔에서 설정 토큰 입력.",
|
"setupTokenDescription": "서버 콘솔에서 설정 토큰 입력.",
|
||||||
"setupTokenRequired": "설정 토큰이 필요합니다",
|
"setupTokenRequired": "설정 토큰이 필요합니다",
|
||||||
"actionUpdateSite": "사이트 업데이트",
|
"actionUpdateSite": "사이트 업데이트",
|
||||||
"actionApproveSite": "사이트 승인",
|
|
||||||
"actionRejectSite": "사이트 거부",
|
|
||||||
"actionResetSiteBandwidth": "조직 대역폭 재설정",
|
"actionResetSiteBandwidth": "조직 대역폭 재설정",
|
||||||
"actionListSiteRoles": "허용된 사이트 역할 목록",
|
"actionListSiteRoles": "허용된 사이트 역할 목록",
|
||||||
"actionCreateResource": "리소스 생성",
|
"actionCreateResource": "리소스 생성",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "초대 목록",
|
"actionListInvitations": "초대 목록",
|
||||||
"actionExportLogs": "로그 내보내기",
|
"actionExportLogs": "로그 내보내기",
|
||||||
"actionViewLogs": "로그 보기",
|
"actionViewLogs": "로그 보기",
|
||||||
"actionCreateSiteProvisioningKey": "사이트 프로비저닝 키 생성",
|
|
||||||
"actionListSiteProvisioningKeys": "사이트 프로비저닝 키 목록",
|
|
||||||
"actionUpdateSiteProvisioningKey": "사이트 프로비저닝 키 업데이트",
|
|
||||||
"actionDeleteSiteProvisioningKey": "사이트 프로비저닝 키 삭제",
|
|
||||||
"noneSelected": "선택된 항목 없음",
|
"noneSelected": "선택된 항목 없음",
|
||||||
"orgNotFound2": "조직이 없습니다.",
|
"orgNotFound2": "조직이 없습니다.",
|
||||||
"search": "검색…",
|
"search": "검색…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "인증 앱에서 코드를 입력하거나 단일 사용 백업 코드 중 하나를 입력하세요.",
|
"otpAuthDescription": "인증 앱에서 코드를 입력하거나 단일 사용 백업 코드 중 하나를 입력하세요.",
|
||||||
"otpAuthSubmit": "코드 제출",
|
"otpAuthSubmit": "코드 제출",
|
||||||
"idpContinue": "또는 계속 진행하십시오.",
|
"idpContinue": "또는 계속 진행하십시오.",
|
||||||
"idpLastUsed": "마지막 사용",
|
|
||||||
"otpAuthBack": "비밀번호로 돌아가기",
|
"otpAuthBack": "비밀번호로 돌아가기",
|
||||||
"navbar": "탐색 메뉴",
|
"navbar": "탐색 메뉴",
|
||||||
"navbarDescription": "애플리케이션의 주요 탐색 메뉴",
|
"navbarDescription": "애플리케이션의 주요 탐색 메뉴",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "업로드 실패",
|
"rdpUploadFailed": "업로드 실패",
|
||||||
"rdpUnicodeKeyboardMode": "유니코드 키보드 모드",
|
"rdpUnicodeKeyboardMode": "유니코드 키보드 모드",
|
||||||
"sessionToolbarShow": "툴바 보기",
|
"sessionToolbarShow": "툴바 보기",
|
||||||
"sessionToolbarHide": "툴바 숨기기",
|
"sessionToolbarHide": "툴바 숨기기"
|
||||||
"actionUpdateSiteApprovals": "사이트 승인 업데이트"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-21
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Levering",
|
"provisioningManage": "Levering",
|
||||||
"provisioningDescription": "Administrer foreløpig nøkler og gjennomgå ventende nettsteder som venter på godkjenning.",
|
"provisioningDescription": "Administrer foreløpig nøkler og gjennomgå ventende nettsteder som venter på godkjenning.",
|
||||||
"pendingSites": "Ventende nettsteder",
|
"pendingSites": "Ventende nettsteder",
|
||||||
"siteApproveSuccess": "Område og tilknyttede ressurser godkjent",
|
"siteApproveSuccess": "Vellykket godkjenning av nettsted",
|
||||||
"siteApproveError": "Feil ved godkjenning av side",
|
"siteApproveError": "Feil ved godkjenning av side",
|
||||||
"siteReject": "Avvis Område",
|
|
||||||
"siteQuestionReject": "Er du sikker på at du vil avvise dette området?",
|
|
||||||
"siteMessageReject": "Dette vil permanent slette området og eventuelle tilknyttede ressurser som fortsatt venter.",
|
|
||||||
"siteConfirmReject": "Bekreft Avvisning av Område",
|
|
||||||
"siteRejectSuccess": "Område avvist",
|
|
||||||
"siteRejectError": "Feil ved avvisning av området",
|
|
||||||
"provisioningKeys": "Foreløpig nøkler",
|
"provisioningKeys": "Foreløpig nøkler",
|
||||||
"searchProvisioningKeys": "Søk varer i lagrings nøkler...",
|
"searchProvisioningKeys": "Søk varer i lagrings nøkler...",
|
||||||
"provisioningKeysAdd": "Generer fremvisende nøkkel",
|
"provisioningKeysAdd": "Generer fremvisende nøkkel",
|
||||||
@@ -472,12 +466,12 @@
|
|||||||
"provisioningKeysSave": "Lagre den midlertidig nøkkelen",
|
"provisioningKeysSave": "Lagre den midlertidig nøkkelen",
|
||||||
"provisioningKeysSaveDescription": "Du kan bare se denne én gang. Kopier det til et sikkert sted.",
|
"provisioningKeysSaveDescription": "Du kan bare se denne én gang. Kopier det til et sikkert sted.",
|
||||||
"provisioningKeysErrorCreate": "Feil under oppretting av foreløpig nøkkel",
|
"provisioningKeysErrorCreate": "Feil under oppretting av foreløpig nøkkel",
|
||||||
"provisioningKeysList": "Ny Forsyningsnøkkel",
|
"provisioningKeysList": "Ny provisorisk nøkkel",
|
||||||
"provisioningKeysMaxBatchSize": "Maks Batch Størrelse",
|
"provisioningKeysMaxBatchSize": "Maks størrelse på bunt",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Ubegrenset mengde bunt (ingen begrensning)",
|
"provisioningKeysUnlimitedBatchSize": "Ubegrenset mengde bunt (ingen begrensning)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Ubegrenset",
|
"provisioningKeysMaxBatchUnlimited": "Ubegrenset",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Angi en gyldig sjakkstørrelse (1–1 000.000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Angi en gyldig sjakkstørrelse (1–1 000.000).",
|
||||||
"provisioningKeysValidUntil": "Gyldig Til",
|
"provisioningKeysValidUntil": "Gyldig til",
|
||||||
"provisioningKeysValidUntilHint": "La stå tomt for ingen utløp.",
|
"provisioningKeysValidUntilHint": "La stå tomt for ingen utløp.",
|
||||||
"provisioningKeysValidUntilInvalid": "Angi en gyldig dato og klokkeslett.",
|
"provisioningKeysValidUntilInvalid": "Angi en gyldig dato og klokkeslett.",
|
||||||
"provisioningKeysNumUsed": "Antall ganger brukt",
|
"provisioningKeysNumUsed": "Antall ganger brukt",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "Aldri",
|
"provisioningKeysNeverUsed": "Aldri",
|
||||||
"provisioningKeysEdit": "Rediger bestemmelsesnøkkel",
|
"provisioningKeysEdit": "Rediger bestemmelsesnøkkel",
|
||||||
"provisioningKeysEditDescription": "Oppdater maksimal størrelse for bunt og utløpstid for denne nøkkelen.",
|
"provisioningKeysEditDescription": "Oppdater maksimal størrelse for bunt og utløpstid for denne nøkkelen.",
|
||||||
"provisioningKeysApproveNewSites": "Godkjenn Nye Områder",
|
"provisioningKeysApproveNewSites": "Godkjenn nye nettsteder",
|
||||||
"provisioningKeysApproveNewSitesDescription": "Godkjenn automatisk nettsteder som registrerer deg med denne nøkkelen.",
|
"provisioningKeysApproveNewSitesDescription": "Godkjenn automatisk nettsteder som registrerer deg med denne nøkkelen.",
|
||||||
"provisioningKeysUpdateError": "Feil under oppdatering av foreløpig nøkkel",
|
"provisioningKeysUpdateError": "Feil under oppdatering av foreløpig nøkkel",
|
||||||
"provisioningKeysUpdated": "Foreslå nøkkel oppdatert",
|
"provisioningKeysUpdated": "Foreslå nøkkel oppdatert",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Opprett Org bruker",
|
"createOrgUser": "Opprett Org bruker",
|
||||||
"actionUpdateOrg": "Oppdater organisasjon",
|
"actionUpdateOrg": "Oppdater organisasjon",
|
||||||
"actionRemoveInvitation": "Fjern invitasjon",
|
"actionRemoveInvitation": "Fjern invitasjon",
|
||||||
"actionRemoveUserRole": "Fjern Brukerrolle",
|
|
||||||
"actionUpdateUser": "Oppdater bruker",
|
"actionUpdateUser": "Oppdater bruker",
|
||||||
"actionGetUser": "Hent bruker",
|
"actionGetUser": "Hent bruker",
|
||||||
"actionGetOrgUser": "Hent organisasjonsbruker",
|
"actionGetOrgUser": "Hent organisasjonsbruker",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Skriv inn oppsetttoken fra serverkonsollen.",
|
"setupTokenDescription": "Skriv inn oppsetttoken fra serverkonsollen.",
|
||||||
"setupTokenRequired": "Oppsetttoken er nødvendig",
|
"setupTokenRequired": "Oppsetttoken er nødvendig",
|
||||||
"actionUpdateSite": "Oppdater område",
|
"actionUpdateSite": "Oppdater område",
|
||||||
"actionApproveSite": "Godkjenn Område",
|
|
||||||
"actionRejectSite": "Avvis Område",
|
|
||||||
"actionResetSiteBandwidth": "Tilbakestill organisasjons-båndbredde",
|
"actionResetSiteBandwidth": "Tilbakestill organisasjons-båndbredde",
|
||||||
"actionListSiteRoles": "List opp tillatte områderoller",
|
"actionListSiteRoles": "List opp tillatte områderoller",
|
||||||
"actionCreateResource": "Opprett ressurs",
|
"actionCreateResource": "Opprett ressurs",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Liste invitasjoner",
|
"actionListInvitations": "Liste invitasjoner",
|
||||||
"actionExportLogs": "Eksportlogger",
|
"actionExportLogs": "Eksportlogger",
|
||||||
"actionViewLogs": "Vis logger",
|
"actionViewLogs": "Vis logger",
|
||||||
"actionCreateSiteProvisioningKey": "Opprett Klargjøringsnøkkel for Sted",
|
|
||||||
"actionListSiteProvisioningKeys": "List Klargjøringsnøkler for Sted",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Oppdater Klargjøringsnøkkel for Sted",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Slett Klargjøringsnøkkel for Sted",
|
|
||||||
"noneSelected": "Ingen valgt",
|
"noneSelected": "Ingen valgt",
|
||||||
"orgNotFound2": "Ingen organisasjoner funnet.",
|
"orgNotFound2": "Ingen organisasjoner funnet.",
|
||||||
"search": "Søk…",
|
"search": "Søk…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Skriv inn koden fra autentiseringsappen din eller en av dine engangs reservekoder.",
|
"otpAuthDescription": "Skriv inn koden fra autentiseringsappen din eller en av dine engangs reservekoder.",
|
||||||
"otpAuthSubmit": "Send inn kode",
|
"otpAuthSubmit": "Send inn kode",
|
||||||
"idpContinue": "Eller fortsett med",
|
"idpContinue": "Eller fortsett med",
|
||||||
"idpLastUsed": "Sist brukt",
|
|
||||||
"otpAuthBack": "Tilbake til passord",
|
"otpAuthBack": "Tilbake til passord",
|
||||||
"navbar": "Navigasjonsmeny",
|
"navbar": "Navigasjonsmeny",
|
||||||
"navbarDescription": "Hovednavigasjonsmeny for applikasjonen",
|
"navbarDescription": "Hovednavigasjonsmeny for applikasjonen",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Opplastningen mislyktes",
|
"rdpUploadFailed": "Opplastningen mislyktes",
|
||||||
"rdpUnicodeKeyboardMode": "Unicode tastaturmodus",
|
"rdpUnicodeKeyboardMode": "Unicode tastaturmodus",
|
||||||
"sessionToolbarShow": "Vis verktøylinje",
|
"sessionToolbarShow": "Vis verktøylinje",
|
||||||
"sessionToolbarHide": "Skjul verktøylinje",
|
"sessionToolbarHide": "Skjul verktøylinje"
|
||||||
"actionUpdateSiteApprovals": "Oppdater Stedsgodkjenninger"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-19
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Provisie",
|
"provisioningManage": "Provisie",
|
||||||
"provisioningDescription": "Voorzieningssleutels beheren en sites beoordelen in afwachting van goedkeuring.",
|
"provisioningDescription": "Voorzieningssleutels beheren en sites beoordelen in afwachting van goedkeuring.",
|
||||||
"pendingSites": "Openstaande sites",
|
"pendingSites": "Openstaande sites",
|
||||||
"siteApproveSuccess": "Site en bijbehorende middelen succesvol goedgekeurd",
|
"siteApproveSuccess": "Site succesvol goedgekeurd",
|
||||||
"siteApproveError": "Fout bij goedkeuren website",
|
"siteApproveError": "Fout bij goedkeuren website",
|
||||||
"siteReject": "Afwijzen Site",
|
|
||||||
"siteQuestionReject": "Weet u zeker dat u deze site wilt afwijzen?",
|
|
||||||
"siteMessageReject": "Hiermee wordt de site en alle bijbehorende middelen die nog in behandeling zijn permanent verwijderd.",
|
|
||||||
"siteConfirmReject": "Afwijzing van site bevestigen",
|
|
||||||
"siteRejectSuccess": "Site succesvol afgewezen",
|
|
||||||
"siteRejectError": "Fout bij het afwijzen van site",
|
|
||||||
"provisioningKeys": "Verhelderende sleutels",
|
"provisioningKeys": "Verhelderende sleutels",
|
||||||
"searchProvisioningKeys": "Zoek provisioningsleutels ...",
|
"searchProvisioningKeys": "Zoek provisioningsleutels ...",
|
||||||
"provisioningKeysAdd": "Genereer Provisioning Sleutel",
|
"provisioningKeysAdd": "Genereer Provisioning Sleutel",
|
||||||
@@ -472,7 +466,7 @@
|
|||||||
"provisioningKeysSave": "Sla de provisioning sleutel op",
|
"provisioningKeysSave": "Sla de provisioning sleutel op",
|
||||||
"provisioningKeysSaveDescription": "Je kunt dit slechts één keer zien. Kopieer het naar een veilige plaats.",
|
"provisioningKeysSaveDescription": "Je kunt dit slechts één keer zien. Kopieer het naar een veilige plaats.",
|
||||||
"provisioningKeysErrorCreate": "Fout bij aanmaken provisioning sleutel",
|
"provisioningKeysErrorCreate": "Fout bij aanmaken provisioning sleutel",
|
||||||
"provisioningKeysList": "Nieuwe provisioning-sleutel",
|
"provisioningKeysList": "Nieuwe provisioning sleutel",
|
||||||
"provisioningKeysMaxBatchSize": "Maximale batchgrootte",
|
"provisioningKeysMaxBatchSize": "Maximale batchgrootte",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Onbeperkte batchgrootte (geen limiet)",
|
"provisioningKeysUnlimitedBatchSize": "Onbeperkte batchgrootte (geen limiet)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Onbeperkt",
|
"provisioningKeysMaxBatchUnlimited": "Onbeperkt",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "Nooit",
|
"provisioningKeysNeverUsed": "Nooit",
|
||||||
"provisioningKeysEdit": "Wijzig Provisioning Sleutel",
|
"provisioningKeysEdit": "Wijzig Provisioning Sleutel",
|
||||||
"provisioningKeysEditDescription": "Werk de maximale batchgrootte en verlooptijd voor deze sleutel bij.",
|
"provisioningKeysEditDescription": "Werk de maximale batchgrootte en verlooptijd voor deze sleutel bij.",
|
||||||
"provisioningKeysApproveNewSites": "Nieuwe sites goedkeuren",
|
"provisioningKeysApproveNewSites": "Goedkeuren van nieuwe sites",
|
||||||
"provisioningKeysApproveNewSitesDescription": "Automatisch sites goedkeuren die zich registreren met deze sleutel.",
|
"provisioningKeysApproveNewSitesDescription": "Automatisch sites goedkeuren die zich registreren met deze sleutel.",
|
||||||
"provisioningKeysUpdateError": "Fout tijdens bijwerken provisioning sleutel",
|
"provisioningKeysUpdateError": "Fout tijdens bijwerken provisioning sleutel",
|
||||||
"provisioningKeysUpdated": "Provisie sleutel bijgewerkt",
|
"provisioningKeysUpdated": "Provisie sleutel bijgewerkt",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Org gebruiker aanmaken",
|
"createOrgUser": "Org gebruiker aanmaken",
|
||||||
"actionUpdateOrg": "Organisatie bijwerken",
|
"actionUpdateOrg": "Organisatie bijwerken",
|
||||||
"actionRemoveInvitation": "Verwijder uitnodiging",
|
"actionRemoveInvitation": "Verwijder uitnodiging",
|
||||||
"actionRemoveUserRole": "Gebruikersrol verwijderen",
|
|
||||||
"actionUpdateUser": "Gebruiker bijwerken",
|
"actionUpdateUser": "Gebruiker bijwerken",
|
||||||
"actionGetUser": "Gebruiker ophalen",
|
"actionGetUser": "Gebruiker ophalen",
|
||||||
"actionGetOrgUser": "Krijg organisatie-gebruiker",
|
"actionGetOrgUser": "Krijg organisatie-gebruiker",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Voer het setup-token in vanaf de serverconsole.",
|
"setupTokenDescription": "Voer het setup-token in vanaf de serverconsole.",
|
||||||
"setupTokenRequired": "Setup-token is vereist",
|
"setupTokenRequired": "Setup-token is vereist",
|
||||||
"actionUpdateSite": "Site bijwerken",
|
"actionUpdateSite": "Site bijwerken",
|
||||||
"actionApproveSite": "Verleen site goedkeuring",
|
|
||||||
"actionRejectSite": "Afwijzen Site",
|
|
||||||
"actionResetSiteBandwidth": "Reset organisatieschandbreedte",
|
"actionResetSiteBandwidth": "Reset organisatieschandbreedte",
|
||||||
"actionListSiteRoles": "Toon toegestane sitenollen",
|
"actionListSiteRoles": "Toon toegestane sitenollen",
|
||||||
"actionCreateResource": "Bron maken",
|
"actionCreateResource": "Bron maken",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Toon uitnodigingen",
|
"actionListInvitations": "Toon uitnodigingen",
|
||||||
"actionExportLogs": "Logboeken exporteren",
|
"actionExportLogs": "Logboeken exporteren",
|
||||||
"actionViewLogs": "Logboeken bekijken",
|
"actionViewLogs": "Logboeken bekijken",
|
||||||
"actionCreateSiteProvisioningKey": "Een sitevoorzie-ningssleutel aanmaken",
|
|
||||||
"actionListSiteProvisioningKeys": "Sitevoorzie-ningssleutels weergeven",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Sitevoorzie-ningssleutel bijwerken",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Sitevoorzie-ningssleutel verwijderen",
|
|
||||||
"noneSelected": "Niet geselecteerd",
|
"noneSelected": "Niet geselecteerd",
|
||||||
"orgNotFound2": "Geen organisaties gevonden.",
|
"orgNotFound2": "Geen organisaties gevonden.",
|
||||||
"search": "Zoeken…",
|
"search": "Zoeken…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Voer de code van je authenticator-app of een van je reservekopiecodes voor het eenmalig gebruik in.",
|
"otpAuthDescription": "Voer de code van je authenticator-app of een van je reservekopiecodes voor het eenmalig gebruik in.",
|
||||||
"otpAuthSubmit": "Code indienen",
|
"otpAuthSubmit": "Code indienen",
|
||||||
"idpContinue": "Of ga verder met",
|
"idpContinue": "Of ga verder met",
|
||||||
"idpLastUsed": "Laatst gebruikt",
|
|
||||||
"otpAuthBack": "Terug naar wachtwoord",
|
"otpAuthBack": "Terug naar wachtwoord",
|
||||||
"navbar": "Navigatiemenu",
|
"navbar": "Navigatiemenu",
|
||||||
"navbarDescription": "Hoofd navigatie menu voor de applicatie",
|
"navbarDescription": "Hoofd navigatie menu voor de applicatie",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Upload mislukt",
|
"rdpUploadFailed": "Upload mislukt",
|
||||||
"rdpUnicodeKeyboardMode": "Unicode toetsenbordmodus",
|
"rdpUnicodeKeyboardMode": "Unicode toetsenbordmodus",
|
||||||
"sessionToolbarShow": "Toon werkbalk",
|
"sessionToolbarShow": "Toon werkbalk",
|
||||||
"sessionToolbarHide": "Verberg werkbalk",
|
"sessionToolbarHide": "Verberg werkbalk"
|
||||||
"actionUpdateSiteApprovals": "Sitegoedkeuringen bijwerken"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-18
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Dostarczanie",
|
"provisioningManage": "Dostarczanie",
|
||||||
"provisioningDescription": "Zarządzaj kluczami rezerwacji i sprawdzaj oczekujące strony oczekujące na zatwierdzenie.",
|
"provisioningDescription": "Zarządzaj kluczami rezerwacji i sprawdzaj oczekujące strony oczekujące na zatwierdzenie.",
|
||||||
"pendingSites": "Witryny oczekujące",
|
"pendingSites": "Witryny oczekujące",
|
||||||
"siteApproveSuccess": "Witryna i powiązane zasoby zostały zatwierdzone pomyślnie",
|
"siteApproveSuccess": "Witryna została pomyślnie zatwierdzona",
|
||||||
"siteApproveError": "Błąd zatwierdzania witryny",
|
"siteApproveError": "Błąd zatwierdzania witryny",
|
||||||
"siteReject": "Odrzuć witrynę",
|
|
||||||
"siteQuestionReject": "Czy na pewno chcesz odrzucić tę witrynę?",
|
|
||||||
"siteMessageReject": "Spowoduje to trwałe usunięcie witryny i wszelkich powiązanych, nadal oczekujących zasobów.",
|
|
||||||
"siteConfirmReject": "Potwierdź odrzucenie witryny",
|
|
||||||
"siteRejectSuccess": "Witryna została odrzucona pomyślnie",
|
|
||||||
"siteRejectError": "Błąd podczas odrzucania witryny",
|
|
||||||
"provisioningKeys": "Klucze Zaopatrzenia",
|
"provisioningKeys": "Klucze Zaopatrzenia",
|
||||||
"searchProvisioningKeys": "Szukaj kluczy zaopatrzenia...",
|
"searchProvisioningKeys": "Szukaj kluczy zaopatrzenia...",
|
||||||
"provisioningKeysAdd": "Wygeneruj klucz zaopatrzenia",
|
"provisioningKeysAdd": "Wygeneruj klucz zaopatrzenia",
|
||||||
@@ -472,7 +466,7 @@
|
|||||||
"provisioningKeysSave": "Zapisz klucz zaopatrzenia",
|
"provisioningKeysSave": "Zapisz klucz zaopatrzenia",
|
||||||
"provisioningKeysSaveDescription": "Możesz to zobaczyć tylko raz. Skopiuj je do bezpiecznego miejsca.",
|
"provisioningKeysSaveDescription": "Możesz to zobaczyć tylko raz. Skopiuj je do bezpiecznego miejsca.",
|
||||||
"provisioningKeysErrorCreate": "Błąd podczas tworzenia klucza zaopatrzenia",
|
"provisioningKeysErrorCreate": "Błąd podczas tworzenia klucza zaopatrzenia",
|
||||||
"provisioningKeysList": "Nowy klucz aprowizacyjny",
|
"provisioningKeysList": "Nowy klucz rezerwacji",
|
||||||
"provisioningKeysMaxBatchSize": "Maksymalny rozmiar partii",
|
"provisioningKeysMaxBatchSize": "Maksymalny rozmiar partii",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Nieograniczony rozmiar partii (bez limitu)",
|
"provisioningKeysUnlimitedBatchSize": "Nieograniczony rozmiar partii (bez limitu)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Nieograniczona",
|
"provisioningKeysMaxBatchUnlimited": "Nieograniczona",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Utwórz użytkownika Org",
|
"createOrgUser": "Utwórz użytkownika Org",
|
||||||
"actionUpdateOrg": "Aktualizuj organizację",
|
"actionUpdateOrg": "Aktualizuj organizację",
|
||||||
"actionRemoveInvitation": "Usuń zaproszenie",
|
"actionRemoveInvitation": "Usuń zaproszenie",
|
||||||
"actionRemoveUserRole": "Usuń rolę użytkownika",
|
|
||||||
"actionUpdateUser": "Zaktualizuj użytkownika",
|
"actionUpdateUser": "Zaktualizuj użytkownika",
|
||||||
"actionGetUser": "Pobierz użytkownika",
|
"actionGetUser": "Pobierz użytkownika",
|
||||||
"actionGetOrgUser": "Pobierz użytkownika organizacji",
|
"actionGetOrgUser": "Pobierz użytkownika organizacji",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Wprowadź token konfiguracji z konsoli serwera.",
|
"setupTokenDescription": "Wprowadź token konfiguracji z konsoli serwera.",
|
||||||
"setupTokenRequired": "Wymagany jest token konfiguracji",
|
"setupTokenRequired": "Wymagany jest token konfiguracji",
|
||||||
"actionUpdateSite": "Aktualizuj witrynę",
|
"actionUpdateSite": "Aktualizuj witrynę",
|
||||||
"actionApproveSite": "Zatwierdź witrynę",
|
|
||||||
"actionRejectSite": "Odrzuć witrynę",
|
|
||||||
"actionResetSiteBandwidth": "Zresetuj przepustowość organizacji",
|
"actionResetSiteBandwidth": "Zresetuj przepustowość organizacji",
|
||||||
"actionListSiteRoles": "Lista dozwolonych ról witryny",
|
"actionListSiteRoles": "Lista dozwolonych ról witryny",
|
||||||
"actionCreateResource": "Utwórz zasób",
|
"actionCreateResource": "Utwórz zasób",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Lista zaproszeń",
|
"actionListInvitations": "Lista zaproszeń",
|
||||||
"actionExportLogs": "Eksportuj dzienniki",
|
"actionExportLogs": "Eksportuj dzienniki",
|
||||||
"actionViewLogs": "Zobacz dzienniki",
|
"actionViewLogs": "Zobacz dzienniki",
|
||||||
"actionCreateSiteProvisioningKey": "Utwórz klucz konfiguracji strony",
|
|
||||||
"actionListSiteProvisioningKeys": "Lista kluczy konfiguracji strony",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Zaktualizuj klucz konfiguracji strony",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Usuń klucz konfiguracji strony",
|
|
||||||
"noneSelected": "Nie wybrano",
|
"noneSelected": "Nie wybrano",
|
||||||
"orgNotFound2": "Nie znaleziono organizacji.",
|
"orgNotFound2": "Nie znaleziono organizacji.",
|
||||||
"search": "Szukaj…",
|
"search": "Szukaj…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Wprowadź kod z aplikacji uwierzytelniającej lub jeden z jednorazowych kodów zapasowych.",
|
"otpAuthDescription": "Wprowadź kod z aplikacji uwierzytelniającej lub jeden z jednorazowych kodów zapasowych.",
|
||||||
"otpAuthSubmit": "Wyślij kod",
|
"otpAuthSubmit": "Wyślij kod",
|
||||||
"idpContinue": "Lub kontynuuj z",
|
"idpContinue": "Lub kontynuuj z",
|
||||||
"idpLastUsed": "Ostatnio używany",
|
|
||||||
"otpAuthBack": "Powrót do hasła",
|
"otpAuthBack": "Powrót do hasła",
|
||||||
"navbar": "Menu nawigacyjne",
|
"navbar": "Menu nawigacyjne",
|
||||||
"navbarDescription": "Główne menu nawigacyjne aplikacji",
|
"navbarDescription": "Główne menu nawigacyjne aplikacji",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Niepowodzenie przesyłania",
|
"rdpUploadFailed": "Niepowodzenie przesyłania",
|
||||||
"rdpUnicodeKeyboardMode": "Tryb klawiatury Unicode",
|
"rdpUnicodeKeyboardMode": "Tryb klawiatury Unicode",
|
||||||
"sessionToolbarShow": "Pokaż pasek narzędzi",
|
"sessionToolbarShow": "Pokaż pasek narzędzi",
|
||||||
"sessionToolbarHide": "Ukryj pasek narzędzi",
|
"sessionToolbarHide": "Ukryj pasek narzędzi"
|
||||||
"actionUpdateSiteApprovals": "Zaktualizuj zgody na stronę"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-21
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Provisionamento",
|
"provisioningManage": "Provisionamento",
|
||||||
"provisioningDescription": "Gerenciar chaves de provisionamento e revisar sites pendentes aguardando aprovação.",
|
"provisioningDescription": "Gerenciar chaves de provisionamento e revisar sites pendentes aguardando aprovação.",
|
||||||
"pendingSites": "Sites pendentes",
|
"pendingSites": "Sites pendentes",
|
||||||
"siteApproveSuccess": "Site e recursos associados aprovados com sucesso",
|
"siteApproveSuccess": "Site aprovado com sucesso",
|
||||||
"siteApproveError": "Erro ao aprovar site",
|
"siteApproveError": "Erro ao aprovar site",
|
||||||
"siteReject": "Rejeitar Site",
|
|
||||||
"siteQuestionReject": "Tem certeza de que deseja rejeitar este site?",
|
|
||||||
"siteMessageReject": "Isto eliminará permanentemente o site e quaisquer recursos associados que ainda estejam pendentes.",
|
|
||||||
"siteConfirmReject": "Confirmar Rejeição de Site",
|
|
||||||
"siteRejectSuccess": "Site rejeitado com sucesso",
|
|
||||||
"siteRejectError": "Erro ao rejeitar site",
|
|
||||||
"provisioningKeys": "Posicionando chaves",
|
"provisioningKeys": "Posicionando chaves",
|
||||||
"searchProvisioningKeys": "Pesquisar chaves de provisionamento...",
|
"searchProvisioningKeys": "Pesquisar chaves de provisionamento...",
|
||||||
"provisioningKeysAdd": "Gerar chave de provisionamento",
|
"provisioningKeysAdd": "Gerar chave de provisionamento",
|
||||||
@@ -472,12 +466,12 @@
|
|||||||
"provisioningKeysSave": "Salvar a chave de provisionamento",
|
"provisioningKeysSave": "Salvar a chave de provisionamento",
|
||||||
"provisioningKeysSaveDescription": "Você só será capaz de ver esta vez. Copiá-lo para um lugar seguro.",
|
"provisioningKeysSaveDescription": "Você só será capaz de ver esta vez. Copiá-lo para um lugar seguro.",
|
||||||
"provisioningKeysErrorCreate": "Erro ao criar chave de provisionamento",
|
"provisioningKeysErrorCreate": "Erro ao criar chave de provisionamento",
|
||||||
"provisioningKeysList": "Nova Chave de Provisionamento",
|
"provisioningKeysList": "Nova chave de aprovisionamento",
|
||||||
"provisioningKeysMaxBatchSize": "Tamanho Máximo do Lote",
|
"provisioningKeysMaxBatchSize": "Tamanho máximo do lote",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Tamanho ilimitado em lote (sem limite)",
|
"provisioningKeysUnlimitedBatchSize": "Tamanho ilimitado em lote (sem limite)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Ilimitado",
|
"provisioningKeysMaxBatchUnlimited": "Ilimitado",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Informe um tamanho máximo válido em lote (1–1,000,000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Informe um tamanho máximo válido em lote (1–1,000,000).",
|
||||||
"provisioningKeysValidUntil": "Válido Até",
|
"provisioningKeysValidUntil": "Valido ate",
|
||||||
"provisioningKeysValidUntilHint": "Deixe em branco para nenhuma expiração.",
|
"provisioningKeysValidUntilHint": "Deixe em branco para nenhuma expiração.",
|
||||||
"provisioningKeysValidUntilInvalid": "Informe uma data e hora válidas.",
|
"provisioningKeysValidUntilInvalid": "Informe uma data e hora válidas.",
|
||||||
"provisioningKeysNumUsed": "Use percentual",
|
"provisioningKeysNumUsed": "Use percentual",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "nunca",
|
"provisioningKeysNeverUsed": "nunca",
|
||||||
"provisioningKeysEdit": "Editar chave de provisionamento",
|
"provisioningKeysEdit": "Editar chave de provisionamento",
|
||||||
"provisioningKeysEditDescription": "Atualizar o tamanho máximo do lote e tempo de expiração para esta chave.",
|
"provisioningKeysEditDescription": "Atualizar o tamanho máximo do lote e tempo de expiração para esta chave.",
|
||||||
"provisioningKeysApproveNewSites": "Aprovar Novos Sites",
|
"provisioningKeysApproveNewSites": "Aprovar novos sites",
|
||||||
"provisioningKeysApproveNewSitesDescription": "Aprovar automaticamente sites que se registram com esta chave.",
|
"provisioningKeysApproveNewSitesDescription": "Aprovar automaticamente sites que se registram com esta chave.",
|
||||||
"provisioningKeysUpdateError": "Erro ao atualizar chave de provisionamento",
|
"provisioningKeysUpdateError": "Erro ao atualizar chave de provisionamento",
|
||||||
"provisioningKeysUpdated": "Chave de provisionamento atualizada",
|
"provisioningKeysUpdated": "Chave de provisionamento atualizada",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Criar utilizador Org",
|
"createOrgUser": "Criar utilizador Org",
|
||||||
"actionUpdateOrg": "Atualizar Organização",
|
"actionUpdateOrg": "Atualizar Organização",
|
||||||
"actionRemoveInvitation": "Remover Convite",
|
"actionRemoveInvitation": "Remover Convite",
|
||||||
"actionRemoveUserRole": "Remover Função de Utilizador",
|
|
||||||
"actionUpdateUser": "Atualizar Usuário",
|
"actionUpdateUser": "Atualizar Usuário",
|
||||||
"actionGetUser": "Obter Usuário",
|
"actionGetUser": "Obter Usuário",
|
||||||
"actionGetOrgUser": "Obter Utilizador da Organização",
|
"actionGetOrgUser": "Obter Utilizador da Organização",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Digite o token de configuração do console do servidor.",
|
"setupTokenDescription": "Digite o token de configuração do console do servidor.",
|
||||||
"setupTokenRequired": "Token de configuração é necessário",
|
"setupTokenRequired": "Token de configuração é necessário",
|
||||||
"actionUpdateSite": "Atualizar Site",
|
"actionUpdateSite": "Atualizar Site",
|
||||||
"actionApproveSite": "Aprovar Site",
|
|
||||||
"actionRejectSite": "Rejeitar Site",
|
|
||||||
"actionResetSiteBandwidth": "Redefinir banda da organização",
|
"actionResetSiteBandwidth": "Redefinir banda da organização",
|
||||||
"actionListSiteRoles": "Listar Funções Permitidas do Site",
|
"actionListSiteRoles": "Listar Funções Permitidas do Site",
|
||||||
"actionCreateResource": "Criar Recurso",
|
"actionCreateResource": "Criar Recurso",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Listar Convites",
|
"actionListInvitations": "Listar Convites",
|
||||||
"actionExportLogs": "Exportar logs",
|
"actionExportLogs": "Exportar logs",
|
||||||
"actionViewLogs": "Visualizar registros",
|
"actionViewLogs": "Visualizar registros",
|
||||||
"actionCreateSiteProvisioningKey": "Criar Chave de Provisionamento do Site",
|
|
||||||
"actionListSiteProvisioningKeys": "Listar Chaves de Provisionamento do Site",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Atualizar Chave de Provisionamento do Site",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Excluir Chave de Provisionamento do Site",
|
|
||||||
"noneSelected": "Nenhum selecionado",
|
"noneSelected": "Nenhum selecionado",
|
||||||
"orgNotFound2": "Nenhuma organização encontrada.",
|
"orgNotFound2": "Nenhuma organização encontrada.",
|
||||||
"search": "Pesquisar…",
|
"search": "Pesquisar…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Insira o código da sua aplicação de autenticação ou um dos seus códigos de backup de uso único.",
|
"otpAuthDescription": "Insira o código da sua aplicação de autenticação ou um dos seus códigos de backup de uso único.",
|
||||||
"otpAuthSubmit": "Submeter Código",
|
"otpAuthSubmit": "Submeter Código",
|
||||||
"idpContinue": "Ou continuar com",
|
"idpContinue": "Ou continuar com",
|
||||||
"idpLastUsed": "Última Utilização",
|
|
||||||
"otpAuthBack": "Voltar à Palavra-passe",
|
"otpAuthBack": "Voltar à Palavra-passe",
|
||||||
"navbar": "Menu de Navegação",
|
"navbar": "Menu de Navegação",
|
||||||
"navbarDescription": "Menu de navegação principal da aplicação",
|
"navbarDescription": "Menu de navegação principal da aplicação",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Falha no upload",
|
"rdpUploadFailed": "Falha no upload",
|
||||||
"rdpUnicodeKeyboardMode": "Modo de teclado Unicode",
|
"rdpUnicodeKeyboardMode": "Modo de teclado Unicode",
|
||||||
"sessionToolbarShow": "Mostrar barra de ferramentas",
|
"sessionToolbarShow": "Mostrar barra de ferramentas",
|
||||||
"sessionToolbarHide": "Ocultar barra de ferramentas",
|
"sessionToolbarHide": "Ocultar barra de ferramentas"
|
||||||
"actionUpdateSiteApprovals": "Atualizar Aprovações do Site"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-19
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Подготовка",
|
"provisioningManage": "Подготовка",
|
||||||
"provisioningDescription": "Управляйте предоставленными ключами и проверять непроверенные сайты, ожидающие утверждения.",
|
"provisioningDescription": "Управляйте предоставленными ключами и проверять непроверенные сайты, ожидающие утверждения.",
|
||||||
"pendingSites": "Ожидающие сайты",
|
"pendingSites": "Ожидающие сайты",
|
||||||
"siteApproveSuccess": "Сайт и связанные ресурсы успешно одобрены",
|
"siteApproveSuccess": "Сайт успешно утвержден",
|
||||||
"siteApproveError": "Ошибка при утверждении сайта",
|
"siteApproveError": "Ошибка при утверждении сайта",
|
||||||
"siteReject": "Отклонить сайт",
|
|
||||||
"siteQuestionReject": "Вы уверены, что хотите отклонить этот сайт?",
|
|
||||||
"siteMessageReject": "Этот процесс окончательно удалит сайт и все связанные с ним ресурсы, которые еще ожидают.",
|
|
||||||
"siteConfirmReject": "Подтвердите отклонение сайта",
|
|
||||||
"siteRejectSuccess": "Сайт успешно отклонен",
|
|
||||||
"siteRejectError": "Ошибка при отклонении сайта",
|
|
||||||
"provisioningKeys": "Ключи подготовки",
|
"provisioningKeys": "Ключи подготовки",
|
||||||
"searchProvisioningKeys": "Поиск подготовительных ключей...",
|
"searchProvisioningKeys": "Поиск подготовительных ключей...",
|
||||||
"provisioningKeysAdd": "Сгенерировать ключ подготовки",
|
"provisioningKeysAdd": "Сгенерировать ключ подготовки",
|
||||||
@@ -472,8 +466,8 @@
|
|||||||
"provisioningKeysSave": "Сохранить ключ подготовки",
|
"provisioningKeysSave": "Сохранить ключ подготовки",
|
||||||
"provisioningKeysSaveDescription": "Вы сможете увидеть это только один раз. Скопируйте его в безопасное место.",
|
"provisioningKeysSaveDescription": "Вы сможете увидеть это только один раз. Скопируйте его в безопасное место.",
|
||||||
"provisioningKeysErrorCreate": "Ошибка при создании ключа подготовки",
|
"provisioningKeysErrorCreate": "Ошибка при создании ключа подготовки",
|
||||||
"provisioningKeysList": "Новый ключ для подготовки",
|
"provisioningKeysList": "Новый подготовительный ключ",
|
||||||
"provisioningKeysMaxBatchSize": "Максимальный размер партии",
|
"provisioningKeysMaxBatchSize": "Макс. размер партии",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Неограниченный размер партии (без ограничений)",
|
"provisioningKeysUnlimitedBatchSize": "Неограниченный размер партии (без ограничений)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Неограниченный",
|
"provisioningKeysMaxBatchUnlimited": "Неограниченный",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Введите максимальный размер пакета (1–1,000,000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Введите максимальный размер пакета (1–1,000,000).",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Создать пользователя Org",
|
"createOrgUser": "Создать пользователя Org",
|
||||||
"actionUpdateOrg": "Обновить организацию",
|
"actionUpdateOrg": "Обновить организацию",
|
||||||
"actionRemoveInvitation": "Удалить приглашение",
|
"actionRemoveInvitation": "Удалить приглашение",
|
||||||
"actionRemoveUserRole": "Удалить роль пользователя",
|
|
||||||
"actionUpdateUser": "Обновить пользователя",
|
"actionUpdateUser": "Обновить пользователя",
|
||||||
"actionGetUser": "Получить пользователя",
|
"actionGetUser": "Получить пользователя",
|
||||||
"actionGetOrgUser": "Получить пользователя организации",
|
"actionGetOrgUser": "Получить пользователя организации",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Введите токен настройки из консоли сервера.",
|
"setupTokenDescription": "Введите токен настройки из консоли сервера.",
|
||||||
"setupTokenRequired": "Токен настройки обязателен",
|
"setupTokenRequired": "Токен настройки обязателен",
|
||||||
"actionUpdateSite": "Обновить сайт",
|
"actionUpdateSite": "Обновить сайт",
|
||||||
"actionApproveSite": "Одобрить сайт",
|
|
||||||
"actionRejectSite": "Отклонить сайт",
|
|
||||||
"actionResetSiteBandwidth": "Сброс пропускной способности организации",
|
"actionResetSiteBandwidth": "Сброс пропускной способности организации",
|
||||||
"actionListSiteRoles": "Список разрешенных ролей сайта",
|
"actionListSiteRoles": "Список разрешенных ролей сайта",
|
||||||
"actionCreateResource": "Создать ресурс",
|
"actionCreateResource": "Создать ресурс",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Список приглашений",
|
"actionListInvitations": "Список приглашений",
|
||||||
"actionExportLogs": "Экспорт журналов",
|
"actionExportLogs": "Экспорт журналов",
|
||||||
"actionViewLogs": "Просмотр журналов",
|
"actionViewLogs": "Просмотр журналов",
|
||||||
"actionCreateSiteProvisioningKey": "Создать ключ конфигурации сайта",
|
|
||||||
"actionListSiteProvisioningKeys": "Список ключей конфигурации сайтов",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Обновить ключ конфигурации сайта",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Удалить ключ конфигурации сайта",
|
|
||||||
"noneSelected": "Ничего не выбрано",
|
"noneSelected": "Ничего не выбрано",
|
||||||
"orgNotFound2": "Организации не найдены.",
|
"orgNotFound2": "Организации не найдены.",
|
||||||
"search": "Поиск…",
|
"search": "Поиск…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Введите код из вашего приложения-аутентификатора или один из ваших одноразовых резервных кодов.",
|
"otpAuthDescription": "Введите код из вашего приложения-аутентификатора или один из ваших одноразовых резервных кодов.",
|
||||||
"otpAuthSubmit": "Отправить код",
|
"otpAuthSubmit": "Отправить код",
|
||||||
"idpContinue": "Или продолжить с",
|
"idpContinue": "Или продолжить с",
|
||||||
"idpLastUsed": "Последнее использование",
|
|
||||||
"otpAuthBack": "Назад к паролю",
|
"otpAuthBack": "Назад к паролю",
|
||||||
"navbar": "Навигационное меню",
|
"navbar": "Навигационное меню",
|
||||||
"navbarDescription": "Главное навигационное меню приложения",
|
"navbarDescription": "Главное навигационное меню приложения",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Ошибка загрузки",
|
"rdpUploadFailed": "Ошибка загрузки",
|
||||||
"rdpUnicodeKeyboardMode": "Режим клавиатуры Unicode",
|
"rdpUnicodeKeyboardMode": "Режим клавиатуры Unicode",
|
||||||
"sessionToolbarShow": "Показать панель инструментов",
|
"sessionToolbarShow": "Показать панель инструментов",
|
||||||
"sessionToolbarHide": "Скрыть панель инструментов",
|
"sessionToolbarHide": "Скрыть панель инструментов"
|
||||||
"actionUpdateSiteApprovals": "Обновить утверждения сайта"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-21
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "Tedarik",
|
"provisioningManage": "Tedarik",
|
||||||
"provisioningDescription": "Tedarik anahtarlarını yönetin ve onay bekleyen siteleri gözden geçirin.",
|
"provisioningDescription": "Tedarik anahtarlarını yönetin ve onay bekleyen siteleri gözden geçirin.",
|
||||||
"pendingSites": "Bekleyen Siteler",
|
"pendingSites": "Bekleyen Siteler",
|
||||||
"siteApproveSuccess": "Site ve ilgili kaynaklar başarıyla onaylandı",
|
"siteApproveSuccess": "Site başarıyla onaylandı",
|
||||||
"siteApproveError": "Site onaylanırken hata oluştu",
|
"siteApproveError": "Site onaylanırken hata oluştu",
|
||||||
"siteReject": "Siteyi Reddet",
|
|
||||||
"siteQuestionReject": "Bu siteyi reddetmek istediğinizden emin misiniz?",
|
|
||||||
"siteMessageReject": "Bu işlem, siteyi ve hala beklemede olan herhangi bir ilgili kaynağı kalıcı olarak silecektir.",
|
|
||||||
"siteConfirmReject": "Site Reddetmeyi Onayla",
|
|
||||||
"siteRejectSuccess": "Site başarıyla reddedildi",
|
|
||||||
"siteRejectError": "Site reddedilirken hata oluştu",
|
|
||||||
"provisioningKeys": "Tedarik Anahtarları",
|
"provisioningKeys": "Tedarik Anahtarları",
|
||||||
"searchProvisioningKeys": "Tedarik anahtarlarını ara...",
|
"searchProvisioningKeys": "Tedarik anahtarlarını ara...",
|
||||||
"provisioningKeysAdd": "Tedarik Anahtarı Üret",
|
"provisioningKeysAdd": "Tedarik Anahtarı Üret",
|
||||||
@@ -472,12 +466,12 @@
|
|||||||
"provisioningKeysSave": "Tedarik anahtarını kaydet",
|
"provisioningKeysSave": "Tedarik anahtarını kaydet",
|
||||||
"provisioningKeysSaveDescription": "Bunu yalnızca bir kez görebileceksiniz. Güvenli bir yere kopyalayın.",
|
"provisioningKeysSaveDescription": "Bunu yalnızca bir kez görebileceksiniz. Güvenli bir yere kopyalayın.",
|
||||||
"provisioningKeysErrorCreate": "Tedarik anahtarı oluşturulurken hata oluştu",
|
"provisioningKeysErrorCreate": "Tedarik anahtarı oluşturulurken hata oluştu",
|
||||||
"provisioningKeysList": "Yeni Sağlama Anahtarı",
|
"provisioningKeysList": "Yeni tedarik anahtarı",
|
||||||
"provisioningKeysMaxBatchSize": "Maksimum Toplu İş Boyutu",
|
"provisioningKeysMaxBatchSize": "Maksimum toplu iş boyutu",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Sınırsız toplu iş boyutu (sınırlama yok)",
|
"provisioningKeysUnlimitedBatchSize": "Sınırsız toplu iş boyutu (sınırlama yok)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Sınırsız",
|
"provisioningKeysMaxBatchUnlimited": "Sınırsız",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Geçerli bir maksimum toplu iş boyutu girin (1–1,000,000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Geçerli bir maksimum toplu iş boyutu girin (1–1,000,000).",
|
||||||
"provisioningKeysValidUntil": "Geçerli Olma Süresi",
|
"provisioningKeysValidUntil": "Geçerlilik tarihi",
|
||||||
"provisioningKeysValidUntilHint": "Son kullanım tarihi için boş bırakın.",
|
"provisioningKeysValidUntilHint": "Son kullanım tarihi için boş bırakın.",
|
||||||
"provisioningKeysValidUntilInvalid": "Geçerli bir tarih ve saat girin.",
|
"provisioningKeysValidUntilInvalid": "Geçerli bir tarih ve saat girin.",
|
||||||
"provisioningKeysNumUsed": "Kullanım Sayısı",
|
"provisioningKeysNumUsed": "Kullanım Sayısı",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "Asla",
|
"provisioningKeysNeverUsed": "Asla",
|
||||||
"provisioningKeysEdit": "Tedarik Anahtarını Düzenle",
|
"provisioningKeysEdit": "Tedarik Anahtarını Düzenle",
|
||||||
"provisioningKeysEditDescription": "Bu anahtar için maksimum toplu iş boyutunu ve son kullanma zamanını güncelleyin.",
|
"provisioningKeysEditDescription": "Bu anahtar için maksimum toplu iş boyutunu ve son kullanma zamanını güncelleyin.",
|
||||||
"provisioningKeysApproveNewSites": "Yeni Siteleri Onayla",
|
"provisioningKeysApproveNewSites": "Yeni siteleri onayla",
|
||||||
"provisioningKeysApproveNewSitesDescription": "Bu anahtar ile kayıt olan siteleri otomatik olarak onayla.",
|
"provisioningKeysApproveNewSitesDescription": "Bu anahtar ile kayıt olan siteleri otomatik olarak onayla.",
|
||||||
"provisioningKeysUpdateError": "Tedarik anahtarı güncellenirken hata oluştu",
|
"provisioningKeysUpdateError": "Tedarik anahtarı güncellenirken hata oluştu",
|
||||||
"provisioningKeysUpdated": "Tedarik anahtarı güncellendi",
|
"provisioningKeysUpdated": "Tedarik anahtarı güncellendi",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "Organizasyon Kullanıcısı Oluştur",
|
"createOrgUser": "Organizasyon Kullanıcısı Oluştur",
|
||||||
"actionUpdateOrg": "Kuruluşu Güncelle",
|
"actionUpdateOrg": "Kuruluşu Güncelle",
|
||||||
"actionRemoveInvitation": "Daveti Kaldır",
|
"actionRemoveInvitation": "Daveti Kaldır",
|
||||||
"actionRemoveUserRole": "Kullanıcı Rolünü Kaldır",
|
|
||||||
"actionUpdateUser": "Kullanıcıyı Güncelle",
|
"actionUpdateUser": "Kullanıcıyı Güncelle",
|
||||||
"actionGetUser": "Kullanıcıyı Getir",
|
"actionGetUser": "Kullanıcıyı Getir",
|
||||||
"actionGetOrgUser": "Kuruluş Kullanıcısını Al",
|
"actionGetOrgUser": "Kuruluş Kullanıcısını Al",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "Sunucu konsolundan kurulum simgesini girin.",
|
"setupTokenDescription": "Sunucu konsolundan kurulum simgesini girin.",
|
||||||
"setupTokenRequired": "Kurulum simgesi gerekli",
|
"setupTokenRequired": "Kurulum simgesi gerekli",
|
||||||
"actionUpdateSite": "Siteyi Güncelle",
|
"actionUpdateSite": "Siteyi Güncelle",
|
||||||
"actionApproveSite": "Siteyi Onayla",
|
|
||||||
"actionRejectSite": "Siteyi Reddet",
|
|
||||||
"actionResetSiteBandwidth": "Organizasyon Bant Genişliğini Sıfırla",
|
"actionResetSiteBandwidth": "Organizasyon Bant Genişliğini Sıfırla",
|
||||||
"actionListSiteRoles": "İzin Verilen Site Rolleri Listele",
|
"actionListSiteRoles": "İzin Verilen Site Rolleri Listele",
|
||||||
"actionCreateResource": "Kaynak Oluştur",
|
"actionCreateResource": "Kaynak Oluştur",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "Davetiyeleri Listele",
|
"actionListInvitations": "Davetiyeleri Listele",
|
||||||
"actionExportLogs": "Kayıtları Dışa Aktar",
|
"actionExportLogs": "Kayıtları Dışa Aktar",
|
||||||
"actionViewLogs": "Kayıtları Görüntüle",
|
"actionViewLogs": "Kayıtları Görüntüle",
|
||||||
"actionCreateSiteProvisioningKey": "Site Sağlama Anahtarı Oluştur",
|
|
||||||
"actionListSiteProvisioningKeys": "Site Sağlama Anahtarlarını Listele",
|
|
||||||
"actionUpdateSiteProvisioningKey": "Site Sağlama Anahtarını Güncelle",
|
|
||||||
"actionDeleteSiteProvisioningKey": "Site Sağlama Anahtarını Sil",
|
|
||||||
"noneSelected": "Hiçbiri seçili değil",
|
"noneSelected": "Hiçbiri seçili değil",
|
||||||
"orgNotFound2": "Hiçbir organizasyon bulunamadı.",
|
"orgNotFound2": "Hiçbir organizasyon bulunamadı.",
|
||||||
"search": "Ara…",
|
"search": "Ara…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "Authenticator uygulamanızdan veya tek kullanımlık yedek kodlarınızdan birini girin.",
|
"otpAuthDescription": "Authenticator uygulamanızdan veya tek kullanımlık yedek kodlarınızdan birini girin.",
|
||||||
"otpAuthSubmit": "Kodu Gönder",
|
"otpAuthSubmit": "Kodu Gönder",
|
||||||
"idpContinue": "Veya devam et:",
|
"idpContinue": "Veya devam et:",
|
||||||
"idpLastUsed": "Son Kullanılan",
|
|
||||||
"otpAuthBack": "Şifreye Geri Dön",
|
"otpAuthBack": "Şifreye Geri Dön",
|
||||||
"navbar": "Navigasyon Menüsü",
|
"navbar": "Navigasyon Menüsü",
|
||||||
"navbarDescription": "Uygulamanın ana navigasyon menüsü",
|
"navbarDescription": "Uygulamanın ana navigasyon menüsü",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "Yükleme başarısız",
|
"rdpUploadFailed": "Yükleme başarısız",
|
||||||
"rdpUnicodeKeyboardMode": "Unicode klavye modu",
|
"rdpUnicodeKeyboardMode": "Unicode klavye modu",
|
||||||
"sessionToolbarShow": "Araç çubuğunu göster",
|
"sessionToolbarShow": "Araç çubuğunu göster",
|
||||||
"sessionToolbarHide": "Araç çubuğunu gizle",
|
"sessionToolbarHide": "Araç çubuğunu gizle"
|
||||||
"actionUpdateSiteApprovals": "Site Onaylarını Güncelle"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-20
@@ -449,14 +449,8 @@
|
|||||||
"provisioningManage": "预配",
|
"provisioningManage": "预配",
|
||||||
"provisioningDescription": "管理预配密钥,并审核待批准的站点。",
|
"provisioningDescription": "管理预配密钥,并审核待批准的站点。",
|
||||||
"pendingSites": "待审批站点",
|
"pendingSites": "待审批站点",
|
||||||
"siteApproveSuccess": "站点及相关资源已成功审批",
|
"siteApproveSuccess": "站点批准成功",
|
||||||
"siteApproveError": "批准站点出错",
|
"siteApproveError": "批准站点出错",
|
||||||
"siteReject": "拒绝站点",
|
|
||||||
"siteQuestionReject": "您确定要拒绝此站点吗?",
|
|
||||||
"siteMessageReject": "这将永久删除站点及所有尚未处理的相关资源。",
|
|
||||||
"siteConfirmReject": "确认拒绝站点",
|
|
||||||
"siteRejectSuccess": "站点已被成功拒绝",
|
|
||||||
"siteRejectError": "拒绝站点时出错",
|
|
||||||
"provisioningKeys": "置备键",
|
"provisioningKeys": "置备键",
|
||||||
"searchProvisioningKeys": "搜索配备密钥...",
|
"searchProvisioningKeys": "搜索配备密钥...",
|
||||||
"provisioningKeysAdd": "生成预配密钥",
|
"provisioningKeysAdd": "生成预配密钥",
|
||||||
@@ -472,8 +466,8 @@
|
|||||||
"provisioningKeysSave": "保存预配键",
|
"provisioningKeysSave": "保存预配键",
|
||||||
"provisioningKeysSaveDescription": "您只能看到一次。复制它到一个安全的地方。",
|
"provisioningKeysSaveDescription": "您只能看到一次。复制它到一个安全的地方。",
|
||||||
"provisioningKeysErrorCreate": "创建预配键时出错",
|
"provisioningKeysErrorCreate": "创建预配键时出错",
|
||||||
"provisioningKeysList": "新预配密钥",
|
"provisioningKeysList": "新建预配键",
|
||||||
"provisioningKeysMaxBatchSize": "最大批次大小",
|
"provisioningKeysMaxBatchSize": "最大批量大小",
|
||||||
"provisioningKeysUnlimitedBatchSize": "无限批量大小(无限制)",
|
"provisioningKeysUnlimitedBatchSize": "无限批量大小(无限制)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "无限制",
|
"provisioningKeysMaxBatchUnlimited": "无限制",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "输入一个有效的最大批处理大小(1-1,000,000)。",
|
"provisioningKeysMaxBatchSizeInvalid": "输入一个有效的最大批处理大小(1-1,000,000)。",
|
||||||
@@ -486,7 +480,7 @@
|
|||||||
"provisioningKeysNeverUsed": "永不过期",
|
"provisioningKeysNeverUsed": "永不过期",
|
||||||
"provisioningKeysEdit": "编辑置备键",
|
"provisioningKeysEdit": "编辑置备键",
|
||||||
"provisioningKeysEditDescription": "更新此密钥的最大批量大小和过期时间。",
|
"provisioningKeysEditDescription": "更新此密钥的最大批量大小和过期时间。",
|
||||||
"provisioningKeysApproveNewSites": "批准新站点",
|
"provisioningKeysApproveNewSites": "批准新节点",
|
||||||
"provisioningKeysApproveNewSitesDescription": "自动批准使用此密钥注册的节点。",
|
"provisioningKeysApproveNewSitesDescription": "自动批准使用此密钥注册的节点。",
|
||||||
"provisioningKeysUpdateError": "更新预配键时出错",
|
"provisioningKeysUpdateError": "更新预配键时出错",
|
||||||
"provisioningKeysUpdated": "置备密钥已更新",
|
"provisioningKeysUpdated": "置备密钥已更新",
|
||||||
@@ -1403,7 +1397,6 @@
|
|||||||
"createOrgUser": "创建组织用户",
|
"createOrgUser": "创建组织用户",
|
||||||
"actionUpdateOrg": "更新组织",
|
"actionUpdateOrg": "更新组织",
|
||||||
"actionRemoveInvitation": "移除邀请",
|
"actionRemoveInvitation": "移除邀请",
|
||||||
"actionRemoveUserRole": "移除用户角色",
|
|
||||||
"actionUpdateUser": "更新用户",
|
"actionUpdateUser": "更新用户",
|
||||||
"actionGetUser": "获取用户",
|
"actionGetUser": "获取用户",
|
||||||
"actionGetOrgUser": "获取组织用户",
|
"actionGetOrgUser": "获取组织用户",
|
||||||
@@ -1426,8 +1419,6 @@
|
|||||||
"setupTokenDescription": "从服务器控制台输入设置令牌。",
|
"setupTokenDescription": "从服务器控制台输入设置令牌。",
|
||||||
"setupTokenRequired": "需要设置令牌",
|
"setupTokenRequired": "需要设置令牌",
|
||||||
"actionUpdateSite": "更新站点",
|
"actionUpdateSite": "更新站点",
|
||||||
"actionApproveSite": "批准站点",
|
|
||||||
"actionRejectSite": "拒绝站点",
|
|
||||||
"actionResetSiteBandwidth": "重置组织带宽",
|
"actionResetSiteBandwidth": "重置组织带宽",
|
||||||
"actionListSiteRoles": "允许站点角色列表",
|
"actionListSiteRoles": "允许站点角色列表",
|
||||||
"actionCreateResource": "创建资源",
|
"actionCreateResource": "创建资源",
|
||||||
@@ -1511,10 +1502,6 @@
|
|||||||
"actionListInvitations": "邀请列表",
|
"actionListInvitations": "邀请列表",
|
||||||
"actionExportLogs": "导出日志",
|
"actionExportLogs": "导出日志",
|
||||||
"actionViewLogs": "查看日志",
|
"actionViewLogs": "查看日志",
|
||||||
"actionCreateSiteProvisioningKey": "创建站点预配密钥",
|
|
||||||
"actionListSiteProvisioningKeys": "列出站点预配密钥",
|
|
||||||
"actionUpdateSiteProvisioningKey": "更新站点预配密钥",
|
|
||||||
"actionDeleteSiteProvisioningKey": "删除站点预配密钥",
|
|
||||||
"noneSelected": "未选择",
|
"noneSelected": "未选择",
|
||||||
"orgNotFound2": "未找到组织。",
|
"orgNotFound2": "未找到组织。",
|
||||||
"search": "搜索…",
|
"search": "搜索…",
|
||||||
@@ -1529,7 +1516,6 @@
|
|||||||
"otpAuthDescription": "从您的身份验证程序中输入代码或您的单次备份代码。",
|
"otpAuthDescription": "从您的身份验证程序中输入代码或您的单次备份代码。",
|
||||||
"otpAuthSubmit": "提交代码",
|
"otpAuthSubmit": "提交代码",
|
||||||
"idpContinue": "或者继续",
|
"idpContinue": "或者继续",
|
||||||
"idpLastUsed": "上次使用",
|
|
||||||
"otpAuthBack": "回到密码",
|
"otpAuthBack": "回到密码",
|
||||||
"navbar": "导航菜单",
|
"navbar": "导航菜单",
|
||||||
"navbarDescription": "应用程序的主导航菜单",
|
"navbarDescription": "应用程序的主导航菜单",
|
||||||
@@ -3815,6 +3801,5 @@
|
|||||||
"rdpUploadFailed": "上传失败",
|
"rdpUploadFailed": "上传失败",
|
||||||
"rdpUnicodeKeyboardMode": "Unicode 键盘模式",
|
"rdpUnicodeKeyboardMode": "Unicode 键盘模式",
|
||||||
"sessionToolbarShow": "显示工具栏",
|
"sessionToolbarShow": "显示工具栏",
|
||||||
"sessionToolbarHide": "隐藏工具栏",
|
"sessionToolbarHide": "隐藏工具栏"
|
||||||
"actionUpdateSiteApprovals": "更新站点审批"
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 556 KiB |
@@ -21,7 +21,6 @@ export enum ActionsEnum {
|
|||||||
getSite = "getSite",
|
getSite = "getSite",
|
||||||
listSites = "listSites",
|
listSites = "listSites",
|
||||||
updateSite = "updateSite",
|
updateSite = "updateSite",
|
||||||
updateSiteApprovals = "updateSiteApprovals",
|
|
||||||
restartSite = "restartSite",
|
restartSite = "restartSite",
|
||||||
resetSiteBandwidth = "resetSiteBandwidth",
|
resetSiteBandwidth = "resetSiteBandwidth",
|
||||||
reGenerateSecret = "reGenerateSecret",
|
reGenerateSecret = "reGenerateSecret",
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ export const sites = pgTable(
|
|||||||
lastPing: integer("lastPing"),
|
lastPing: integer("lastPing"),
|
||||||
address: varchar("address"),
|
address: varchar("address"),
|
||||||
endpoint: varchar("endpoint"),
|
endpoint: varchar("endpoint"),
|
||||||
localEndpoints: varchar("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to
|
|
||||||
publicKey: varchar("publicKey"),
|
publicKey: varchar("publicKey"),
|
||||||
lastHolePunch: bigint("lastHolePunch", { mode: "number" }),
|
lastHolePunch: bigint("lastHolePunch", { mode: "number" }),
|
||||||
listenPort: integer("listenPort"),
|
listenPort: integer("listenPort"),
|
||||||
@@ -201,10 +200,7 @@ export const resources = pgTable(
|
|||||||
authDaemonMode: varchar("authDaemonMode", { length: 32 })
|
authDaemonMode: varchar("authDaemonMode", { length: 32 })
|
||||||
.$type<"site" | "remote" | "native">()
|
.$type<"site" | "remote" | "native">()
|
||||||
.default("site"),
|
.default("site"),
|
||||||
authDaemonPort: integer("authDaemonPort").default(22123),
|
authDaemonPort: integer("authDaemonPort").default(22123)
|
||||||
status: varchar("status")
|
|
||||||
.$type<"pending" | "approved">()
|
|
||||||
.default("approved")
|
|
||||||
},
|
},
|
||||||
(t) => [
|
(t) => [
|
||||||
index("idx_resources_fulldomain")
|
index("idx_resources_fulldomain")
|
||||||
@@ -455,10 +451,7 @@ export const siteResources = pgTable(
|
|||||||
onDelete: "set null"
|
onDelete: "set null"
|
||||||
}),
|
}),
|
||||||
subdomain: varchar("subdomain"),
|
subdomain: varchar("subdomain"),
|
||||||
fullDomain: varchar("fullDomain"),
|
fullDomain: varchar("fullDomain")
|
||||||
status: varchar("status")
|
|
||||||
.$type<"pending" | "approved">()
|
|
||||||
.default("approved")
|
|
||||||
},
|
},
|
||||||
(t) => [index("idx_siteresources_orgid_niceid").on(t.orgId, t.niceId)]
|
(t) => [index("idx_siteresources_orgid_niceid").on(t.orgId, t.niceId)]
|
||||||
);
|
);
|
||||||
@@ -906,16 +899,12 @@ export const resourceAccessToken = pgTable("resourceAccessToken", {
|
|||||||
resourceId: integer("resourceId")
|
resourceId: integer("resourceId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||||
userId: varchar("userId").references(() => users.userId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
path: varchar("path"),
|
path: varchar("path"),
|
||||||
tokenHash: varchar("tokenHash").notNull(),
|
tokenHash: varchar("tokenHash").notNull(),
|
||||||
sessionLength: bigint("sessionLength", { mode: "number" }).notNull(),
|
sessionLength: bigint("sessionLength", { mode: "number" }).notNull(),
|
||||||
expiresAt: bigint("expiresAt", { mode: "number" }),
|
expiresAt: bigint("expiresAt", { mode: "number" }),
|
||||||
title: varchar("title"),
|
title: varchar("title"),
|
||||||
description: varchar("description"),
|
description: varchar("description"),
|
||||||
persistSession: boolean("persistSession").notNull().default(false),
|
|
||||||
createdAt: bigint("createdAt", { mode: "number" }).notNull()
|
createdAt: bigint("createdAt", { mode: "number" }).notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ export const sites = sqliteTable("sites", {
|
|||||||
// exit node stuff that is how to connect to the site when it has a wg server
|
// exit node stuff that is how to connect to the site when it has a wg server
|
||||||
address: text("address"), // this is the address of the wireguard interface in newt
|
address: text("address"), // this is the address of the wireguard interface in newt
|
||||||
endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config
|
endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config
|
||||||
localEndpoints: text("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to
|
|
||||||
publicKey: text("publicKey"), // TODO: Fix typo in publicKey
|
publicKey: text("publicKey"), // TODO: Fix typo in publicKey
|
||||||
lastHolePunch: integer("lastHolePunch"),
|
lastHolePunch: integer("lastHolePunch"),
|
||||||
listenPort: integer("listenPort"),
|
listenPort: integer("listenPort"),
|
||||||
@@ -210,8 +209,7 @@ export const resources = sqliteTable("resources", {
|
|||||||
authDaemonMode: text("authDaemonMode")
|
authDaemonMode: text("authDaemonMode")
|
||||||
.$type<"site" | "remote" | "native">()
|
.$type<"site" | "remote" | "native">()
|
||||||
.default("site"),
|
.default("site"),
|
||||||
authDaemonPort: integer("authDaemonPort").default(22123),
|
authDaemonPort: integer("authDaemonPort").default(22123)
|
||||||
status: text("status").$type<"pending" | "approved">().default("approved")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const labels = sqliteTable("labels", {
|
export const labels = sqliteTable("labels", {
|
||||||
@@ -449,8 +447,7 @@ export const siteResources = sqliteTable("siteResources", {
|
|||||||
onDelete: "set null"
|
onDelete: "set null"
|
||||||
}),
|
}),
|
||||||
subdomain: text("subdomain"),
|
subdomain: text("subdomain"),
|
||||||
fullDomain: text("fullDomain"),
|
fullDomain: text("fullDomain")
|
||||||
status: text("status").$type<"pending" | "approved">().default("approved")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const networks = sqliteTable("networks", {
|
export const networks = sqliteTable("networks", {
|
||||||
@@ -1126,18 +1123,12 @@ export const resourceAccessToken = sqliteTable("resourceAccessToken", {
|
|||||||
resourceId: integer("resourceId")
|
resourceId: integer("resourceId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||||
userId: text("userId").references(() => users.userId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
path: text("path"),
|
path: text("path"),
|
||||||
tokenHash: text("tokenHash").notNull(),
|
tokenHash: text("tokenHash").notNull(),
|
||||||
sessionLength: integer("sessionLength").notNull(),
|
sessionLength: integer("sessionLength").notNull(),
|
||||||
expiresAt: integer("expiresAt"),
|
expiresAt: integer("expiresAt"),
|
||||||
title: text("title"),
|
title: text("title"),
|
||||||
description: text("description"),
|
description: text("description"),
|
||||||
persistSession: integer("persistSession", { mode: "boolean" })
|
|
||||||
.notNull()
|
|
||||||
.default(false),
|
|
||||||
createdAt: integer("createdAt").notNull()
|
createdAt: integer("createdAt").notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ import {
|
|||||||
rebuildClientAssociationsFromSiteResource,
|
rebuildClientAssociationsFromSiteResource,
|
||||||
waitForSiteResourceRebuildIdle
|
waitForSiteResourceRebuildIdle
|
||||||
} from "../rebuildClientAssociations";
|
} from "../rebuildClientAssociations";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import next from "next";
|
||||||
|
import { LimitId } from "../billing";
|
||||||
|
import { usageService } from "../billing/usageService";
|
||||||
|
|
||||||
type ApplyBlueprintArgs = {
|
type ApplyBlueprintArgs = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ import { createCertificate } from "#dynamic/routers/certificates/createCertifica
|
|||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
||||||
import { tierMatrix } from "../billing/tierMatrix";
|
import { tierMatrix } from "../billing/tierMatrix";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import next from "next";
|
||||||
import { LimitId } from "../billing";
|
import { LimitId } from "../billing";
|
||||||
import { usageService } from "../billing/usageService";
|
import { usageService } from "../billing/usageService";
|
||||||
|
|
||||||
@@ -198,19 +201,17 @@ export async function updatePrivateResources(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let resourceStatusFromSite: "approved" | "pending" = "approved";
|
|
||||||
if (siteId && allSites.length === 0) {
|
if (siteId && allSites.length === 0) {
|
||||||
// only add if there are not provided sites
|
// only add if there are not provided sites
|
||||||
// Use the provided siteId directly, but verify it belongs to the org
|
// Use the provided siteId directly, but verify it belongs to the org
|
||||||
const [siteSingle] = await trx
|
const [siteSingle] = await trx
|
||||||
.select({ siteId: sites.siteId, status: sites.status })
|
.select({ siteId: sites.siteId })
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)))
|
.where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
if (siteSingle) {
|
if (siteSingle) {
|
||||||
allSites.push(siteSingle);
|
allSites.push(siteSingle);
|
||||||
}
|
}
|
||||||
resourceStatusFromSite = siteSingle.status ?? "approved";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allSites.length === 0) {
|
if (allSites.length === 0) {
|
||||||
@@ -219,13 +220,6 @@ export async function updatePrivateResources(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceEnabled =
|
|
||||||
resourceData.enabled == undefined || resourceData.enabled == null
|
|
||||||
? true
|
|
||||||
: resourceStatusFromSite === "pending"
|
|
||||||
? false
|
|
||||||
: resourceData.enabled;
|
|
||||||
|
|
||||||
if (existingResource) {
|
if (existingResource) {
|
||||||
let domainInfo:
|
let domainInfo:
|
||||||
| { subdomain: string | null; domainId: string }
|
| { subdomain: string | null; domainId: string }
|
||||||
@@ -239,31 +233,6 @@ export async function updatePrivateResources(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceData.alias) {
|
|
||||||
const [aliasConflict] = await trx
|
|
||||||
.select({
|
|
||||||
siteResourceId: siteResources.siteResourceId
|
|
||||||
})
|
|
||||||
.from(siteResources)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(siteResources.orgId, orgId),
|
|
||||||
eq(siteResources.alias, resourceData.alias),
|
|
||||||
ne(
|
|
||||||
siteResources.siteResourceId,
|
|
||||||
existingResource.siteResourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (aliasConflict) {
|
|
||||||
throw new Error(
|
|
||||||
`Alias ${resourceData.alias} already in use by another site resource in org ${orgId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update existing resource
|
// Update existing resource
|
||||||
const [updatedResource] = await trx
|
const [updatedResource] = await trx
|
||||||
.update(siteResources)
|
.update(siteResources)
|
||||||
@@ -274,7 +243,8 @@ export async function updatePrivateResources(
|
|||||||
scheme: resourceData.scheme,
|
scheme: resourceData.scheme,
|
||||||
destination: resourceData.destination,
|
destination: resourceData.destination,
|
||||||
destinationPort: resourceData["destination-port"],
|
destinationPort: resourceData["destination-port"],
|
||||||
enabled: resourceEnabled,
|
enabled: true, // hardcoded for now
|
||||||
|
// enabled: resourceData.enabled ?? true,
|
||||||
alias: resourceData.alias || null,
|
alias: resourceData.alias || null,
|
||||||
disableIcmp:
|
disableIcmp:
|
||||||
resourceData["disable-icmp"] ||
|
resourceData["disable-icmp"] ||
|
||||||
@@ -293,8 +263,7 @@ export async function updatePrivateResources(
|
|||||||
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
|
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
|
||||||
authDaemonMode:
|
authDaemonMode:
|
||||||
resourceData["auth-daemon"]?.mode || "native",
|
resourceData["auth-daemon"]?.mode || "native",
|
||||||
authDaemonPort: resourceData["auth-daemon"]?.port || 22123,
|
authDaemonPort: resourceData["auth-daemon"]?.port || 22123
|
||||||
status: resourceStatusFromSite
|
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
eq(
|
eq(
|
||||||
@@ -505,27 +474,6 @@ export async function updatePrivateResources(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceData.alias) {
|
|
||||||
const [aliasConflict] = await trx
|
|
||||||
.select({
|
|
||||||
siteResourceId: siteResources.siteResourceId
|
|
||||||
})
|
|
||||||
.from(siteResources)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(siteResources.orgId, orgId),
|
|
||||||
eq(siteResources.alias, resourceData.alias)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (aliasConflict) {
|
|
||||||
throw new Error(
|
|
||||||
`Alias ${resourceData.alias} already in use by another site resource in org ${orgId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [network] = await trx
|
const [network] = await trx
|
||||||
.insert(networks)
|
.insert(networks)
|
||||||
.values({
|
.values({
|
||||||
@@ -548,7 +496,8 @@ export async function updatePrivateResources(
|
|||||||
scheme: resourceData.scheme,
|
scheme: resourceData.scheme,
|
||||||
destination: resourceData.destination,
|
destination: resourceData.destination,
|
||||||
destinationPort: resourceData["destination-port"],
|
destinationPort: resourceData["destination-port"],
|
||||||
enabled: resourceEnabled,
|
enabled: true, // hardcoded for now
|
||||||
|
// enabled: resourceData.enabled ?? true,
|
||||||
alias: resourceData.alias || null,
|
alias: resourceData.alias || null,
|
||||||
aliasAddress: aliasAddress,
|
aliasAddress: aliasAddress,
|
||||||
disableIcmp:
|
disableIcmp:
|
||||||
@@ -568,8 +517,7 @@ export async function updatePrivateResources(
|
|||||||
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
|
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
|
||||||
authDaemonMode:
|
authDaemonMode:
|
||||||
resourceData["auth-daemon"]?.mode || "native",
|
resourceData["auth-daemon"]?.mode || "native",
|
||||||
authDaemonPort: resourceData["auth-daemon"]?.port || 22123,
|
authDaemonPort: resourceData["auth-daemon"]?.port || 22123
|
||||||
status: resourceStatusFromSite
|
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
rolePolicies,
|
rolePolicies,
|
||||||
roleResources,
|
roleResources,
|
||||||
roles,
|
roles,
|
||||||
Site,
|
|
||||||
sites,
|
sites,
|
||||||
Target,
|
Target,
|
||||||
TargetHealthCheck,
|
TargetHealthCheck,
|
||||||
@@ -75,40 +74,19 @@ export async function updatePublicResources(
|
|||||||
)) {
|
)) {
|
||||||
const targetsToUpdate: Target[] = [];
|
const targetsToUpdate: Target[] = [];
|
||||||
const healthchecksToUpdate: TargetHealthCheck[] = [];
|
const healthchecksToUpdate: TargetHealthCheck[] = [];
|
||||||
|
|
||||||
let resource: Resource;
|
let resource: Resource;
|
||||||
let resourceStatusFromSite: "approved" | "pending" = "approved";
|
|
||||||
let providedSite: Partial<Site> | undefined;
|
|
||||||
if (siteId) {
|
|
||||||
// Use the provided siteId directly, but verify it belongs to the org
|
|
||||||
[providedSite] = await trx
|
|
||||||
.select({
|
|
||||||
siteId: sites.siteId,
|
|
||||||
type: sites.type,
|
|
||||||
status: sites.status
|
|
||||||
})
|
|
||||||
.from(sites)
|
|
||||||
.where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
resourceStatusFromSite = providedSite?.status ?? "approved";
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createTarget( // reusable function to create a target
|
async function createTarget( // reusable function to create a target
|
||||||
resourceId: number,
|
resourceId: number,
|
||||||
targetData: TargetData
|
targetData: TargetData
|
||||||
) {
|
) {
|
||||||
const targetSiteId = targetData.site;
|
const targetSiteId = targetData.site;
|
||||||
let site: Partial<Site> | undefined;
|
let site;
|
||||||
|
|
||||||
if (targetSiteId) {
|
if (targetSiteId) {
|
||||||
// Look up site by niceId
|
// Look up site by niceId
|
||||||
[site] = await trx
|
[site] = await trx
|
||||||
.select({
|
.select({ siteId: sites.siteId, type: sites.type })
|
||||||
siteId: sites.siteId,
|
|
||||||
type: sites.type,
|
|
||||||
status: sites.status
|
|
||||||
})
|
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
@@ -117,9 +95,15 @@ export async function updatePublicResources(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
} else if (siteId && providedSite) {
|
} else if (siteId) {
|
||||||
// Use the provided siteId directly, but verify it belongs to the org
|
// Use the provided siteId directly, but verify it belongs to the org
|
||||||
site = providedSite;
|
[site] = await trx
|
||||||
|
.select({ siteId: sites.siteId, type: sites.type })
|
||||||
|
.from(sites)
|
||||||
|
.where(
|
||||||
|
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Target site is required`);
|
throw new Error(`Target site is required`);
|
||||||
}
|
}
|
||||||
@@ -155,7 +139,7 @@ export async function updatePublicResources(
|
|||||||
.insert(targets)
|
.insert(targets)
|
||||||
.values({
|
.values({
|
||||||
resourceId: resourceId,
|
resourceId: resourceId,
|
||||||
siteId: site.siteId!,
|
siteId: site.siteId,
|
||||||
ip: targetData.hostname,
|
ip: targetData.hostname,
|
||||||
mode: resourceData.mode as Target["mode"],
|
mode: resourceData.mode as Target["mode"],
|
||||||
method: targetData.method,
|
method: targetData.method,
|
||||||
@@ -188,7 +172,7 @@ export async function updatePublicResources(
|
|||||||
.insert(targetHealthCheck)
|
.insert(targetHealthCheck)
|
||||||
.values({
|
.values({
|
||||||
name: `${targetData.hostname}:${targetData.port}`,
|
name: `${targetData.hostname}:${targetData.port}`,
|
||||||
siteId: site.siteId!,
|
siteId: site.siteId,
|
||||||
targetId: newTarget.targetId,
|
targetId: newTarget.targetId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
hcEnabled: healthcheckData?.enabled || false,
|
hcEnabled: healthcheckData?.enabled || false,
|
||||||
@@ -246,10 +230,7 @@ export async function updatePublicResources(
|
|||||||
const resourceEnabled =
|
const resourceEnabled =
|
||||||
resourceData.enabled == undefined || resourceData.enabled == null
|
resourceData.enabled == undefined || resourceData.enabled == null
|
||||||
? true
|
? true
|
||||||
: resourceStatusFromSite === "pending"
|
|
||||||
? false
|
|
||||||
: resourceData.enabled;
|
: resourceData.enabled;
|
||||||
|
|
||||||
const resourceSsl =
|
const resourceSsl =
|
||||||
resourceData.ssl == undefined || resourceData.ssl == null
|
resourceData.ssl == undefined || resourceData.ssl == null
|
||||||
? true
|
? true
|
||||||
@@ -425,8 +406,7 @@ export async function updatePublicResources(
|
|||||||
? (resourceData["proxy-protocol-version"] ??
|
? (resourceData["proxy-protocol-version"] ??
|
||||||
1)
|
1)
|
||||||
: 1,
|
: 1,
|
||||||
resourcePolicyId: sharedPolicy.resourcePolicyId,
|
resourcePolicyId: sharedPolicy.resourcePolicyId
|
||||||
status: resourceStatusFromSite
|
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
eq(
|
eq(
|
||||||
@@ -610,8 +590,7 @@ export async function updatePublicResources(
|
|||||||
authDaemonPort:
|
authDaemonPort:
|
||||||
resourceData["auth-daemon"]?.port || 22123,
|
resourceData["auth-daemon"]?.port || 22123,
|
||||||
resourcePolicyId: null,
|
resourcePolicyId: null,
|
||||||
defaultResourcePolicyId: inlinePolicyId,
|
defaultResourcePolicyId: inlinePolicyId
|
||||||
status: resourceStatusFromSite
|
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
eq(
|
eq(
|
||||||
@@ -1152,7 +1131,6 @@ export async function updatePublicResources(
|
|||||||
.values({
|
.values({
|
||||||
orgId,
|
orgId,
|
||||||
niceId: resourceNiceId,
|
niceId: resourceNiceId,
|
||||||
status: resourceStatusFromSite,
|
|
||||||
name: resourceData.name || "Unnamed Resource",
|
name: resourceData.name || "Unnamed Resource",
|
||||||
mode: resourceData.mode,
|
mode: resourceData.mode,
|
||||||
proxyPort: ["http", "ssh", "rdp", "vnc"].includes(
|
proxyPort: ["http", "ssh", "rdp", "vnc"].includes(
|
||||||
|
|||||||
@@ -470,7 +470,7 @@ export const PrivateResourceSchema = z
|
|||||||
// proxyPort: z.int().positive().optional(),
|
// proxyPort: z.int().positive().optional(),
|
||||||
"destination-port": z.int().positive().optional(),
|
"destination-port": z.int().positive().optional(),
|
||||||
destination: z.string().min(1).optional(),
|
destination: z.string().min(1).optional(),
|
||||||
enabled: z.boolean().default(true),
|
// enabled: z.boolean().default(true),
|
||||||
"tcp-ports": portRangeStringSchema.optional().default("*"),
|
"tcp-ports": portRangeStringSchema.optional().default("*"),
|
||||||
"udp-ports": portRangeStringSchema.optional().default("*"),
|
"udp-ports": portRangeStringSchema.optional().default("*"),
|
||||||
"disable-icmp": z.boolean().optional().default(false),
|
"disable-icmp": z.boolean().optional().default(false),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import path from "path";
|
|||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
// This is a placeholder value replaced by the build process
|
// This is a placeholder value replaced by the build process
|
||||||
export const APP_VERSION = "1.21.0";
|
export const APP_VERSION = "1.20.0";
|
||||||
|
|
||||||
export const __FILENAME = fileURLToPath(import.meta.url);
|
export const __FILENAME = fileURLToPath(import.meta.url);
|
||||||
export const __DIRNAME = path.dirname(__FILENAME);
|
export const __DIRNAME = path.dirname(__FILENAME);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { removeTargets } from "@server/routers/newt/targets";
|
import { removeTargets } from "@server/routers/newt/targets";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
|
||||||
export type DeleteResourceResult = {
|
export type DeleteResourceResult = {
|
||||||
deletedResource: Resource;
|
deletedResource: Resource;
|
||||||
@@ -115,10 +117,10 @@ export async function runResourceDeleteSideEffects(
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (!site) {
|
if (!site) {
|
||||||
logger.debug(
|
throw createHttpError(
|
||||||
`Site with ID ${target.siteId} not found during resource delete side effects; skipping target removal`
|
HttpCode.NOT_FOUND,
|
||||||
|
`Site with ID ${target.siteId} not found`
|
||||||
);
|
);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (site.pubKey && site.type === "newt") {
|
if (site.pubKey && site.type === "newt") {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { and, eq, inArray, sql } from "drizzle-orm";
|
import { and, eq, sql } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
db,
|
db,
|
||||||
resources,
|
|
||||||
siteNetworks,
|
siteNetworks,
|
||||||
siteResources,
|
siteResources,
|
||||||
targets,
|
targets,
|
||||||
@@ -98,64 +97,6 @@ export function exceedsSiteAssociatedResourceDeleteLimit(
|
|||||||
return resourceCount > MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE;
|
return resourceCount > MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPendingResourceIdsForSite(
|
|
||||||
siteId: number,
|
|
||||||
trx: Transaction | typeof db = db
|
|
||||||
): Promise<number[]> {
|
|
||||||
const resourceIds = await getResourceIdsForSite(siteId, trx);
|
|
||||||
if (resourceIds.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = await trx
|
|
||||||
.select({ resourceId: resources.resourceId })
|
|
||||||
.from(resources)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
inArray(resources.resourceId, resourceIds),
|
|
||||||
eq(resources.status, "pending")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return rows.map((row) => row.resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPendingSiteResourceIdsForSite(
|
|
||||||
siteId: number,
|
|
||||||
orgId: string,
|
|
||||||
trx: Transaction | typeof db = db
|
|
||||||
): Promise<number[]> {
|
|
||||||
const siteResourceIds = await getSiteResourceIdsForSite(siteId, orgId, trx);
|
|
||||||
if (siteResourceIds.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = await trx
|
|
||||||
.select({ siteResourceId: siteResources.siteResourceId })
|
|
||||||
.from(siteResources)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
inArray(siteResources.siteResourceId, siteResourceIds),
|
|
||||||
eq(siteResources.status, "pending")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return rows.map((row) => row.siteResourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPendingAssociatedResourceCountForSite(
|
|
||||||
siteId: number,
|
|
||||||
orgId: string,
|
|
||||||
trx: Transaction | typeof db = db
|
|
||||||
): Promise<number> {
|
|
||||||
const [resourceIds, siteResourceIds] = await Promise.all([
|
|
||||||
getPendingResourceIdsForSite(siteId, trx),
|
|
||||||
getPendingSiteResourceIdsForSite(siteId, orgId, trx)
|
|
||||||
]);
|
|
||||||
|
|
||||||
return resourceIds.length + siteResourceIds.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteAssociatedResourcesForSite(
|
export async function deleteAssociatedResourcesForSite(
|
||||||
siteId: number,
|
siteId: number,
|
||||||
orgId: string,
|
orgId: string,
|
||||||
@@ -164,32 +105,12 @@ export async function deleteAssociatedResourcesForSite(
|
|||||||
const resourceIds = await getResourceIdsForSite(siteId, trx);
|
const resourceIds = await getResourceIdsForSite(siteId, trx);
|
||||||
const siteResourceIds = await getSiteResourceIdsForSite(siteId, orgId, trx);
|
const siteResourceIds = await getSiteResourceIdsForSite(siteId, orgId, trx);
|
||||||
|
|
||||||
const [deletedResources, siteResourcesDeleted] = await Promise.all([
|
const [resources, siteResourcesDeleted] = await Promise.all([
|
||||||
performDeleteResources(resourceIds, trx),
|
performDeleteResources(resourceIds, trx),
|
||||||
performDeleteSiteResources(siteResourceIds, trx)
|
performDeleteSiteResources(siteResourceIds, trx)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { resources: deletedResources, siteResources: siteResourcesDeleted };
|
return { resources, siteResources: siteResourcesDeleted };
|
||||||
}
|
|
||||||
|
|
||||||
export async function deletePendingAssociatedResourcesForSite(
|
|
||||||
siteId: number,
|
|
||||||
orgId: string,
|
|
||||||
trx: Transaction | typeof db = db
|
|
||||||
): Promise<DeleteSiteAssociatedResourcesSideEffects> {
|
|
||||||
const resourceIds = await getPendingResourceIdsForSite(siteId, trx);
|
|
||||||
const siteResourceIds = await getPendingSiteResourceIdsForSite(
|
|
||||||
siteId,
|
|
||||||
orgId,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
|
|
||||||
const [deletedResources, siteResourcesDeleted] = await Promise.all([
|
|
||||||
performDeleteResources(resourceIds, trx),
|
|
||||||
performDeleteSiteResources(siteResourceIds, trx)
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { resources: deletedResources, siteResources: siteResourcesDeleted };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runDeleteSiteAssociatedResourcesSideEffects(
|
export async function runDeleteSiteAssociatedResourcesSideEffects(
|
||||||
|
|||||||
@@ -496,7 +496,6 @@ export function generateRemoteSubnets(
|
|||||||
): string[] {
|
): string[] {
|
||||||
const remoteSubnets = allSiteResources
|
const remoteSubnets = allSiteResources
|
||||||
.filter((sr) => {
|
.filter((sr) => {
|
||||||
if (!sr.enabled) return false;
|
|
||||||
if (!sr.destination) return false;
|
if (!sr.destination) return false;
|
||||||
|
|
||||||
if (sr.mode === "cidr") {
|
if (sr.mode === "cidr") {
|
||||||
@@ -531,7 +530,6 @@ export function generateAliasConfig(allSiteResources: SiteResource[]): Alias[] {
|
|||||||
return allSiteResources
|
return allSiteResources
|
||||||
.filter(
|
.filter(
|
||||||
(sr) =>
|
(sr) =>
|
||||||
sr.enabled &&
|
|
||||||
sr.aliasAddress &&
|
sr.aliasAddress &&
|
||||||
((sr.alias && (sr.mode == "host" || sr.mode == "ssh")) ||
|
((sr.alias && (sr.mode == "host" || sr.mode == "ssh")) ||
|
||||||
(sr.fullDomain && sr.mode == "http"))
|
(sr.fullDomain && sr.mode == "http"))
|
||||||
@@ -664,13 +662,6 @@ export async function generateSubnetProxyTargetV2(
|
|||||||
subnet: string | null;
|
subnet: string | null;
|
||||||
}[]
|
}[]
|
||||||
): Promise<SubnetProxyTargetV2[] | undefined> {
|
): Promise<SubnetProxyTargetV2[] | undefined> {
|
||||||
if (!siteResource.enabled) {
|
|
||||||
logger.debug(
|
|
||||||
`Site resource ${siteResource.siteResourceId} is disabled, skipping target generation.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clients.length === 0) {
|
if (clients.length === 0) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`
|
`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`
|
||||||
|
|||||||
@@ -1561,19 +1561,9 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
updatedSiteResource.udpPortRangeString ||
|
updatedSiteResource.udpPortRangeString ||
|
||||||
existingSiteResource.disableIcmp !==
|
existingSiteResource.disableIcmp !==
|
||||||
updatedSiteResource.disableIcmp);
|
updatedSiteResource.disableIcmp);
|
||||||
// Toggling enabled on/off doesn't change any of the fields above, but it
|
|
||||||
// does change whether targets/peer data should exist at all, so it needs
|
|
||||||
// to drive the same old->new diff machinery: going enabled->disabled
|
|
||||||
// diffs "real data" against "nothing" (a remove), and disabled->enabled
|
|
||||||
// diffs "nothing" against "real data" (an add). generateSubnetProxyTargetV2/
|
|
||||||
// generateRemoteSubnets/generateAliasConfig already return nothing for a
|
|
||||||
// disabled resource, so no other changes are needed here.
|
|
||||||
const enabledChanged =
|
|
||||||
existingSiteResource &&
|
|
||||||
existingSiteResource.enabled !== updatedSiteResource.enabled;
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`handleMessagingForUpdatedSiteResource: change flags destinationChanged=${Boolean(destinationChanged)} destinationPortChanged=${Boolean(destinationPortChanged)} aliasChanged=${Boolean(aliasChanged)} fullDomainChanged=${Boolean(fullDomainChanged)} sslChanged=${Boolean(sslChanged)} portRangesChanged=${Boolean(portRangesChanged)} enabledChanged=${Boolean(enabledChanged)}`
|
`handleMessagingForUpdatedSiteResource: change flags destinationChanged=${Boolean(destinationChanged)} destinationPortChanged=${Boolean(destinationPortChanged)} aliasChanged=${Boolean(aliasChanged)} fullDomainChanged=${Boolean(fullDomainChanged)} sslChanged=${Boolean(sslChanged)} portRangesChanged=${Boolean(portRangesChanged)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// if the existingSiteResource is undefined (new resource) we don't need to do anything here, the rebuild above handled it all
|
// if the existingSiteResource is undefined (new resource) we don't need to do anything here, the rebuild above handled it all
|
||||||
@@ -1584,16 +1574,14 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
fullDomainChanged ||
|
fullDomainChanged ||
|
||||||
sslChanged ||
|
sslChanged ||
|
||||||
portRangesChanged ||
|
portRangesChanged ||
|
||||||
destinationPortChanged ||
|
destinationPortChanged
|
||||||
enabledChanged
|
|
||||||
) {
|
) {
|
||||||
const shouldUpdateTargets =
|
const shouldUpdateTargets =
|
||||||
destinationChanged ||
|
destinationChanged ||
|
||||||
sslChanged ||
|
sslChanged ||
|
||||||
portRangesChanged ||
|
portRangesChanged ||
|
||||||
fullDomainChanged ||
|
fullDomainChanged ||
|
||||||
destinationPortChanged ||
|
destinationPortChanged;
|
||||||
enabledChanged;
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`handleMessagingForUpdatedSiteResource: entering unchanged-site update path shouldUpdateTargets=${shouldUpdateTargets}`
|
`handleMessagingForUpdatedSiteResource: entering unchanged-site update path shouldUpdateTargets=${shouldUpdateTargets}`
|
||||||
@@ -1669,11 +1657,9 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
peerDataUpdateBatch.push({
|
peerDataUpdateBatch.push({
|
||||||
clientId: client.clientId,
|
clientId: client.clientId,
|
||||||
siteId,
|
siteId,
|
||||||
remoteSubnets:
|
remoteSubnets: destinationChanged
|
||||||
destinationChanged || enabledChanged
|
|
||||||
? {
|
? {
|
||||||
oldRemoteSubnets:
|
oldRemoteSubnets: !oldDestinationStillInUseBySite
|
||||||
!oldDestinationStillInUseBySite
|
|
||||||
? generateRemoteSubnets([
|
? generateRemoteSubnets([
|
||||||
existingSiteResource
|
existingSiteResource
|
||||||
])
|
])
|
||||||
@@ -1684,7 +1670,7 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
aliases:
|
aliases:
|
||||||
aliasChanged || fullDomainChanged || enabledChanged // the full domain is sent down as an alias
|
aliasChanged || fullDomainChanged // the full domain is sent down as an alias
|
||||||
? {
|
? {
|
||||||
oldAliases: generateAliasConfig([
|
oldAliases: generateAliasConfig([
|
||||||
existingSiteResource
|
existingSiteResource
|
||||||
|
|||||||
+97
-50
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, logsDb, statusHistory } from "@server/db";
|
import { db, logsDb, statusHistory } from "@server/db";
|
||||||
import { and, eq, gte, lt, asc, desc } from "drizzle-orm";
|
import { and, eq, gte, lt, asc, desc, inArray } from "drizzle-orm";
|
||||||
import { regionalCache as cache } from "#dynamic/lib/cache";
|
import { regionalCache as cache } from "#dynamic/lib/cache";
|
||||||
|
|
||||||
const STATUS_HISTORY_CACHE_TTL = 60; // seconds
|
const STATUS_HISTORY_CACHE_TTL = 60; // seconds
|
||||||
@@ -8,42 +8,26 @@ const STATUS_HISTORY_CACHE_TTL = 60; // seconds
|
|||||||
function statusHistoryCacheKey(
|
function statusHistoryCacheKey(
|
||||||
entityType: string,
|
entityType: string,
|
||||||
entityId: number,
|
entityId: number,
|
||||||
days: number,
|
days: number
|
||||||
tzOffsetMinutes: number
|
|
||||||
): string {
|
): string {
|
||||||
return `statusHistory:${entityType}:${entityId}:${days}:${tzOffsetMinutes}`;
|
return `statusHistory:${entityType}:${entityId}:${days}`;
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the epoch seconds of the most recent local-calendar-day midnight,
|
|
||||||
// where "local" is defined by tzOffsetMinutes (minutes to ADD to UTC to get
|
|
||||||
// local time, e.g. Australia/Sydney standard time is 600). Defaults to 0
|
|
||||||
// (UTC) so callers that don't pass a timezone keep the original behavior.
|
|
||||||
function localMidnightSec(tzOffsetMinutes: number): number {
|
|
||||||
const localNow = new Date(Date.now() + tzOffsetMinutes * 60_000);
|
|
||||||
localNow.setUTCHours(0, 0, 0, 0);
|
|
||||||
return Math.floor(localNow.getTime() / 1000) - tzOffsetMinutes * 60;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCachedStatusHistory(
|
export async function getCachedStatusHistory(
|
||||||
entityType: string,
|
entityType: string,
|
||||||
entityId: number,
|
entityId: number,
|
||||||
days: number,
|
days: number
|
||||||
tzOffsetMinutes: number = 0
|
|
||||||
): Promise<StatusHistoryResponse> {
|
): Promise<StatusHistoryResponse> {
|
||||||
const cacheKey = statusHistoryCacheKey(
|
const cacheKey = statusHistoryCacheKey(entityType, entityId, days);
|
||||||
entityType,
|
|
||||||
entityId,
|
|
||||||
days,
|
|
||||||
tzOffsetMinutes
|
|
||||||
);
|
|
||||||
const cached = await cache.get<StatusHistoryResponse>(cacheKey);
|
const cached = await cache.get<StatusHistoryResponse>(cacheKey);
|
||||||
if (cached !== undefined) {
|
if (cached !== undefined) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anchor to local midnight (UTC when tzOffsetMinutes is 0) so the query
|
// Anchor to UTC midnight so the query window aligns with stable calendar days
|
||||||
// window aligns with stable calendar days for the requesting client
|
const utcToday = new Date();
|
||||||
const todayMidnightSec = localMidnightSec(tzOffsetMinutes);
|
utcToday.setUTCHours(0, 0, 0, 0);
|
||||||
|
const todayMidnightSec = Math.floor(utcToday.getTime() / 1000);
|
||||||
const startSec = todayMidnightSec - days * 86400;
|
const startSec = todayMidnightSec - days * 86400;
|
||||||
|
|
||||||
const events = await logsDb
|
const events = await logsDb
|
||||||
@@ -79,8 +63,7 @@ export async function getCachedStatusHistory(
|
|||||||
const { buckets, totalDowntime } = computeBuckets(
|
const { buckets, totalDowntime } = computeBuckets(
|
||||||
events,
|
events,
|
||||||
days,
|
days,
|
||||||
priorStatus,
|
priorStatus
|
||||||
tzOffsetMinutes
|
|
||||||
);
|
);
|
||||||
const totalWindow = days * 86400;
|
const totalWindow = days * 86400;
|
||||||
const overallUptime =
|
const overallUptime =
|
||||||
@@ -116,19 +99,11 @@ export const statusHistoryQuerySchema = z
|
|||||||
days: z
|
days: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.transform((v) => (v ? parseInt(v, 10) : 90)),
|
.transform((v) => (v ? parseInt(v, 10) : 90))
|
||||||
// Minutes to add to UTC to get the requesting client's local time
|
|
||||||
// (e.g. Australia/Sydney standard time is 600). Optional and
|
|
||||||
// defaults to 0 (UTC) so older clients keep the prior behavior.
|
|
||||||
tzOffsetMinutes: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.transform((v) => (v ? parseInt(v, 10) : 0))
|
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
z.object({
|
z.object({
|
||||||
days: z.number().int().min(1).max(365),
|
days: z.number().int().min(1).max(365)
|
||||||
tzOffsetMinutes: z.number().int().min(-720).max(840)
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -158,15 +133,15 @@ export function computeBuckets(
|
|||||||
id: number;
|
id: number;
|
||||||
}[],
|
}[],
|
||||||
days: number,
|
days: number,
|
||||||
priorStatus: string | null = null,
|
priorStatus: string | null = null
|
||||||
tzOffsetMinutes: number = 0
|
|
||||||
): { buckets: StatusHistoryDayBucket[]; totalDowntime: number } {
|
): { buckets: StatusHistoryDayBucket[]; totalDowntime: number } {
|
||||||
const nowSec = Math.floor(Date.now() / 1000);
|
const nowSec = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
// Anchor bucket boundaries to local midnight (UTC when tzOffsetMinutes is
|
// Anchor bucket boundaries to UTC midnight so dates are stable calendar days
|
||||||
// 0) so dates are stable calendar days for the requesting client and
|
// and don't drift as the cache expires and is recomputed
|
||||||
// don't drift as the cache expires and is recomputed
|
const utcToday = new Date();
|
||||||
const todayMidnightSec = localMidnightSec(tzOffsetMinutes);
|
utcToday.setUTCHours(0, 0, 0, 0);
|
||||||
|
const todayMidnightSec = Math.floor(utcToday.getTime() / 1000);
|
||||||
|
|
||||||
const buckets: StatusHistoryDayBucket[] = [];
|
const buckets: StatusHistoryDayBucket[] = [];
|
||||||
let totalDowntime = 0;
|
let totalDowntime = 0;
|
||||||
@@ -262,13 +237,7 @@ export function computeBuckets(
|
|||||||
)
|
)
|
||||||
: 100;
|
: 100;
|
||||||
|
|
||||||
// Shift by the client's offset before formatting so the label reflects
|
const dateStr = new Date(dayStartSec * 1000).toISOString().slice(0, 10);
|
||||||
// their local calendar date rather than the UTC date of dayStartSec
|
|
||||||
const dateStr = new Date(
|
|
||||||
(dayStartSec + tzOffsetMinutes * 60) * 1000
|
|
||||||
)
|
|
||||||
.toISOString()
|
|
||||||
.slice(0, 10);
|
|
||||||
|
|
||||||
const hasAnyData = currentStatus !== null || dayEvents.length > 0;
|
const hasAnyData = currentStatus !== null || dayEvents.length > 0;
|
||||||
|
|
||||||
@@ -304,3 +273,81 @@ export function computeBuckets(
|
|||||||
|
|
||||||
return { buckets, totalDowntime };
|
return { buckets, totalDowntime };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BatchedStatusHistoryResponse {
|
||||||
|
entityType: string;
|
||||||
|
entityIds: number[];
|
||||||
|
days: StatusHistoryDayBucket[];
|
||||||
|
overallUptimePercent: number;
|
||||||
|
totalDowntimeSeconds: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBatchedStatusHistory(
|
||||||
|
entityType: string,
|
||||||
|
entityIds: number[],
|
||||||
|
days: number
|
||||||
|
): Promise<BatchedStatusHistoryResponse> {
|
||||||
|
// const cacheKey = statusHistoryCacheKey(entityType, entityId, days);
|
||||||
|
// const cached = await cache.get<StatusHistoryResponse>(cacheKey);
|
||||||
|
// if (cached !== undefined) {
|
||||||
|
// return cached;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Anchor to UTC midnight so the query window aligns with stable calendar days
|
||||||
|
const utcToday = new Date();
|
||||||
|
utcToday.setUTCHours(0, 0, 0, 0);
|
||||||
|
const todayMidnightSec = Math.floor(utcToday.getTime() / 1000);
|
||||||
|
const startSec = todayMidnightSec - days * 86400;
|
||||||
|
|
||||||
|
const events = await logsDb
|
||||||
|
.select()
|
||||||
|
.from(statusHistory)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(statusHistory.entityType, entityType),
|
||||||
|
inArray(statusHistory.entityId, entityIds),
|
||||||
|
gte(statusHistory.timestamp, startSec)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(asc(statusHistory.timestamp));
|
||||||
|
|
||||||
|
// Fetch the last known state before the window so that entities that
|
||||||
|
// haven't changed status recently still show the correct status rather
|
||||||
|
// than appearing as "no_data".
|
||||||
|
const [lastKnownEvent] = await logsDb
|
||||||
|
.select()
|
||||||
|
.from(statusHistory)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(statusHistory.entityType, entityType),
|
||||||
|
inArray(statusHistory.entityId, entityIds),
|
||||||
|
lt(statusHistory.timestamp, startSec)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(desc(statusHistory.timestamp))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const priorStatus = lastKnownEvent?.status ?? null;
|
||||||
|
|
||||||
|
const { buckets, totalDowntime } = computeBuckets(
|
||||||
|
events,
|
||||||
|
days,
|
||||||
|
priorStatus
|
||||||
|
);
|
||||||
|
const totalWindow = days * 86400;
|
||||||
|
const overallUptime =
|
||||||
|
totalWindow > 0
|
||||||
|
? Math.max(0, ((totalWindow - totalDowntime) / totalWindow) * 100)
|
||||||
|
: 100;
|
||||||
|
|
||||||
|
const result: BatchedStatusHistoryResponse = {
|
||||||
|
entityType,
|
||||||
|
entityIds,
|
||||||
|
days: buckets,
|
||||||
|
overallUptimePercent: Math.round(overallUptime * 100) / 100,
|
||||||
|
totalDowntimeSeconds: totalDowntime
|
||||||
|
};
|
||||||
|
|
||||||
|
// await cache.set(cacheKey, result, STATUS_HISTORY_CACHE_TTL);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
db,
|
db,
|
||||||
Org,
|
Org,
|
||||||
orgs,
|
orgs,
|
||||||
resourceAccessToken,
|
|
||||||
resources,
|
resources,
|
||||||
siteResources,
|
siteResources,
|
||||||
sites,
|
sites,
|
||||||
@@ -84,15 +83,6 @@ export async function removeUserFromOrg(
|
|||||||
.delete(userOrgs)
|
.delete(userOrgs)
|
||||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, org.orgId)));
|
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, org.orgId)));
|
||||||
|
|
||||||
await trx
|
|
||||||
.delete(resourceAccessToken)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(resourceAccessToken.userId, userId),
|
|
||||||
eq(resourceAccessToken.orgId, org.orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
await trx.delete(userResources).where(
|
await trx.delete(userResources).where(
|
||||||
and(
|
and(
|
||||||
eq(userResources.userId, userId),
|
eq(userResources.userId, userId),
|
||||||
|
|||||||
@@ -17,4 +17,3 @@ export * from "./verifyApiKeySiteResourceAccess";
|
|||||||
export * from "./verifyApiKeyIdpAccess";
|
export * from "./verifyApiKeyIdpAccess";
|
||||||
export * from "./verifyApiKeyDomainAccess";
|
export * from "./verifyApiKeyDomainAccess";
|
||||||
export * from "./verifyApiKeyResourcePolicyAccess";
|
export * from "./verifyApiKeyResourcePolicyAccess";
|
||||||
export * from "./verifyApiKeySiteProvisioningKeyAccess";
|
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
|
||||||
import {
|
|
||||||
db,
|
|
||||||
siteProvisioningKeys,
|
|
||||||
siteProvisioningKeyOrg,
|
|
||||||
apiKeyOrg
|
|
||||||
} from "@server/db";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyApiKeySiteProvisioningKeyAccess(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const apiKey = req.apiKey;
|
|
||||||
const siteProvisioningKeyId =
|
|
||||||
getFirstString(req.params.siteProvisioningKeyId) ||
|
|
||||||
getFirstString(req.body.siteProvisioningKeyId) ||
|
|
||||||
getFirstString(req.query.siteProvisioningKeyId);
|
|
||||||
const orgId = getFirstString(req.params.orgId);
|
|
||||||
|
|
||||||
if (!apiKey) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!orgId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!siteProvisioningKeyId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid key ID")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (apiKey.isRoot) {
|
|
||||||
// Root keys can access any site provisioning key in any org
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
const [row] = await db
|
|
||||||
.select()
|
|
||||||
.from(siteProvisioningKeys)
|
|
||||||
.innerJoin(
|
|
||||||
siteProvisioningKeyOrg,
|
|
||||||
and(
|
|
||||||
eq(
|
|
||||||
siteProvisioningKeys.siteProvisioningKeyId,
|
|
||||||
siteProvisioningKeyOrg.siteProvisioningKeyId
|
|
||||||
),
|
|
||||||
eq(siteProvisioningKeyOrg.orgId, orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
eq(
|
|
||||||
siteProvisioningKeys.siteProvisioningKeyId,
|
|
||||||
siteProvisioningKeyId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!row?.siteProvisioningKeys || !row.siteProvisioningKeyOrg) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Site provisioning key with ID ${siteProvisioningKeyId} not found for organization ${orgId}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!req.apiKeyOrg) {
|
|
||||||
const apiKeyOrgRes = await db
|
|
||||||
.select()
|
|
||||||
.from(apiKeyOrg)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(apiKeyOrg.apiKeyId, apiKey.apiKeyId),
|
|
||||||
eq(apiKeyOrg.orgId, row.siteProvisioningKeyOrg.orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
req.apiKeyOrg = apiKeyOrgRes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!req.apiKeyOrg) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.FORBIDDEN,
|
|
||||||
"Key does not have access to this organization"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (error) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"Error verifying site provisioning key access"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ export enum OpenAPITags {
|
|||||||
OrgIdp = "Identity Provider (Organization Only)",
|
OrgIdp = "Identity Provider (Organization Only)",
|
||||||
Client = "Client",
|
Client = "Client",
|
||||||
ApiKey = "API Key",
|
ApiKey = "API Key",
|
||||||
SiteProvisioningKey = "Site Provisioning Key",
|
|
||||||
Domain = "Domain",
|
Domain = "Domain",
|
||||||
Blueprint = "Blueprint",
|
Blueprint = "Blueprint",
|
||||||
Ssh = "SSH",
|
Ssh = "SSH",
|
||||||
|
|||||||
@@ -55,14 +55,9 @@ export async function getHealthCheckStatusHistory(
|
|||||||
|
|
||||||
const entityType = "health_check";
|
const entityType = "health_check";
|
||||||
const entityId = parsedParams.data.healthCheckId;
|
const entityId = parsedParams.data.healthCheckId;
|
||||||
const { days, tzOffsetMinutes } = parsedQuery.data;
|
const { days } = parsedQuery.data;
|
||||||
|
|
||||||
const data = await getCachedStatusHistory(
|
const data = await getCachedStatusHistory(entityType, entityId, days);
|
||||||
entityType,
|
|
||||||
entityId,
|
|
||||||
days,
|
|
||||||
tzOffsetMinutes
|
|
||||||
);
|
|
||||||
|
|
||||||
return response<StatusHistoryResponse>(res, {
|
return response<StatusHistoryResponse>(res, {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ import {
|
|||||||
resourceRules,
|
resourceRules,
|
||||||
resourcePolicyRules,
|
resourcePolicyRules,
|
||||||
userOrgRoles,
|
userOrgRoles,
|
||||||
roles,
|
roles
|
||||||
resourceAccessToken
|
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { eq, and, inArray, isNotNull, isNull, ne, or, sql } from "drizzle-orm";
|
import { eq, and, inArray, isNotNull, isNull, ne, or, sql } from "drizzle-orm";
|
||||||
import { alias } from "@server/db";
|
import { alias } from "@server/db";
|
||||||
@@ -82,16 +81,11 @@ import config from "@server/lib/config";
|
|||||||
import { exchangeSession } from "@server/routers/badger";
|
import { exchangeSession } from "@server/routers/badger";
|
||||||
import {
|
import {
|
||||||
ResourceSessionValidationResult,
|
ResourceSessionValidationResult,
|
||||||
createResourceSession,
|
|
||||||
serializeResourceSessionCookie,
|
|
||||||
validateResourceSessionToken
|
validateResourceSessionToken
|
||||||
} from "@server/auth/sessions/resource";
|
} from "@server/auth/sessions/resource";
|
||||||
import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes";
|
import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes";
|
||||||
import { maxmindLookup } from "@server/db/maxmind";
|
import { maxmindLookup } from "@server/db/maxmind";
|
||||||
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
||||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
|
||||||
import { logAccessAudit } from "#private/lib/logAccessAudit";
|
|
||||||
import { getUserOrgRoles } from "@server/lib/userOrgRoles";
|
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import { maxmindAsnLookup } from "@server/db/maxmindAsn";
|
import { maxmindAsnLookup } from "@server/db/maxmindAsn";
|
||||||
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy";
|
||||||
@@ -184,73 +178,6 @@ const validateResourceAccessTokenBodySchema = z.strictObject({
|
|||||||
accessToken: z.string()
|
accessToken: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
const createAccessTokenSessionParamsSchema = z.strictObject({
|
|
||||||
resourceId: z.coerce.number().int().positive()
|
|
||||||
});
|
|
||||||
|
|
||||||
const createAccessTokenSessionBodySchema = z.strictObject({
|
|
||||||
accessTokenId: z.string().min(1)
|
|
||||||
});
|
|
||||||
|
|
||||||
const getAccessTokenParamsSchema = z.strictObject({
|
|
||||||
accessTokenId: z.string().min(1)
|
|
||||||
});
|
|
||||||
|
|
||||||
const logAccessAuditBodySchema = z.strictObject({
|
|
||||||
action: z.boolean(),
|
|
||||||
type: z.string(),
|
|
||||||
orgId: z.string(),
|
|
||||||
resourceId: z.number().optional(),
|
|
||||||
siteResourceId: z.number().optional(),
|
|
||||||
user: z
|
|
||||||
.object({
|
|
||||||
username: z.string(),
|
|
||||||
userId: z.string()
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
apiKey: z
|
|
||||||
.object({
|
|
||||||
name: z.string().nullable(),
|
|
||||||
apiKeyId: z.string()
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
metadata: z.any().optional(),
|
|
||||||
userAgent: z.string().optional(),
|
|
||||||
requestIp: z.string().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
type AccessTokenUserData = {
|
|
||||||
userId: string;
|
|
||||||
username: string;
|
|
||||||
email: string | null;
|
|
||||||
name: string | null;
|
|
||||||
role: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function resolveAccessTokenUserData(
|
|
||||||
userId: string,
|
|
||||||
orgId: string
|
|
||||||
): Promise<AccessTokenUserData | undefined> {
|
|
||||||
const [user] = await db
|
|
||||||
.select()
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.userId, userId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userOrgRoles = await getUserOrgRoles(user.userId, orgId);
|
|
||||||
return {
|
|
||||||
userId: user.userId,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
role: userOrgRoles.map((r) => r.roleName).join(", ") || null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certificates by domains query validation
|
// Certificates by domains query validation
|
||||||
const getCertificatesByDomainsQuerySchema = z.strictObject({
|
const getCertificatesByDomainsQuerySchema = z.strictObject({
|
||||||
// Accept domains as string or array (domains or domains[])
|
// Accept domains as string or array (domains or domains[])
|
||||||
@@ -1902,23 +1829,8 @@ hybridRouter.post(
|
|||||||
resourceId
|
resourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
let userData: AccessTokenUserData | undefined;
|
|
||||||
if (
|
|
||||||
result.valid &&
|
|
||||||
result.tokenItem?.userId &&
|
|
||||||
result.tokenItem.orgId
|
|
||||||
) {
|
|
||||||
userData = await resolveAccessTokenUserData(
|
|
||||||
result.tokenItem.userId,
|
|
||||||
result.tokenItem.orgId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
data: {
|
data: result,
|
||||||
...result,
|
|
||||||
userData
|
|
||||||
},
|
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: result.valid
|
message: result.valid
|
||||||
@@ -1938,233 +1850,6 @@ hybridRouter.post(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a resource session from a valid access token (for remote nodes)
|
|
||||||
hybridRouter.post(
|
|
||||||
"/resource/:resourceId/session/create-access-token",
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const parsedParams = createAccessTokenSessionParamsSchema.safeParse(
|
|
||||||
req.params
|
|
||||||
);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedBody = createAccessTokenSessionBodySchema.safeParse(
|
|
||||||
req.body
|
|
||||||
);
|
|
||||||
if (!parsedBody.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedBody.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { resourceId } = parsedParams.data;
|
|
||||||
const { accessTokenId } = parsedBody.data;
|
|
||||||
|
|
||||||
const [tokenItem] = await db
|
|
||||||
.select()
|
|
||||||
.from(resourceAccessToken)
|
|
||||||
.where(eq(resourceAccessToken.accessTokenId, accessTokenId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!tokenItem || tokenItem.resourceId !== resourceId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
"Access token not found"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tokenItem.persistSession) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"Access token does not allow session persistence"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [resource] = await db
|
|
||||||
.select()
|
|
||||||
.from(resources)
|
|
||||||
.where(eq(resources.resourceId, resourceId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!resource || !resource.fullDomain) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = generateSessionToken();
|
|
||||||
const sess = await createResourceSession({
|
|
||||||
resourceId: resource.resourceId,
|
|
||||||
token,
|
|
||||||
accessTokenId: tokenItem.accessTokenId,
|
|
||||||
sessionLength: tokenItem.sessionLength,
|
|
||||||
expiresAt: tokenItem.expiresAt,
|
|
||||||
doNotExtend: tokenItem.expiresAt ? true : false
|
|
||||||
});
|
|
||||||
|
|
||||||
const cookieName = config.getRawConfig().server.session_cookie_name;
|
|
||||||
const cookie = serializeResourceSessionCookie(
|
|
||||||
cookieName,
|
|
||||||
resource.fullDomain,
|
|
||||||
token,
|
|
||||||
!resource.ssl,
|
|
||||||
new Date(sess.expiresAt)
|
|
||||||
);
|
|
||||||
|
|
||||||
return response(res, {
|
|
||||||
data: { cookie },
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Access token session created successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"Failed to create access token session"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Resolve access token metadata for remote nodes (cookie session path)
|
|
||||||
hybridRouter.get(
|
|
||||||
"/resource/access-token/:accessTokenId",
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const parsedParams = getAccessTokenParamsSchema.safeParse(
|
|
||||||
req.params
|
|
||||||
);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { accessTokenId } = parsedParams.data;
|
|
||||||
|
|
||||||
const [tokenItem] = await db
|
|
||||||
.select()
|
|
||||||
.from(resourceAccessToken)
|
|
||||||
.where(eq(resourceAccessToken.accessTokenId, accessTokenId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!tokenItem) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
"Access token not found"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let userData: AccessTokenUserData | undefined;
|
|
||||||
if (tokenItem.userId) {
|
|
||||||
userData = await resolveAccessTokenUserData(
|
|
||||||
tokenItem.userId,
|
|
||||||
tokenItem.orgId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response(res, {
|
|
||||||
data: { tokenItem, userData },
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Access token retrieved successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"Failed to get access token"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Access audit log from remote nodes
|
|
||||||
hybridRouter.post(
|
|
||||||
"/logs/access",
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const parsedBody = logAccessAuditBodySchema.safeParse(req.body);
|
|
||||||
if (!parsedBody.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedBody.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const remoteExitNode = req.remoteExitNode;
|
|
||||||
if (!remoteExitNode || !remoteExitNode.exitNodeId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"Remote exit node not found"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
await checkExitNodeOrg(
|
|
||||||
remoteExitNode.exitNodeId,
|
|
||||||
parsedBody.data.orgId
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.FORBIDDEN,
|
|
||||||
"Exit node not allowed for this organization"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await logAccessAudit(parsedBody.data);
|
|
||||||
|
|
||||||
return response(res, {
|
|
||||||
data: null,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Access audit log saved successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"Failed to save access audit log"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const geoIpLookupParamsSchema = z.object({
|
const geoIpLookupParamsSchema = z.object({
|
||||||
ip: z.union([z.ipv4(), z.ipv6()])
|
ip: z.union([z.ipv4(), z.ipv6()])
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import * as org from "#private/routers/org";
|
|||||||
import * as logs from "#private/routers/auditLogs";
|
import * as logs from "#private/routers/auditLogs";
|
||||||
import * as alertEvents from "#private/routers/alertEvents";
|
import * as alertEvents from "#private/routers/alertEvents";
|
||||||
import * as certificates from "#private/routers/certificates";
|
import * as certificates from "#private/routers/certificates";
|
||||||
import * as siteProvisioning from "#private/routers/siteProvisioning";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
verifyApiKeyHasAction,
|
verifyApiKeyHasAction,
|
||||||
@@ -25,7 +24,6 @@ import {
|
|||||||
verifyApiKeyIdpAccess,
|
verifyApiKeyIdpAccess,
|
||||||
verifyApiKeyRoleAccess,
|
verifyApiKeyRoleAccess,
|
||||||
verifyApiKeyUserAccess,
|
verifyApiKeyUserAccess,
|
||||||
verifyApiKeySiteProvisioningKeyAccess,
|
|
||||||
verifyLimits
|
verifyLimits
|
||||||
} from "@server/middlewares";
|
} from "@server/middlewares";
|
||||||
import * as user from "#private/routers/user";
|
import * as user from "#private/routers/user";
|
||||||
@@ -217,45 +215,3 @@ authenticated.delete(
|
|||||||
logActionAudit(ActionsEnum.removeUserRole),
|
logActionAudit(ActionsEnum.removeUserRole),
|
||||||
user.removeUserRole
|
user.removeUserRole
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.put(
|
|
||||||
"/org/:orgId/site-provisioning-key",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyValidSubscription(tierMatrix.siteProvisioningKeys),
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyLimits,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.createSiteProvisioningKey),
|
|
||||||
logActionAudit(ActionsEnum.createSiteProvisioningKey),
|
|
||||||
siteProvisioning.createSiteProvisioningKey
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.get(
|
|
||||||
"/org/:orgId/site-provisioning-keys",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyValidSubscription(tierMatrix.siteProvisioningKeys),
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.listSiteProvisioningKeys),
|
|
||||||
siteProvisioning.listSiteProvisioningKeys
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.delete(
|
|
||||||
"/org/:orgId/site-provisioning-key/:siteProvisioningKeyId",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyValidSubscription(tierMatrix.siteProvisioningKeys),
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyApiKeySiteProvisioningKeyAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.deleteSiteProvisioningKey),
|
|
||||||
logActionAudit(ActionsEnum.deleteSiteProvisioningKey),
|
|
||||||
siteProvisioning.deleteSiteProvisioningKey
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.patch(
|
|
||||||
"/org/:orgId/site-provisioning-key/:siteProvisioningKeyId",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyValidSubscription(tierMatrix.siteProvisioningKeys),
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyApiKeySiteProvisioningKeyAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.updateSiteProvisioningKey),
|
|
||||||
logActionAudit(ActionsEnum.updateSiteProvisioningKey),
|
|
||||||
siteProvisioning.updateSiteProvisioningKey
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ import {
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { hashPassword } from "@server/auth/password";
|
import { hashPassword } from "@server/auth/password";
|
||||||
import type { CreateSiteProvisioningKeyResponse } from "@server/routers/siteProvisioning/types";
|
import type { CreateSiteProvisioningKeyResponse } from "@server/routers/siteProvisioning/types";
|
||||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
|
||||||
|
|
||||||
const paramsSchema = z.object({
|
const paramsSchema = z.object({
|
||||||
orgId: z.string().nonempty()
|
orgId: z.string().nonempty()
|
||||||
@@ -36,17 +34,10 @@ const paramsSchema = z.object({
|
|||||||
const bodySchema = z
|
const bodySchema = z
|
||||||
.strictObject({
|
.strictObject({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
maxBatchSize: z
|
maxBatchSize: z.union([
|
||||||
.union([
|
|
||||||
z.null(),
|
z.null(),
|
||||||
z.coerce.number().int().positive().max(1_000_000)
|
z.coerce.number().int().positive().max(1_000_000)
|
||||||
])
|
]),
|
||||||
.openapi({
|
|
||||||
type: "number",
|
|
||||||
default: null,
|
|
||||||
description:
|
|
||||||
"Maximum number of sites that can be provisioned in a single batch. If null, there is no limit."
|
|
||||||
}),
|
|
||||||
validUntil: z.string().max(255).optional(),
|
validUntil: z.string().max(255).optional(),
|
||||||
approveNewSites: z.boolean().optional().default(true)
|
approveNewSites: z.boolean().optional().default(true)
|
||||||
})
|
})
|
||||||
@@ -66,49 +57,6 @@ const bodySchema = z
|
|||||||
|
|
||||||
export type CreateSiteProvisioningKeyBody = z.infer<typeof bodySchema>;
|
export type CreateSiteProvisioningKeyBody = z.infer<typeof bodySchema>;
|
||||||
|
|
||||||
const CreateSiteProvisioningKeyResponseDataSchema = z.object({
|
|
||||||
siteProvisioningKeyId: z.string(),
|
|
||||||
orgId: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
siteProvisioningKey: z.string(),
|
|
||||||
lastChars: z.string(),
|
|
||||||
createdAt: z.string(),
|
|
||||||
lastUsed: z.string().nullable(),
|
|
||||||
maxBatchSize: z.number().nullable(),
|
|
||||||
numUsed: z.number(),
|
|
||||||
validUntil: z.string().nullable(),
|
|
||||||
approveNewSites: z.boolean()
|
|
||||||
});
|
|
||||||
|
|
||||||
registry.registerPath({
|
|
||||||
method: "put",
|
|
||||||
path: "/org/{orgId}/site-provisioning-key",
|
|
||||||
description: "Create a new site provisioning key for the organization.",
|
|
||||||
tags: [OpenAPITags.SiteProvisioningKey],
|
|
||||||
request: {
|
|
||||||
params: paramsSchema,
|
|
||||||
body: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: bodySchema
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
201: {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: createApiResponseSchema(
|
|
||||||
CreateSiteProvisioningKeyResponseDataSchema
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function createSiteProvisioningKey(
|
export async function createSiteProvisioningKey(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
|
|||||||
@@ -13,46 +13,23 @@
|
|||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, siteProvisioningKeyOrg, siteProvisioningKeys } from "@server/db";
|
import {
|
||||||
|
db,
|
||||||
|
siteProvisioningKeyOrg,
|
||||||
|
siteProvisioningKeys
|
||||||
|
} from "@server/db";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
|
||||||
|
|
||||||
const paramsSchema = z.object({
|
const paramsSchema = z.object({
|
||||||
siteProvisioningKeyId: z.string().nonempty(),
|
siteProvisioningKeyId: z.string().nonempty(),
|
||||||
orgId: z.string().nonempty()
|
orgId: z.string().nonempty()
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerPath({
|
|
||||||
method: "delete",
|
|
||||||
path: "/org/{orgId}/site-provisioning-key/{siteProvisioningKeyId}",
|
|
||||||
description: "Delete a site provisioning key.",
|
|
||||||
tags: [OpenAPITags.SiteProvisioningKey],
|
|
||||||
request: {
|
|
||||||
params: paramsSchema
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: z.object({
|
|
||||||
data: z.record(z.string(), z.any()).nullable(),
|
|
||||||
success: z.boolean(),
|
|
||||||
error: z.boolean(),
|
|
||||||
message: z.string(),
|
|
||||||
status: z.number()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function deleteSiteProvisioningKey(
|
export async function deleteSiteProvisioningKey(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
* This file is not licensed under the AGPLv3.
|
* This file is not licensed under the AGPLv3.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { db, siteProvisioningKeyOrg, siteProvisioningKeys } from "@server/db";
|
import {
|
||||||
|
db,
|
||||||
|
siteProvisioningKeyOrg,
|
||||||
|
siteProvisioningKeys
|
||||||
|
} from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
@@ -21,8 +25,6 @@ import { z } from "zod";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import type { ListSiteProvisioningKeysResponse } from "@server/routers/siteProvisioning/types";
|
import type { ListSiteProvisioningKeysResponse } from "@server/routers/siteProvisioning/types";
|
||||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
|
||||||
|
|
||||||
const paramsSchema = z.object({
|
const paramsSchema = z.object({
|
||||||
orgId: z.string().nonempty()
|
orgId: z.string().nonempty()
|
||||||
@@ -43,55 +45,11 @@ const querySchema = z.object({
|
|||||||
.pipe(z.int().nonnegative())
|
.pipe(z.int().nonnegative())
|
||||||
});
|
});
|
||||||
|
|
||||||
const ListSiteProvisioningKeysResponseDataSchema = z.object({
|
|
||||||
siteProvisioningKeys: z.array(
|
|
||||||
z.object({
|
|
||||||
siteProvisioningKeyId: z.string(),
|
|
||||||
orgId: z.string(),
|
|
||||||
lastChars: z.string(),
|
|
||||||
createdAt: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
lastUsed: z.string().nullable(),
|
|
||||||
maxBatchSize: z.number().nullable(),
|
|
||||||
numUsed: z.number(),
|
|
||||||
validUntil: z.string().nullable(),
|
|
||||||
approveNewSites: z.boolean()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
pagination: z.object({
|
|
||||||
total: z.number(),
|
|
||||||
limit: z.number(),
|
|
||||||
offset: z.number()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
registry.registerPath({
|
|
||||||
method: "get",
|
|
||||||
path: "/org/{orgId}/site-provisioning-keys",
|
|
||||||
description: "List all site provisioning keys for an organization.",
|
|
||||||
tags: [OpenAPITags.SiteProvisioningKey],
|
|
||||||
request: {
|
|
||||||
params: paramsSchema,
|
|
||||||
query: querySchema
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: createApiResponseSchema(
|
|
||||||
ListSiteProvisioningKeysResponseDataSchema
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function querySiteProvisioningKeys(orgId: string) {
|
function querySiteProvisioningKeys(orgId: string) {
|
||||||
return db
|
return db
|
||||||
.select({
|
.select({
|
||||||
siteProvisioningKeyId: siteProvisioningKeys.siteProvisioningKeyId,
|
siteProvisioningKeyId:
|
||||||
|
siteProvisioningKeys.siteProvisioningKeyId,
|
||||||
orgId: siteProvisioningKeyOrg.orgId,
|
orgId: siteProvisioningKeyOrg.orgId,
|
||||||
lastChars: siteProvisioningKeys.lastChars,
|
lastChars: siteProvisioningKeys.lastChars,
|
||||||
createdAt: siteProvisioningKeys.createdAt,
|
createdAt: siteProvisioningKeys.createdAt,
|
||||||
|
|||||||
@@ -13,7 +13,11 @@
|
|||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, siteProvisioningKeyOrg, siteProvisioningKeys } from "@server/db";
|
import {
|
||||||
|
db,
|
||||||
|
siteProvisioningKeyOrg,
|
||||||
|
siteProvisioningKeys
|
||||||
|
} from "@server/db";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -21,8 +25,6 @@ import createHttpError from "http-errors";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import type { UpdateSiteProvisioningKeyResponse } from "@server/routers/siteProvisioning/types";
|
import type { UpdateSiteProvisioningKeyResponse } from "@server/routers/siteProvisioning/types";
|
||||||
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
|
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
|
||||||
|
|
||||||
const paramsSchema = z.object({
|
const paramsSchema = z.object({
|
||||||
siteProvisioningKeyId: z.string().nonempty(),
|
siteProvisioningKeyId: z.string().nonempty(),
|
||||||
@@ -36,13 +38,7 @@ const bodySchema = z
|
|||||||
z.null(),
|
z.null(),
|
||||||
z.coerce.number().int().positive().max(1_000_000)
|
z.coerce.number().int().positive().max(1_000_000)
|
||||||
])
|
])
|
||||||
.optional()
|
.optional(),
|
||||||
.openapi({
|
|
||||||
type: "number",
|
|
||||||
default: null,
|
|
||||||
description:
|
|
||||||
"Maximum number of sites that can be provisioned in a single batch. If null, there is no limit."
|
|
||||||
}),
|
|
||||||
validUntil: z.string().max(255).optional(),
|
validUntil: z.string().max(255).optional(),
|
||||||
approveNewSites: z.boolean().optional()
|
approveNewSites: z.boolean().optional()
|
||||||
})
|
})
|
||||||
@@ -54,8 +50,7 @@ const bodySchema = z
|
|||||||
) {
|
) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: "custom",
|
code: "custom",
|
||||||
message:
|
message: "Provide maxBatchSize and/or validUntil and/or approveNewSites",
|
||||||
"Provide maxBatchSize and/or validUntil and/or approveNewSites",
|
|
||||||
path: ["maxBatchSize"]
|
path: ["maxBatchSize"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -74,48 +69,6 @@ const bodySchema = z
|
|||||||
|
|
||||||
export type UpdateSiteProvisioningKeyBody = z.infer<typeof bodySchema>;
|
export type UpdateSiteProvisioningKeyBody = z.infer<typeof bodySchema>;
|
||||||
|
|
||||||
const UpdateSiteProvisioningKeyResponseDataSchema = z.object({
|
|
||||||
siteProvisioningKeyId: z.string(),
|
|
||||||
orgId: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
lastChars: z.string(),
|
|
||||||
createdAt: z.string(),
|
|
||||||
lastUsed: z.string().nullable(),
|
|
||||||
maxBatchSize: z.number().nullable(),
|
|
||||||
numUsed: z.number(),
|
|
||||||
validUntil: z.string().nullable(),
|
|
||||||
approveNewSites: z.boolean()
|
|
||||||
});
|
|
||||||
|
|
||||||
registry.registerPath({
|
|
||||||
method: "patch",
|
|
||||||
path: "/org/{orgId}/site-provisioning-key/{siteProvisioningKeyId}",
|
|
||||||
description: "Update a site provisioning key.",
|
|
||||||
tags: [OpenAPITags.SiteProvisioningKey],
|
|
||||||
request: {
|
|
||||||
params: paramsSchema,
|
|
||||||
body: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: bodySchema
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: createApiResponseSchema(
|
|
||||||
UpdateSiteProvisioningKeyResponseDataSchema
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function updateSiteProvisioningKey(
|
export async function updateSiteProvisioningKey(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { hash } from "@node-rs/argon2";
|
||||||
import {
|
import {
|
||||||
generateId,
|
generateId,
|
||||||
generateIdFromEntropySize,
|
generateIdFromEntropySize,
|
||||||
@@ -7,18 +8,18 @@ import { db } from "@server/db";
|
|||||||
import {
|
import {
|
||||||
ResourceAccessToken,
|
ResourceAccessToken,
|
||||||
resourceAccessToken,
|
resourceAccessToken,
|
||||||
resources,
|
resources
|
||||||
userOrgs
|
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { createDate, TimeSpan } from "oslo";
|
import { createDate, TimeSpan } from "oslo";
|
||||||
|
import { hashPassword } from "@server/auth/password";
|
||||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||||
import { sha256 } from "@oslojs/crypto/sha2";
|
import { sha256 } from "@oslojs/crypto/sha2";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
@@ -27,9 +28,7 @@ export const generateAccessTokenBodySchema = z.strictObject({
|
|||||||
validForSeconds: z.int().positive().optional(), // seconds
|
validForSeconds: z.int().positive().optional(), // seconds
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
path: z.string().optional(),
|
path: z.string().optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional()
|
||||||
persistSession: z.boolean().optional().default(false),
|
|
||||||
userId: z.string().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const generateAccssTokenParamsSchema = z.strictObject({
|
export const generateAccssTokenParamsSchema = z.strictObject({
|
||||||
@@ -102,14 +101,7 @@ export async function generateAccessToken(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { resourceId } = parsedParams.data;
|
const { resourceId } = parsedParams.data;
|
||||||
const {
|
const { validForSeconds, title, path, description } = parsedBody.data;
|
||||||
validForSeconds,
|
|
||||||
title,
|
|
||||||
path,
|
|
||||||
description,
|
|
||||||
persistSession,
|
|
||||||
userId
|
|
||||||
} = parsedBody.data;
|
|
||||||
|
|
||||||
const [resource] = await db
|
const [resource] = await db
|
||||||
.select()
|
.select()
|
||||||
@@ -120,28 +112,6 @@ export async function generateAccessToken(
|
|||||||
return next(createHttpError(HttpCode.NOT_FOUND, "Resource not found"));
|
return next(createHttpError(HttpCode.NOT_FOUND, "Resource not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
const [membership] = await db
|
|
||||||
.select()
|
|
||||||
.from(userOrgs)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(userOrgs.userId, userId),
|
|
||||||
eq(userOrgs.orgId, resource.orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!membership) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"User is not a member of this organization"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sessionLength = validForSeconds
|
const sessionLength = validForSeconds
|
||||||
? validForSeconds * 1000
|
? validForSeconds * 1000
|
||||||
@@ -163,27 +133,23 @@ export async function generateAccessToken(
|
|||||||
accessTokenId: id,
|
accessTokenId: id,
|
||||||
orgId: resource.orgId,
|
orgId: resource.orgId,
|
||||||
resourceId,
|
resourceId,
|
||||||
userId: userId || null,
|
|
||||||
tokenHash,
|
tokenHash,
|
||||||
expiresAt: expiresAt || null,
|
expiresAt: expiresAt || null,
|
||||||
sessionLength: sessionLength,
|
sessionLength: sessionLength,
|
||||||
title: title || null,
|
title: title || null,
|
||||||
path: path || null,
|
path: path || null,
|
||||||
description: description || null,
|
description: description || null,
|
||||||
persistSession,
|
|
||||||
createdAt: new Date().getTime()
|
createdAt: new Date().getTime()
|
||||||
})
|
})
|
||||||
.returning({
|
.returning({
|
||||||
accessTokenId: resourceAccessToken.accessTokenId,
|
accessTokenId: resourceAccessToken.accessTokenId,
|
||||||
orgId: resourceAccessToken.orgId,
|
orgId: resourceAccessToken.orgId,
|
||||||
resourceId: resourceAccessToken.resourceId,
|
resourceId: resourceAccessToken.resourceId,
|
||||||
userId: resourceAccessToken.userId,
|
|
||||||
expiresAt: resourceAccessToken.expiresAt,
|
expiresAt: resourceAccessToken.expiresAt,
|
||||||
sessionLength: resourceAccessToken.sessionLength,
|
sessionLength: resourceAccessToken.sessionLength,
|
||||||
title: resourceAccessToken.title,
|
title: resourceAccessToken.title,
|
||||||
path: resourceAccessToken.path,
|
path: resourceAccessToken.path,
|
||||||
description: resourceAccessToken.description,
|
description: resourceAccessToken.description,
|
||||||
persistSession: resourceAccessToken.persistSession,
|
|
||||||
createdAt: resourceAccessToken.createdAt
|
createdAt: resourceAccessToken.createdAt
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ import {
|
|||||||
userResources,
|
userResources,
|
||||||
roleResources,
|
roleResources,
|
||||||
resourceAccessToken,
|
resourceAccessToken,
|
||||||
sites,
|
sites
|
||||||
users
|
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { sql, eq, or, inArray, and, count, isNull, gt } from "drizzle-orm";
|
import { sql, eq, or, inArray, and, count, isNull, lt, gt } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import stoi from "@server/lib/stoi";
|
import stoi from "@server/lib/stoi";
|
||||||
import { fromZodError } from "zod-validation-error";
|
import { fromZodError } from "zod-validation-error";
|
||||||
@@ -56,16 +55,11 @@ function queryAccessTokens(
|
|||||||
accessTokenId: resourceAccessToken.accessTokenId,
|
accessTokenId: resourceAccessToken.accessTokenId,
|
||||||
orgId: resourceAccessToken.orgId,
|
orgId: resourceAccessToken.orgId,
|
||||||
resourceId: resourceAccessToken.resourceId,
|
resourceId: resourceAccessToken.resourceId,
|
||||||
userId: resourceAccessToken.userId,
|
|
||||||
userName: users.name,
|
|
||||||
username: users.username,
|
|
||||||
userEmail: users.email,
|
|
||||||
sessionLength: resourceAccessToken.sessionLength,
|
sessionLength: resourceAccessToken.sessionLength,
|
||||||
expiresAt: resourceAccessToken.expiresAt,
|
expiresAt: resourceAccessToken.expiresAt,
|
||||||
tokenHash: resourceAccessToken.tokenHash,
|
tokenHash: resourceAccessToken.tokenHash,
|
||||||
title: resourceAccessToken.title,
|
title: resourceAccessToken.title,
|
||||||
description: resourceAccessToken.description,
|
description: resourceAccessToken.description,
|
||||||
persistSession: resourceAccessToken.persistSession,
|
|
||||||
createdAt: resourceAccessToken.createdAt,
|
createdAt: resourceAccessToken.createdAt,
|
||||||
resourceName: resources.name,
|
resourceName: resources.name,
|
||||||
resourceNiceId: resources.niceId,
|
resourceNiceId: resources.niceId,
|
||||||
@@ -81,7 +75,6 @@ function queryAccessTokens(
|
|||||||
eq(resourceAccessToken.resourceId, resources.resourceId)
|
eq(resourceAccessToken.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.leftJoin(sites, eq(resources.resourceId, sites.siteId))
|
.leftJoin(sites, eq(resources.resourceId, sites.siteId))
|
||||||
.leftJoin(users, eq(resourceAccessToken.userId, users.userId))
|
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(
|
inArray(
|
||||||
@@ -104,7 +97,6 @@ function queryAccessTokens(
|
|||||||
eq(resourceAccessToken.resourceId, resources.resourceId)
|
eq(resourceAccessToken.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.leftJoin(sites, eq(resources.resourceId, sites.siteId))
|
.leftJoin(sites, eq(resources.resourceId, sites.siteId))
|
||||||
.leftJoin(users, eq(resourceAccessToken.userId, users.userId))
|
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(
|
inArray(
|
||||||
|
|||||||
@@ -16,26 +16,18 @@ export async function logout(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const { user, session } = await verifySession(req);
|
const { user, session } = await verifySession(req);
|
||||||
const isSecure = req.protocol === "https";
|
|
||||||
|
|
||||||
// Always clear the session cookie so logout is idempotent, even when
|
|
||||||
// the session is already missing or invalid
|
|
||||||
res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure));
|
|
||||||
|
|
||||||
if (!user || !session) {
|
if (!user || !session) {
|
||||||
if (config.getRawConfig().app.log_failed_attempts) {
|
if (config.getRawConfig().app.log_failed_attempts) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Log out with missing or invalid session. IP: ${req.ip}.`
|
`Log out failed because missing or invalid session. IP: ${req.ip}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return next(
|
||||||
return response<null>(res, {
|
createHttpError(
|
||||||
data: null,
|
HttpCode.BAD_REQUEST,
|
||||||
success: true,
|
"You must be logged in to sign out"
|
||||||
error: false,
|
)
|
||||||
message: "Logged out successfully",
|
);
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -45,6 +37,9 @@ export async function logout(
|
|||||||
logger.error("Failed to invalidate session", error);
|
logger.error("Failed to invalidate session", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSecure = req.protocol === "https";
|
||||||
|
res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure));
|
||||||
|
|
||||||
return response<null>(res, {
|
return response<null>(res, {
|
||||||
data: null,
|
data: null,
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export async function requestPasswordReset(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = `${config.getRawConfig().app.dashboard_url}/auth/reset-password?email=${encodeURIComponent(email)}&token=${token}`;
|
const url = `${config.getRawConfig().app.dashboard_url}/auth/reset-password?email=${email}&token=${token}`;
|
||||||
|
|
||||||
if (!config.getRawConfig().email) {
|
if (!config.getRawConfig().email) {
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { validateResourceSessionToken } from "@server/auth/sessions/resource";
|
||||||
createResourceSession,
|
|
||||||
serializeResourceSessionCookie,
|
|
||||||
validateResourceSessionToken
|
|
||||||
} from "@server/auth/sessions/resource";
|
|
||||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
|
||||||
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
||||||
import {
|
import {
|
||||||
getResourceByDomain,
|
getResourceByDomain,
|
||||||
@@ -18,7 +13,6 @@ import {
|
|||||||
LoginPage,
|
LoginPage,
|
||||||
Org,
|
Org,
|
||||||
Resource,
|
Resource,
|
||||||
ResourceAccessToken,
|
|
||||||
ResourceHeaderAuth,
|
ResourceHeaderAuth,
|
||||||
ResourceHeaderAuthExtendedCompatibility,
|
ResourceHeaderAuthExtendedCompatibility,
|
||||||
ResourcePassword,
|
ResourcePassword,
|
||||||
@@ -27,10 +21,7 @@ import {
|
|||||||
ResourcePolicyPassword,
|
ResourcePolicyPassword,
|
||||||
ResourcePolicyHeaderAuth,
|
ResourcePolicyHeaderAuth,
|
||||||
ResourceRule,
|
ResourceRule,
|
||||||
ResourceSession,
|
ResourceSession
|
||||||
db,
|
|
||||||
resourceAccessToken,
|
|
||||||
users
|
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { isIpInCidr, stripPortFromHost } from "@server/lib/ip";
|
import { isIpInCidr, stripPortFromHost } from "@server/lib/ip";
|
||||||
@@ -50,13 +41,11 @@ import {
|
|||||||
enforceResourceSessionLength
|
enforceResourceSessionLength
|
||||||
} from "#dynamic/lib/checkOrgAccessPolicy";
|
} from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
import { logRequestAudit } from "./logRequestAudit";
|
import { logRequestAudit } from "./logRequestAudit";
|
||||||
import { logAccessAudit } from "#dynamic/lib/logAccessAudit";
|
|
||||||
import { REGIONS } from "@server/db/regions";
|
import { REGIONS } from "@server/db/regions";
|
||||||
import { localCache } from "#dynamic/lib/cache";
|
import { localCache } from "#dynamic/lib/cache";
|
||||||
import { APP_VERSION } from "@server/lib/consts";
|
import { APP_VERSION } from "@server/lib/consts";
|
||||||
import { isSubscribed } from "#dynamic/lib/isSubscribed";
|
import { isSubscribed } from "#dynamic/lib/isSubscribed";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
const verifyResourceSessionSchema = z.object({
|
const verifyResourceSessionSchema = z.object({
|
||||||
sessions: z.record(z.string(), z.string()).optional(),
|
sessions: z.record(z.string(), z.string()).optional(),
|
||||||
@@ -361,15 +350,22 @@ export async function verifyResourceSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (valid && tokenItem) {
|
if (valid && tokenItem) {
|
||||||
return await allowAccessToken(
|
logRequestAudit(
|
||||||
res,
|
{
|
||||||
resource,
|
action: true,
|
||||||
tokenItem,
|
reason: 102, // valid access token
|
||||||
sessions,
|
resourceId: resource.resourceId,
|
||||||
dontStripSession,
|
orgId: resource.orgId,
|
||||||
parsedBody.data,
|
location: ipCC,
|
||||||
ipCC
|
apiKey: {
|
||||||
|
name: tokenItem.title,
|
||||||
|
apiKeyId: tokenItem.accessTokenId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parsedBody.data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return allowed(res, undefined, dontStripSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,15 +401,22 @@ export async function verifyResourceSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (valid && tokenItem) {
|
if (valid && tokenItem) {
|
||||||
return await allowAccessToken(
|
logRequestAudit(
|
||||||
res,
|
{
|
||||||
resource,
|
action: true,
|
||||||
tokenItem,
|
reason: 102, // valid access token
|
||||||
sessions,
|
resourceId: resource.resourceId,
|
||||||
dontStripSession,
|
orgId: resource.orgId,
|
||||||
parsedBody.data,
|
location: ipCC,
|
||||||
ipCC
|
apiKey: {
|
||||||
|
name: tokenItem.title,
|
||||||
|
apiKeyId: tokenItem.accessTokenId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parsedBody.data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return allowed(res, undefined, dontStripSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,37 +666,22 @@ export async function verifyResourceSession(
|
|||||||
"Resource allowed because access token session is valid"
|
"Resource allowed because access token session is valid"
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tokenItem] = await db
|
logRequestAudit(
|
||||||
.select()
|
|
||||||
.from(resourceAccessToken)
|
|
||||||
.where(
|
|
||||||
eq(
|
|
||||||
resourceAccessToken.accessTokenId,
|
|
||||||
resourceSession.accessTokenId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
const userData = tokenItem
|
|
||||||
? await getAccessTokenUserData(
|
|
||||||
tokenItem,
|
|
||||||
resource.orgId
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
logAccessTokenRequestAudit(
|
|
||||||
{
|
{
|
||||||
|
action: true,
|
||||||
|
reason: 102, // valid access token
|
||||||
resourceId: resource.resourceId,
|
resourceId: resource.resourceId,
|
||||||
orgId: resource.orgId,
|
orgId: resource.orgId,
|
||||||
location: ipCC,
|
location: ipCC,
|
||||||
accessTokenId: resourceSession.accessTokenId,
|
apiKey: {
|
||||||
tokenTitle: tokenItem?.title ?? null,
|
name: null,
|
||||||
userData
|
apiKeyId: resourceSession.accessTokenId
|
||||||
|
}
|
||||||
},
|
},
|
||||||
parsedBody.data
|
parsedBody.data
|
||||||
);
|
);
|
||||||
|
|
||||||
return allowed(res, userData, dontStripSession);
|
return allowed(res, undefined, dontStripSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceSession.userSessionId && sso) {
|
if (resourceSession.userSessionId && sso) {
|
||||||
@@ -904,227 +892,6 @@ function allowed(
|
|||||||
return response<VerifyUserResponse>(res, data);
|
return response<VerifyUserResponse>(res, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function allowAccessToken(
|
|
||||||
res: Response,
|
|
||||||
resource: Resource,
|
|
||||||
tokenItem: ResourceAccessToken,
|
|
||||||
sessions: Record<string, string> | undefined,
|
|
||||||
dontStripSession: boolean | undefined,
|
|
||||||
auditBody: VerifyResourceSessionSchema,
|
|
||||||
location?: string
|
|
||||||
) {
|
|
||||||
const userData = await getAccessTokenUserData(tokenItem, resource.orgId);
|
|
||||||
|
|
||||||
logAccessTokenRequestAudit(
|
|
||||||
{
|
|
||||||
resourceId: resource.resourceId,
|
|
||||||
orgId: resource.orgId,
|
|
||||||
location,
|
|
||||||
accessTokenId: tokenItem.accessTokenId,
|
|
||||||
tokenTitle: tokenItem.title,
|
|
||||||
userData
|
|
||||||
},
|
|
||||||
auditBody
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!tokenItem.persistSession) {
|
|
||||||
logAccessTokenAccessAudit(tokenItem, resource, userData, auditBody);
|
|
||||||
return allowed(res, userData, dontStripSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceSessionToken = extractResourceSessionToken(
|
|
||||||
sessions ?? {},
|
|
||||||
resource.ssl
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resourceSessionToken) {
|
|
||||||
const sessionCacheKey = `session:${resourceSessionToken}`;
|
|
||||||
let resourceSession: ResourceSession | null | undefined =
|
|
||||||
localCache.get(sessionCacheKey);
|
|
||||||
|
|
||||||
if (!resourceSession) {
|
|
||||||
const result = await validateResourceSessionToken(
|
|
||||||
resourceSessionToken,
|
|
||||||
resource.resourceId
|
|
||||||
);
|
|
||||||
resourceSession = result?.resourceSession;
|
|
||||||
localCache.set(sessionCacheKey, resourceSession, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
resourceSession &&
|
|
||||||
!resourceSession.isRequestToken &&
|
|
||||||
resourceSession.accessTokenId === tokenItem.accessTokenId
|
|
||||||
) {
|
|
||||||
logger.debug(
|
|
||||||
"Resource allowed because existing access token session is valid"
|
|
||||||
);
|
|
||||||
return allowed(res, userData, dontStripSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logAccessTokenAccessAudit(tokenItem, resource, userData, auditBody);
|
|
||||||
return await createAccessTokenSession(res, resource, tokenItem, userData);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createAccessTokenSession(
|
|
||||||
res: Response,
|
|
||||||
resource: Resource,
|
|
||||||
tokenItem: ResourceAccessToken,
|
|
||||||
userData?: BasicUserData
|
|
||||||
) {
|
|
||||||
const token = generateSessionToken();
|
|
||||||
const sess = await createResourceSession({
|
|
||||||
resourceId: resource.resourceId,
|
|
||||||
token,
|
|
||||||
accessTokenId: tokenItem.accessTokenId,
|
|
||||||
sessionLength: tokenItem.sessionLength,
|
|
||||||
expiresAt: tokenItem.expiresAt,
|
|
||||||
doNotExtend: tokenItem.expiresAt ? true : false
|
|
||||||
});
|
|
||||||
const cookieName = config.getRawConfig().server.session_cookie_name;
|
|
||||||
const cookie = serializeResourceSessionCookie(
|
|
||||||
cookieName,
|
|
||||||
resource.fullDomain!,
|
|
||||||
token,
|
|
||||||
!resource.ssl,
|
|
||||||
new Date(sess.expiresAt)
|
|
||||||
);
|
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
|
||||||
logger.debug("Access token is valid, creating new session");
|
|
||||||
return allowed(res, userData);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAccessTokenUserData(
|
|
||||||
tokenItem: ResourceAccessToken,
|
|
||||||
orgId: string
|
|
||||||
): Promise<BasicUserData | undefined> {
|
|
||||||
if (!tokenItem.userId) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheKey = `accessTokenUser:${tokenItem.userId}:${orgId}`;
|
|
||||||
const cached = localCache.get(cacheKey) as BasicUserData | null | undefined;
|
|
||||||
if (cached !== undefined) {
|
|
||||||
return cached ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [user] = await db
|
|
||||||
.select()
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.userId, tokenItem.userId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
localCache.set(cacheKey, null, 5);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userOrgRoles = await getUserOrgRoles(user.userId, orgId);
|
|
||||||
const userData: BasicUserData = {
|
|
||||||
userId: user.userId,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
role: userOrgRoles.map((r) => r.roleName).join(", ") || null
|
|
||||||
};
|
|
||||||
|
|
||||||
localCache.set(cacheKey, userData, 12);
|
|
||||||
return userData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function logAccessTokenRequestAudit(
|
|
||||||
data: {
|
|
||||||
resourceId: number;
|
|
||||||
orgId: string;
|
|
||||||
location?: string;
|
|
||||||
accessTokenId: string;
|
|
||||||
tokenTitle: string | null;
|
|
||||||
userData?: BasicUserData;
|
|
||||||
},
|
|
||||||
body: VerifyResourceSessionSchema
|
|
||||||
) {
|
|
||||||
if (data.userData) {
|
|
||||||
logRequestAudit(
|
|
||||||
{
|
|
||||||
action: true,
|
|
||||||
reason: 102, // valid access token
|
|
||||||
resourceId: data.resourceId,
|
|
||||||
orgId: data.orgId,
|
|
||||||
location: data.location,
|
|
||||||
user: {
|
|
||||||
username: data.userData.username,
|
|
||||||
userId: data.userData.userId
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
accessTokenId: data.accessTokenId,
|
|
||||||
accessTokenTitle: data.tokenTitle
|
|
||||||
}
|
|
||||||
},
|
|
||||||
body
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logRequestAudit(
|
|
||||||
{
|
|
||||||
action: true,
|
|
||||||
reason: 102, // valid access token
|
|
||||||
resourceId: data.resourceId,
|
|
||||||
orgId: data.orgId,
|
|
||||||
location: data.location,
|
|
||||||
apiKey: {
|
|
||||||
name: data.tokenTitle,
|
|
||||||
apiKeyId: data.accessTokenId
|
|
||||||
}
|
|
||||||
},
|
|
||||||
body
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function logAccessTokenAccessAudit(
|
|
||||||
tokenItem: ResourceAccessToken,
|
|
||||||
resource: Resource,
|
|
||||||
userData: BasicUserData | undefined,
|
|
||||||
body: VerifyResourceSessionSchema
|
|
||||||
) {
|
|
||||||
const userAgent =
|
|
||||||
body.headers?.["user-agent"] || body.headers?.["User-Agent"];
|
|
||||||
|
|
||||||
if (userData) {
|
|
||||||
logAccessAudit({
|
|
||||||
orgId: resource.orgId,
|
|
||||||
resourceId: resource.resourceId,
|
|
||||||
action: true,
|
|
||||||
type: "accessToken",
|
|
||||||
user: {
|
|
||||||
username: userData.username,
|
|
||||||
userId: userData.userId
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
accessTokenId: tokenItem.accessTokenId,
|
|
||||||
accessTokenTitle: tokenItem.title
|
|
||||||
},
|
|
||||||
userAgent,
|
|
||||||
requestIp: body.requestIp
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logAccessAudit({
|
|
||||||
orgId: resource.orgId,
|
|
||||||
resourceId: resource.resourceId,
|
|
||||||
action: true,
|
|
||||||
type: "accessToken",
|
|
||||||
apiKey: {
|
|
||||||
name: tokenItem.title,
|
|
||||||
apiKeyId: tokenItem.accessTokenId
|
|
||||||
},
|
|
||||||
userAgent,
|
|
||||||
requestIp: body.requestIp
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function headerAuthChallenged(
|
async function headerAuthChallenged(
|
||||||
res: Response,
|
res: Response,
|
||||||
redirectPath?: string,
|
redirectPath?: string,
|
||||||
|
|||||||
@@ -248,22 +248,6 @@ authenticated.post(
|
|||||||
site.updateSite
|
site.updateSite
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.post(
|
|
||||||
"/site/:siteId/approve",
|
|
||||||
verifySiteAccess,
|
|
||||||
verifyUserHasAction(ActionsEnum.updateSiteApprovals),
|
|
||||||
logActionAudit(ActionsEnum.updateSiteApprovals),
|
|
||||||
site.approveSite
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.post(
|
|
||||||
"/site/:siteId/reject",
|
|
||||||
verifySiteAccess,
|
|
||||||
verifyUserHasAction(ActionsEnum.updateSiteApprovals),
|
|
||||||
logActionAudit(ActionsEnum.updateSiteApprovals),
|
|
||||||
site.rejectSite
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.delete(
|
authenticated.delete(
|
||||||
"/site/:siteId",
|
"/site/:siteId",
|
||||||
verifySiteAccess,
|
verifySiteAccess,
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ authenticated.post(
|
|||||||
logActionAudit(ActionsEnum.updateSite),
|
logActionAudit(ActionsEnum.updateSite),
|
||||||
site.updateSite
|
site.updateSite
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/org/:orgId/reset-bandwidth",
|
"/org/:orgId/reset-bandwidth",
|
||||||
verifyApiKeyOrgAccess,
|
verifyApiKeyOrgAccess,
|
||||||
@@ -809,6 +808,16 @@ authenticated.post(
|
|||||||
accessToken.generateAccessToken
|
accessToken.generateAccessToken
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.post(
|
||||||
|
`/resource/:resourceId/session-token`,
|
||||||
|
verifyApiKeyResourceAccess,
|
||||||
|
verifyApiKeyUserAccess,
|
||||||
|
verifyLimits,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.createResourceSessionToken),
|
||||||
|
logActionAudit(ActionsEnum.createResourceSessionToken),
|
||||||
|
resource.createResourceSessionToken
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.delete(
|
authenticated.delete(
|
||||||
`/access-token/:accessTokenId`,
|
`/access-token/:accessTokenId`,
|
||||||
verifyApiKeyAccessTokenAccess,
|
verifyApiKeyAccessTokenAccess,
|
||||||
|
|||||||
@@ -157,8 +157,7 @@ async function resolveAccessibleIdsUncached(
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(userResources.userId, userId),
|
eq(userResources.userId, userId),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId)
|
||||||
eq(resources.status, "approved")
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
userRoleIds.length > 0
|
userRoleIds.length > 0
|
||||||
@@ -172,8 +171,7 @@ async function resolveAccessibleIdsUncached(
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(roleResources.roleId, userRoleIds),
|
inArray(roleResources.roleId, userRoleIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId)
|
||||||
eq(resources.status, "approved")
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
: Promise.resolve([]),
|
: Promise.resolve([]),
|
||||||
@@ -185,11 +183,7 @@ async function resolveAccessibleIdsUncached(
|
|||||||
eq(effectiveResourcePolicyId, userPolicies.resourcePolicyId)
|
eq(effectiveResourcePolicyId, userPolicies.resourcePolicyId)
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(eq(userPolicies.userId, userId), eq(resources.orgId, orgId))
|
||||||
eq(userPolicies.userId, userId),
|
|
||||||
eq(resources.orgId, orgId),
|
|
||||||
eq(resources.status, "approved")
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
userRoleIds.length > 0
|
userRoleIds.length > 0
|
||||||
? db
|
? db
|
||||||
@@ -205,48 +199,21 @@ async function resolveAccessibleIdsUncached(
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(rolePolicies.roleId, userRoleIds),
|
inArray(rolePolicies.roleId, userRoleIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId)
|
||||||
eq(resources.status, "approved")
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
: Promise.resolve([]),
|
: Promise.resolve([]),
|
||||||
db
|
db
|
||||||
.select({ siteResourceId: userSiteResources.siteResourceId })
|
.select({ siteResourceId: userSiteResources.siteResourceId })
|
||||||
.from(userSiteResources)
|
.from(userSiteResources)
|
||||||
.innerJoin(
|
.where(eq(userSiteResources.userId, userId)),
|
||||||
siteResources,
|
|
||||||
eq(
|
|
||||||
userSiteResources.siteResourceId,
|
|
||||||
siteResources.siteResourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(userSiteResources.userId, userId),
|
|
||||||
eq(siteResources.orgId, orgId),
|
|
||||||
eq(siteResources.status, "approved")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
userRoleIds.length > 0
|
userRoleIds.length > 0
|
||||||
? db
|
? db
|
||||||
.select({
|
.select({
|
||||||
siteResourceId: roleSiteResources.siteResourceId
|
siteResourceId: roleSiteResources.siteResourceId
|
||||||
})
|
})
|
||||||
.from(roleSiteResources)
|
.from(roleSiteResources)
|
||||||
.innerJoin(
|
.where(inArray(roleSiteResources.roleId, userRoleIds))
|
||||||
siteResources,
|
|
||||||
eq(
|
|
||||||
roleSiteResources.siteResourceId,
|
|
||||||
siteResources.siteResourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
inArray(roleSiteResources.roleId, userRoleIds),
|
|
||||||
eq(siteResources.orgId, orgId),
|
|
||||||
eq(siteResources.status, "approved")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: Promise.resolve([])
|
: Promise.resolve([])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -398,7 +365,6 @@ async function filterPublicResourceIdsByTextSearch(
|
|||||||
inArray(resources.resourceId, resourceIds),
|
inArray(resources.resourceId, resourceIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId),
|
||||||
eq(resources.enabled, true),
|
eq(resources.enabled, true),
|
||||||
eq(resources.status, "approved"),
|
|
||||||
textMatch
|
textMatch
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -436,7 +402,6 @@ async function filterSiteResourceIdsByTextSearch(
|
|||||||
inArray(siteResources.siteResourceId, siteResourceIds),
|
inArray(siteResources.siteResourceId, siteResourceIds),
|
||||||
eq(siteResources.orgId, orgId),
|
eq(siteResources.orgId, orgId),
|
||||||
eq(siteResources.enabled, true),
|
eq(siteResources.enabled, true),
|
||||||
eq(siteResources.status, "approved"),
|
|
||||||
textMatch
|
textMatch
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -538,8 +503,7 @@ async function listSiteGroups(
|
|||||||
const publicConditions = [
|
const publicConditions = [
|
||||||
inArray(resources.resourceId, accessible.resourceIds),
|
inArray(resources.resourceId, accessible.resourceIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId),
|
||||||
eq(resources.enabled, true),
|
eq(resources.enabled, true)
|
||||||
eq(resources.status, "approved")
|
|
||||||
];
|
];
|
||||||
if (searchPublic) {
|
if (searchPublic) {
|
||||||
publicConditions.push(searchPublic);
|
publicConditions.push(searchPublic);
|
||||||
@@ -594,8 +558,7 @@ async function listSiteGroups(
|
|||||||
const siteConditions = [
|
const siteConditions = [
|
||||||
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
||||||
eq(siteResources.orgId, orgId),
|
eq(siteResources.orgId, orgId),
|
||||||
eq(siteResources.enabled, true),
|
eq(siteResources.enabled, true)
|
||||||
eq(siteResources.status, "approved")
|
|
||||||
];
|
];
|
||||||
if (searchSite) {
|
if (searchSite) {
|
||||||
siteConditions.push(searchSite);
|
siteConditions.push(searchSite);
|
||||||
@@ -658,8 +621,7 @@ async function listSiteGroups(
|
|||||||
const noSitePublicConditions = [
|
const noSitePublicConditions = [
|
||||||
inArray(resources.resourceId, accessible.resourceIds),
|
inArray(resources.resourceId, accessible.resourceIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId),
|
||||||
eq(resources.enabled, true),
|
eq(resources.enabled, true)
|
||||||
eq(resources.status, "approved")
|
|
||||||
];
|
];
|
||||||
if (searchPublic) {
|
if (searchPublic) {
|
||||||
noSitePublicConditions.push(searchPublic);
|
noSitePublicConditions.push(searchPublic);
|
||||||
@@ -693,8 +655,7 @@ async function listSiteGroups(
|
|||||||
const noSiteSiteConditions = [
|
const noSiteSiteConditions = [
|
||||||
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
||||||
eq(siteResources.orgId, orgId),
|
eq(siteResources.orgId, orgId),
|
||||||
eq(siteResources.enabled, true),
|
eq(siteResources.enabled, true)
|
||||||
eq(siteResources.status, "approved")
|
|
||||||
];
|
];
|
||||||
if (searchSite) {
|
if (searchSite) {
|
||||||
noSiteSiteConditions.push(searchSite);
|
noSiteSiteConditions.push(searchSite);
|
||||||
@@ -785,8 +746,7 @@ async function listLabelGroups(
|
|||||||
const publicConditions = [
|
const publicConditions = [
|
||||||
inArray(resources.resourceId, accessible.resourceIds),
|
inArray(resources.resourceId, accessible.resourceIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId),
|
||||||
eq(resources.enabled, true),
|
eq(resources.enabled, true)
|
||||||
eq(resources.status, "approved")
|
|
||||||
];
|
];
|
||||||
const searchPublic = buildSearchConditionForPublic(query.query);
|
const searchPublic = buildSearchConditionForPublic(query.query);
|
||||||
if (searchPublic) {
|
if (searchPublic) {
|
||||||
@@ -850,8 +810,7 @@ async function listLabelGroups(
|
|||||||
const siteConditions = [
|
const siteConditions = [
|
||||||
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
||||||
eq(siteResources.orgId, orgId),
|
eq(siteResources.orgId, orgId),
|
||||||
eq(siteResources.enabled, true),
|
eq(siteResources.enabled, true)
|
||||||
eq(siteResources.status, "approved")
|
|
||||||
];
|
];
|
||||||
const searchSite = buildSearchConditionForSiteResource(query.query);
|
const searchSite = buildSearchConditionForSiteResource(query.query);
|
||||||
if (searchSite) {
|
if (searchSite) {
|
||||||
@@ -1038,7 +997,6 @@ async function mapPublicResources(
|
|||||||
inArray(resources.resourceId, resourceIds),
|
inArray(resources.resourceId, resourceIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId),
|
||||||
eq(resources.enabled, true),
|
eq(resources.enabled, true),
|
||||||
eq(resources.status, "approved"),
|
|
||||||
siteIdFilter != null
|
siteIdFilter != null
|
||||||
? eq(sites.siteId, siteIdFilter)
|
? eq(sites.siteId, siteIdFilter)
|
||||||
: undefined
|
: undefined
|
||||||
@@ -1130,7 +1088,6 @@ async function mapSiteResources(
|
|||||||
inArray(siteResources.siteResourceId, siteResourceIds),
|
inArray(siteResources.siteResourceId, siteResourceIds),
|
||||||
eq(siteResources.orgId, orgId),
|
eq(siteResources.orgId, orgId),
|
||||||
eq(siteResources.enabled, true),
|
eq(siteResources.enabled, true),
|
||||||
eq(siteResources.status, "approved"),
|
|
||||||
siteIdFilter != null
|
siteIdFilter != null
|
||||||
? eq(sites.siteId, siteIdFilter)
|
? eq(sites.siteId, siteIdFilter)
|
||||||
: undefined
|
: undefined
|
||||||
@@ -1425,8 +1382,7 @@ async function collectAccessibleSites(
|
|||||||
const publicConditions = [
|
const publicConditions = [
|
||||||
inArray(resources.resourceId, accessible.resourceIds),
|
inArray(resources.resourceId, accessible.resourceIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId),
|
||||||
eq(resources.enabled, true),
|
eq(resources.enabled, true)
|
||||||
eq(resources.status, "approved")
|
|
||||||
];
|
];
|
||||||
if (siteNameSearch) {
|
if (siteNameSearch) {
|
||||||
publicConditions.push(siteNameSearch);
|
publicConditions.push(siteNameSearch);
|
||||||
@@ -1466,8 +1422,7 @@ async function collectAccessibleSites(
|
|||||||
const siteConditions = [
|
const siteConditions = [
|
||||||
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
||||||
eq(siteResources.orgId, orgId),
|
eq(siteResources.orgId, orgId),
|
||||||
eq(siteResources.enabled, true),
|
eq(siteResources.enabled, true)
|
||||||
eq(siteResources.status, "approved")
|
|
||||||
];
|
];
|
||||||
if (siteNameSearch) {
|
if (siteNameSearch) {
|
||||||
siteConditions.push(siteNameSearch);
|
siteConditions.push(siteNameSearch);
|
||||||
@@ -1521,7 +1476,6 @@ async function collectAccessibleLabels(
|
|||||||
inArray(resources.resourceId, accessible.resourceIds),
|
inArray(resources.resourceId, accessible.resourceIds),
|
||||||
eq(resources.orgId, orgId),
|
eq(resources.orgId, orgId),
|
||||||
eq(resources.enabled, true),
|
eq(resources.enabled, true),
|
||||||
eq(resources.status, "approved"),
|
|
||||||
eq(labels.orgId, orgId)
|
eq(labels.orgId, orgId)
|
||||||
];
|
];
|
||||||
if (labelNameSearch) {
|
if (labelNameSearch) {
|
||||||
@@ -1557,7 +1511,6 @@ async function collectAccessibleLabels(
|
|||||||
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
||||||
eq(siteResources.orgId, orgId),
|
eq(siteResources.orgId, orgId),
|
||||||
eq(siteResources.enabled, true),
|
eq(siteResources.enabled, true),
|
||||||
eq(siteResources.status, "approved"),
|
|
||||||
eq(labels.orgId, orgId)
|
eq(labels.orgId, orgId)
|
||||||
];
|
];
|
||||||
if (labelNameSearch) {
|
if (labelNameSearch) {
|
||||||
|
|||||||
@@ -148,12 +148,7 @@ export async function buildClientConfigurationForNewtClient(
|
|||||||
.from(siteResources)
|
.from(siteResources)
|
||||||
.innerJoin(networks, eq(siteResources.networkId, networks.networkId))
|
.innerJoin(networks, eq(siteResources.networkId, networks.networkId))
|
||||||
.innerJoin(siteNetworks, eq(networks.networkId, siteNetworks.networkId))
|
.innerJoin(siteNetworks, eq(networks.networkId, siteNetworks.networkId))
|
||||||
.where(
|
.where(eq(siteNetworks.siteId, siteId))
|
||||||
and(
|
|
||||||
eq(siteNetworks.siteId, siteId),
|
|
||||||
eq(siteResources.enabled, true)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then((rows) => rows.map((r) => r.siteResources));
|
.then((rows) => rows.map((r) => r.siteResources));
|
||||||
|
|
||||||
const targetsToSend: SubnetProxyTargetV2[] = [];
|
const targetsToSend: SubnetProxyTargetV2[] = [];
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { publicKey, port, localEndpoints, chainId } = message.data;
|
const { publicKey, port, chainId } = message.data;
|
||||||
const siteId = newt.siteId;
|
const siteId = newt.siteId;
|
||||||
|
|
||||||
// Get the current site data
|
// Get the current site data
|
||||||
@@ -69,10 +69,7 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => {
|
|||||||
.update(sites)
|
.update(sites)
|
||||||
.set({
|
.set({
|
||||||
publicKey,
|
publicKey,
|
||||||
listenPort: port,
|
listenPort: port
|
||||||
localEndpoints: localEndpoints
|
|
||||||
? JSON.stringify(localEndpoints)
|
|
||||||
: null
|
|
||||||
})
|
})
|
||||||
.where(eq(sites.siteId, siteId))
|
.where(eq(sites.siteId, siteId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
generateRemoteSubnets
|
generateRemoteSubnets
|
||||||
} from "@server/lib/ip";
|
} from "@server/lib/ip";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { eq, inArray } from "drizzle-orm";
|
||||||
import { addPeer, deletePeer } from "../newt/peers";
|
import { addPeer, deletePeer } from "../newt/peers";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
|
|
||||||
@@ -30,7 +30,6 @@ export async function buildSiteConfigurationForOlmClient(
|
|||||||
siteId: number;
|
siteId: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
localEndpoints?: string[];
|
|
||||||
publicKey?: string;
|
publicKey?: string;
|
||||||
serverIP?: string | null;
|
serverIP?: string | null;
|
||||||
serverPort?: number | null;
|
serverPort?: number | null;
|
||||||
@@ -71,13 +70,7 @@ export async function buildSiteConfigurationForOlmClient(
|
|||||||
.innerJoin(networks, eq(siteResources.networkId, networks.networkId))
|
.innerJoin(networks, eq(siteResources.networkId, networks.networkId))
|
||||||
.innerJoin(siteNetworks, eq(networks.networkId, siteNetworks.networkId))
|
.innerJoin(siteNetworks, eq(networks.networkId, siteNetworks.networkId))
|
||||||
.where(
|
.where(
|
||||||
and(
|
eq(clientSiteResourcesAssociationsCache.clientId, client.clientId)
|
||||||
eq(
|
|
||||||
clientSiteResourcesAssociationsCache.clientId,
|
|
||||||
client.clientId
|
|
||||||
),
|
|
||||||
eq(siteResources.enabled, true)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const siteResourcesBySiteId = new Map<number, SiteResource[]>();
|
const siteResourcesBySiteId = new Map<number, SiteResource[]>();
|
||||||
@@ -207,9 +200,6 @@ export async function buildSiteConfigurationForOlmClient(
|
|||||||
name: site.name,
|
name: site.name,
|
||||||
// relayEndpoint: relayEndpoint, // this can be undefined now if not relayed // lets not do this for now because it would conflict with the hole punch testing
|
// relayEndpoint: relayEndpoint, // this can be undefined now if not relayed // lets not do this for now because it would conflict with the hole punch testing
|
||||||
endpoint: site.endpoint,
|
endpoint: site.endpoint,
|
||||||
localEndpoints: site.localEndpoints
|
|
||||||
? JSON.parse(site.localEndpoints)
|
|
||||||
: undefined,
|
|
||||||
publicKey: site.publicKey,
|
publicKey: site.publicKey,
|
||||||
serverIP: site.address,
|
serverIP: site.address,
|
||||||
serverPort: site.listenPort,
|
serverPort: site.listenPort,
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import { db, sites } from "@server/db";
|
|
||||||
import { MessageHandler } from "@server/routers/ws";
|
|
||||||
import { clients, Olm } from "@server/db";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import { updatePeer as newtUpdatePeer } from "../newt/peers";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
|
|
||||||
export const handleOlmLocalMessage: MessageHandler = async (context) => {
|
|
||||||
const { message, client: c, sendToClient } = context;
|
|
||||||
const olm = c as Olm;
|
|
||||||
|
|
||||||
logger.info("Handling local olm message!");
|
|
||||||
|
|
||||||
if (!olm) {
|
|
||||||
logger.warn("Olm not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!olm.clientId) {
|
|
||||||
logger.warn("Olm has no client!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientId = olm.clientId;
|
|
||||||
|
|
||||||
const [client] = await db
|
|
||||||
.select()
|
|
||||||
.from(clients)
|
|
||||||
.where(eq(clients.clientId, clientId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
logger.warn("Client not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we hand endpoints for both the site and the client and the lastHolePunch is not too old
|
|
||||||
if (!client.pubKey) {
|
|
||||||
logger.warn("Client has no endpoint or listen port");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { siteId, chainId } = message.data;
|
|
||||||
|
|
||||||
// Get the site
|
|
||||||
const [site] = await db
|
|
||||||
.select()
|
|
||||||
.from(sites)
|
|
||||||
.where(eq(sites.siteId, siteId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!site || !site.exitNodeId) {
|
|
||||||
logger.warn("Site not found or has no exit node");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the peer on the newt
|
|
||||||
await newtUpdatePeer(siteId, client.pubKey, {
|
|
||||||
endpoint: "" // this removes the endpoint so the newt knows to accept local
|
|
||||||
});
|
|
||||||
|
|
||||||
// Just ack the message, we don't keep sending it
|
|
||||||
return {
|
|
||||||
message: {
|
|
||||||
type: "olm/wg/peer/local",
|
|
||||||
data: {
|
|
||||||
siteId: siteId,
|
|
||||||
chainId
|
|
||||||
}
|
|
||||||
},
|
|
||||||
broadcast: false,
|
|
||||||
excludeSender: false
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -79,9 +79,9 @@ export const handleOlmRelayMessage: MessageHandler = async (context) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// update the peer on the newt
|
// update the peer on the exit node
|
||||||
await newtUpdatePeer(siteId, client.pubKey, {
|
await newtUpdatePeer(siteId, client.pubKey, {
|
||||||
endpoint: "" // this removes the endpoint so the newt knows to relay
|
endpoint: "" // this removes the endpoint so the exit node knows to relay
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,15 +3,24 @@ import {
|
|||||||
db,
|
db,
|
||||||
networks,
|
networks,
|
||||||
siteNetworks,
|
siteNetworks,
|
||||||
siteResources
|
siteResources,
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { MessageHandler } from "@server/routers/ws";
|
import { MessageHandler } from "@server/routers/ws";
|
||||||
import { clients, clientSitesAssociationsCache, Olm, sites } from "@server/db";
|
import {
|
||||||
|
clients,
|
||||||
|
clientSitesAssociationsCache,
|
||||||
|
Olm,
|
||||||
|
sites
|
||||||
|
} from "@server/db";
|
||||||
import { and, eq, inArray, isNotNull, isNull } from "drizzle-orm";
|
import { and, eq, inArray, isNotNull, isNull } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { generateAliasConfig } from "@server/lib/ip";
|
import {
|
||||||
|
generateAliasConfig,
|
||||||
|
} from "@server/lib/ip";
|
||||||
import { generateRemoteSubnets } from "@server/lib/ip";
|
import { generateRemoteSubnets } from "@server/lib/ip";
|
||||||
import { addPeer as newtAddPeer } from "@server/routers/newt/peers";
|
import {
|
||||||
|
addPeer as newtAddPeer,
|
||||||
|
} from "@server/routers/newt/peers";
|
||||||
|
|
||||||
export const handleOlmServerPeerAddMessage: MessageHandler = async (
|
export const handleOlmServerPeerAddMessage: MessageHandler = async (
|
||||||
context
|
context
|
||||||
@@ -126,7 +135,10 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async (
|
|||||||
clientSiteResourcesAssociationsCache.siteResourceId
|
clientSiteResourcesAssociationsCache.siteResourceId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.innerJoin(networks, eq(siteResources.networkId, networks.networkId))
|
.innerJoin(
|
||||||
|
networks,
|
||||||
|
eq(siteResources.networkId, networks.networkId)
|
||||||
|
)
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
siteNetworks,
|
siteNetworks,
|
||||||
and(
|
and(
|
||||||
@@ -135,7 +147,10 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
eq(clientSiteResourcesAssociationsCache.clientId, client.clientId)
|
eq(
|
||||||
|
clientSiteResourcesAssociationsCache.clientId,
|
||||||
|
client.clientId
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return connect message with all site configurations
|
// Return connect message with all site configurations
|
||||||
@@ -146,9 +161,6 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async (
|
|||||||
siteId: site.siteId,
|
siteId: site.siteId,
|
||||||
name: site.name,
|
name: site.name,
|
||||||
endpoint: site.endpoint,
|
endpoint: site.endpoint,
|
||||||
localEndpoints: site.localEndpoints
|
|
||||||
? JSON.parse(site.localEndpoints)
|
|
||||||
: undefined,
|
|
||||||
publicKey: site.publicKey,
|
publicKey: site.publicKey,
|
||||||
serverIP: site.address,
|
serverIP: site.address,
|
||||||
serverPort: site.listenPort,
|
serverPort: site.listenPort,
|
||||||
@@ -158,7 +170,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async (
|
|||||||
aliases: generateAliasConfig(
|
aliases: generateAliasConfig(
|
||||||
allSiteResources.map(({ siteResources }) => siteResources)
|
allSiteResources.map(({ siteResources }) => siteResources)
|
||||||
),
|
),
|
||||||
chainId: chainId
|
chainId: chainId,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
broadcast: false,
|
broadcast: false,
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
import { db, exitNodes, sites } from "@server/db";
|
|
||||||
import { MessageHandler } from "@server/routers/ws";
|
|
||||||
import { clients, clientSitesAssociationsCache, Olm } from "@server/db";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import { updatePeer as newtUpdatePeer } from "../newt/peers";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
|
|
||||||
export const handleOlmUnLocalMessage: MessageHandler = async (context) => {
|
|
||||||
const { message, client: c, sendToClient } = context;
|
|
||||||
const olm = c as Olm;
|
|
||||||
|
|
||||||
logger.info("Handling unlocal olm message!");
|
|
||||||
|
|
||||||
if (!olm) {
|
|
||||||
logger.warn("Olm not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!olm.clientId) {
|
|
||||||
logger.warn("Olm has no client!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientId = olm.clientId;
|
|
||||||
|
|
||||||
const [client] = await db
|
|
||||||
.select()
|
|
||||||
.from(clients)
|
|
||||||
.where(eq(clients.clientId, clientId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
logger.warn("Client not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we hand endpoints for both the site and the client and the lastHolePunch is not too old
|
|
||||||
if (!client.pubKey) {
|
|
||||||
logger.warn("Client has no endpoint or listen port");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { siteId, chainId } = message.data;
|
|
||||||
|
|
||||||
// Get the site
|
|
||||||
const [site] = await db
|
|
||||||
.select()
|
|
||||||
.from(sites)
|
|
||||||
.where(eq(sites.siteId, siteId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!site) {
|
|
||||||
logger.warn("Site not found or has no exit node");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [clientSiteAssociation] = await db
|
|
||||||
.select()
|
|
||||||
.from(clientSitesAssociationsCache)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(clientSitesAssociationsCache.clientId, olm.clientId),
|
|
||||||
eq(clientSitesAssociationsCache.siteId, siteId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!clientSiteAssociation) {
|
|
||||||
logger.warn("Client-Site association not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!clientSiteAssociation.endpoint) {
|
|
||||||
logger.warn("Client-Site association has no endpoint, cannot unrelay");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the peer on the newt
|
|
||||||
await newtUpdatePeer(siteId, client.pubKey, {
|
|
||||||
endpoint: clientSiteAssociation.isRelayed
|
|
||||||
? ""
|
|
||||||
: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the newt
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
message: {
|
|
||||||
type: "olm/wg/peer/unlocal",
|
|
||||||
data: {
|
|
||||||
siteId: siteId,
|
|
||||||
chainId
|
|
||||||
}
|
|
||||||
},
|
|
||||||
broadcast: false,
|
|
||||||
excludeSender: false
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -77,9 +77,9 @@ export const handleOlmUnRelayMessage: MessageHandler = async (context) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the peer on the newt
|
// update the peer on the exit node
|
||||||
await newtUpdatePeer(siteId, client.pubKey, {
|
await newtUpdatePeer(siteId, client.pubKey, {
|
||||||
endpoint: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the newt
|
endpoint: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the exit node
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -13,5 +13,3 @@ export * from "./recoverOlmWithFingerprint";
|
|||||||
export * from "./handleOlmDisconnectingMessage";
|
export * from "./handleOlmDisconnectingMessage";
|
||||||
export * from "./handleOlmServerInitAddPeerHandshake";
|
export * from "./handleOlmServerInitAddPeerHandshake";
|
||||||
export * from "./offlineChecker";
|
export * from "./offlineChecker";
|
||||||
export * from "./handleOlmUnLocalMessage";
|
|
||||||
export * from "./handleOlmLocalMessage";
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export async function addPeer(
|
|||||||
serverPort: number | null;
|
serverPort: number | null;
|
||||||
remoteSubnets: string[] | null; // optional, comma-separated list of subnets that this site can access
|
remoteSubnets: string[] | null; // optional, comma-separated list of subnets that this site can access
|
||||||
aliases: Alias[];
|
aliases: Alias[];
|
||||||
localEndpoints?: string[]; // optional, list of local endpoints for the peer
|
|
||||||
},
|
},
|
||||||
olmId?: string,
|
olmId?: string,
|
||||||
version?: string | null
|
version?: string | null
|
||||||
@@ -45,7 +44,6 @@ export async function addPeer(
|
|||||||
name: peer.name,
|
name: peer.name,
|
||||||
publicKey: peer.publicKey,
|
publicKey: peer.publicKey,
|
||||||
endpoint: peer.endpoint,
|
endpoint: peer.endpoint,
|
||||||
localEndpoints: peer.localEndpoints,
|
|
||||||
relayEndpoint: peer.relayEndpoint,
|
relayEndpoint: peer.relayEndpoint,
|
||||||
serverIP: peer.serverIP,
|
serverIP: peer.serverIP,
|
||||||
serverPort: peer.serverPort,
|
serverPort: peer.serverPort,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { Resource, resources, users } from "@server/db";
|
import { Resource, resources } from "@server/db";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
@@ -156,42 +156,11 @@ export async function authWithAccessToken(
|
|||||||
doNotExtend: true
|
doNotExtend: true
|
||||||
});
|
});
|
||||||
|
|
||||||
let accessAuditUser: { username: string; userId: string } | undefined;
|
|
||||||
if (tokenItem.userId) {
|
|
||||||
const [associatedUser] = await db
|
|
||||||
.select({
|
|
||||||
userId: users.userId,
|
|
||||||
username: users.username
|
|
||||||
})
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.userId, tokenItem.userId))
|
|
||||||
.limit(1);
|
|
||||||
if (associatedUser) {
|
|
||||||
accessAuditUser = {
|
|
||||||
userId: associatedUser.userId,
|
|
||||||
username: associatedUser.username
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logAccessAudit({
|
logAccessAudit({
|
||||||
orgId: resource.orgId,
|
orgId: resource.orgId,
|
||||||
resourceId: resource.resourceId,
|
resourceId: resource.resourceId,
|
||||||
action: true,
|
action: true,
|
||||||
type: "accessToken",
|
type: "accessToken",
|
||||||
apiKey: accessAuditUser
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
name: tokenItem.title,
|
|
||||||
apiKeyId: tokenItem.accessTokenId
|
|
||||||
},
|
|
||||||
user: accessAuditUser,
|
|
||||||
metadata: accessAuditUser
|
|
||||||
? {
|
|
||||||
accessTokenId: tokenItem.accessTokenId,
|
|
||||||
accessTokenTitle: tokenItem.title
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
userAgent: req.headers["user-agent"],
|
userAgent: req.headers["user-agent"],
|
||||||
requestIp: req.ip
|
requestIp: req.ip
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { db } from "@server/db";
|
||||||
|
import { resources, users, userOrgs } from "@server/db";
|
||||||
|
import { eq, and } from "drizzle-orm";
|
||||||
|
import { createResourceSession } from "@server/auth/sessions/resource";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { createSession, generateSessionToken } from "@server/auth/sessions/app";
|
||||||
|
import { response } from "@server/lib/response";
|
||||||
|
|
||||||
|
const createResourceSessionTokenParams = z.strictObject({
|
||||||
|
resourceId: z.coerce.number().int().positive()
|
||||||
|
});
|
||||||
|
|
||||||
|
const createResourceSessionTokenBody = z.strictObject({
|
||||||
|
userId: z.string().nonempty(),
|
||||||
|
idpId: z.coerce.number().int().positive().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CreateResourceSessionTokenResponse = {
|
||||||
|
requestToken: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createResourceSessionToken(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = createResourceSessionTokenParams.safeParse(
|
||||||
|
req.params
|
||||||
|
);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedBody = createResourceSessionTokenBody.safeParse(req.body);
|
||||||
|
if (!parsedBody.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedBody.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { resourceId } = parsedParams.data;
|
||||||
|
const { userId, idpId } = parsedBody.data;
|
||||||
|
|
||||||
|
const [resource] = await db
|
||||||
|
.select()
|
||||||
|
.from(resources)
|
||||||
|
.where(eq(resources.resourceId, resourceId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!resource) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Resource with ID ${resourceId} not found`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = await db
|
||||||
|
.select({ userId: users.userId })
|
||||||
|
.from(userOrgs)
|
||||||
|
.innerJoin(users, eq(userOrgs.userId, users.userId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(users.userId, userId),
|
||||||
|
eq(userOrgs.orgId, resource.orgId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`User not found in the organization that owns this resource`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.length > 1) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Multiple users match this username (external users from different identity providers). Specify idpId to disambiguate."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetUserId = candidates[0].userId;
|
||||||
|
|
||||||
|
const appSessionToken = generateSessionToken();
|
||||||
|
const appSession = await createSession(appSessionToken, targetUserId);
|
||||||
|
|
||||||
|
const requestToken = generateSessionToken();
|
||||||
|
await createResourceSession({
|
||||||
|
resourceId,
|
||||||
|
token: requestToken,
|
||||||
|
userSessionId: appSession.sessionId,
|
||||||
|
isRequestToken: true,
|
||||||
|
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||||
|
sessionLength: 1000 * 30,
|
||||||
|
doNotExtend: true
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("Resource session token created successfully");
|
||||||
|
|
||||||
|
return response<CreateResourceSessionTokenResponse>(res, {
|
||||||
|
data: { requestToken },
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Resource session token created successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,14 +42,9 @@ export async function getResourceStatusHistory(
|
|||||||
|
|
||||||
const entityType = "resource";
|
const entityType = "resource";
|
||||||
const entityId = parsedParams.data.resourceId;
|
const entityId = parsedParams.data.resourceId;
|
||||||
const { days, tzOffsetMinutes } = parsedQuery.data;
|
const { days } = parsedQuery.data;
|
||||||
|
|
||||||
const data = await getCachedStatusHistory(
|
const data = await getCachedStatusHistory(entityType, entityId, days);
|
||||||
entityType,
|
|
||||||
entityId,
|
|
||||||
days,
|
|
||||||
tzOffsetMinutes
|
|
||||||
);
|
|
||||||
|
|
||||||
return response<StatusHistoryResponse>(res, {
|
return response<StatusHistoryResponse>(res, {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export * from "./getResourceWhitelist";
|
|||||||
export * from "./authWithWhitelist";
|
export * from "./authWithWhitelist";
|
||||||
export * from "./authWithAccessToken";
|
export * from "./authWithAccessToken";
|
||||||
export * from "./getExchangeToken";
|
export * from "./getExchangeToken";
|
||||||
|
export * from "./createResourceSessionToken";
|
||||||
export * from "./createResourceRule";
|
export * from "./createResourceRule";
|
||||||
export * from "./deleteResourceRule";
|
export * from "./deleteResourceRule";
|
||||||
export * from "./listResourceRules";
|
export * from "./listResourceRules";
|
||||||
|
|||||||
@@ -138,15 +138,6 @@ const listResourcesSchema = z.strictObject({
|
|||||||
description:
|
description:
|
||||||
"When set, only resources that have at least one target on this site are returned"
|
"When set, only resources that have at least one target on this site are returned"
|
||||||
}),
|
}),
|
||||||
status: z
|
|
||||||
.enum(["pending", "approved"])
|
|
||||||
.optional()
|
|
||||||
.catch(undefined)
|
|
||||||
.openapi({
|
|
||||||
type: "string",
|
|
||||||
enum: ["pending", "approved"],
|
|
||||||
description: "Filter by resource status"
|
|
||||||
}),
|
|
||||||
labels: z
|
labels: z
|
||||||
.preprocess((val) => {
|
.preprocess((val) => {
|
||||||
if (val === undefined || val === null || val === "") {
|
if (val === undefined || val === null || val === "") {
|
||||||
@@ -460,7 +451,6 @@ export async function listResources(
|
|||||||
sort_by,
|
sort_by,
|
||||||
order,
|
order,
|
||||||
siteId,
|
siteId,
|
||||||
status,
|
|
||||||
labels: labelFilter
|
labels: labelFilter
|
||||||
} = parsedQuery.data;
|
} = parsedQuery.data;
|
||||||
|
|
||||||
@@ -670,10 +660,6 @@ export async function listResources(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof status !== "undefined") {
|
|
||||||
conditions.push(eq(resources.status, status));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (siteId != null) {
|
if (siteId != null) {
|
||||||
const resourcesWithSite = db
|
const resourcesWithSite = db
|
||||||
.select({ resourceId: targets.resourceId })
|
.select({ resourceId: targets.resourceId })
|
||||||
|
|||||||
@@ -45,19 +45,18 @@ function userResourceAliasesCacheKey(
|
|||||||
page: number,
|
page: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
includeLabels: boolean,
|
includeLabels: boolean,
|
||||||
labelFilter: string[],
|
labelFilter: string[]
|
||||||
status?: "pending" | "approved"
|
|
||||||
) {
|
) {
|
||||||
const labelsKey =
|
const labelsKey =
|
||||||
labelFilter.length > 0 ? labelFilter.slice().sort().join(",") : "all";
|
labelFilter.length > 0 ? labelFilter.slice().sort().join(",") : "all";
|
||||||
return `userResourceAliases:${orgId}:${userId}:${page}:${pageSize}:${includeLabels ? "labels" : "plain"}:${labelsKey}:${status ?? "all"}`;
|
return `userResourceAliases:${orgId}:${userId}:${page}:${pageSize}:${includeLabels ? "labels" : "plain"}:${labelsKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const listUserResourceAliasesParamsSchema = z.strictObject({
|
const listUserResourceAliasesParamsSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
const listUserResourceAliasesQuerySchema = z.object({
|
const listUserResourceAliasesQuerySchema = z.strictObject({
|
||||||
pageSize: z.coerce
|
pageSize: z.coerce
|
||||||
.number<string>()
|
.number<string>()
|
||||||
.int()
|
.int()
|
||||||
@@ -97,15 +96,6 @@ const listUserResourceAliasesQuerySchema = z.object({
|
|||||||
type: "array",
|
type: "array",
|
||||||
description:
|
description:
|
||||||
"Filter by resource labels. A resource matches when it has any of the given labels (OR)."
|
"Filter by resource labels. A resource matches when it has any of the given labels (OR)."
|
||||||
}),
|
|
||||||
status: z
|
|
||||||
.enum(["pending", "approved"])
|
|
||||||
.optional()
|
|
||||||
.catch(undefined)
|
|
||||||
.openapi({
|
|
||||||
type: "string",
|
|
||||||
enum: ["pending", "approved"],
|
|
||||||
description: "Filter by site resource status"
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,8 +130,7 @@ export async function listUserResourceAliases(
|
|||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
includeLabels,
|
includeLabels,
|
||||||
labels: labelFilter,
|
labels: labelFilter
|
||||||
status
|
|
||||||
} = parsedQuery.data;
|
} = parsedQuery.data;
|
||||||
|
|
||||||
const parsedParams = listUserResourceAliasesParamsSchema.safeParse(
|
const parsedParams = listUserResourceAliasesParamsSchema.safeParse(
|
||||||
@@ -183,8 +172,7 @@ export async function listUserResourceAliases(
|
|||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
includeLabels,
|
includeLabels,
|
||||||
labelFilter ?? [],
|
labelFilter ?? []
|
||||||
status
|
|
||||||
);
|
);
|
||||||
const cachedData: ListUserResourceAliasesResponse | undefined =
|
const cachedData: ListUserResourceAliasesResponse | undefined =
|
||||||
await cache.get(cacheKey);
|
await cache.get(cacheKey);
|
||||||
@@ -269,10 +257,6 @@ export async function listUserResourceAliases(
|
|||||||
inArray(siteResources.siteResourceId, accessibleSiteResourceIds)
|
inArray(siteResources.siteResourceId, accessibleSiteResourceIds)
|
||||||
];
|
];
|
||||||
|
|
||||||
if (typeof status !== "undefined") {
|
|
||||||
whereConditions.push(eq(siteResources.status, status));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (labelFilter && labelFilter.length > 0) {
|
if (labelFilter && labelFilter.length > 0) {
|
||||||
whereConditions.push(
|
whereConditions.push(
|
||||||
inArray(
|
inArray(
|
||||||
|
|||||||
@@ -1,251 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
|
||||||
db,
|
|
||||||
resources,
|
|
||||||
siteNetworks,
|
|
||||||
siteResources,
|
|
||||||
sites,
|
|
||||||
type Site,
|
|
||||||
type SiteResource
|
|
||||||
} from "@server/db";
|
|
||||||
import { and, eq, inArray } from "drizzle-orm";
|
|
||||||
import response from "@server/lib/response";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import { fromError } from "zod-validation-error";
|
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
|
||||||
import {
|
|
||||||
getResourceIdsForSite,
|
|
||||||
getSiteResourceIdsForSite
|
|
||||||
} from "@server/lib/deleteSiteAssociatedResources";
|
|
||||||
import {
|
|
||||||
handleMessagingForUpdatedSiteResource,
|
|
||||||
rebuildClientAssociationsFromSiteResource,
|
|
||||||
waitForSiteResourceRebuildIdle
|
|
||||||
} from "@server/lib/rebuildClientAssociations";
|
|
||||||
|
|
||||||
const approveSiteParamsSchema = z.strictObject({
|
|
||||||
siteId: z.coerce.number().int().positive()
|
|
||||||
});
|
|
||||||
|
|
||||||
registry.registerPath({
|
|
||||||
method: "post",
|
|
||||||
path: "/site/{siteId}/approve",
|
|
||||||
description:
|
|
||||||
"Approve a pending site and approve (and enable when needed) its associated resources.",
|
|
||||||
tags: [OpenAPITags.Site],
|
|
||||||
request: {
|
|
||||||
params: approveSiteParamsSchema
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: z.object({
|
|
||||||
data: z.record(z.string(), z.any()).nullable(),
|
|
||||||
success: z.boolean(),
|
|
||||||
error: z.boolean(),
|
|
||||||
message: z.string(),
|
|
||||||
status: z.number()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
type SiteResourceEnableSideEffect = {
|
|
||||||
existing: SiteResource;
|
|
||||||
updated: SiteResource;
|
|
||||||
siteIds: number[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function approveSite(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
): Promise<any> {
|
|
||||||
try {
|
|
||||||
const parsedParams = approveSiteParamsSchema.safeParse(req.params);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { siteId } = parsedParams.data;
|
|
||||||
|
|
||||||
const [existingSite] = await db
|
|
||||||
.select()
|
|
||||||
.from(sites)
|
|
||||||
.where(eq(sites.siteId, siteId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!existingSite) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Site with ID ${siteId} not found`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existingSite.orgId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
`Site with ID ${siteId} has no organization`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const orgId = existingSite.orgId;
|
|
||||||
let updatedSite: Site | undefined;
|
|
||||||
const siteResourceEnableSideEffects: SiteResourceEnableSideEffect[] =
|
|
||||||
[];
|
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
|
||||||
[updatedSite] = await trx
|
|
||||||
.update(sites)
|
|
||||||
.set({ status: "approved" })
|
|
||||||
.where(eq(sites.siteId, siteId))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
const resourceIds = await getResourceIdsForSite(siteId, trx);
|
|
||||||
const siteResourceIds = await getSiteResourceIdsForSite(
|
|
||||||
siteId,
|
|
||||||
orgId,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resourceIds.length > 0) {
|
|
||||||
const pendingDisabledResources = await trx
|
|
||||||
.select({ resourceId: resources.resourceId })
|
|
||||||
.from(resources)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
inArray(resources.resourceId, resourceIds),
|
|
||||||
eq(resources.status, "pending"),
|
|
||||||
eq(resources.enabled, false)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
await trx
|
|
||||||
.update(resources)
|
|
||||||
.set({ status: "approved" })
|
|
||||||
.where(inArray(resources.resourceId, resourceIds));
|
|
||||||
|
|
||||||
if (pendingDisabledResources.length > 0) {
|
|
||||||
await trx
|
|
||||||
.update(resources)
|
|
||||||
.set({ enabled: true })
|
|
||||||
.where(
|
|
||||||
inArray(
|
|
||||||
resources.resourceId,
|
|
||||||
pendingDisabledResources.map(
|
|
||||||
(r) => r.resourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (siteResourceIds.length > 0) {
|
|
||||||
const existingSiteResources = await trx
|
|
||||||
.select()
|
|
||||||
.from(siteResources)
|
|
||||||
.where(
|
|
||||||
inArray(siteResources.siteResourceId, siteResourceIds)
|
|
||||||
);
|
|
||||||
|
|
||||||
const pendingDisabledSiteResources =
|
|
||||||
existingSiteResources.filter(
|
|
||||||
(sr) => sr.status === "pending" && !sr.enabled
|
|
||||||
);
|
|
||||||
|
|
||||||
await trx
|
|
||||||
.update(siteResources)
|
|
||||||
.set({ status: "approved" })
|
|
||||||
.where(
|
|
||||||
inArray(siteResources.siteResourceId, siteResourceIds)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pendingDisabledSiteResources.length > 0) {
|
|
||||||
const enableIds = pendingDisabledSiteResources.map(
|
|
||||||
(sr) => sr.siteResourceId
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedEnabledSiteResources = await trx
|
|
||||||
.update(siteResources)
|
|
||||||
.set({ enabled: true })
|
|
||||||
.where(inArray(siteResources.siteResourceId, enableIds))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
for (const updated of updatedEnabledSiteResources) {
|
|
||||||
const existing = pendingDisabledSiteResources.find(
|
|
||||||
(sr) => sr.siteResourceId === updated.siteResourceId
|
|
||||||
);
|
|
||||||
if (!existing || !updated.networkId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const networkSites = await trx
|
|
||||||
.select({ siteId: siteNetworks.siteId })
|
|
||||||
.from(siteNetworks)
|
|
||||||
.where(
|
|
||||||
eq(siteNetworks.networkId, updated.networkId)
|
|
||||||
);
|
|
||||||
|
|
||||||
siteResourceEnableSideEffects.push({
|
|
||||||
existing,
|
|
||||||
updated,
|
|
||||||
siteIds: networkSites.map((s) => s.siteId)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const sideEffect of siteResourceEnableSideEffects) {
|
|
||||||
rebuildClientAssociationsFromSiteResource(sideEffect.updated)
|
|
||||||
.then(() =>
|
|
||||||
waitForSiteResourceRebuildIdle(
|
|
||||||
sideEffect.updated.siteResourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(() =>
|
|
||||||
handleMessagingForUpdatedSiteResource(
|
|
||||||
sideEffect.existing,
|
|
||||||
sideEffect.updated,
|
|
||||||
sideEffect.siteIds,
|
|
||||||
sideEffect.siteIds
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.catch((e) => {
|
|
||||||
logger.error(
|
|
||||||
`Failed to rebuild and handle messaging for site resource ${sideEffect.updated.siteResourceId} after site approval:`,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return response(res, {
|
|
||||||
data: updatedSite ?? null,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Site approved successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -159,39 +159,15 @@ export async function deleteSite(
|
|||||||
siteResources: []
|
siteResources: []
|
||||||
};
|
};
|
||||||
|
|
||||||
if (deleteResources) {
|
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
|
if (deleteResources) {
|
||||||
resourceSideEffects = await deleteAssociatedResourcesForSite(
|
resourceSideEffects = await deleteAssociatedResourcesForSite(
|
||||||
siteId,
|
siteId,
|
||||||
site.orgId,
|
site.orgId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
|
||||||
if (resourceSideEffects.resources.length > 0) {
|
|
||||||
await usageService.add(
|
|
||||||
site.orgId,
|
|
||||||
LimitId.PUBLIC_RESOURCES,
|
|
||||||
-resourceSideEffects.resources.length,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceSideEffects.siteResources.length > 0) {
|
|
||||||
await usageService.add(
|
|
||||||
site.orgId,
|
|
||||||
LimitId.PRIVATE_RESOURCES,
|
|
||||||
-resourceSideEffects.siteResources.length,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await runDeleteSiteAssociatedResourcesSideEffects(
|
|
||||||
resourceSideEffects
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
|
||||||
if (site.type == "wireguard") {
|
if (site.type == "wireguard") {
|
||||||
if (site.pubKey) {
|
if (site.pubKey) {
|
||||||
await deletePeer(site.exitNodeId!, site.pubKey);
|
await deletePeer(site.exitNodeId!, site.pubKey);
|
||||||
@@ -204,6 +180,12 @@ export async function deleteSite(
|
|||||||
await usageService.add(site.orgId, LimitId.SITES, -1, trx);
|
await usageService.add(site.orgId, LimitId.SITES, -1, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (deleteResources) {
|
||||||
|
await runDeleteSiteAssociatedResourcesSideEffects(
|
||||||
|
resourceSideEffects
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (deletedNewt) {
|
if (deletedNewt) {
|
||||||
const payload = {
|
const payload = {
|
||||||
type: `newt/wg/terminate`,
|
type: `newt/wg/terminate`,
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
import {
|
||||||
|
getCachedStatusHistory,
|
||||||
|
statusHistoryQuerySchema,
|
||||||
|
StatusHistoryResponse
|
||||||
|
} from "@server/lib/statusHistory";
|
||||||
|
|
||||||
|
const siteParamsSchema = z.object({
|
||||||
|
siteId: z.string().transform((v) => parseInt(v, 10))
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function getBatchedSiteStatusHistory(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = siteParamsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const parsedQuery = statusHistoryQuerySchema.safeParse(req.query);
|
||||||
|
if (!parsedQuery.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedQuery.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityType = "site";
|
||||||
|
const entityId = parsedParams.data.siteId;
|
||||||
|
const { days } = parsedQuery.data;
|
||||||
|
|
||||||
|
const data = await getCachedStatusHistory(entityType, entityId, days);
|
||||||
|
|
||||||
|
return response<StatusHistoryResponse>(res, {
|
||||||
|
data,
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Status history retrieved successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,14 +42,9 @@ export async function getSiteStatusHistory(
|
|||||||
|
|
||||||
const entityType = "site";
|
const entityType = "site";
|
||||||
const entityId = parsedParams.data.siteId;
|
const entityId = parsedParams.data.siteId;
|
||||||
const { days, tzOffsetMinutes } = parsedQuery.data;
|
const { days } = parsedQuery.data;
|
||||||
|
|
||||||
const data = await getCachedStatusHistory(
|
const data = await getCachedStatusHistory(entityType, entityId, days);
|
||||||
entityType,
|
|
||||||
entityId,
|
|
||||||
days,
|
|
||||||
tzOffsetMinutes
|
|
||||||
);
|
|
||||||
|
|
||||||
return response<StatusHistoryResponse>(res, {
|
return response<StatusHistoryResponse>(res, {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ export * from "./getStatusHistory";
|
|||||||
export * from "./createSite";
|
export * from "./createSite";
|
||||||
export * from "./deleteSite";
|
export * from "./deleteSite";
|
||||||
export * from "./updateSite";
|
export * from "./updateSite";
|
||||||
export * from "./approveSite";
|
|
||||||
export * from "./rejectSite";
|
|
||||||
export * from "./listSites";
|
export * from "./listSites";
|
||||||
export * from "./listSiteRoles";
|
export * from "./listSiteRoles";
|
||||||
export * from "./pickSiteDefaults";
|
export * from "./pickSiteDefaults";
|
||||||
|
|||||||
@@ -1,196 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { db } from "@server/db";
|
|
||||||
import { newts, sites } from "@server/db";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import response from "@server/lib/response";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import { deletePeer } from "../gerbil/peers";
|
|
||||||
import { fromError } from "zod-validation-error";
|
|
||||||
import { sendToClient } from "#dynamic/routers/ws";
|
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
|
||||||
import { cleanupSiteAssociations } from "@server/lib/rebuildClientAssociations";
|
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
|
||||||
import { LimitId } from "@server/lib/billing";
|
|
||||||
import {
|
|
||||||
deletePendingAssociatedResourcesForSite,
|
|
||||||
exceedsSiteAssociatedResourceDeleteLimit,
|
|
||||||
getPendingAssociatedResourceCountForSite,
|
|
||||||
runDeleteSiteAssociatedResourcesSideEffects,
|
|
||||||
MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE,
|
|
||||||
type DeleteSiteAssociatedResourcesSideEffects
|
|
||||||
} from "@server/lib/deleteSiteAssociatedResources";
|
|
||||||
|
|
||||||
const rejectSiteParamsSchema = z.strictObject({
|
|
||||||
siteId: z.coerce.number().int().positive()
|
|
||||||
});
|
|
||||||
|
|
||||||
registry.registerPath({
|
|
||||||
method: "post",
|
|
||||||
path: "/site/{siteId}/reject",
|
|
||||||
description:
|
|
||||||
"Reject a pending site by deleting it and any associated resources that are still pending.",
|
|
||||||
tags: [OpenAPITags.Site],
|
|
||||||
request: {
|
|
||||||
params: rejectSiteParamsSchema
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: z.object({
|
|
||||||
data: z.record(z.string(), z.any()).nullable(),
|
|
||||||
success: z.boolean(),
|
|
||||||
error: z.boolean(),
|
|
||||||
message: z.string(),
|
|
||||||
status: z.number()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function rejectSite(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
): Promise<any> {
|
|
||||||
try {
|
|
||||||
const parsedParams = rejectSiteParamsSchema.safeParse(req.params);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { siteId } = parsedParams.data;
|
|
||||||
|
|
||||||
const [site] = await db
|
|
||||||
.select()
|
|
||||||
.from(sites)
|
|
||||||
.where(eq(sites.siteId, siteId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!site) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Site with ID ${siteId} not found`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!site.orgId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
`Site with ID ${siteId} has no organization`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendingAssociatedResourceCount =
|
|
||||||
await getPendingAssociatedResourceCountForSite(siteId, site.orgId);
|
|
||||||
|
|
||||||
if (
|
|
||||||
exceedsSiteAssociatedResourceDeleteLimit(
|
|
||||||
pendingAssociatedResourceCount
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
`Cannot reject site and associated pending resources when the site has more than ${MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE} pending resources`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [deletedNewt] = await db
|
|
||||||
.select()
|
|
||||||
.from(newts)
|
|
||||||
.where(eq(newts.siteId, siteId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
let resourceSideEffects: DeleteSiteAssociatedResourcesSideEffects = {
|
|
||||||
resources: [],
|
|
||||||
siteResources: []
|
|
||||||
};
|
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
|
||||||
resourceSideEffects = await deletePendingAssociatedResourcesForSite(
|
|
||||||
siteId,
|
|
||||||
site.orgId,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resourceSideEffects.resources.length > 0) {
|
|
||||||
await usageService.add(
|
|
||||||
site.orgId,
|
|
||||||
LimitId.PUBLIC_RESOURCES,
|
|
||||||
-resourceSideEffects.resources.length,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourceSideEffects.siteResources.length > 0) {
|
|
||||||
await usageService.add(
|
|
||||||
site.orgId,
|
|
||||||
LimitId.PRIVATE_RESOURCES,
|
|
||||||
-resourceSideEffects.siteResources.length,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await runDeleteSiteAssociatedResourcesSideEffects(resourceSideEffects);
|
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
|
||||||
if (site.type == "wireguard") {
|
|
||||||
if (site.pubKey) {
|
|
||||||
await deletePeer(site.exitNodeId!, site.pubKey);
|
|
||||||
}
|
|
||||||
} else if (site.type == "newt") {
|
|
||||||
await cleanupSiteAssociations(site, trx);
|
|
||||||
}
|
|
||||||
|
|
||||||
await trx.delete(sites).where(eq(sites.siteId, siteId));
|
|
||||||
await usageService.add(site.orgId, LimitId.SITES, -1, trx);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (deletedNewt) {
|
|
||||||
const payload = {
|
|
||||||
type: `newt/wg/terminate`,
|
|
||||||
data: {}
|
|
||||||
};
|
|
||||||
sendToClient(deletedNewt.newtId, payload).catch((error) => {
|
|
||||||
logger.error(
|
|
||||||
"Failed to send termination message to newt:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return response(res, {
|
|
||||||
data: null,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Site rejected successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
if (createHttpError.isHttpError(error)) {
|
|
||||||
return next(error);
|
|
||||||
}
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,6 +21,7 @@ const updateSiteBodySchema = z
|
|||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
niceId: z.string().min(1).max(255).optional(),
|
niceId: z.string().min(1).max(255).optional(),
|
||||||
dockerSocketEnabled: z.boolean().optional(),
|
dockerSocketEnabled: z.boolean().optional(),
|
||||||
|
status: z.enum(["pending", "approved"]).optional(),
|
||||||
autoUpdateEnabled: z.boolean().optional(),
|
autoUpdateEnabled: z.boolean().optional(),
|
||||||
autoUpdateOverrideOrg: z.boolean().optional()
|
autoUpdateOverrideOrg: z.boolean().optional()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const createSiteResourceSchema = z
|
|||||||
siteId: z.number().int().positive().optional(), // DEPRECATED: for backward compatibility, we will convert this to siteIds array if provided
|
siteId: z.number().int().positive().optional(), // DEPRECATED: for backward compatibility, we will convert this to siteIds array if provided
|
||||||
destinationPort: z.int().positive().optional(),
|
destinationPort: z.int().positive().optional(),
|
||||||
destination: z.string().min(1).nullish(),
|
destination: z.string().min(1).nullish(),
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
alias: z
|
alias: z
|
||||||
.string()
|
.string()
|
||||||
.regex(
|
.regex(
|
||||||
@@ -274,6 +275,7 @@ export async function createSiteResource(
|
|||||||
scheme,
|
scheme,
|
||||||
destinationPort,
|
destinationPort,
|
||||||
destination,
|
destination,
|
||||||
|
enabled,
|
||||||
ssl,
|
ssl,
|
||||||
alias,
|
alias,
|
||||||
userIds,
|
userIds,
|
||||||
@@ -537,6 +539,7 @@ export async function createSiteResource(
|
|||||||
destination: destination, // the ssh can be null
|
destination: destination, // the ssh can be null
|
||||||
scheme,
|
scheme,
|
||||||
destinationPort,
|
destinationPort,
|
||||||
|
enabled,
|
||||||
alias: alias ? alias.trim() : null,
|
alias: alias ? alias.trim() : null,
|
||||||
aliasAddress,
|
aliasAddress,
|
||||||
tcpPortRangeString: tcpPortRangeStringAdjusted,
|
tcpPortRangeString: tcpPortRangeStringAdjusted,
|
||||||
|
|||||||
@@ -86,15 +86,6 @@ const listAllSiteResourcesByOrgQuerySchema = z.strictObject({
|
|||||||
description:
|
description:
|
||||||
"When set, only site resources associated with this site (via network) are returned"
|
"When set, only site resources associated with this site (via network) are returned"
|
||||||
}),
|
}),
|
||||||
status: z
|
|
||||||
.enum(["pending", "approved"])
|
|
||||||
.optional()
|
|
||||||
.catch(undefined)
|
|
||||||
.openapi({
|
|
||||||
type: "string",
|
|
||||||
enum: ["pending", "approved"],
|
|
||||||
description: "Filter by site resource status"
|
|
||||||
}),
|
|
||||||
labels: z
|
labels: z
|
||||||
.preprocess((val) => {
|
.preprocess((val) => {
|
||||||
if (val === undefined || val === null || val === "") {
|
if (val === undefined || val === null || val === "") {
|
||||||
@@ -292,7 +283,6 @@ export async function listAllSiteResourcesByOrg(
|
|||||||
sort_by,
|
sort_by,
|
||||||
order,
|
order,
|
||||||
siteId,
|
siteId,
|
||||||
status,
|
|
||||||
labels: labelFilter
|
labels: labelFilter
|
||||||
} = parsedQuery.data;
|
} = parsedQuery.data;
|
||||||
|
|
||||||
@@ -325,10 +315,6 @@ export async function listAllSiteResourcesByOrg(
|
|||||||
conditions.push(eq(siteResources.mode, mode));
|
conditions.push(eq(siteResources.mode, mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof status !== "undefined") {
|
|
||||||
conditions.push(eq(siteResources.status, status));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (labelFilter && labelFilter.length > 0) {
|
if (labelFilter && labelFilter.length > 0) {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
inArray(
|
inArray(
|
||||||
|
|||||||
@@ -47,15 +47,6 @@ const listSiteResourcesQuerySchema = z.strictObject({
|
|||||||
enum: ["asc", "desc"],
|
enum: ["asc", "desc"],
|
||||||
default: "asc",
|
default: "asc",
|
||||||
description: "Sort order"
|
description: "Sort order"
|
||||||
}),
|
|
||||||
status: z
|
|
||||||
.enum(["pending", "approved"])
|
|
||||||
.optional()
|
|
||||||
.catch(undefined)
|
|
||||||
.openapi({
|
|
||||||
type: "string",
|
|
||||||
enum: ["pending", "approved"],
|
|
||||||
description: "Filter by site resource status"
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,7 +110,7 @@ export async function listSiteResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { siteId, orgId } = parsedParams.data;
|
const { siteId, orgId } = parsedParams.data;
|
||||||
const { limit, offset, sort_by, order, status } = parsedQuery.data;
|
const { limit, offset, sort_by, order } = parsedQuery.data;
|
||||||
|
|
||||||
// Verify the site exists and belongs to the org
|
// Verify the site exists and belongs to the org
|
||||||
const site = await db
|
const site = await db
|
||||||
@@ -133,15 +124,6 @@ export async function listSiteResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get site resources by joining networks to siteResources via siteNetworks
|
// Get site resources by joining networks to siteResources via siteNetworks
|
||||||
const conditions = [
|
|
||||||
eq(siteNetworks.siteId, siteId),
|
|
||||||
eq(siteResources.orgId, orgId)
|
|
||||||
];
|
|
||||||
|
|
||||||
if (typeof status !== "undefined") {
|
|
||||||
conditions.push(eq(siteResources.status, status));
|
|
||||||
}
|
|
||||||
|
|
||||||
const siteResourcesList = await db
|
const siteResourcesList = await db
|
||||||
.select()
|
.select()
|
||||||
.from(siteNetworks)
|
.from(siteNetworks)
|
||||||
@@ -150,7 +132,12 @@ export async function listSiteResources(
|
|||||||
siteResources,
|
siteResources,
|
||||||
eq(siteResources.networkId, networks.networkId)
|
eq(siteResources.networkId, networks.networkId)
|
||||||
)
|
)
|
||||||
.where(and(...conditions))
|
.where(
|
||||||
|
and(
|
||||||
|
eq(siteNetworks.siteId, siteId),
|
||||||
|
eq(siteResources.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
.orderBy(
|
.orderBy(
|
||||||
sort_by
|
sort_by
|
||||||
? order === "asc"
|
? order === "asc"
|
||||||
|
|||||||
@@ -152,11 +152,6 @@ const updateSiteResourceSchema = z
|
|||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
// this is a partial update; only enforce destination when the
|
|
||||||
// caller is actually changing mode or destination
|
|
||||||
if (data.mode === undefined && data.destination === undefined) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// destination is only optional for ssh mode with native authDaemonMode
|
// destination is only optional for ssh mode with native authDaemonMode
|
||||||
if (data.mode === "ssh" && data.authDaemonMode === "native") {
|
if (data.mode === "ssh" && data.authDaemonMode === "native") {
|
||||||
return true;
|
return true;
|
||||||
@@ -416,10 +411,8 @@ export async function updateSiteResource(
|
|||||||
: [];
|
: [];
|
||||||
const existingSiteIds = existingSiteNetworks.map((sn) => sn.siteId);
|
const existingSiteIds = existingSiteNetworks.map((sn) => sn.siteId);
|
||||||
|
|
||||||
// undefined means "leave unchanged" (partial update); only nulled out
|
let fullDomain: string | null = null;
|
||||||
// when the mode is explicitly being changed away from http
|
let finalSubdomain: string | null = null;
|
||||||
let fullDomain: string | null | undefined = undefined;
|
|
||||||
let finalSubdomain: string | null | undefined = undefined;
|
|
||||||
if (domainId) {
|
if (domainId) {
|
||||||
// Validate domain and construct full domain
|
// Validate domain and construct full domain
|
||||||
const domainResult = await validateAndConstructDomain(
|
const domainResult = await validateAndConstructDomain(
|
||||||
@@ -455,11 +448,6 @@ export async function updateSiteResource(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (mode !== undefined && mode !== "http") {
|
|
||||||
// mode is explicitly changing away from http, so the resource
|
|
||||||
// can no longer have a domain associated with it
|
|
||||||
fullDomain = null;
|
|
||||||
finalSubdomain = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the alias is unique within the org if provided
|
// make sure the alias is unique within the org if provided
|
||||||
@@ -528,28 +516,15 @@ export async function updateSiteResource(
|
|||||||
destination: destination,
|
destination: destination,
|
||||||
destinationPort: destinationPort,
|
destinationPort: destinationPort,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
alias:
|
alias: alias ? alias.trim() : null,
|
||||||
alias !== undefined
|
|
||||||
? alias
|
|
||||||
? alias.trim()
|
|
||||||
: null
|
|
||||||
: mode !== undefined &&
|
|
||||||
mode !== "host" &&
|
|
||||||
mode !== "ssh"
|
|
||||||
? null
|
|
||||||
: undefined,
|
|
||||||
tcpPortRangeString: tcpPortRangeStringAdjusted,
|
tcpPortRangeString: tcpPortRangeStringAdjusted,
|
||||||
udpPortRangeString:
|
udpPortRangeString:
|
||||||
mode == "http" || mode == "ssh"
|
mode == "http" || mode == "ssh"
|
||||||
? ""
|
? ""
|
||||||
: udpPortRangeString,
|
: udpPortRangeString,
|
||||||
disableIcmp:
|
disableIcmp:
|
||||||
mode !== undefined
|
disableIcmp ||
|
||||||
? disableIcmp ||
|
(mode == "http" || mode == "ssh" ? true : false),
|
||||||
(mode == "http" || mode == "ssh"
|
|
||||||
? true
|
|
||||||
: false)
|
|
||||||
: disableIcmp,
|
|
||||||
domainId,
|
domainId,
|
||||||
subdomain: finalSubdomain,
|
subdomain: finalSubdomain,
|
||||||
fullDomain,
|
fullDomain,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export async function adminGeneratePasswordResetCode(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = `${config.getRawConfig().app.dashboard_url}/auth/reset-password?email=${encodeURIComponent(existingUser[0].email!)}&token=${token}`;
|
const url = `${config.getRawConfig().app.dashboard_url}/auth/reset-password?email=${existingUser[0].email}&token=${token}`;
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`Admin generated password reset code for user ${existingUser[0].email} (${userId})`
|
`Admin generated password reset code for user ${existingUser[0].email} (${userId})`
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ export async function inviteUser(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`;
|
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${email}`;
|
||||||
|
|
||||||
if (doEmail) {
|
if (doEmail) {
|
||||||
await sendEmail(
|
await sendEmail(
|
||||||
@@ -341,7 +341,7 @@ export async function inviteUser(
|
|||||||
.values(uniqueRoleIds.map((roleId) => ({ inviteId, roleId })));
|
.values(uniqueRoleIds.map((roleId) => ({ inviteId, roleId })));
|
||||||
});
|
});
|
||||||
|
|
||||||
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`;
|
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${email}`;
|
||||||
|
|
||||||
if (doEmail) {
|
if (doEmail) {
|
||||||
await sendEmail(
|
await sendEmail(
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ import {
|
|||||||
handleOlmServerPeerAddMessage,
|
handleOlmServerPeerAddMessage,
|
||||||
handleOlmUnRelayMessage,
|
handleOlmUnRelayMessage,
|
||||||
handleOlmDisconnectingMessage,
|
handleOlmDisconnectingMessage,
|
||||||
handleOlmServerInitAddPeerHandshake,
|
handleOlmServerInitAddPeerHandshake
|
||||||
handleOlmLocalMessage,
|
|
||||||
handleOlmUnLocalMessage
|
|
||||||
} from "../olm";
|
} from "../olm";
|
||||||
import { handleHealthcheckStatusMessage } from "../target";
|
import { handleHealthcheckStatusMessage } from "../target";
|
||||||
import { handleRoundTripMessage } from "./handleRoundTripMessage";
|
import { handleRoundTripMessage } from "./handleRoundTripMessage";
|
||||||
@@ -34,8 +32,6 @@ export const messageHandlers: Record<string, MessageHandler> = {
|
|||||||
"olm/wg/register": handleOlmRegisterMessage,
|
"olm/wg/register": handleOlmRegisterMessage,
|
||||||
"olm/wg/relay": handleOlmRelayMessage,
|
"olm/wg/relay": handleOlmRelayMessage,
|
||||||
"olm/wg/unrelay": handleOlmUnRelayMessage,
|
"olm/wg/unrelay": handleOlmUnRelayMessage,
|
||||||
"olm/wg/local": handleOlmLocalMessage,
|
|
||||||
"olm/wg/unlocal": handleOlmUnLocalMessage,
|
|
||||||
"olm/ping": handleOlmPingMessage,
|
"olm/ping": handleOlmPingMessage,
|
||||||
"olm/disconnecting": handleOlmDisconnectingMessage,
|
"olm/disconnecting": handleOlmDisconnectingMessage,
|
||||||
"newt/disconnecting": handleNewtDisconnectingMessage,
|
"newt/disconnecting": handleNewtDisconnectingMessage,
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import m18 from "./scriptsPg/1.18.3";
|
|||||||
import m19 from "./scriptsPg/1.18.4";
|
import m19 from "./scriptsPg/1.18.4";
|
||||||
import m20 from "./scriptsPg/1.19.0";
|
import m20 from "./scriptsPg/1.19.0";
|
||||||
import m21 from "./scriptsPg/1.20.0";
|
import m21 from "./scriptsPg/1.20.0";
|
||||||
import m22 from "./scriptsPg/1.21.0";
|
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
@@ -54,8 +53,7 @@ const migrations = [
|
|||||||
{ version: "1.18.3", run: m18 },
|
{ version: "1.18.3", run: m18 },
|
||||||
{ version: "1.18.4", run: m19 },
|
{ version: "1.18.4", run: m19 },
|
||||||
{ version: "1.19.0", run: m20 },
|
{ version: "1.19.0", run: m20 },
|
||||||
{ version: "1.20.0", run: m21 },
|
{ version: "1.20.0", run: m21 }
|
||||||
{ version: "1.21.0", run: m22 }
|
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as {
|
] as {
|
||||||
version: string;
|
version: string;
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import m40 from "./scriptsSqlite/1.18.4";
|
|||||||
import m41 from "./scriptsSqlite/1.19.0";
|
import m41 from "./scriptsSqlite/1.19.0";
|
||||||
import m42 from "./scriptsSqlite/1.19.1";
|
import m42 from "./scriptsSqlite/1.19.1";
|
||||||
import m43 from "./scriptsSqlite/1.20.0";
|
import m43 from "./scriptsSqlite/1.20.0";
|
||||||
import m44 from "./scriptsSqlite/1.21.0";
|
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
@@ -90,8 +89,7 @@ const migrations = [
|
|||||||
{ version: "1.18.4", run: m40 },
|
{ version: "1.18.4", run: m40 },
|
||||||
{ version: "1.19.0", run: m41 },
|
{ version: "1.19.0", run: m41 },
|
||||||
{ version: "1.19.1", run: m42 },
|
{ version: "1.19.1", run: m42 },
|
||||||
{ version: "1.20.0", run: m43 },
|
{ version: "1.20.0", run: m43 }
|
||||||
{ version: "1.21.0", run: m44 }
|
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
import { db } from "@server/db/pg/driver";
|
|
||||||
import { APP_PATH } from "@server/lib/consts";
|
|
||||||
import { sql } from "drizzle-orm";
|
|
||||||
import fs from "fs";
|
|
||||||
import yaml from "js-yaml";
|
|
||||||
import path from "path";
|
|
||||||
import z from "zod";
|
|
||||||
import { fromZodError } from "zod-validation-error";
|
|
||||||
|
|
||||||
const version = "1.21.0";
|
|
||||||
|
|
||||||
export default async function migration() {
|
|
||||||
console.log(`Running setup script ${version}...`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.execute(sql`BEGIN`);
|
|
||||||
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "resourceAccessToken" ADD COLUMN "userId" varchar;
|
|
||||||
`);
|
|
||||||
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "resourceAccessToken" ADD COLUMN "persistSession" boolean DEFAULT false NOT NULL;
|
|
||||||
`);
|
|
||||||
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "resources" ADD COLUMN "status" varchar DEFAULT 'approved';
|
|
||||||
`);
|
|
||||||
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "siteResources" ADD COLUMN "status" varchar DEFAULT 'approved';
|
|
||||||
`);
|
|
||||||
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "sites" ADD COLUMN "localEndpoints" varchar;
|
|
||||||
`);
|
|
||||||
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "resourceAccessToken" ADD CONSTRAINT "resourceAccessToken_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
|
||||||
`);
|
|
||||||
|
|
||||||
await db.execute(sql`COMMIT`);
|
|
||||||
console.log("Migrated database");
|
|
||||||
} catch (e) {
|
|
||||||
await db.execute(sql`ROLLBACK`);
|
|
||||||
console.log("Unable to migrate database");
|
|
||||||
console.log(e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const traefikPath = path.join(
|
|
||||||
APP_PATH,
|
|
||||||
"traefik",
|
|
||||||
"traefik_config.yml"
|
|
||||||
);
|
|
||||||
|
|
||||||
const schema = z.object({
|
|
||||||
experimental: z.object({
|
|
||||||
plugins: z.object({
|
|
||||||
badger: z.object({
|
|
||||||
moduleName: z.string(),
|
|
||||||
version: z.string()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const traefikFileContents = fs.readFileSync(traefikPath, "utf8");
|
|
||||||
const traefikConfig = yaml.load(traefikFileContents) as any;
|
|
||||||
|
|
||||||
const parsedConfig = schema.safeParse(traefikConfig);
|
|
||||||
|
|
||||||
if (!parsedConfig.success) {
|
|
||||||
throw new Error(fromZodError(parsedConfig.error).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
traefikConfig.experimental.plugins.badger.version = "v1.5.0";
|
|
||||||
|
|
||||||
const updatedTraefikYaml = yaml.dump(traefikConfig);
|
|
||||||
|
|
||||||
fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8");
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"Updated the version of Badger in your Traefik configuration to v1.5.0"
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(
|
|
||||||
"We were unable to update the version of Badger in your Traefik configuration. Please update it manually. Check the release notes for this version for more information."
|
|
||||||
);
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`${version} migration complete`);
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import { APP_PATH } from "@server/lib/consts";
|
|
||||||
import Database from "better-sqlite3";
|
|
||||||
import fs from "fs";
|
|
||||||
import yaml from "js-yaml";
|
|
||||||
import path from "path";
|
|
||||||
import z from "zod";
|
|
||||||
import { fromZodError } from "zod-validation-error";
|
|
||||||
|
|
||||||
const version = "1.21.0";
|
|
||||||
|
|
||||||
export default async function migration() {
|
|
||||||
console.log(`Running setup script ${version}...`);
|
|
||||||
|
|
||||||
const location = path.join(APP_PATH, "db", "db.sqlite");
|
|
||||||
const db = new Database(location);
|
|
||||||
|
|
||||||
try {
|
|
||||||
db.pragma("foreign_keys = OFF");
|
|
||||||
|
|
||||||
db.transaction(() => {
|
|
||||||
db.prepare(
|
|
||||||
`
|
|
||||||
ALTER TABLE 'resourceAccessToken' ADD 'userId' text REFERENCES user(id);
|
|
||||||
`
|
|
||||||
).run();
|
|
||||||
|
|
||||||
db.prepare(
|
|
||||||
`
|
|
||||||
ALTER TABLE 'resourceAccessToken' ADD 'persistSession' integer DEFAULT false NOT NULL;
|
|
||||||
`
|
|
||||||
).run();
|
|
||||||
|
|
||||||
db.prepare(
|
|
||||||
`
|
|
||||||
ALTER TABLE 'resources' ADD 'status' text DEFAULT 'approved';
|
|
||||||
`
|
|
||||||
).run();
|
|
||||||
|
|
||||||
db.prepare(
|
|
||||||
`
|
|
||||||
ALTER TABLE 'siteResources' ADD 'status' text DEFAULT 'approved';
|
|
||||||
`
|
|
||||||
).run();
|
|
||||||
|
|
||||||
db.prepare(
|
|
||||||
`
|
|
||||||
ALTER TABLE 'sites' ADD 'localEndpoints' text;
|
|
||||||
`
|
|
||||||
).run();
|
|
||||||
})();
|
|
||||||
|
|
||||||
db.pragma("foreign_keys = ON");
|
|
||||||
|
|
||||||
console.log("Migrated database");
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Failed to migrate db:", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const traefikPath = path.join(
|
|
||||||
APP_PATH,
|
|
||||||
"traefik",
|
|
||||||
"traefik_config.yml"
|
|
||||||
);
|
|
||||||
|
|
||||||
const schema = z.object({
|
|
||||||
experimental: z.object({
|
|
||||||
plugins: z.object({
|
|
||||||
badger: z.object({
|
|
||||||
moduleName: z.string(),
|
|
||||||
version: z.string()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const traefikFileContents = fs.readFileSync(traefikPath, "utf8");
|
|
||||||
const traefikConfig = yaml.load(traefikFileContents) as any;
|
|
||||||
|
|
||||||
const parsedConfig = schema.safeParse(traefikConfig);
|
|
||||||
|
|
||||||
if (!parsedConfig.success) {
|
|
||||||
throw new Error(fromZodError(parsedConfig.error).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
traefikConfig.experimental.plugins.badger.version = "v1.5.0";
|
|
||||||
|
|
||||||
const updatedTraefikYaml = yaml.dump(traefikConfig);
|
|
||||||
|
|
||||||
fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8");
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"Updated the version of Badger in your Traefik configuration to v1.5.0"
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(
|
|
||||||
"We were unable to update the version of Badger in your Traefik configuration. Please update it manually. Check the release notes for this version for more information."
|
|
||||||
);
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`${version} migration complete`);
|
|
||||||
}
|
|
||||||
@@ -248,39 +248,6 @@ export async function loginProxy(
|
|||||||
return await makeApiRequest<LoginResponse>(url, "POST", request);
|
return await makeApiRequest<LoginResponse>(url, "POST", request);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logoutProxy(): Promise<ResponseT<null>> {
|
|
||||||
const env = pullEnv();
|
|
||||||
const serverPort = process.env.SERVER_EXTERNAL_PORT;
|
|
||||||
const url = `http://localhost:${serverPort}/api/v1/auth/logout`;
|
|
||||||
|
|
||||||
const result = await makeApiRequest<null>(url, "POST");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const headersList = await reqHeaders();
|
|
||||||
const host = headersList.get("host")?.split(":")[0];
|
|
||||||
const allCookies = await cookies();
|
|
||||||
const clearOptions = {
|
|
||||||
httpOnly: true,
|
|
||||||
secure: true,
|
|
||||||
sameSite: "lax" as const,
|
|
||||||
path: "/",
|
|
||||||
maxAge: 0
|
|
||||||
};
|
|
||||||
// Clear both host-only and domain-scoped variants.
|
|
||||||
allCookies.set(env.server.sessionCookieName, "", clearOptions);
|
|
||||||
if (host) {
|
|
||||||
allCookies.set(env.server.sessionCookieName, "", {
|
|
||||||
...clearOptions,
|
|
||||||
domain: host
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (cookieError) {
|
|
||||||
console.error("Failed to clear session cookie:", cookieError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function securityKeyStartProxy(
|
export async function securityKeyStartProxy(
|
||||||
request: SecurityKeyStartRequest,
|
request: SecurityKeyStartRequest,
|
||||||
forceLogin?: boolean
|
forceLogin?: boolean
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ import {
|
|||||||
import { ChevronsUpDown } from "lucide-react";
|
import { ChevronsUpDown } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import type { Control, FieldPath, FieldValues } from "react-hook-form";
|
import type { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||||
import { PrivateResourceMultiSiteRoutingHelp } from "@app/components/PrivateResourceMultiSiteRoutingHelp";
|
import { PrivateResourceMultiSiteRoutingHelp } from "./PrivateResourceMultiSiteRoutingHelp";
|
||||||
|
|
||||||
type PrivateResourceSitesFieldProps<T extends FieldValues> = {
|
type PrivateResourceSitesFieldProps<T extends FieldValues> = {
|
||||||
control: Control<T>;
|
control: Control<T>;
|
||||||
+3
-3
@@ -9,10 +9,10 @@ import {
|
|||||||
} from "@app/components/Settings";
|
} from "@app/components/Settings";
|
||||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
import { SshServerSettingsFields } from "@app/components/SshServerSettingsFields";
|
import { SshServerSettingsFields } from "@app/components/SshServerSettingsFields";
|
||||||
import { PrivateResourceAliasField } from "@app/components/PrivateResourceDestinationFields";
|
import { PrivateResourceAliasField } from "./PrivateResourceDestinationFields";
|
||||||
import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField";
|
import { PrivateResourceSitesField } from "./PrivateResourceSitesField";
|
||||||
|
import { getSshUseMultiSiteTargetForm } from "./privateResourceUtils";
|
||||||
import { inferSshPamMode } from "@app/lib/privateResourceForm";
|
import { inferSshPamMode } from "@app/lib/privateResourceForm";
|
||||||
import { getSshUseMultiSiteTargetForm } from "@app/lib/privateResourceUtils";
|
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormField,
|
FormField,
|
||||||
@@ -29,7 +29,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useActionState, useEffect } from "react";
|
import { useActionState, useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { PrivateResourceAccessFields } from "@app/components/PrivateResourceAccessFields";
|
import { PrivateResourceAccessFields } from "../../PrivateResourceAccessFields";
|
||||||
|
|
||||||
export default function PrivateResourceAccessPage() {
|
export default function PrivateResourceAccessPage() {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ import { useTranslations } from "next-intl";
|
|||||||
import { useActionState, useMemo, useState } from "react";
|
import { useActionState, useMemo, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField";
|
import { PrivateResourceSitesField } from "../../PrivateResourceSitesField";
|
||||||
import { PrivateResourceCidrDestinationField } from "@app/components/PrivateResourceDestinationFields";
|
import { PrivateResourceCidrDestinationField } from "../../PrivateResourceDestinationFields";
|
||||||
import { PrivateResourcePortRanges } from "@app/components/PrivateResourcePortRanges";
|
import { PrivateResourcePortRanges } from "../../PrivateResourcePortRanges";
|
||||||
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
|
import { buildSelectedSitesForResource } from "../../privateResourceUtils";
|
||||||
import { asAnyControl, asAnySetValue } from "@app/lib/formControlUtils";
|
import { asAnyControl, asAnySetValue } from "../../formControlUtils";
|
||||||
import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils";
|
import { useSaveSiteResource } from "../../useSaveSiteResource";
|
||||||
|
|
||||||
export default function PrivateResourceCidrPage() {
|
export default function PrivateResourceCidrPage() {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|||||||
@@ -16,21 +16,19 @@ import { Button } from "@app/components/ui/button";
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage
|
FormMessage
|
||||||
} from "@app/components/ui/form";
|
} from "@app/components/ui/form";
|
||||||
import { Input } from "@app/components/ui/input";
|
import { Input } from "@app/components/ui/input";
|
||||||
import { SwitchInput } from "@app/components/SwitchInput";
|
|
||||||
import { createGeneralFormSchema } from "@app/lib/privateResourceForm";
|
import { createGeneralFormSchema } from "@app/lib/privateResourceForm";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useActionState, useMemo } from "react";
|
import { useActionState, useMemo } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
|
import { useSaveSiteResource } from "../../useSaveSiteResource";
|
||||||
|
|
||||||
export default function PrivateResourceGeneralPage() {
|
export default function PrivateResourceGeneralPage() {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
@@ -43,8 +41,7 @@ export default function PrivateResourceGeneralPage() {
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: siteResource.name,
|
name: siteResource.name,
|
||||||
niceId: siteResource.niceId,
|
niceId: siteResource.niceId
|
||||||
enabled: siteResource.enabled
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,8 +52,7 @@ export default function PrivateResourceGeneralPage() {
|
|||||||
const data = form.getValues();
|
const data = form.getValues();
|
||||||
await save({
|
await save({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
niceId: data.niceId,
|
niceId: data.niceId
|
||||||
enabled: data.enabled
|
|
||||||
});
|
});
|
||||||
}, null);
|
}, null);
|
||||||
|
|
||||||
@@ -80,42 +76,6 @@ export default function PrivateResourceGeneralPage() {
|
|||||||
id="private-resource-general-form"
|
id="private-resource-general-form"
|
||||||
>
|
>
|
||||||
<SettingsFormGrid>
|
<SettingsFormGrid>
|
||||||
<SettingsFormCell span="full">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enabled"
|
|
||||||
render={() => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<SwitchInput
|
|
||||||
id="enable-resource"
|
|
||||||
defaultChecked={
|
|
||||||
siteResource.enabled
|
|
||||||
}
|
|
||||||
label={t(
|
|
||||||
"resourceEnable"
|
|
||||||
)}
|
|
||||||
onCheckedChange={(
|
|
||||||
val
|
|
||||||
) =>
|
|
||||||
form.setValue(
|
|
||||||
"enabled",
|
|
||||||
val
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
{t(
|
|
||||||
"disabledResourceDescription"
|
|
||||||
)}
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SettingsFormCell>
|
|
||||||
|
|
||||||
<SettingsFormCell span="half">
|
<SettingsFormCell span="half">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ import { useTranslations } from "next-intl";
|
|||||||
import { useActionState, useMemo, useState } from "react";
|
import { useActionState, useMemo, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField";
|
import { PrivateResourceSitesField } from "../../PrivateResourceSitesField";
|
||||||
import { PrivateResourceHostDestinationFields } from "@app/components/PrivateResourceDestinationFields";
|
import { PrivateResourceHostDestinationFields } from "../../PrivateResourceDestinationFields";
|
||||||
import { PrivateResourcePortRanges } from "@app/components/PrivateResourcePortRanges";
|
import { PrivateResourcePortRanges } from "../../PrivateResourcePortRanges";
|
||||||
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
|
import { buildSelectedSitesForResource } from "../../privateResourceUtils";
|
||||||
import {
|
import {
|
||||||
asAnyControl,
|
asAnyControl,
|
||||||
asAnySetValue,
|
asAnySetValue,
|
||||||
asAnyWatch
|
asAnyWatch
|
||||||
} from "@app/lib/formControlUtils";
|
} from "../../formControlUtils";
|
||||||
import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils";
|
import { useSaveSiteResource } from "../../useSaveSiteResource";
|
||||||
|
|
||||||
export default function PrivateResourceHostPage() {
|
export default function PrivateResourceHostPage() {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ import { useTranslations } from "next-intl";
|
|||||||
import { useActionState, useMemo, useState } from "react";
|
import { useActionState, useMemo, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField";
|
import { PrivateResourceSitesField } from "../../PrivateResourceSitesField";
|
||||||
import { PrivateResourceHttpFields } from "@app/components/PrivateResourceHttpFields";
|
import { PrivateResourceHttpFields } from "../../PrivateResourceHttpFields";
|
||||||
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
|
import { buildSelectedSitesForResource } from "../../privateResourceUtils";
|
||||||
import {
|
import {
|
||||||
asAnyControl,
|
asAnyControl,
|
||||||
asAnySetValue,
|
asAnySetValue,
|
||||||
asAnyWatch
|
asAnyWatch
|
||||||
} from "@app/lib/formControlUtils";
|
} from "../../formControlUtils";
|
||||||
import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils";
|
import { useSaveSiteResource } from "../../useSaveSiteResource";
|
||||||
|
|
||||||
export default function PrivateResourceHttpPage() {
|
export default function PrivateResourceHttpPage() {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user