Compare commits
1 Commits
main
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8d1b779cc |
1
.github/CODEOWNERS
vendored
@@ -1 +0,0 @@
|
|||||||
* @oschwartz10612 @miloschwartz
|
|
||||||
8
.github/workflows/cicd.yml
vendored
@@ -77,7 +77,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -204,7 +204,7 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -407,7 +407,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry (for cosign)
|
- name: Login to GitHub Container Registry (for cosign)
|
||||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
58
README.md
@@ -35,53 +35,43 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://docs.pangolin.net/careers/join-us">
|
||||||
|
<img src="https://img.shields.io/badge/🚀_We're_Hiring!-Join_Our_Team-brightgreen?style=for-the-badge" alt="We're Hiring!" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>
|
<strong>
|
||||||
Get started with Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
|
Get started with Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
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.
|
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, all with zero-trust security and granular access control.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
- Get started for free with [Pangolin Cloud](https://app.pangolin.net/).
|
- Check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to install and set up Pangolin.
|
||||||
- Or, check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to self-host Pangolin.
|
- Install from the [DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/pangolin-ce-1?refcode=edf0480eeb81) for a one-click pre-configured installer.
|
||||||
- Install from the [DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/pangolin-ce-1?refcode=edf0480eeb81) for a one-click pre-configured installer.
|
|
||||||
|
|
||||||
<img src="public/screenshots/hero.png" alt="Pangolin" width="100%" />
|
<img src="public/screenshots/hero.png" />
|
||||||
|
|
||||||
## Deployment Options
|
## Deployment Options
|
||||||
|
|
||||||
- **Pangolin Cloud** — Fully managed service - no infrastructure required.
|
| <img width=500 /> | Description |
|
||||||
- **Self-Host: Community Edition** — Free, open source, and licensed under AGPL-3.
|
|-----------------|--------------|
|
||||||
- **Self-Host: Enterprise Edition** — Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses making less than \$100K USD gross annual revenue.
|
| **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing - no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/understanding-nodes) and connect to our control plane. |
|
||||||
|
| **Self-Host: Community Edition** | Free, open source, and licensed under AGPL-3. |
|
||||||
|
| **Self-Host: Enterprise Edition** | Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses earning under \$100K USD annually. |
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
### Connect remote networks with sites and NAT traversal
|
| <img width=500 /> | <img width=500 /> |
|
||||||
|
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
|
||||||
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.
|
| **Connect remote networks with sites**<br /><br />Pangolin's lightweight site connectors create secure tunnels from remote networks without requiring public IP addresses or open ports. Sites make any network anywhere available for authorized access. | <img src="public/screenshots/sites.png" width=500 /><tr></tr> |
|
||||||
|
| **Browser-based reverse proxy access**<br /><br />Expose web applications through identity and context-aware tunneled reverse proxies. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet. Users access applications through any web browser with authentication and granular access control. | <img src="public/clip.gif" width=500 /><tr></tr> |
|
||||||
<img src="public/screenshots/sites.png" alt="Sites" width="100%" />
|
| **Client-based private resource access**<br /><br />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. | <img src="public/screenshots/private-resources.png" width=500 /><tr></tr> |
|
||||||
|
| **Zero-trust granular access**<br /><br />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 and services you explicitly define, reducing security risk and attack surface. | <img src="public/screenshots/user-devices.png" width=500 /><tr></tr> |
|
||||||
### Browser-based reverse proxy access
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
<img src="public/clip.gif" alt="Reverse proxy access" width="100%" />
|
|
||||||
|
|
||||||
### Client-based private resource access
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
<img src="public/screenshots/private-resources.png" alt="Private resources" width="100%" />
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
<img src="public/screenshots/users.png" alt="Users from identity provider with roles" width="100%" />
|
|
||||||
|
|
||||||
## Download Clients
|
## Download Clients
|
||||||
|
|
||||||
@@ -97,7 +87,7 @@ Download the Pangolin client for your platform:
|
|||||||
|
|
||||||
### Sign up now
|
### Sign up now
|
||||||
|
|
||||||
Create a free account at [app.pangolin.net](https://app.pangolin.net) to get started with Pangolin Cloud.
|
Create an account at [app.pangolin.net](https://app.pangolin.net) to get started with Pangolin Cloud. A generous free tier is available.
|
||||||
|
|
||||||
### Check out the docs
|
### Check out the docs
|
||||||
|
|
||||||
@@ -112,3 +102,7 @@ Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License
|
|||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
|
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
WireGuard® is a registered trademark of Jason A. Donenfeld.
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Неуспешно активиране на лицензионния ключ",
|
"licenseErrorKeyActivate": "Неуспешно активиране на лицензионния ключ",
|
||||||
"licenseErrorKeyActivateDescription": "Възникна грешка при активирането на лицензионния ключ.",
|
"licenseErrorKeyActivateDescription": "Възникна грешка при активирането на лицензионния ключ.",
|
||||||
"licenseAbout": "Относно лицензите",
|
"licenseAbout": "Относно лицензите",
|
||||||
"licenseBannerTitle": "Активирайте своята корпоративна лицензия",
|
|
||||||
"licenseBannerDescription": "Отключете корпоративните функции за вашият хостинг на Pangolin. Закупете лицензионен ключ, за да активирате премиум възможности, след това го добавете по-долу.",
|
|
||||||
"licenseBannerGetLicense": "Вземете лиценз",
|
|
||||||
"licenseBannerViewDocs": "Преглед на документацията",
|
|
||||||
"communityEdition": "Комюнити издание",
|
"communityEdition": "Комюнити издание",
|
||||||
"licenseAboutDescription": "Това е за бизнес и корпоративни потребители, които използват Pangolin в търговска среда. Ако използвате Pangolin за лична употреба, можете да игнорирате този раздел.",
|
"licenseAboutDescription": "Това е за бизнес и корпоративни потребители, които използват Pangolin в търговска среда. Ако използвате Pangolin за лична употреба, можете да игнорирате този раздел.",
|
||||||
"licenseKeyActivated": "Лицензионният ключ е активиран",
|
"licenseKeyActivated": "Лицензионният ключ е активиран",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Изберете домейн за страницата за удостоверяване на организацията",
|
"selectDomainForOrgAuthPage": "Изберете домейн за страницата за удостоверяване на организацията",
|
||||||
"domainPickerProvidedDomain": "Предоставен домейн",
|
"domainPickerProvidedDomain": "Предоставен домейн",
|
||||||
"domainPickerFreeProvidedDomain": "Безплатен предоставен домейн",
|
"domainPickerFreeProvidedDomain": "Безплатен предоставен домейн",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Предоставените домейни са платена функция. Абонирайте се, за да получите домейн, включен във вашия план - няма нужда да използвате вашия собствен.",
|
|
||||||
"domainPickerVerified": "Проверено",
|
"domainPickerVerified": "Проверено",
|
||||||
"domainPickerUnverified": "Непроверено",
|
"domainPickerUnverified": "Непроверено",
|
||||||
"domainPickerManual": "Ръчно",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Този поддомен съдържа невалидни знаци или структура. Ще бъде автоматично пречистен при запазване.",
|
"domainPickerInvalidSubdomainStructure": "Този поддомен съдържа невалидни знаци или структура. Ще бъде автоматично пречистен при запазване.",
|
||||||
"domainPickerError": "Грешка",
|
"domainPickerError": "Грешка",
|
||||||
"domainPickerErrorLoadDomains": "Неуспешно зареждане на домейни на организацията",
|
"domainPickerErrorLoadDomains": "Неуспешно зареждане на домейни на организацията",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Nepodařilo se aktivovat licenční klíč",
|
"licenseErrorKeyActivate": "Nepodařilo se aktivovat licenční klíč",
|
||||||
"licenseErrorKeyActivateDescription": "Došlo k chybě při aktivaci licenčního klíče.",
|
"licenseErrorKeyActivateDescription": "Došlo k chybě při aktivaci licenčního klíče.",
|
||||||
"licenseAbout": "O licencích",
|
"licenseAbout": "O licencích",
|
||||||
"licenseBannerTitle": "Aktivovat vaši firemní licenci",
|
|
||||||
"licenseBannerDescription": "Odemkněte firemní funkce pro vaši samohostovanou instanci Pangolin. Zakupte si licenční klíč pro aktivaci prémiových možností a poté jej přidejte níže.",
|
|
||||||
"licenseBannerGetLicense": "Zakoupit licenci",
|
|
||||||
"licenseBannerViewDocs": "Zobrazit dokumentaci",
|
|
||||||
"communityEdition": "Komunitní edice",
|
"communityEdition": "Komunitní edice",
|
||||||
"licenseAboutDescription": "To je pro obchodní a podnikové uživatele, kteří používají Pangolin v komerčním prostředí. Pokud používáte Pangolin pro osobní použití, můžete tuto sekci ignorovat.",
|
"licenseAboutDescription": "To je pro obchodní a podnikové uživatele, kteří používají Pangolin v komerčním prostředí. Pokud používáte Pangolin pro osobní použití, můžete tuto sekci ignorovat.",
|
||||||
"licenseKeyActivated": "Licenční klíč aktivován",
|
"licenseKeyActivated": "Licenční klíč aktivován",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Vyberte doménu pro ověřovací stránku organizace",
|
"selectDomainForOrgAuthPage": "Vyberte doménu pro ověřovací stránku organizace",
|
||||||
"domainPickerProvidedDomain": "Poskytnutá doména",
|
"domainPickerProvidedDomain": "Poskytnutá doména",
|
||||||
"domainPickerFreeProvidedDomain": "Zdarma poskytnutá doména",
|
"domainPickerFreeProvidedDomain": "Zdarma poskytnutá doména",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Poskytnuté domény jsou placenou funkcí. Předplaťte si plán, abyste získali doménu zahrnutou v plánu – nemusíte si přinést vlastní.",
|
|
||||||
"domainPickerVerified": "Ověřeno",
|
"domainPickerVerified": "Ověřeno",
|
||||||
"domainPickerUnverified": "Neověřeno",
|
"domainPickerUnverified": "Neověřeno",
|
||||||
"domainPickerManual": "Ruční nastavení",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Tato subdoména obsahuje neplatné znaky nebo strukturu. Bude automaticky sanitována při uložení.",
|
"domainPickerInvalidSubdomainStructure": "Tato subdoména obsahuje neplatné znaky nebo strukturu. Bude automaticky sanitována při uložení.",
|
||||||
"domainPickerError": "Chyba",
|
"domainPickerError": "Chyba",
|
||||||
"domainPickerErrorLoadDomains": "Nepodařilo se načíst domény organizace",
|
"domainPickerErrorLoadDomains": "Nepodařilo se načíst domény organizace",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Fehler beim Aktivieren des Lizenzschlüssels",
|
"licenseErrorKeyActivate": "Fehler beim Aktivieren des Lizenzschlüssels",
|
||||||
"licenseErrorKeyActivateDescription": "Beim Aktivieren des Lizenzschlüssels ist ein Fehler aufgetreten.",
|
"licenseErrorKeyActivateDescription": "Beim Aktivieren des Lizenzschlüssels ist ein Fehler aufgetreten.",
|
||||||
"licenseAbout": "Über Lizenzierung",
|
"licenseAbout": "Über Lizenzierung",
|
||||||
"licenseBannerTitle": "Aktivieren Sie Ihre Enterprise-Lizenz",
|
|
||||||
"licenseBannerDescription": "Schalten Sie Unternehmensfunktionen für Ihre selbstgehostete Pangolin-Instanz frei. Kaufen Sie einen Lizenzschlüssel, um Premium-Funktionen zu aktivieren, und fügen Sie ihn dann unten hinzu.",
|
|
||||||
"licenseBannerGetLicense": "Lizenz erhalten",
|
|
||||||
"licenseBannerViewDocs": "Dokumentation anzeigen",
|
|
||||||
"communityEdition": "Community-Edition",
|
"communityEdition": "Community-Edition",
|
||||||
"licenseAboutDescription": "Dies ist für Geschäfts- und Unternehmensanwender, die Pangolin in einem kommerziellen Umfeld einsetzen. Wenn Sie Pangolin für den persönlichen Gebrauch verwenden, können Sie diesen Abschnitt ignorieren.",
|
"licenseAboutDescription": "Dies ist für Geschäfts- und Unternehmensanwender, die Pangolin in einem kommerziellen Umfeld einsetzen. Wenn Sie Pangolin für den persönlichen Gebrauch verwenden, können Sie diesen Abschnitt ignorieren.",
|
||||||
"licenseKeyActivated": "Lizenzschlüssel aktiviert",
|
"licenseKeyActivated": "Lizenzschlüssel aktiviert",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Wählen Sie eine Domain für die Authentifizierungsseite der Organisation",
|
"selectDomainForOrgAuthPage": "Wählen Sie eine Domain für die Authentifizierungsseite der Organisation",
|
||||||
"domainPickerProvidedDomain": "Angegebene Domain",
|
"domainPickerProvidedDomain": "Angegebene Domain",
|
||||||
"domainPickerFreeProvidedDomain": "Kostenlose Domain",
|
"domainPickerFreeProvidedDomain": "Kostenlose Domain",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Bereitgestellte Domains sind ein kostenpflichtiges Feature. Abonnieren Sie, um eine Domain in Ihrem Tarif zu erhalten – keine Notwendigkeit, Ihre eigene mitzubringen.",
|
|
||||||
"domainPickerVerified": "Verifiziert",
|
"domainPickerVerified": "Verifiziert",
|
||||||
"domainPickerUnverified": "Nicht verifiziert",
|
"domainPickerUnverified": "Nicht verifiziert",
|
||||||
"domainPickerManual": "Manuell",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Diese Subdomain enthält ungültige Zeichen oder Struktur. Sie wird beim Speichern automatisch bereinigt.",
|
"domainPickerInvalidSubdomainStructure": "Diese Subdomain enthält ungültige Zeichen oder Struktur. Sie wird beim Speichern automatisch bereinigt.",
|
||||||
"domainPickerError": "Fehler",
|
"domainPickerError": "Fehler",
|
||||||
"domainPickerErrorLoadDomains": "Fehler beim Laden der Organisations-Domains",
|
"domainPickerErrorLoadDomains": "Fehler beim Laden der Organisations-Domains",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Failed to activate license key",
|
"licenseErrorKeyActivate": "Failed to activate license key",
|
||||||
"licenseErrorKeyActivateDescription": "An error occurred while activating the license key.",
|
"licenseErrorKeyActivateDescription": "An error occurred while activating the license key.",
|
||||||
"licenseAbout": "About Licensing",
|
"licenseAbout": "About Licensing",
|
||||||
"licenseBannerTitle": "Enable Your Enterprise License",
|
|
||||||
"licenseBannerDescription": "Unlock enterprise features for your self-hosted Pangolin instance. Purchase a license key to activate premium capabilities, then add it below.",
|
|
||||||
"licenseBannerGetLicense": "Get a License",
|
|
||||||
"licenseBannerViewDocs": "View Documentation",
|
|
||||||
"communityEdition": "Community Edition",
|
"communityEdition": "Community Edition",
|
||||||
"licenseAboutDescription": "This is for business and enterprise users who are using Pangolin in a commercial environment. If you are using Pangolin for personal use, you can ignore this section.",
|
"licenseAboutDescription": "This is for business and enterprise users who are using Pangolin in a commercial environment. If you are using Pangolin for personal use, you can ignore this section.",
|
||||||
"licenseKeyActivated": "License key activated",
|
"licenseKeyActivated": "License key activated",
|
||||||
@@ -2117,11 +2113,9 @@
|
|||||||
"addDomainToEnableCustomAuthPages": "Users will be able to access the organization's login page and complete resource authentication using this domain.",
|
"addDomainToEnableCustomAuthPages": "Users will be able to access the organization's login page and complete resource authentication using this domain.",
|
||||||
"selectDomainForOrgAuthPage": "Select a domain for the organization's authentication page",
|
"selectDomainForOrgAuthPage": "Select a domain for the organization's authentication page",
|
||||||
"domainPickerProvidedDomain": "Provided Domain",
|
"domainPickerProvidedDomain": "Provided Domain",
|
||||||
"domainPickerFreeProvidedDomain": "Provided Domain",
|
"domainPickerFreeProvidedDomain": "Free Provided Domain",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Provided domains are a paid feature. Subscribe to get a domain included with your plan — no need to bring your own.",
|
|
||||||
"domainPickerVerified": "Verified",
|
"domainPickerVerified": "Verified",
|
||||||
"domainPickerUnverified": "Unverified",
|
"domainPickerUnverified": "Unverified",
|
||||||
"domainPickerManual": "Manual",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "This subdomain contains invalid characters or structure. It will be sanitized automatically when you save.",
|
"domainPickerInvalidSubdomainStructure": "This subdomain contains invalid characters or structure. It will be sanitized automatically when you save.",
|
||||||
"domainPickerError": "Error",
|
"domainPickerError": "Error",
|
||||||
"domainPickerErrorLoadDomains": "Failed to load organization domains",
|
"domainPickerErrorLoadDomains": "Failed to load organization domains",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Error al activar la clave de licencia",
|
"licenseErrorKeyActivate": "Error al activar la clave de licencia",
|
||||||
"licenseErrorKeyActivateDescription": "Se ha producido un error al activar la clave de licencia.",
|
"licenseErrorKeyActivateDescription": "Se ha producido un error al activar la clave de licencia.",
|
||||||
"licenseAbout": "Acerca de la licencia",
|
"licenseAbout": "Acerca de la licencia",
|
||||||
"licenseBannerTitle": "Habilitar su Licencia Enterprise",
|
|
||||||
"licenseBannerDescription": "Desbloquea funciones empresariales para tu instancia autohospedada de Pangolin. Compra una clave de licencia para activar capacidades premium, luego agréguela a continuación.",
|
|
||||||
"licenseBannerGetLicense": "Obtener una Licencia",
|
|
||||||
"licenseBannerViewDocs": "Ver Documentación",
|
|
||||||
"communityEdition": "Edición comunitaria",
|
"communityEdition": "Edición comunitaria",
|
||||||
"licenseAboutDescription": "Esto es para usuarios empresariales y empresariales que utilizan Pangolin en un entorno comercial. Si estás usando Pangolin para uso personal, puedes ignorar esta sección.",
|
"licenseAboutDescription": "Esto es para usuarios empresariales y empresariales que utilizan Pangolin en un entorno comercial. Si estás usando Pangolin para uso personal, puedes ignorar esta sección.",
|
||||||
"licenseKeyActivated": "Clave de licencia activada",
|
"licenseKeyActivated": "Clave de licencia activada",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Seleccione un dominio para la página de autenticación de la organización",
|
"selectDomainForOrgAuthPage": "Seleccione un dominio para la página de autenticación de la organización",
|
||||||
"domainPickerProvidedDomain": "Dominio proporcionado",
|
"domainPickerProvidedDomain": "Dominio proporcionado",
|
||||||
"domainPickerFreeProvidedDomain": "Dominio proporcionado gratis",
|
"domainPickerFreeProvidedDomain": "Dominio proporcionado gratis",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Los dominios proporcionados son una función de pago. Suscríbete para obtener un dominio incluido con tu plan — no necesitas traer el tuyo propio.",
|
|
||||||
"domainPickerVerified": "Verificado",
|
"domainPickerVerified": "Verificado",
|
||||||
"domainPickerUnverified": "Sin verificar",
|
"domainPickerUnverified": "Sin verificar",
|
||||||
"domainPickerManual": "Manual",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Este subdominio contiene caracteres o estructura no válidos. Se limpiará automáticamente al guardar.",
|
"domainPickerInvalidSubdomainStructure": "Este subdominio contiene caracteres o estructura no válidos. Se limpiará automáticamente al guardar.",
|
||||||
"domainPickerError": "Error",
|
"domainPickerError": "Error",
|
||||||
"domainPickerErrorLoadDomains": "Error al cargar los dominios de la organización",
|
"domainPickerErrorLoadDomains": "Error al cargar los dominios de la organización",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Échec de l'activation de la clé de licence",
|
"licenseErrorKeyActivate": "Échec de l'activation de la clé de licence",
|
||||||
"licenseErrorKeyActivateDescription": "Une erreur s'est produite lors de l'activation de la clé de licence.",
|
"licenseErrorKeyActivateDescription": "Une erreur s'est produite lors de l'activation de la clé de licence.",
|
||||||
"licenseAbout": "À propos de la licence",
|
"licenseAbout": "À propos de la licence",
|
||||||
"licenseBannerTitle": "Activer Votre Licence Entreprise",
|
|
||||||
"licenseBannerDescription": "Débloquez les fonctionnalités d'entreprise pour votre instance autohébergée de Pangolin. Achetez une clé de licence pour activer les capacités premium, puis ajoutez-la ci-dessous.",
|
|
||||||
"licenseBannerGetLicense": "Obtenez une Licence",
|
|
||||||
"licenseBannerViewDocs": "Afficher la Documentation",
|
|
||||||
"communityEdition": "Edition Communautaire",
|
"communityEdition": "Edition Communautaire",
|
||||||
"licenseAboutDescription": "Ceci est destiné aux entreprises qui utilisent Pangolin dans un environnement commercial. Si vous utilisez Pangolin pour un usage personnel, vous pouvez ignorer cette section.",
|
"licenseAboutDescription": "Ceci est destiné aux entreprises qui utilisent Pangolin dans un environnement commercial. Si vous utilisez Pangolin pour un usage personnel, vous pouvez ignorer cette section.",
|
||||||
"licenseKeyActivated": "Clé de licence activée",
|
"licenseKeyActivated": "Clé de licence activée",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Sélectionnez un domaine pour la page d'authentification de l'organisation",
|
"selectDomainForOrgAuthPage": "Sélectionnez un domaine pour la page d'authentification de l'organisation",
|
||||||
"domainPickerProvidedDomain": "Domaine fourni",
|
"domainPickerProvidedDomain": "Domaine fourni",
|
||||||
"domainPickerFreeProvidedDomain": "Domaine fourni gratuitement",
|
"domainPickerFreeProvidedDomain": "Domaine fourni gratuitement",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Les domaines fournis sont une fonctionnalité payante. Abonnez-vous pour obtenir un domaine inclus avec votre plan — plus besoin de fournir le vôtre.",
|
|
||||||
"domainPickerVerified": "Vérifié",
|
"domainPickerVerified": "Vérifié",
|
||||||
"domainPickerUnverified": "Non vérifié",
|
"domainPickerUnverified": "Non vérifié",
|
||||||
"domainPickerManual": "Manuel",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Ce sous-domaine contient des caractères ou une structure non valide. Il sera automatiquement nettoyé lorsque vous enregistrez.",
|
"domainPickerInvalidSubdomainStructure": "Ce sous-domaine contient des caractères ou une structure non valide. Il sera automatiquement nettoyé lorsque vous enregistrez.",
|
||||||
"domainPickerError": "Erreur",
|
"domainPickerError": "Erreur",
|
||||||
"domainPickerErrorLoadDomains": "Impossible de charger les domaines de l'organisation",
|
"domainPickerErrorLoadDomains": "Impossible de charger les domaines de l'organisation",
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"setupCreate": "Creare l'organizzazione, il sito e le risorse",
|
"setupCreate": "Creare l'organizzazione, il sito e le risorse",
|
||||||
"headerAuthCompatibilityInfo": "Abilita questa funzionalità per forzare una risposta 401 Unauthorized quando manca un token di autenticazione. Questo è richiesto per browser o librerie HTTP specifiche che non inviano credenziali senza una sfida del server.",
|
"headerAuthCompatibilityInfo": "Abilita questo per forzare una risposta 401 Unauthorized quando manca un token di autenticazione. Questo è richiesto per browser o librerie HTTP specifiche che non inviano credenziali senza una sfida del server.",
|
||||||
"headerAuthCompatibility": "Compatibilità estesa",
|
"headerAuthCompatibility": "Compatibilità estesa",
|
||||||
"setupNewOrg": "Nuova Organizzazione",
|
"setupNewOrg": "Nuova Organizzazione",
|
||||||
"setupCreateOrg": "Crea Organizzazione",
|
"setupCreateOrg": "Crea Organizzazione",
|
||||||
"setupCreateResources": "Crea Risorse",
|
"setupCreateResources": "Crea Risorse",
|
||||||
"setupOrgName": "Nome dell'Organizzazione",
|
"setupOrgName": "Nome Dell'Organizzazione",
|
||||||
"orgDisplayName": "Questo è il nome visualizzato dell'organizzazione.",
|
"orgDisplayName": "Questo è il nome visualizzato dell'organizzazione.",
|
||||||
"orgId": "Id Organizzazione",
|
"orgId": "Id Organizzazione",
|
||||||
"setupIdentifierMessage": "Questo è l'identificatore univoco per l'organizzazione.",
|
"setupIdentifierMessage": "Questo è l'identificatore univoco per l'organizzazione.",
|
||||||
"setupErrorIdentifier": "L'ID dell'organizzazione è già utilizzato. Si prega di sceglierne uno diverso.",
|
"setupErrorIdentifier": "L'ID dell'organizzazione è già utilizzato. Si prega di sceglierne uno diverso.",
|
||||||
"componentsErrorNoMemberCreate": "Al momento non sei un membro di nessuna organizzazione. Crea un'organizzazione per iniziare.",
|
"componentsErrorNoMemberCreate": "Al momento non sei un membro di nessuna organizzazione. Crea un'organizzazione per iniziare.",
|
||||||
"componentsErrorNoMember": "Attualmente non sei membro di nessuna organizzazione.",
|
"componentsErrorNoMember": "Attualmente non sei membro di nessuna organizzazione.",
|
||||||
"welcome": "Benvenuto su Pangolin!",
|
"welcome": "Benvenuti a Pangolin",
|
||||||
"welcomeTo": "Benvenuto su Pangolin!",
|
"welcomeTo": "Benvenuto a",
|
||||||
"componentsCreateOrg": "Crea un'organizzazione",
|
"componentsCreateOrg": "Crea un'organizzazione",
|
||||||
"componentsMember": "Sei un membro di {count, plural, =0 {nessuna organizzazione} one {un'organizzazione} other {# organizzazioni}}.",
|
"componentsMember": "Sei un membro di {count, plural, =0 {nessuna organizzazione} one {un'organizzazione} other {# organizzazioni}}.",
|
||||||
"componentsInvalidKey": "Rilevata chiave di licenza non valida o scaduta. Segui i termini di licenza per continuare a utilizzare tutte le funzionalità.",
|
"componentsInvalidKey": "Rilevata chiave di licenza non valida o scaduta. Segui i termini di licenza per continuare a utilizzare tutte le funzionalità.",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"inviteLoginUser": "Assicurati di aver effettuato l'accesso come utente corretto.",
|
"inviteLoginUser": "Assicurati di aver effettuato l'accesso come utente corretto.",
|
||||||
"inviteErrorNoUser": "Siamo spiacenti, ma sembra che l'invito che stai cercando di accedere non sia per un utente che esiste.",
|
"inviteErrorNoUser": "Siamo spiacenti, ma sembra che l'invito che stai cercando di accedere non sia per un utente che esiste.",
|
||||||
"inviteCreateUser": "Si prega di creare un account prima.",
|
"inviteCreateUser": "Si prega di creare un account prima.",
|
||||||
"goHome": "Vai alla Home",
|
"goHome": "Vai A Home",
|
||||||
"inviteLogInOtherUser": "Accedi come utente diverso",
|
"inviteLogInOtherUser": "Accedi come utente diverso",
|
||||||
"createAnAccount": "Crea un account",
|
"createAnAccount": "Crea un account",
|
||||||
"inviteNotAccepted": "Invito Non Accettato",
|
"inviteNotAccepted": "Invito Non Accettato",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"edit": "Modifica",
|
"edit": "Modifica",
|
||||||
"siteConfirmDelete": "Conferma Eliminazione Sito",
|
"siteConfirmDelete": "Conferma Eliminazione Sito",
|
||||||
"siteDelete": "Elimina Sito",
|
"siteDelete": "Elimina Sito",
|
||||||
"siteMessageRemove": "Una volta rimosso il sito non sarà più accessibile. Tutti gli oggetti associati al sito verranno rimossi.",
|
"siteMessageRemove": "Una volta rimosso il sito non sarà più accessibile. Tutti gli obiettivi associati al sito verranno rimossi.",
|
||||||
"siteQuestionRemove": "Sei sicuro di voler rimuovere il sito dall'organizzazione?",
|
"siteQuestionRemove": "Sei sicuro di voler rimuovere il sito dall'organizzazione?",
|
||||||
"siteManageSites": "Gestisci Siti",
|
"siteManageSites": "Gestisci Siti",
|
||||||
"siteDescription": "Creare e gestire siti per abilitare la connettività a reti private",
|
"siteDescription": "Creare e gestire siti per abilitare la connettività a reti private",
|
||||||
@@ -75,9 +75,9 @@
|
|||||||
"siteLoadWGConfig": "Caricamento configurazione WireGuard...",
|
"siteLoadWGConfig": "Caricamento configurazione WireGuard...",
|
||||||
"siteDocker": "Espandi per i dettagli di distribuzione Docker",
|
"siteDocker": "Espandi per i dettagli di distribuzione Docker",
|
||||||
"toggle": "Attiva/disattiva",
|
"toggle": "Attiva/disattiva",
|
||||||
"dockerCompose": "Docker Compose",
|
"dockerCompose": "Composizione Docker",
|
||||||
"dockerRun": "Corsa Docker",
|
"dockerRun": "Corsa Docker",
|
||||||
"siteLearnLocal": "I siti locali non effettuano il tunnel, per saperne di più",
|
"siteLearnLocal": "I siti locali non tunnel, saperne di più",
|
||||||
"siteConfirmCopy": "Ho copiato la configurazione",
|
"siteConfirmCopy": "Ho copiato la configurazione",
|
||||||
"searchSitesProgress": "Cerca siti...",
|
"searchSitesProgress": "Cerca siti...",
|
||||||
"siteAdd": "Aggiungi Sito",
|
"siteAdd": "Aggiungi Sito",
|
||||||
@@ -88,29 +88,29 @@
|
|||||||
"operatingSystem": "Sistema Operativo",
|
"operatingSystem": "Sistema Operativo",
|
||||||
"commands": "Comandi",
|
"commands": "Comandi",
|
||||||
"recommended": "Consigliato",
|
"recommended": "Consigliato",
|
||||||
"siteNewtDescription": "Per la migliore esperienza utente utilizzare Newt, che usa WireGuard sotto il cofano e ti permette di indirizzare le tue risorse private tramite il loro indirizzo LAN sulla tua rete privata dall'interno della dashboard Pangolin.",
|
"siteNewtDescription": "Per la migliore esperienza utente, utilizzare Newt. Utilizza WireGuard sotto il cofano e ti permette di indirizzare le tue risorse private tramite il loro indirizzo LAN sulla tua rete privata dall'interno della dashboard Pangolin.",
|
||||||
"siteRunsInDocker": "Esegue nel Docker",
|
"siteRunsInDocker": "Esegue nel Docker",
|
||||||
"siteRunsInShell": "Esegue in shell su macOS, Linux e Windows",
|
"siteRunsInShell": "Esegue in shell su macOS, Linux e Windows",
|
||||||
"siteErrorDelete": "Errore nella eliminazione del sito",
|
"siteErrorDelete": "Errore nell'eliminare il sito",
|
||||||
"siteErrorUpdate": "Impossibile aggiornare il sito",
|
"siteErrorUpdate": "Impossibile aggiornare il sito",
|
||||||
"siteErrorUpdateDescription": "Si è verificato un errore durante l'aggiornamento del sito.",
|
"siteErrorUpdateDescription": "Si è verificato un errore durante l'aggiornamento del sito.",
|
||||||
"siteUpdated": "Sito aggiornato",
|
"siteUpdated": "Sito aggiornato",
|
||||||
"siteUpdatedDescription": "Il sito è stato aggiornato.",
|
"siteUpdatedDescription": "Il sito è stato aggiornato.",
|
||||||
"siteGeneralDescription": "Configura le impostazioni generali per questo sito",
|
"siteGeneralDescription": "Configura le impostazioni generali per questo sito",
|
||||||
"siteSettingDescription": "Configura le impostazioni del sito",
|
"siteSettingDescription": "Configura le impostazioni del sito",
|
||||||
"siteSetting": "Impostazioni del sito {siteName}",
|
"siteSetting": "Impostazioni {siteName}",
|
||||||
"siteNewtTunnel": "Nuovo Sito (Consigliato)",
|
"siteNewtTunnel": "Nuovo Sito (Consigliato)",
|
||||||
"siteNewtTunnelDescription": "Modo più semplice per creare un entrypoint in qualsiasi rete. Nessuna configurazione aggiuntiva.",
|
"siteNewtTunnelDescription": "Modo più semplice per creare un entrypoint in qualsiasi rete. Nessuna configurazione aggiuntiva.",
|
||||||
"siteWg": "WireGuard Base",
|
"siteWg": "WireGuard Base",
|
||||||
"siteWgDescription": "Usa un qualsiasi client WireGuard per stabilire un tunnel. Impostazione NAT manuale richiesta.",
|
"siteWgDescription": "Usa qualsiasi client WireGuard per stabilire un tunnel. Impostazione NAT manuale richiesta.",
|
||||||
"siteWgDescriptionSaas": "Usa un qualsiasi client WireGuard per stabilire un tunnel. Impostazione NAT manuale richiesta.",
|
"siteWgDescriptionSaas": "Usa qualsiasi client WireGuard per stabilire un tunnel. Impostazione NAT manuale richiesta. FUNZIONA SOLO SU NODI AUTO-OSPITATI",
|
||||||
"siteLocalDescription": "Solo risorse locali. Nessun tunneling.",
|
"siteLocalDescription": "Solo risorse locali. Nessun tunneling.",
|
||||||
"siteLocalDescriptionSaas": "Solo risorse locali. Nessun tunneling. Disponibile solo su nodi remoti.",
|
"siteLocalDescriptionSaas": "Solo risorse locali. Nessun tunneling. Disponibile solo su nodi remoti.",
|
||||||
"siteSeeAll": "Vedi Tutti I Siti",
|
"siteSeeAll": "Vedi Tutti I Siti",
|
||||||
"siteTunnelDescription": "Selezionare la modalità con la quale si desidera connettersi al sito",
|
"siteTunnelDescription": "Determinare come si desidera connettersi al sito",
|
||||||
"siteNewtCredentials": "Credenziali",
|
"siteNewtCredentials": "Credenziali",
|
||||||
"siteNewtCredentialsDescription": "Questo è come il sito si autenticherà con il server",
|
"siteNewtCredentialsDescription": "Questo è come il sito si autenticerà con il server",
|
||||||
"remoteNodeCredentialsDescription": "Questo è il modo in cui il nodo remoto si autenticherà con il server",
|
"remoteNodeCredentialsDescription": "Questo è come il nodo remoto si autenticherà con il server",
|
||||||
"siteCredentialsSave": "Salva le credenziali",
|
"siteCredentialsSave": "Salva le credenziali",
|
||||||
"siteCredentialsSaveDescription": "Potrai vederlo solo una volta. Assicurati di copiarlo in un luogo sicuro.",
|
"siteCredentialsSaveDescription": "Potrai vederlo solo una volta. Assicurati di copiarlo in un luogo sicuro.",
|
||||||
"siteInfo": "Informazioni Sito",
|
"siteInfo": "Informazioni Sito",
|
||||||
@@ -140,8 +140,8 @@
|
|||||||
"shareCreateDescription": "Chiunque con questo link può accedere alla risorsa",
|
"shareCreateDescription": "Chiunque con questo link può accedere alla risorsa",
|
||||||
"shareTitleOptional": "Titolo (facoltativo)",
|
"shareTitleOptional": "Titolo (facoltativo)",
|
||||||
"expireIn": "Scadenza In",
|
"expireIn": "Scadenza In",
|
||||||
"neverExpire": "Nessuna scadenza",
|
"neverExpire": "Mai scadere",
|
||||||
"shareExpireDescription": "Il tempo di scadenza indica per quanto tempo il link sarà utilizzabile e fornirà accesso alla risorsa. Dopo questo tempo, il link non funzionerà più e gli utenti che hanno utilizzato questo link perderanno l'accesso alla risorsa.",
|
"shareExpireDescription": "Il tempo di scadenza è per quanto tempo il link sarà utilizzabile e fornirà accesso alla risorsa. Dopo questo tempo, il link non funzionerà più e gli utenti che hanno utilizzato questo link perderanno l'accesso alla risorsa.",
|
||||||
"shareSeeOnce": "Potrai vedere questo link solo una volta. Assicurati di copiarlo.",
|
"shareSeeOnce": "Potrai vedere questo link solo una volta. Assicurati di copiarlo.",
|
||||||
"shareAccessHint": "Chiunque abbia questo link può accedere alla risorsa. Condividilo con cura.",
|
"shareAccessHint": "Chiunque abbia questo link può accedere alla risorsa. Condividilo con cura.",
|
||||||
"shareTokenUsage": "Vedi Utilizzo Token Di Accesso",
|
"shareTokenUsage": "Vedi Utilizzo Token Di Accesso",
|
||||||
@@ -161,9 +161,9 @@
|
|||||||
"never": "Mai",
|
"never": "Mai",
|
||||||
"shareErrorSelectResource": "Seleziona una risorsa",
|
"shareErrorSelectResource": "Seleziona una risorsa",
|
||||||
"proxyResourceTitle": "Gestisci Risorse Pubbliche",
|
"proxyResourceTitle": "Gestisci Risorse Pubbliche",
|
||||||
"proxyResourceDescription": "Creare e gestire risorse pubbliche accessibili tramite un browser web",
|
"proxyResourceDescription": "Creare e gestire risorse accessibili al pubblico tramite un browser web",
|
||||||
"proxyResourcesBannerTitle": "Accesso Pubblico Basato sul Web",
|
"proxyResourcesBannerTitle": "Accesso Pubblico Basato sul Web",
|
||||||
"proxyResourcesBannerDescription": "Le risorse pubbliche sono proxy HTTPS o TCP/UDP accessibili da chiunque tramite Internet da un browser web. A differenza delle risorse private non richiedono software lato client e possono includere politiche di accesso basate su identità e contesto.",
|
"proxyResourcesBannerDescription": "Le risorse pubbliche sono proxy HTTPS o TCP/UDP accessibili a chiunque su Internet tramite un browser web. A differenza delle risorse private, non richiedono software lato client e possono includere politiche di accesso basate su identità e contesto.",
|
||||||
"clientResourceTitle": "Gestisci Risorse Private",
|
"clientResourceTitle": "Gestisci Risorse Private",
|
||||||
"clientResourceDescription": "Crea e gestisci risorse accessibili solo tramite un client connesso",
|
"clientResourceDescription": "Crea e gestisci risorse accessibili solo tramite un client connesso",
|
||||||
"privateResourcesBannerTitle": "Accesso Privato Zero-Trust",
|
"privateResourcesBannerTitle": "Accesso Privato Zero-Trust",
|
||||||
@@ -174,12 +174,12 @@
|
|||||||
"authentication": "Autenticazione",
|
"authentication": "Autenticazione",
|
||||||
"protected": "Protetto",
|
"protected": "Protetto",
|
||||||
"notProtected": "Non Protetto",
|
"notProtected": "Non Protetto",
|
||||||
"resourceMessageRemove": "Una volta rimossa la risorsa non sarà più accessibile. Tutti gli oggetti target associati alla risorsa saranno rimossi.",
|
"resourceMessageRemove": "Una volta rimossa, la risorsa non sarà più accessibile. Tutti gli obiettivi associati alla risorsa saranno rimossi.",
|
||||||
"resourceQuestionRemove": "Sei sicuro di voler rimuovere la risorsa dall'organizzazione?",
|
"resourceQuestionRemove": "Sei sicuro di voler rimuovere la risorsa dall'organizzazione?",
|
||||||
"resourceHTTP": "Risorsa HTTPS",
|
"resourceHTTP": "Risorsa HTTPS",
|
||||||
"resourceHTTPDescription": "Richieste proxy su HTTPS usando un nome di dominio completo.",
|
"resourceHTTPDescription": "Richieste proxy su HTTPS usando un nome di dominio completo.",
|
||||||
"resourceRaw": "Risorsa Raw TCP/UDP",
|
"resourceRaw": "Risorsa Raw TCP/UDP",
|
||||||
"resourceRawDescription": "Richieste proxy su TCP/UDP raw utilizzando un numero di porta.",
|
"resourceRawDescription": "Richieste proxy su TCP/UDP grezzo utilizzando un numero di porta.",
|
||||||
"resourceRawDescriptionCloud": "Richiesta proxy su TCP/UDP grezzo utilizzando un numero di porta. Richiede siti per connettersi a un nodo remoto.",
|
"resourceRawDescriptionCloud": "Richiesta proxy su TCP/UDP grezzo utilizzando un numero di porta. Richiede siti per connettersi a un nodo remoto.",
|
||||||
"resourceCreate": "Crea Risorsa",
|
"resourceCreate": "Crea Risorsa",
|
||||||
"resourceCreateDescription": "Segui i passaggi seguenti per creare una nuova risorsa",
|
"resourceCreateDescription": "Segui i passaggi seguenti per creare una nuova risorsa",
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
"selectCountry": "Seleziona paese",
|
"selectCountry": "Seleziona paese",
|
||||||
"searchCountries": "Cerca paesi...",
|
"searchCountries": "Cerca paesi...",
|
||||||
"noCountryFound": "Nessun paese trovato.",
|
"noCountryFound": "Nessun paese trovato.",
|
||||||
"siteSelectionDescription": "Questo sito fornirà connettività all'oggetto target.",
|
"siteSelectionDescription": "Questo sito fornirà connettività all'obiettivo.",
|
||||||
"resourceType": "Tipo Di Risorsa",
|
"resourceType": "Tipo Di Risorsa",
|
||||||
"resourceTypeDescription": "Determinare come accedere alla risorsa",
|
"resourceTypeDescription": "Determinare come accedere alla risorsa",
|
||||||
"resourceHTTPSSettings": "Impostazioni HTTPS",
|
"resourceHTTPSSettings": "Impostazioni HTTPS",
|
||||||
@@ -206,13 +206,13 @@
|
|||||||
"protocol": "Protocollo",
|
"protocol": "Protocollo",
|
||||||
"protocolSelect": "Seleziona un protocollo",
|
"protocolSelect": "Seleziona un protocollo",
|
||||||
"resourcePortNumber": "Numero Porta",
|
"resourcePortNumber": "Numero Porta",
|
||||||
"resourcePortNumberDescription": "Il numero di porta esterna per le richieste proxy.",
|
"resourcePortNumberDescription": "Il numero di porta esterna per le richieste di proxy.",
|
||||||
"back": "Indietro",
|
"back": "Indietro",
|
||||||
"cancel": "Annulla",
|
"cancel": "Annulla",
|
||||||
"resourceConfig": "Snippet Di Configurazione",
|
"resourceConfig": "Snippet Di Configurazione",
|
||||||
"resourceConfigDescription": "Copia e incolla questi snippet di configurazione per configurare la risorsa TCP/UDP",
|
"resourceConfigDescription": "Copia e incolla questi snippet di configurazione per configurare la risorsa TCP/UDP",
|
||||||
"resourceAddEntrypoints": "Traefik: Aggiungi Entrypoint",
|
"resourceAddEntrypoints": "Traefik: Aggiungi Ingresso",
|
||||||
"resourceExposePorts": "Gerbil: espone le porte in Docker Compose",
|
"resourceExposePorts": "Gerbil: espone le porte in Docker componi",
|
||||||
"resourceLearnRaw": "Scopri come configurare le risorse TCP/UDP",
|
"resourceLearnRaw": "Scopri come configurare le risorse TCP/UDP",
|
||||||
"resourceBack": "Torna alle risorse",
|
"resourceBack": "Torna alle risorse",
|
||||||
"resourceGoTo": "Vai alla Risorsa",
|
"resourceGoTo": "Vai alla Risorsa",
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
"rules": "Regole",
|
"rules": "Regole",
|
||||||
"resourceSettingDescription": "Configura le impostazioni sulla risorsa",
|
"resourceSettingDescription": "Configura le impostazioni sulla risorsa",
|
||||||
"resourceSetting": "Impostazioni {resourceName}",
|
"resourceSetting": "Impostazioni {resourceName}",
|
||||||
"alwaysAllow": "Bypass Autenticazione",
|
"alwaysAllow": "Autenticazione Bypass",
|
||||||
"alwaysDeny": "Blocca Accesso",
|
"alwaysDeny": "Blocca Accesso",
|
||||||
"passToAuth": "Passa all'autenticazione",
|
"passToAuth": "Passa all'autenticazione",
|
||||||
"orgSettingsDescription": "Configura le impostazioni dell'organizzazione",
|
"orgSettingsDescription": "Configura le impostazioni dell'organizzazione",
|
||||||
@@ -237,11 +237,11 @@
|
|||||||
"saveGeneralSettings": "Salva Impostazioni Generali",
|
"saveGeneralSettings": "Salva Impostazioni Generali",
|
||||||
"saveSettings": "Salva Impostazioni",
|
"saveSettings": "Salva Impostazioni",
|
||||||
"orgDangerZone": "Zona Pericolosa",
|
"orgDangerZone": "Zona Pericolosa",
|
||||||
"orgDangerZoneDescription": "Una volta che si elimina questa org non sarà possibile tornare indietro, assicurarsi quindi di essere certi della decisione.",
|
"orgDangerZoneDescription": "Una volta che si elimina questo org, non c'è ritorno. Si prega di essere certi.",
|
||||||
"orgDelete": "Elimina Organizzazione",
|
"orgDelete": "Elimina Organizzazione",
|
||||||
"orgDeleteConfirm": "Conferma Elimina Organizzazione",
|
"orgDeleteConfirm": "Conferma Elimina Organizzazione",
|
||||||
"orgMessageRemove": "Questa azione è irreversibile e cancellerà tutti i dati associati.",
|
"orgMessageRemove": "Questa azione è irreversibile e cancellerà tutti i dati associati.",
|
||||||
"orgMessageConfirm": "Per confermare digita il nome dell'organizzazione qui sotto.",
|
"orgMessageConfirm": "Per confermare, digita il nome dell'organizzazione qui sotto.",
|
||||||
"orgQuestionRemove": "Sei sicuro di voler rimuovere l'organizzazione?",
|
"orgQuestionRemove": "Sei sicuro di voler rimuovere l'organizzazione?",
|
||||||
"orgUpdated": "Organizzazione aggiornata",
|
"orgUpdated": "Organizzazione aggiornata",
|
||||||
"orgUpdatedDescription": "L'organizzazione è stata aggiornata.",
|
"orgUpdatedDescription": "L'organizzazione è stata aggiornata.",
|
||||||
@@ -254,10 +254,10 @@
|
|||||||
"orgDeleted": "Organizzazione eliminata",
|
"orgDeleted": "Organizzazione eliminata",
|
||||||
"orgDeletedMessage": "L'organizzazione e i suoi dati sono stati eliminati.",
|
"orgDeletedMessage": "L'organizzazione e i suoi dati sono stati eliminati.",
|
||||||
"deleteAccount": "Elimina Account",
|
"deleteAccount": "Elimina Account",
|
||||||
"deleteAccountDescription": "Elimina definitivamente il tuo account, tutte le organizzazioni che possiedi e tutti i dati all'interno di tali organizzazioni. Questa operazione non può essere annullata.",
|
"deleteAccountDescription": "Elimina definitivamente il tuo account, tutte le organizzazioni che possiedi e tutti i dati all'interno di tali organizzazioni. Questo non può essere annullato.",
|
||||||
"deleteAccountButton": "Elimina Account",
|
"deleteAccountButton": "Elimina Account",
|
||||||
"deleteAccountConfirmTitle": "Elimina Account",
|
"deleteAccountConfirmTitle": "Elimina Account",
|
||||||
"deleteAccountConfirmMessage": "Questa operazione cancellerà definitivamente il tuo account, tutte le organizzazioni che possiedi e tutti i dati all'interno di tali organizzazioni. Questa operazione non può essere annullata.",
|
"deleteAccountConfirmMessage": "Questo cancellerà definitivamente il tuo account, tutte le organizzazioni che possiedi e tutti i dati all'interno di tali organizzazioni. Questo non può essere annullato.",
|
||||||
"deleteAccountConfirmString": "elimina account",
|
"deleteAccountConfirmString": "elimina account",
|
||||||
"deleteAccountSuccess": "Account Eliminato",
|
"deleteAccountSuccess": "Account Eliminato",
|
||||||
"deleteAccountSuccessMessage": "Il tuo account è stato eliminato.",
|
"deleteAccountSuccessMessage": "Il tuo account è stato eliminato.",
|
||||||
@@ -272,7 +272,7 @@
|
|||||||
"accessUserCreate": "Crea Utente",
|
"accessUserCreate": "Crea Utente",
|
||||||
"accessUserRemove": "Rimuovi Utente",
|
"accessUserRemove": "Rimuovi Utente",
|
||||||
"username": "Nome utente",
|
"username": "Nome utente",
|
||||||
"identityProvider": "Provider Identità",
|
"identityProvider": "Provider Di Identità",
|
||||||
"role": "Ruolo",
|
"role": "Ruolo",
|
||||||
"nameRequired": "Il nome è obbligatorio",
|
"nameRequired": "Il nome è obbligatorio",
|
||||||
"accessRolesManage": "Gestisci Ruoli",
|
"accessRolesManage": "Gestisci Ruoli",
|
||||||
@@ -328,8 +328,8 @@
|
|||||||
"apiKeysDelete": "Elimina Chiave API",
|
"apiKeysDelete": "Elimina Chiave API",
|
||||||
"apiKeysManage": "Gestisci Chiavi API",
|
"apiKeysManage": "Gestisci Chiavi API",
|
||||||
"apiKeysDescription": "Le chiavi API sono utilizzate per autenticarsi con l'API di integrazione",
|
"apiKeysDescription": "Le chiavi API sono utilizzate per autenticarsi con l'API di integrazione",
|
||||||
"provisioningKeysTitle": "Chiave di provisioning",
|
"provisioningKeysTitle": "Chiave Di Provvedimento",
|
||||||
"provisioningKeysManage": "Gestisci Chiavi di provisioning",
|
"provisioningKeysManage": "Gestisci Chiavi Di Provvedimento",
|
||||||
"provisioningKeysDescription": "Le chiavi di provisioning vengono utilizzate per autenticare il provisioning automatico del sito per la tua organizzazione.",
|
"provisioningKeysDescription": "Le chiavi di provisioning vengono utilizzate per autenticare il provisioning automatico del sito per la tua organizzazione.",
|
||||||
"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.",
|
||||||
@@ -337,25 +337,25 @@
|
|||||||
"siteApproveSuccess": "Sito approvato con successo",
|
"siteApproveSuccess": "Sito approvato con successo",
|
||||||
"siteApproveError": "Errore nell'approvazione del sito",
|
"siteApproveError": "Errore nell'approvazione del sito",
|
||||||
"provisioningKeys": "Chiavi Di Provvedimento",
|
"provisioningKeys": "Chiavi Di Provvedimento",
|
||||||
"searchProvisioningKeys": "Cerca le chiavi di provisioning...",
|
"searchProvisioningKeys": "Cerca i tasti di provisioning ...",
|
||||||
"provisioningKeysAdd": "Genera Chiave di provisioning",
|
"provisioningKeysAdd": "Genera Chiave Di Provvedimento",
|
||||||
"provisioningKeysErrorDelete": "Errore nell'eliminazione della chiave di provisioning",
|
"provisioningKeysErrorDelete": "Errore nell'eliminare la chiave di provisioning",
|
||||||
"provisioningKeysErrorDeleteMessage": "Errore nell'eliminazione della chiave di provisioning",
|
"provisioningKeysErrorDeleteMessage": "Errore nell'eliminare la chiave di provisioning",
|
||||||
"provisioningKeysQuestionRemove": "Sei sicuro di voler rimuovere questa chiave di provisioning dall'organizzazione?",
|
"provisioningKeysQuestionRemove": "Sei sicuro di voler rimuovere questa chiave di provisioning dall'organizzazione?",
|
||||||
"provisioningKeysMessageRemove": "Una volta rimossa, la chiave non può più essere utilizzata per il provisioning.",
|
"provisioningKeysMessageRemove": "Una volta rimossa, la chiave non può più essere utilizzata per il provisioning.",
|
||||||
"provisioningKeysDeleteConfirm": "Conferma Eliminazione della chiave di provisioning",
|
"provisioningKeysDeleteConfirm": "Conferma Elimina Chiave Provvisoria",
|
||||||
"provisioningKeysDelete": "Elimina chiave di provisioning",
|
"provisioningKeysDelete": "Elimina chiave di provisioning",
|
||||||
"provisioningKeysCreate": "Genera Chiave di provisioning",
|
"provisioningKeysCreate": "Genera Chiave Di Provvedimento",
|
||||||
"provisioningKeysCreateDescription": "Genera una nuova chiave di provisioning per l'organizzazione",
|
"provisioningKeysCreateDescription": "Genera una nuova chiave di provisioning per l'organizzazione",
|
||||||
"provisioningKeysSeeAll": "Vedi tutte le chiavi di provisioning",
|
"provisioningKeysSeeAll": "Vedi tutte le chiavi di provisioning",
|
||||||
"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 lotto",
|
||||||
"provisioningKeysUnlimitedBatchSize": "Dimensione illimitata del batch (nessun limite)",
|
"provisioningKeysUnlimitedBatchSize": "Dimensione illimitata del lotto (nessun limite)",
|
||||||
"provisioningKeysMaxBatchUnlimited": "Illimitato",
|
"provisioningKeysMaxBatchUnlimited": "Illimitato",
|
||||||
"provisioningKeysMaxBatchSizeInvalid": "Inserisci una dimensione massima valida del batch (1–1.000.000).",
|
"provisioningKeysMaxBatchSizeInvalid": "Inserisci un lotto massimo valido (1–1.000.000).",
|
||||||
"provisioningKeysValidUntil": "Valido fino al",
|
"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.",
|
||||||
@@ -363,14 +363,14 @@
|
|||||||
"provisioningKeysLastUsed": "Ultimo utilizzo",
|
"provisioningKeysLastUsed": "Ultimo utilizzo",
|
||||||
"provisioningKeysNoExpiry": "Nessuna scadenza",
|
"provisioningKeysNoExpiry": "Nessuna scadenza",
|
||||||
"provisioningKeysNeverUsed": "Mai",
|
"provisioningKeysNeverUsed": "Mai",
|
||||||
"provisioningKeysEdit": "Modifica Chiave di provisioning",
|
"provisioningKeysEdit": "Modifica Chiave Di Provvedimento",
|
||||||
"provisioningKeysEditDescription": "Aggiorna la dimensione massima del batch e il tempo di scadenza per questa chiave.",
|
"provisioningKeysEditDescription": "Aggiorna la dimensione massima del lotto 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 accantonamento aggiornata",
|
||||||
"provisioningKeysUpdatedDescription": "Le tue modifiche sono state salvate.",
|
"provisioningKeysUpdatedDescription": "Le tue modifiche sono state salvate.",
|
||||||
"provisioningKeysBannerTitle": "Chiavi di provisioning del Sito",
|
"provisioningKeysBannerTitle": "Chiavi Di Provvedimento Sito",
|
||||||
"provisioningKeysBannerDescription": "Genera una chiave di provisioning e usala con il connettore Newt per creare automaticamente i siti al primo avvio - non è necessario configurare credenziali separate per ogni sito.",
|
"provisioningKeysBannerDescription": "Genera una chiave di provisioning e usala con il connettore Newt per creare automaticamente i siti al primo avvio - non è necessario configurare credenziali separate per ogni sito.",
|
||||||
"provisioningKeysBannerButtonText": "Scopri di più",
|
"provisioningKeysBannerButtonText": "Scopri di più",
|
||||||
"pendingSitesBannerTitle": "Siti In Attesa",
|
"pendingSitesBannerTitle": "Siti In Attesa",
|
||||||
@@ -386,7 +386,7 @@
|
|||||||
"userErrorDelete": "Errore nell'eliminare l'utente",
|
"userErrorDelete": "Errore nell'eliminare l'utente",
|
||||||
"userDeleteConfirm": "Conferma Eliminazione Utente",
|
"userDeleteConfirm": "Conferma Eliminazione Utente",
|
||||||
"userDeleteServer": "Elimina utente dal server",
|
"userDeleteServer": "Elimina utente dal server",
|
||||||
"userMessageRemove": "L'utente verrà rimosso da tutte le organizzazioni e verrà completamente rimosso dal server.",
|
"userMessageRemove": "L'utente verrà rimosso da tutte le organizzazioni ed essere completamente rimosso dal server.",
|
||||||
"userQuestionRemove": "Sei sicuro di voler eliminare definitivamente l'utente dal server?",
|
"userQuestionRemove": "Sei sicuro di voler eliminare definitivamente l'utente dal server?",
|
||||||
"licenseKey": "Chiave Di Licenza",
|
"licenseKey": "Chiave Di Licenza",
|
||||||
"valid": "Valido",
|
"valid": "Valido",
|
||||||
@@ -404,13 +404,9 @@
|
|||||||
"licenseKeyDeletedDescription": "La chiave di licenza è stata eliminata.",
|
"licenseKeyDeletedDescription": "La chiave di licenza è stata eliminata.",
|
||||||
"licenseErrorKeyActivate": "Attivazione della chiave di licenza non riuscita",
|
"licenseErrorKeyActivate": "Attivazione della chiave di licenza non riuscita",
|
||||||
"licenseErrorKeyActivateDescription": "Si è verificato un errore nell'attivazione della chiave di licenza.",
|
"licenseErrorKeyActivateDescription": "Si è verificato un errore nell'attivazione della chiave di licenza.",
|
||||||
"licenseAbout": "Informazioni sul Licensing",
|
"licenseAbout": "Informazioni Su Licenze",
|
||||||
"licenseBannerTitle": "Attiva la tua Licenza Enterprise",
|
|
||||||
"licenseBannerDescription": "Sblocca le funzionalità enterprise per la tua istanza Pangolin auto-ospitata. Acquista una chiave di licenza per attivare le capacità premium e poi aggiungila qui sotto.",
|
|
||||||
"licenseBannerGetLicense": "Ottieni una Licenza",
|
|
||||||
"licenseBannerViewDocs": "Visualizza Documentazione",
|
|
||||||
"communityEdition": "Edizione Community",
|
"communityEdition": "Edizione Community",
|
||||||
"licenseAboutDescription": "Questa sezione è per gli utenti aziendali e aziendali che utilizzano Pangolin in un ambiente commerciale. Se stai usando Pangolin per uso personale, puoi ignorare questa sezione.",
|
"licenseAboutDescription": "Questo è per gli utenti aziendali e aziendali che utilizzano Pangolin in un ambiente commerciale. Se stai usando Pangolin per uso personale, puoi ignorare questa sezione.",
|
||||||
"licenseKeyActivated": "Chiave di licenza attivata",
|
"licenseKeyActivated": "Chiave di licenza attivata",
|
||||||
"licenseKeyActivatedDescription": "La chiave di licenza è stata attivata correttamente.",
|
"licenseKeyActivatedDescription": "La chiave di licenza è stata attivata correttamente.",
|
||||||
"licenseErrorKeyRecheck": "Impossibile ricontrollare le chiavi di licenza",
|
"licenseErrorKeyRecheck": "Impossibile ricontrollare le chiavi di licenza",
|
||||||
@@ -433,7 +429,7 @@
|
|||||||
"licenseHostDescription": "Gestisci la chiave di licenza principale per l'host.",
|
"licenseHostDescription": "Gestisci la chiave di licenza principale per l'host.",
|
||||||
"licensedNot": "Non Licenziato",
|
"licensedNot": "Non Licenziato",
|
||||||
"hostId": "ID Host",
|
"hostId": "ID Host",
|
||||||
"licenseReckeckAll": "Ricontrolla Tutte le chiavi",
|
"licenseReckeckAll": "Ricontrolla Tutte Le Tasti",
|
||||||
"licenseSiteUsage": "Utilizzo Siti",
|
"licenseSiteUsage": "Utilizzo Siti",
|
||||||
"licenseSiteUsageDecsription": "Visualizza il numero di siti che utilizzano questa licenza.",
|
"licenseSiteUsageDecsription": "Visualizza il numero di siti che utilizzano questa licenza.",
|
||||||
"licenseNoSiteLimit": "Non c'è alcun limite al numero di siti che utilizzano un host senza licenza.",
|
"licenseNoSiteLimit": "Non c'è alcun limite al numero di siti che utilizzano un host senza licenza.",
|
||||||
@@ -484,7 +480,7 @@
|
|||||||
"userOrgRemoved": "Utente rimosso",
|
"userOrgRemoved": "Utente rimosso",
|
||||||
"userOrgRemovedDescription": "L'utente {email} è stato rimosso dall'organizzazione.",
|
"userOrgRemovedDescription": "L'utente {email} è stato rimosso dall'organizzazione.",
|
||||||
"userQuestionOrgRemove": "Sei sicuro di voler rimuovere questo utente dall'organizzazione?",
|
"userQuestionOrgRemove": "Sei sicuro di voler rimuovere questo utente dall'organizzazione?",
|
||||||
"userMessageOrgRemove": "Una volta rimosso questo utente non avrà più accesso all'organizzazione. Puoi sempre reinvitarlo in seguito, ma dovrà accettare nuovamente l'invito.",
|
"userMessageOrgRemove": "Una volta rimosso, questo utente non avrà più accesso all'organizzazione. Puoi sempre reinvitarlo in seguito, ma dovrà accettare nuovamente l'invito.",
|
||||||
"userRemoveOrgConfirm": "Conferma Rimozione Utente",
|
"userRemoveOrgConfirm": "Conferma Rimozione Utente",
|
||||||
"userRemoveOrg": "Rimuovi Utente dall'Organizzazione",
|
"userRemoveOrg": "Rimuovi Utente dall'Organizzazione",
|
||||||
"users": "Utenti",
|
"users": "Utenti",
|
||||||
@@ -536,13 +532,13 @@
|
|||||||
"approve": "Approva",
|
"approve": "Approva",
|
||||||
"approved": "Approvato",
|
"approved": "Approvato",
|
||||||
"denied": "Negato",
|
"denied": "Negato",
|
||||||
"deniedApproval": "Approvazione Negata",
|
"deniedApproval": "Omologazione Negata",
|
||||||
"all": "Tutti",
|
"all": "Tutti",
|
||||||
"deny": "Nega",
|
"deny": "Nega",
|
||||||
"viewDetails": "Visualizza Dettagli",
|
"viewDetails": "Visualizza Dettagli",
|
||||||
"requestingNewDeviceApproval": "ha richiesto un nuovo dispositivo",
|
"requestingNewDeviceApproval": "ha richiesto un nuovo dispositivo",
|
||||||
"resetFilters": "Ripristina Filtri",
|
"resetFilters": "Ripristina Filtri",
|
||||||
"totalBlocked": "Richieste Bloccate Da Pangolin",
|
"totalBlocked": "Richieste Bloccate Da Pangolino",
|
||||||
"totalRequests": "Totale Richieste",
|
"totalRequests": "Totale Richieste",
|
||||||
"requestsByCountry": "Richieste Per Paese",
|
"requestsByCountry": "Richieste Per Paese",
|
||||||
"requestsByDay": "Richieste Per Giorno",
|
"requestsByDay": "Richieste Per Giorno",
|
||||||
@@ -550,7 +546,7 @@
|
|||||||
"allowed": "Consentito",
|
"allowed": "Consentito",
|
||||||
"topCountries": "Paesi Principali",
|
"topCountries": "Paesi Principali",
|
||||||
"accessRoleSelect": "Seleziona ruolo",
|
"accessRoleSelect": "Seleziona ruolo",
|
||||||
"inviteEmailSentDescription": "È stata inviata un'email all'utente con il link di accesso qui sotto. L'utente deve accedere al link per accettare l'invito.",
|
"inviteEmailSentDescription": "È stata inviata un'email all'utente con il link di accesso qui sotto. Devono accedere al link per accettare l'invito.",
|
||||||
"inviteSentDescription": "L'utente è stato invitato. Deve accedere al link qui sotto per accettare l'invito.",
|
"inviteSentDescription": "L'utente è stato invitato. Deve accedere al link qui sotto per accettare l'invito.",
|
||||||
"inviteExpiresIn": "L'invito scadrà tra {days, plural, one {# giorno} other {# giorni}}.",
|
"inviteExpiresIn": "L'invito scadrà tra {days, plural, one {# giorno} other {# giorni}}.",
|
||||||
"idpTitle": "Informazioni Generali",
|
"idpTitle": "Informazioni Generali",
|
||||||
@@ -566,7 +562,7 @@
|
|||||||
"userSaved": "Utente salvato",
|
"userSaved": "Utente salvato",
|
||||||
"userSavedDescription": "L'utente è stato aggiornato.",
|
"userSavedDescription": "L'utente è stato aggiornato.",
|
||||||
"autoProvisioned": "Auto Provisioned",
|
"autoProvisioned": "Auto Provisioned",
|
||||||
"autoProvisionSettings": "Impostazioni Automatiche di provisioning",
|
"autoProvisionSettings": "Impostazioni Automatiche Di Fornitura",
|
||||||
"autoProvisionedDescription": "Permetti a questo utente di essere gestito automaticamente dal provider di identità",
|
"autoProvisionedDescription": "Permetti a questo utente di essere gestito automaticamente dal provider di identità",
|
||||||
"accessControlsDescription": "Gestisci cosa questo utente può accedere e fare nell'organizzazione",
|
"accessControlsDescription": "Gestisci cosa questo utente può accedere e fare nell'organizzazione",
|
||||||
"accessControlsSubmit": "Salva Controlli di Accesso",
|
"accessControlsSubmit": "Salva Controlli di Accesso",
|
||||||
@@ -580,9 +576,9 @@
|
|||||||
"proxyErrorInvalidHeader": "Valore dell'intestazione Host personalizzata non valido. Usa il formato nome dominio o salva vuoto per rimuovere l'intestazione Host personalizzata.",
|
"proxyErrorInvalidHeader": "Valore dell'intestazione Host personalizzata non valido. Usa il formato nome dominio o salva vuoto per rimuovere l'intestazione Host personalizzata.",
|
||||||
"proxyErrorTls": "Nome Server TLS non valido. Usa il formato nome dominio o salva vuoto per rimuovere il Nome Server TLS.",
|
"proxyErrorTls": "Nome Server TLS non valido. Usa il formato nome dominio o salva vuoto per rimuovere il Nome Server TLS.",
|
||||||
"proxyEnableSSL": "Abilita SSL",
|
"proxyEnableSSL": "Abilita SSL",
|
||||||
"proxyEnableSSLDescription": "Abilita la crittografia SSL/TLS per connessioni HTTPS sicure alle risorse interne target.",
|
"proxyEnableSSLDescription": "Abilita la crittografia SSL/TLS per connessioni HTTPS sicure agli obiettivi.",
|
||||||
"target": "Target",
|
"target": "Target",
|
||||||
"configureTarget": "Configura Risorse Interne",
|
"configureTarget": "Configura Obiettivi",
|
||||||
"targetErrorFetch": "Impossibile recuperare i target",
|
"targetErrorFetch": "Impossibile recuperare i target",
|
||||||
"targetErrorFetchDescription": "Si è verificato un errore durante il recupero dei target",
|
"targetErrorFetchDescription": "Si è verificato un errore durante il recupero dei target",
|
||||||
"siteErrorFetch": "Impossibile recuperare la risorsa",
|
"siteErrorFetch": "Impossibile recuperare la risorsa",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Seleziona un dominio per la pagina di autenticazione dell'organizzazione",
|
"selectDomainForOrgAuthPage": "Seleziona un dominio per la pagina di autenticazione dell'organizzazione",
|
||||||
"domainPickerProvidedDomain": "Dominio Fornito",
|
"domainPickerProvidedDomain": "Dominio Fornito",
|
||||||
"domainPickerFreeProvidedDomain": "Dominio Fornito Gratuito",
|
"domainPickerFreeProvidedDomain": "Dominio Fornito Gratuito",
|
||||||
"domainPickerFreeDomainsPaidFeature": "I domini forniti sono una funzionalità a pagamento. Abbonati per ricevere un dominio incluso con il tuo piano — non è necessario portare il proprio.",
|
|
||||||
"domainPickerVerified": "Verificato",
|
"domainPickerVerified": "Verificato",
|
||||||
"domainPickerUnverified": "Non Verificato",
|
"domainPickerUnverified": "Non Verificato",
|
||||||
"domainPickerManual": "Manuale",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Questo sottodominio contiene caratteri o struttura non validi. Sarà sanificato automaticamente quando si salva.",
|
"domainPickerInvalidSubdomainStructure": "Questo sottodominio contiene caratteri o struttura non validi. Sarà sanificato automaticamente quando si salva.",
|
||||||
"domainPickerError": "Errore",
|
"domainPickerError": "Errore",
|
||||||
"domainPickerErrorLoadDomains": "Impossibile caricare i domini dell'organizzazione",
|
"domainPickerErrorLoadDomains": "Impossibile caricare i domini dell'organizzazione",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "라이센스 키 활성화에 실패했습니다.",
|
"licenseErrorKeyActivate": "라이센스 키 활성화에 실패했습니다.",
|
||||||
"licenseErrorKeyActivateDescription": "라이센스 키를 활성화하는 동안 오류가 발생했습니다",
|
"licenseErrorKeyActivateDescription": "라이센스 키를 활성화하는 동안 오류가 발생했습니다",
|
||||||
"licenseAbout": "라이센스에 대한 정보",
|
"licenseAbout": "라이센스에 대한 정보",
|
||||||
"licenseBannerTitle": "기업 라이선스 활성화",
|
|
||||||
"licenseBannerDescription": "자체 호스팅된 Pangolin 인스턴스에서 기업 기능을 잠금 해제하십시오. 라이선스 키를 구입하여 프리미엄 기능을 활성화하고 아래에 추가하십시오.",
|
|
||||||
"licenseBannerGetLicense": "라이선스 획득",
|
|
||||||
"licenseBannerViewDocs": "문서 보기",
|
|
||||||
"communityEdition": "커뮤니티 에디션",
|
"communityEdition": "커뮤니티 에디션",
|
||||||
"licenseAboutDescription": "이것은 상업적 환경에서 Pangolin을 사용하는 비즈니스 및 기업 사용자용입니다. 개인 용도로 Pangolin을 사용하는 경우 이 섹션을 무시할 수 있습니다.",
|
"licenseAboutDescription": "이것은 상업적 환경에서 Pangolin을 사용하는 비즈니스 및 기업 사용자용입니다. 개인 용도로 Pangolin을 사용하는 경우 이 섹션을 무시할 수 있습니다.",
|
||||||
"licenseKeyActivated": "라이센스 키가 활성화되었습니다",
|
"licenseKeyActivated": "라이센스 키가 활성화되었습니다",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "조직 인증 페이지에 대한 도메인을 선택하세요.",
|
"selectDomainForOrgAuthPage": "조직 인증 페이지에 대한 도메인을 선택하세요.",
|
||||||
"domainPickerProvidedDomain": "제공된 도메인",
|
"domainPickerProvidedDomain": "제공된 도메인",
|
||||||
"domainPickerFreeProvidedDomain": "무료 제공된 도메인",
|
"domainPickerFreeProvidedDomain": "무료 제공된 도메인",
|
||||||
"domainPickerFreeDomainsPaidFeature": "제공된 도메인은 유료 기능입니다. 요금제에 도메인이 포함되도록 구독하세요. — 별도로 도메인을 준비할 필요 없습니다.",
|
|
||||||
"domainPickerVerified": "검증됨",
|
"domainPickerVerified": "검증됨",
|
||||||
"domainPickerUnverified": "검증되지 않음",
|
"domainPickerUnverified": "검증되지 않음",
|
||||||
"domainPickerManual": "수동",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "이 하위 도메인은 잘못된 문자 또는 구조를 포함하고 있습니다. 저장 시 자동으로 정리됩니다.",
|
"domainPickerInvalidSubdomainStructure": "이 하위 도메인은 잘못된 문자 또는 구조를 포함하고 있습니다. 저장 시 자동으로 정리됩니다.",
|
||||||
"domainPickerError": "오류",
|
"domainPickerError": "오류",
|
||||||
"domainPickerErrorLoadDomains": "조직 도메인 로드 실패",
|
"domainPickerErrorLoadDomains": "조직 도메인 로드 실패",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Aktivering av lisensnøkkel feilet",
|
"licenseErrorKeyActivate": "Aktivering av lisensnøkkel feilet",
|
||||||
"licenseErrorKeyActivateDescription": "Det oppstod en feil under aktivering av lisensnøkkelen.",
|
"licenseErrorKeyActivateDescription": "Det oppstod en feil under aktivering av lisensnøkkelen.",
|
||||||
"licenseAbout": "Om Lisensiering",
|
"licenseAbout": "Om Lisensiering",
|
||||||
"licenseBannerTitle": "Aktiver din bedriftslisens",
|
|
||||||
"licenseBannerDescription": "Lås opp bedriftsfunksjoner for din egenvertede Pangolin-instans. Kjøp en lisensnøkkel for å aktivere premium-funksjoner og legg den inn nedenfor.",
|
|
||||||
"licenseBannerGetLicense": "Få en lisens",
|
|
||||||
"licenseBannerViewDocs": "Vis dokumentasjon",
|
|
||||||
"communityEdition": "Fellesskapsutgave",
|
"communityEdition": "Fellesskapsutgave",
|
||||||
"licenseAboutDescription": "Dette er for bedrifts- og foretaksbrukere som bruker Pangolin i et kommersielt miljø. Hvis du bruker Pangolin til personlig bruk, kan du ignorere denne seksjonen.",
|
"licenseAboutDescription": "Dette er for bedrifts- og foretaksbrukere som bruker Pangolin i et kommersielt miljø. Hvis du bruker Pangolin til personlig bruk, kan du ignorere denne seksjonen.",
|
||||||
"licenseKeyActivated": "Lisensnøkkel aktivert",
|
"licenseKeyActivated": "Lisensnøkkel aktivert",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Velg et domene for organisasjonens autentiseringsside",
|
"selectDomainForOrgAuthPage": "Velg et domene for organisasjonens autentiseringsside",
|
||||||
"domainPickerProvidedDomain": "Gitt domene",
|
"domainPickerProvidedDomain": "Gitt domene",
|
||||||
"domainPickerFreeProvidedDomain": "Gratis oppgitt domene",
|
"domainPickerFreeProvidedDomain": "Gratis oppgitt domene",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Angitte domener er en betalingsfunksjon. Abonner for å få et domene inkludert i din plan – ingen behov for å ta med ditt eget.",
|
|
||||||
"domainPickerVerified": "Bekreftet",
|
"domainPickerVerified": "Bekreftet",
|
||||||
"domainPickerUnverified": "Uverifisert",
|
"domainPickerUnverified": "Uverifisert",
|
||||||
"domainPickerManual": "Manuell",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Dette underdomenet inneholder ugyldige tegn eller struktur. Det vil automatisk bli utsatt når du lagrer.",
|
"domainPickerInvalidSubdomainStructure": "Dette underdomenet inneholder ugyldige tegn eller struktur. Det vil automatisk bli utsatt når du lagrer.",
|
||||||
"domainPickerError": "Feil",
|
"domainPickerError": "Feil",
|
||||||
"domainPickerErrorLoadDomains": "Kan ikke laste organisasjonens domener",
|
"domainPickerErrorLoadDomains": "Kan ikke laste organisasjonens domener",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Licentiesleutel activeren mislukt",
|
"licenseErrorKeyActivate": "Licentiesleutel activeren mislukt",
|
||||||
"licenseErrorKeyActivateDescription": "Er is een fout opgetreden tijdens het activeren van de licentiesleutel.",
|
"licenseErrorKeyActivateDescription": "Er is een fout opgetreden tijdens het activeren van de licentiesleutel.",
|
||||||
"licenseAbout": "Over licenties",
|
"licenseAbout": "Over licenties",
|
||||||
"licenseBannerTitle": "Activeer Uw Enterprise Licentie",
|
|
||||||
"licenseBannerDescription": "Ontgrendel enterprise-functies voor uw zelf-gehoste Pangolin-instantie. Koop een licentiesleutel om premium mogelijkheden te activeren, voeg deze vervolgens hieronder toe.",
|
|
||||||
"licenseBannerGetLicense": "Koop een Licentie",
|
|
||||||
"licenseBannerViewDocs": "Bekijk Documentatie",
|
|
||||||
"communityEdition": "Community editie",
|
"communityEdition": "Community editie",
|
||||||
"licenseAboutDescription": "Dit geldt voor gebruikers van bedrijven en ondernemingen die Pangolin in gebruiken in een commerciële omgeving. Als u Pangolin gebruikt voor persoonlijk gebruik, kunt u dit gedeelte negeren.",
|
"licenseAboutDescription": "Dit geldt voor gebruikers van bedrijven en ondernemingen die Pangolin in gebruiken in een commerciële omgeving. Als u Pangolin gebruikt voor persoonlijk gebruik, kunt u dit gedeelte negeren.",
|
||||||
"licenseKeyActivated": "Licentiesleutel geactiveerd",
|
"licenseKeyActivated": "Licentiesleutel geactiveerd",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Selecteer een domein voor de authenticatiepagina van de organisatie",
|
"selectDomainForOrgAuthPage": "Selecteer een domein voor de authenticatiepagina van de organisatie",
|
||||||
"domainPickerProvidedDomain": "Opgegeven domein",
|
"domainPickerProvidedDomain": "Opgegeven domein",
|
||||||
"domainPickerFreeProvidedDomain": "Gratis verstrekt domein",
|
"domainPickerFreeProvidedDomain": "Gratis verstrekt domein",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Geleverde domeinen zijn een betaalde functie. Abonneer je om een domein bij je plan te krijgen — je hoeft er zelf geen mee te brengen.",
|
|
||||||
"domainPickerVerified": "Geverifieerd",
|
"domainPickerVerified": "Geverifieerd",
|
||||||
"domainPickerUnverified": "Ongeverifieerd",
|
"domainPickerUnverified": "Ongeverifieerd",
|
||||||
"domainPickerManual": "Handleiding",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Dit subdomein bevat ongeldige tekens of structuur. Het zal automatisch worden gesaneerd wanneer u opslaat.",
|
"domainPickerInvalidSubdomainStructure": "Dit subdomein bevat ongeldige tekens of structuur. Het zal automatisch worden gesaneerd wanneer u opslaat.",
|
||||||
"domainPickerError": "Foutmelding",
|
"domainPickerError": "Foutmelding",
|
||||||
"domainPickerErrorLoadDomains": "Fout bij het laden van organisatiedomeinen",
|
"domainPickerErrorLoadDomains": "Fout bij het laden van organisatiedomeinen",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Nie udało się aktywować klucza licencji",
|
"licenseErrorKeyActivate": "Nie udało się aktywować klucza licencji",
|
||||||
"licenseErrorKeyActivateDescription": "Wystąpił błąd podczas aktywacji klucza licencyjnego.",
|
"licenseErrorKeyActivateDescription": "Wystąpił błąd podczas aktywacji klucza licencyjnego.",
|
||||||
"licenseAbout": "O licencjonowaniu",
|
"licenseAbout": "O licencjonowaniu",
|
||||||
"licenseBannerTitle": "Aktywuj swoją licencję Enterprise",
|
|
||||||
"licenseBannerDescription": "Odblokuj funkcje korporacyjne dla swojego autonomicznego wdrożenia Pangolin. Kup klucz licencyjny, aby aktywować możliwości premium, a następnie wprowadź go poniżej.",
|
|
||||||
"licenseBannerGetLicense": "Uzyskaj licencję",
|
|
||||||
"licenseBannerViewDocs": "Zobacz dokumentację",
|
|
||||||
"communityEdition": "Edycja Społecznościowa",
|
"communityEdition": "Edycja Społecznościowa",
|
||||||
"licenseAboutDescription": "Dotyczy to przedsiębiorstw i przedsiębiorstw, którzy stosują Pangolin w środowisku handlowym. Jeśli używasz Pangolin do użytku osobistego, możesz zignorować tę sekcję.",
|
"licenseAboutDescription": "Dotyczy to przedsiębiorstw i przedsiębiorstw, którzy stosują Pangolin w środowisku handlowym. Jeśli używasz Pangolin do użytku osobistego, możesz zignorować tę sekcję.",
|
||||||
"licenseKeyActivated": "Klucz licencyjny aktywowany",
|
"licenseKeyActivated": "Klucz licencyjny aktywowany",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Wybierz domenę dla strony uwierzytelniania organizacji",
|
"selectDomainForOrgAuthPage": "Wybierz domenę dla strony uwierzytelniania organizacji",
|
||||||
"domainPickerProvidedDomain": "Dostarczona domena",
|
"domainPickerProvidedDomain": "Dostarczona domena",
|
||||||
"domainPickerFreeProvidedDomain": "Darmowa oferowana domena",
|
"domainPickerFreeProvidedDomain": "Darmowa oferowana domena",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Dostarczane domeny to funkcja płatna. Subskrybuj, aby uzyskać domenę w ramach swojego planu — nie ma potrzeby przynoszenia własnej.",
|
|
||||||
"domainPickerVerified": "Zweryfikowano",
|
"domainPickerVerified": "Zweryfikowano",
|
||||||
"domainPickerUnverified": "Niezweryfikowane",
|
"domainPickerUnverified": "Niezweryfikowane",
|
||||||
"domainPickerManual": "Podręcznik",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Ta subdomena zawiera nieprawidłowe znaki lub strukturę. Zostanie ona automatycznie oczyszczona po zapisaniu.",
|
"domainPickerInvalidSubdomainStructure": "Ta subdomena zawiera nieprawidłowe znaki lub strukturę. Zostanie ona automatycznie oczyszczona po zapisaniu.",
|
||||||
"domainPickerError": "Błąd",
|
"domainPickerError": "Błąd",
|
||||||
"domainPickerErrorLoadDomains": "Nie udało się załadować domen organizacji",
|
"domainPickerErrorLoadDomains": "Nie udało się załadować domen organizacji",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Falha ao ativar a chave de licença",
|
"licenseErrorKeyActivate": "Falha ao ativar a chave de licença",
|
||||||
"licenseErrorKeyActivateDescription": "Ocorreu um erro ao ativar a chave da licença.",
|
"licenseErrorKeyActivateDescription": "Ocorreu um erro ao ativar a chave da licença.",
|
||||||
"licenseAbout": "Sobre Licenciamento",
|
"licenseAbout": "Sobre Licenciamento",
|
||||||
"licenseBannerTitle": "Ative Sua Licença Corporativa",
|
|
||||||
"licenseBannerDescription": "Desbloqueie recursos empresariais para sua instância de Pangolin autohospedada. Compre uma chave de licença para ativar recursos premium e adicione-a abaixo.",
|
|
||||||
"licenseBannerGetLicense": "Obter Licença",
|
|
||||||
"licenseBannerViewDocs": "Ver Documentação",
|
|
||||||
"communityEdition": "Edição da Comunidade",
|
"communityEdition": "Edição da Comunidade",
|
||||||
"licenseAboutDescription": "Isto destina-se aos utilizadores empresariais e empresariais que estão a usar o Pangolin num ambiente comercial. Se você estiver usando o Pangolin para uso pessoal, você pode ignorar esta seção.",
|
"licenseAboutDescription": "Isto destina-se aos utilizadores empresariais e empresariais que estão a usar o Pangolin num ambiente comercial. Se você estiver usando o Pangolin para uso pessoal, você pode ignorar esta seção.",
|
||||||
"licenseKeyActivated": "Chave de licença ativada",
|
"licenseKeyActivated": "Chave de licença ativada",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Selecione um domínio para a página de autenticação da organização",
|
"selectDomainForOrgAuthPage": "Selecione um domínio para a página de autenticação da organização",
|
||||||
"domainPickerProvidedDomain": "Domínio fornecido",
|
"domainPickerProvidedDomain": "Domínio fornecido",
|
||||||
"domainPickerFreeProvidedDomain": "Domínio fornecido grátis",
|
"domainPickerFreeProvidedDomain": "Domínio fornecido grátis",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Os domínios fornecidos são um recurso pago. Assine para obter um domínio incluído no seu plano — não há necessidade de trazer o seu próprio.",
|
|
||||||
"domainPickerVerified": "Verificada",
|
"domainPickerVerified": "Verificada",
|
||||||
"domainPickerUnverified": "Não verificado",
|
"domainPickerUnverified": "Não verificado",
|
||||||
"domainPickerManual": "Manual",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Este subdomínio contém caracteres ou estrutura inválidos. Ele será eliminado automaticamente quando você salvar.",
|
"domainPickerInvalidSubdomainStructure": "Este subdomínio contém caracteres ou estrutura inválidos. Ele será eliminado automaticamente quando você salvar.",
|
||||||
"domainPickerError": "ERRO",
|
"domainPickerError": "ERRO",
|
||||||
"domainPickerErrorLoadDomains": "Falha ao carregar domínios da organização",
|
"domainPickerErrorLoadDomains": "Falha ao carregar domínios da organização",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Не удалось активировать лицензионный ключ",
|
"licenseErrorKeyActivate": "Не удалось активировать лицензионный ключ",
|
||||||
"licenseErrorKeyActivateDescription": "Произошла ошибка при активации лицензионного ключа.",
|
"licenseErrorKeyActivateDescription": "Произошла ошибка при активации лицензионного ключа.",
|
||||||
"licenseAbout": "О лицензировании",
|
"licenseAbout": "О лицензировании",
|
||||||
"licenseBannerTitle": "Активируйте вашу корпоративную лицензию",
|
|
||||||
"licenseBannerDescription": "Откройте доступ к корпоративным функциям для вашей локально размещаемой версии Pangolin. Приобретите лицензионный ключ, чтобы активировать премиум-функции, затем добавьте его ниже.",
|
|
||||||
"licenseBannerGetLicense": "Получить лицензию",
|
|
||||||
"licenseBannerViewDocs": "Посмотреть документацию",
|
|
||||||
"communityEdition": "Community Edition",
|
"communityEdition": "Community Edition",
|
||||||
"licenseAboutDescription": "Это для бизнес и корпоративных пользователей, использующих Pangolin в коммерческой среде. Если вы используете Pangolin для личного использования, вы можете игнорировать этот раздел.",
|
"licenseAboutDescription": "Это для бизнес и корпоративных пользователей, использующих Pangolin в коммерческой среде. Если вы используете Pangolin для личного использования, вы можете игнорировать этот раздел.",
|
||||||
"licenseKeyActivated": "Лицензионный ключ активирован",
|
"licenseKeyActivated": "Лицензионный ключ активирован",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Выберите домен для страницы аутентификации организации",
|
"selectDomainForOrgAuthPage": "Выберите домен для страницы аутентификации организации",
|
||||||
"domainPickerProvidedDomain": "Домен предоставлен",
|
"domainPickerProvidedDomain": "Домен предоставлен",
|
||||||
"domainPickerFreeProvidedDomain": "Бесплатный домен",
|
"domainPickerFreeProvidedDomain": "Бесплатный домен",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Предоставленные домены являются платной функцией. Подпишитесь, чтобы получить домен, включенный в ваш план — не нужно приносить свой собственный.",
|
|
||||||
"domainPickerVerified": "Подтверждено",
|
"domainPickerVerified": "Подтверждено",
|
||||||
"domainPickerUnverified": "Не подтверждено",
|
"domainPickerUnverified": "Не подтверждено",
|
||||||
"domainPickerManual": "Ручной",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Этот поддомен содержит недопустимые символы или структуру. Он будет очищен автоматически при сохранении.",
|
"domainPickerInvalidSubdomainStructure": "Этот поддомен содержит недопустимые символы или структуру. Он будет очищен автоматически при сохранении.",
|
||||||
"domainPickerError": "Ошибка",
|
"domainPickerError": "Ошибка",
|
||||||
"domainPickerErrorLoadDomains": "Не удалось загрузить домены организации",
|
"domainPickerErrorLoadDomains": "Не удалось загрузить домены организации",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "Lisans anahtarı etkinleştirilemedi",
|
"licenseErrorKeyActivate": "Lisans anahtarı etkinleştirilemedi",
|
||||||
"licenseErrorKeyActivateDescription": "Lisans anahtarı etkinleştirilirken bir hata oluştu.",
|
"licenseErrorKeyActivateDescription": "Lisans anahtarı etkinleştirilirken bir hata oluştu.",
|
||||||
"licenseAbout": "Lisans Hakkında",
|
"licenseAbout": "Lisans Hakkında",
|
||||||
"licenseBannerTitle": "Kurumsal Lisansınızı Etkinleştirin",
|
|
||||||
"licenseBannerDescription": "Kendi barındırdığınız Pangolin örneğiniz için kurumsal özelliklerin kilidini açın. Premium yetenekleri etkinleştirmek için bir lisans anahtarı satın alın, ardından aşağıya ekleyin.",
|
|
||||||
"licenseBannerGetLicense": "Lisans Alın",
|
|
||||||
"licenseBannerViewDocs": "Dokümantasyonu Görüntüleyin",
|
|
||||||
"communityEdition": "Topluluk Sürümü",
|
"communityEdition": "Topluluk Sürümü",
|
||||||
"licenseAboutDescription": "Bu, Pangolin'i ticari bir ortamda kullanan işletme ve kurumsal kullanıcılar içindir. Pangolin'i kişisel kullanım için kullanıyorsanız, bu bölümü görmezden gelebilirsiniz.",
|
"licenseAboutDescription": "Bu, Pangolin'i ticari bir ortamda kullanan işletme ve kurumsal kullanıcılar içindir. Pangolin'i kişisel kullanım için kullanıyorsanız, bu bölümü görmezden gelebilirsiniz.",
|
||||||
"licenseKeyActivated": "Lisans anahtarı etkinleştirildi",
|
"licenseKeyActivated": "Lisans anahtarı etkinleştirildi",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "Kuruluşun kimlik doğrulama sayfası için bir alan seçin",
|
"selectDomainForOrgAuthPage": "Kuruluşun kimlik doğrulama sayfası için bir alan seçin",
|
||||||
"domainPickerProvidedDomain": "Sağlanan Alan Adı",
|
"domainPickerProvidedDomain": "Sağlanan Alan Adı",
|
||||||
"domainPickerFreeProvidedDomain": "Ücretsiz Sağlanan Alan Adı",
|
"domainPickerFreeProvidedDomain": "Ücretsiz Sağlanan Alan Adı",
|
||||||
"domainPickerFreeDomainsPaidFeature": "Sağlanan alan adları ücretli bir özelliktir. Planınıza dahil bir alan adı almak için abone olun - kendi alan adınızı getirmenize gerek yok.",
|
|
||||||
"domainPickerVerified": "Doğrulandı",
|
"domainPickerVerified": "Doğrulandı",
|
||||||
"domainPickerUnverified": "Doğrulanmadı",
|
"domainPickerUnverified": "Doğrulanmadı",
|
||||||
"domainPickerManual": "Manuel",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "Bu alt alan adı geçersiz karakterler veya yapı içeriyor. Kaydettiğinizde otomatik olarak temizlenecektir.",
|
"domainPickerInvalidSubdomainStructure": "Bu alt alan adı geçersiz karakterler veya yapı içeriyor. Kaydettiğinizde otomatik olarak temizlenecektir.",
|
||||||
"domainPickerError": "Hata",
|
"domainPickerError": "Hata",
|
||||||
"domainPickerErrorLoadDomains": "Organizasyon alan adları yüklenemedi",
|
"domainPickerErrorLoadDomains": "Organizasyon alan adları yüklenemedi",
|
||||||
|
|||||||
@@ -405,10 +405,6 @@
|
|||||||
"licenseErrorKeyActivate": "激活许可证密钥失败",
|
"licenseErrorKeyActivate": "激活许可证密钥失败",
|
||||||
"licenseErrorKeyActivateDescription": "激活许可证密钥时出错。",
|
"licenseErrorKeyActivateDescription": "激活许可证密钥时出错。",
|
||||||
"licenseAbout": "关于许可协议",
|
"licenseAbout": "关于许可协议",
|
||||||
"licenseBannerTitle": "启用您的企业许可证",
|
|
||||||
"licenseBannerDescription": "为您自行托管的Pangolin实例解锁企业功能。购买许可证密钥以激活高级功能,然后在下方添加。",
|
|
||||||
"licenseBannerGetLicense": "获取许可证",
|
|
||||||
"licenseBannerViewDocs": "查看文档",
|
|
||||||
"communityEdition": "社区版",
|
"communityEdition": "社区版",
|
||||||
"licenseAboutDescription": "这是针对商业环境中使用Pangolin的商业和企业用户。 如果您正在使用 Pangolin 供个人使用,您可以忽略此部分。",
|
"licenseAboutDescription": "这是针对商业环境中使用Pangolin的商业和企业用户。 如果您正在使用 Pangolin 供个人使用,您可以忽略此部分。",
|
||||||
"licenseKeyActivated": "授权密钥已激活",
|
"licenseKeyActivated": "授权密钥已激活",
|
||||||
@@ -2118,10 +2114,8 @@
|
|||||||
"selectDomainForOrgAuthPage": "选择组织认证页面的域",
|
"selectDomainForOrgAuthPage": "选择组织认证页面的域",
|
||||||
"domainPickerProvidedDomain": "提供的域",
|
"domainPickerProvidedDomain": "提供的域",
|
||||||
"domainPickerFreeProvidedDomain": "免费提供的域",
|
"domainPickerFreeProvidedDomain": "免费提供的域",
|
||||||
"domainPickerFreeDomainsPaidFeature": "提供的域名是付费功能。订阅即可将域名包含在您的计划中—无需自带域名。",
|
|
||||||
"domainPickerVerified": "已验证",
|
"domainPickerVerified": "已验证",
|
||||||
"domainPickerUnverified": "未验证",
|
"domainPickerUnverified": "未验证",
|
||||||
"domainPickerManual": "手动",
|
|
||||||
"domainPickerInvalidSubdomainStructure": "此子域包含无效的字符或结构。当您保存时,它将被自动清除。",
|
"domainPickerInvalidSubdomainStructure": "此子域包含无效的字符或结构。当您保存时,它将被自动清除。",
|
||||||
"domainPickerError": "错误",
|
"domainPickerError": "错误",
|
||||||
"domainPickerErrorLoadDomains": "加载组织域名失败",
|
"domainPickerErrorLoadDomains": "加载组织域名失败",
|
||||||
|
|||||||
4790
messages/zh-TW.json
|
Before Width: | Height: | Size: 765 KiB After Width: | Height: | Size: 484 KiB |
|
Before Width: | Height: | Size: 742 KiB After Width: | Height: | Size: 421 KiB |
|
Before Width: | Height: | Size: 765 KiB After Width: | Height: | Size: 484 KiB |
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 597 KiB |
@@ -19,8 +19,7 @@ export enum TierFeature {
|
|||||||
SshPam = "sshPam",
|
SshPam = "sshPam",
|
||||||
FullRbac = "fullRbac",
|
FullRbac = "fullRbac",
|
||||||
SiteProvisioningKeys = "siteProvisioningKeys", // handle downgrade by revoking keys if needed
|
SiteProvisioningKeys = "siteProvisioningKeys", // handle downgrade by revoking keys if needed
|
||||||
SIEM = "siem", // handle downgrade by disabling SIEM integrations
|
SIEM = "siem" // handle downgrade by disabling SIEM integrations
|
||||||
DomainNamespaces = "domainNamespaces" // handle downgrade by removing custom domain namespaces
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tierMatrix: Record<TierFeature, Tier[]> = {
|
export const tierMatrix: Record<TierFeature, Tier[]> = {
|
||||||
@@ -57,6 +56,5 @@ export const tierMatrix: Record<TierFeature, Tier[]> = {
|
|||||||
[TierFeature.SshPam]: ["tier1", "tier3", "enterprise"],
|
[TierFeature.SshPam]: ["tier1", "tier3", "enterprise"],
|
||||||
[TierFeature.FullRbac]: ["tier1", "tier2", "tier3", "enterprise"],
|
[TierFeature.FullRbac]: ["tier1", "tier2", "tier3", "enterprise"],
|
||||||
[TierFeature.SiteProvisioningKeys]: ["tier3", "enterprise"],
|
[TierFeature.SiteProvisioningKeys]: ["tier3", "enterprise"],
|
||||||
[TierFeature.SIEM]: ["enterprise"],
|
[TierFeature.SIEM]: ["enterprise"]
|
||||||
[TierFeature.DomainNamespaces]: ["tier1", "tier2", "tier3", "enterprise"]
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,15 +22,11 @@ import { OpenAPITags, registry } from "@server/openApi";
|
|||||||
import { db, domainNamespaces, resources } from "@server/db";
|
import { db, domainNamespaces, resources } from "@server/db";
|
||||||
import { inArray } from "drizzle-orm";
|
import { inArray } from "drizzle-orm";
|
||||||
import { CheckDomainAvailabilityResponse } from "@server/routers/domain/types";
|
import { CheckDomainAvailabilityResponse } from "@server/routers/domain/types";
|
||||||
import { build } from "@server/build";
|
|
||||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({});
|
const paramsSchema = z.strictObject({});
|
||||||
|
|
||||||
const querySchema = z.strictObject({
|
const querySchema = z.strictObject({
|
||||||
subdomain: z.string(),
|
subdomain: z.string()
|
||||||
// orgId: build === "saas" ? z.string() : z.string().optional() // Required for saas, optional otherwise
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
@@ -62,23 +58,6 @@ export async function checkDomainNamespaceAvailability(
|
|||||||
}
|
}
|
||||||
const { subdomain } = parsedQuery.data;
|
const { subdomain } = parsedQuery.data;
|
||||||
|
|
||||||
// if (
|
|
||||||
// build == "saas" &&
|
|
||||||
// !isSubscribed(orgId!, tierMatrix.domainNamespaces)
|
|
||||||
// ) {
|
|
||||||
// // return not available
|
|
||||||
// return response<CheckDomainAvailabilityResponse>(res, {
|
|
||||||
// data: {
|
|
||||||
// available: false,
|
|
||||||
// options: []
|
|
||||||
// },
|
|
||||||
// success: true,
|
|
||||||
// error: false,
|
|
||||||
// message: "Your current subscription does not support custom domain namespaces. Please upgrade to access this feature.",
|
|
||||||
// status: HttpCode.OK
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
const namespaces = await db.select().from(domainNamespaces);
|
const namespaces = await db.select().from(domainNamespaces);
|
||||||
let possibleDomains = namespaces.map((ns) => {
|
let possibleDomains = namespaces.map((ns) => {
|
||||||
const desired = `${subdomain}.${ns.domainNamespaceId}`;
|
const desired = `${subdomain}.${ns.domainNamespaceId}`;
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ import { eq, sql } from "drizzle-orm";
|
|||||||
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";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
|
||||||
import { build } from "@server/build";
|
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({});
|
const paramsSchema = z.strictObject({});
|
||||||
|
|
||||||
@@ -40,8 +37,7 @@ const querySchema = z.strictObject({
|
|||||||
.optional()
|
.optional()
|
||||||
.default("0")
|
.default("0")
|
||||||
.transform(Number)
|
.transform(Number)
|
||||||
.pipe(z.int().nonnegative()),
|
.pipe(z.int().nonnegative())
|
||||||
// orgId: build === "saas" ? z.string() : z.string().optional() // Required for saas, optional otherwise
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function query(limit: number, offset: number) {
|
async function query(limit: number, offset: number) {
|
||||||
@@ -103,26 +99,6 @@ export async function listDomainNamespaces(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (
|
|
||||||
// build == "saas" &&
|
|
||||||
// !isSubscribed(orgId!, tierMatrix.domainNamespaces)
|
|
||||||
// ) {
|
|
||||||
// return response<ListDomainNamespacesResponse>(res, {
|
|
||||||
// data: {
|
|
||||||
// domainNamespaces: [],
|
|
||||||
// pagination: {
|
|
||||||
// total: 0,
|
|
||||||
// limit,
|
|
||||||
// offset
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// success: true,
|
|
||||||
// error: false,
|
|
||||||
// message: "No namespaces found. Your current subscription does not support custom domain namespaces. Please upgrade to access this feature.",
|
|
||||||
// status: HttpCode.OK
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
const domainNamespacesList = await query(limit, offset);
|
const domainNamespacesList = await query(limit, offset);
|
||||||
|
|
||||||
const [{ count }] = await db
|
const [{ count }] = await db
|
||||||
|
|||||||
@@ -440,12 +440,6 @@ authenticated.get(
|
|||||||
resource.getUserResources
|
resource.getUserResources
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
|
||||||
"/org/:orgId/user-resource-aliases",
|
|
||||||
verifyOrgAccess,
|
|
||||||
resource.listUserResourceAliases
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/domains",
|
"/org/:orgId/domains",
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, domainNamespaces, loginPage } from "@server/db";
|
import { db, loginPage } from "@server/db";
|
||||||
import {
|
import {
|
||||||
domains,
|
domains,
|
||||||
orgDomains,
|
orgDomains,
|
||||||
@@ -24,8 +24,6 @@ import { build } from "@server/build";
|
|||||||
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
||||||
import { getUniqueResourceName } from "@server/db/names";
|
import { getUniqueResourceName } from "@server/db/names";
|
||||||
import { validateAndConstructDomain } from "@server/lib/domainUtils";
|
import { validateAndConstructDomain } from "@server/lib/domainUtils";
|
||||||
import { isSubscribed } from "#dynamic/lib/isSubscribed";
|
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
|
||||||
|
|
||||||
const createResourceParamsSchema = z.strictObject({
|
const createResourceParamsSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -114,10 +112,7 @@ export async function createResource(
|
|||||||
|
|
||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
|
|
||||||
if (
|
if (req.user && (!req.userOrgRoleIds || req.userOrgRoleIds.length === 0)) {
|
||||||
req.user &&
|
|
||||||
(!req.userOrgRoleIds || req.userOrgRoleIds.length === 0)
|
|
||||||
) {
|
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.FORBIDDEN, "User does not have a role")
|
createHttpError(HttpCode.FORBIDDEN, "User does not have a role")
|
||||||
);
|
);
|
||||||
@@ -198,29 +193,6 @@ async function createHttpResource(
|
|||||||
const subdomain = parsedBody.data.subdomain;
|
const subdomain = parsedBody.data.subdomain;
|
||||||
const stickySession = parsedBody.data.stickySession;
|
const stickySession = parsedBody.data.stickySession;
|
||||||
|
|
||||||
if (build == "saas" && !isSubscribed(orgId!, tierMatrix.domainNamespaces)) {
|
|
||||||
// grandfather in existing users
|
|
||||||
const lastAllowedDate = new Date("2026-04-13");
|
|
||||||
const userCreatedDate = new Date(req.user?.dateCreated || new Date());
|
|
||||||
if (userCreatedDate > lastAllowedDate) {
|
|
||||||
// check if this domain id is a namespace domain and if so, reject
|
|
||||||
const domain = await db
|
|
||||||
.select()
|
|
||||||
.from(domainNamespaces)
|
|
||||||
.where(eq(domainNamespaces.domainId, domainId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (domain.length > 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"Your current subscription does not support custom domain namespaces. Please upgrade to access this feature."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate domain and construct full domain
|
// Validate domain and construct full domain
|
||||||
const domainResult = await validateAndConstructDomain(
|
const domainResult = await validateAndConstructDomain(
|
||||||
domainId,
|
domainId,
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ export async function getUserResources(
|
|||||||
let siteResourcesData: Array<{
|
let siteResourcesData: Array<{
|
||||||
siteResourceId: number;
|
siteResourceId: number;
|
||||||
name: string;
|
name: string;
|
||||||
niceId: string;
|
|
||||||
destination: string;
|
destination: string;
|
||||||
mode: string;
|
mode: string;
|
||||||
protocol: string | null;
|
protocol: string | null;
|
||||||
@@ -155,7 +154,6 @@ export async function getUserResources(
|
|||||||
.select({
|
.select({
|
||||||
siteResourceId: siteResources.siteResourceId,
|
siteResourceId: siteResources.siteResourceId,
|
||||||
name: siteResources.name,
|
name: siteResources.name,
|
||||||
niceId: siteResources.niceId,
|
|
||||||
destination: siteResources.destination,
|
destination: siteResources.destination,
|
||||||
mode: siteResources.mode,
|
mode: siteResources.mode,
|
||||||
protocol: siteResources.protocol,
|
protocol: siteResources.protocol,
|
||||||
@@ -251,7 +249,7 @@ export async function getUserResources(
|
|||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
data: {
|
data: {
|
||||||
resources: resourcesWithAuth,
|
resources: resourcesWithAuth,
|
||||||
siteResources: siteResourcesFormatted
|
siteResources: siteResourcesFormatted
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export * from "./deleteResourceRule";
|
|||||||
export * from "./listResourceRules";
|
export * from "./listResourceRules";
|
||||||
export * from "./updateResourceRule";
|
export * from "./updateResourceRule";
|
||||||
export * from "./getUserResources";
|
export * from "./getUserResources";
|
||||||
export * from "./listUserResourceAliases";
|
|
||||||
export * from "./setResourceHeaderAuth";
|
export * from "./setResourceHeaderAuth";
|
||||||
export * from "./addEmailToResourceWhitelist";
|
export * from "./addEmailToResourceWhitelist";
|
||||||
export * from "./removeEmailFromResourceWhitelist";
|
export * from "./removeEmailFromResourceWhitelist";
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
resourcePincode,
|
resourcePincode,
|
||||||
resources,
|
resources,
|
||||||
roleResources,
|
roleResources,
|
||||||
sites,
|
|
||||||
targetHealthCheck,
|
targetHealthCheck,
|
||||||
targets,
|
targets,
|
||||||
userResources
|
userResources
|
||||||
@@ -139,7 +138,6 @@ export type ResourceWithTargets = {
|
|||||||
port: number;
|
port: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
healthStatus: "healthy" | "unhealthy" | "unknown" | null;
|
healthStatus: "healthy" | "unhealthy" | "unknown" | null;
|
||||||
siteName: string | null;
|
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -448,16 +446,14 @@ export async function listResources(
|
|||||||
port: targets.port,
|
port: targets.port,
|
||||||
enabled: targets.enabled,
|
enabled: targets.enabled,
|
||||||
healthStatus: targetHealthCheck.hcHealth,
|
healthStatus: targetHealthCheck.hcHealth,
|
||||||
hcEnabled: targetHealthCheck.hcEnabled,
|
hcEnabled: targetHealthCheck.hcEnabled
|
||||||
siteName: sites.name
|
|
||||||
})
|
})
|
||||||
.from(targets)
|
.from(targets)
|
||||||
.where(inArray(targets.resourceId, resourceIdList))
|
.where(inArray(targets.resourceId, resourceIdList))
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
targetHealthCheck,
|
targetHealthCheck,
|
||||||
eq(targetHealthCheck.targetId, targets.targetId)
|
eq(targetHealthCheck.targetId, targets.targetId)
|
||||||
)
|
);
|
||||||
.leftJoin(sites, eq(targets.siteId, sites.siteId));
|
|
||||||
|
|
||||||
// avoids TS issues with reduce/never[]
|
// avoids TS issues with reduce/never[]
|
||||||
const map = new Map<number, ResourceWithTargets>();
|
const map = new Map<number, ResourceWithTargets>();
|
||||||
|
|||||||
@@ -1,262 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
|
||||||
import {
|
|
||||||
db,
|
|
||||||
siteResources,
|
|
||||||
userSiteResources,
|
|
||||||
roleSiteResources,
|
|
||||||
userOrgRoles,
|
|
||||||
userOrgs
|
|
||||||
} from "@server/db";
|
|
||||||
import { and, eq, inArray, asc, isNotNull, ne } from "drizzle-orm";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import response from "@server/lib/response";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { fromZodError } from "zod-validation-error";
|
|
||||||
import type { PaginatedResponse } from "@server/types/Pagination";
|
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
|
||||||
import { localCache } from "#dynamic/lib/cache";
|
|
||||||
|
|
||||||
const USER_RESOURCE_ALIASES_CACHE_TTL_SEC = 60;
|
|
||||||
|
|
||||||
function userResourceAliasesCacheKey(
|
|
||||||
orgId: string,
|
|
||||||
userId: string,
|
|
||||||
page: number,
|
|
||||||
pageSize: number
|
|
||||||
) {
|
|
||||||
return `userResourceAliases:${orgId}:${userId}:${page}:${pageSize}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const listUserResourceAliasesParamsSchema = z.strictObject({
|
|
||||||
orgId: z.string()
|
|
||||||
});
|
|
||||||
|
|
||||||
const listUserResourceAliasesQuerySchema = z.object({
|
|
||||||
pageSize: z.coerce
|
|
||||||
.number<string>()
|
|
||||||
.int()
|
|
||||||
.positive()
|
|
||||||
.optional()
|
|
||||||
.catch(20)
|
|
||||||
.default(20)
|
|
||||||
.openapi({
|
|
||||||
type: "integer",
|
|
||||||
default: 20,
|
|
||||||
description: "Number of items per page"
|
|
||||||
}),
|
|
||||||
page: z.coerce
|
|
||||||
.number<string>()
|
|
||||||
.int()
|
|
||||||
.min(0)
|
|
||||||
.optional()
|
|
||||||
.catch(1)
|
|
||||||
.default(1)
|
|
||||||
.openapi({
|
|
||||||
type: "integer",
|
|
||||||
default: 1,
|
|
||||||
description: "Page number to retrieve"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ListUserResourceAliasesResponse = PaginatedResponse<{
|
|
||||||
aliases: string[];
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// registry.registerPath({
|
|
||||||
// method: "get",
|
|
||||||
// path: "/org/{orgId}/user-resource-aliases",
|
|
||||||
// description:
|
|
||||||
// "List private (host-mode) site resource aliases the authenticated user can access in the organization, paginated.",
|
|
||||||
// tags: [OpenAPITags.PrivateResource],
|
|
||||||
// request: {
|
|
||||||
// params: z.object({
|
|
||||||
// orgId: z.string()
|
|
||||||
// }),
|
|
||||||
// query: listUserResourceAliasesQuerySchema
|
|
||||||
// },
|
|
||||||
// responses: {}
|
|
||||||
// });
|
|
||||||
|
|
||||||
export async function listUserResourceAliases(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
): Promise<any> {
|
|
||||||
try {
|
|
||||||
const parsedQuery = listUserResourceAliasesQuerySchema.safeParse(
|
|
||||||
req.query
|
|
||||||
);
|
|
||||||
if (!parsedQuery.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromZodError(parsedQuery.error)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const { page, pageSize } = parsedQuery.data;
|
|
||||||
|
|
||||||
const parsedParams = listUserResourceAliasesParamsSchema.safeParse(
|
|
||||||
req.params
|
|
||||||
);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromZodError(parsedParams.error)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { orgId } = parsedParams.data;
|
|
||||||
const userId = req.user?.userId;
|
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [userOrg] = await db
|
|
||||||
.select()
|
|
||||||
.from(userOrgs)
|
|
||||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!userOrg) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.FORBIDDEN, "User not in organization")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheKey = userResourceAliasesCacheKey(
|
|
||||||
orgId,
|
|
||||||
userId,
|
|
||||||
page,
|
|
||||||
pageSize
|
|
||||||
);
|
|
||||||
const cachedData: ListUserResourceAliasesResponse | undefined =
|
|
||||||
localCache.get(cacheKey);
|
|
||||||
|
|
||||||
if (cachedData) {
|
|
||||||
return response<ListUserResourceAliasesResponse>(res, {
|
|
||||||
data: cachedData,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "User resource aliases retrieved successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const userRoleIds = await db
|
|
||||||
.select({ roleId: userOrgRoles.roleId })
|
|
||||||
.from(userOrgRoles)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(userOrgRoles.userId, userId),
|
|
||||||
eq(userOrgRoles.orgId, orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then((rows) => rows.map((r) => r.roleId));
|
|
||||||
|
|
||||||
const directSiteResourcesQuery = db
|
|
||||||
.select({ siteResourceId: userSiteResources.siteResourceId })
|
|
||||||
.from(userSiteResources)
|
|
||||||
.where(eq(userSiteResources.userId, userId));
|
|
||||||
|
|
||||||
const roleSiteResourcesQuery =
|
|
||||||
userRoleIds.length > 0
|
|
||||||
? db
|
|
||||||
.select({
|
|
||||||
siteResourceId: roleSiteResources.siteResourceId
|
|
||||||
})
|
|
||||||
.from(roleSiteResources)
|
|
||||||
.where(inArray(roleSiteResources.roleId, userRoleIds))
|
|
||||||
: Promise.resolve([]);
|
|
||||||
|
|
||||||
const [directSiteResourceResults, roleSiteResourceResults] =
|
|
||||||
await Promise.all([
|
|
||||||
directSiteResourcesQuery,
|
|
||||||
roleSiteResourcesQuery
|
|
||||||
]);
|
|
||||||
|
|
||||||
const accessibleSiteResourceIds = [
|
|
||||||
...directSiteResourceResults.map((r) => r.siteResourceId),
|
|
||||||
...roleSiteResourceResults.map((r) => r.siteResourceId)
|
|
||||||
];
|
|
||||||
|
|
||||||
if (accessibleSiteResourceIds.length === 0) {
|
|
||||||
const data: ListUserResourceAliasesResponse = {
|
|
||||||
aliases: [],
|
|
||||||
pagination: {
|
|
||||||
total: 0,
|
|
||||||
pageSize,
|
|
||||||
page
|
|
||||||
}
|
|
||||||
};
|
|
||||||
localCache.set(cacheKey, data, USER_RESOURCE_ALIASES_CACHE_TTL_SEC);
|
|
||||||
return response<ListUserResourceAliasesResponse>(res, {
|
|
||||||
data,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "User resource aliases retrieved successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereClause = and(
|
|
||||||
eq(siteResources.orgId, orgId),
|
|
||||||
eq(siteResources.enabled, true),
|
|
||||||
eq(siteResources.mode, "host"),
|
|
||||||
isNotNull(siteResources.alias),
|
|
||||||
ne(siteResources.alias, ""),
|
|
||||||
inArray(siteResources.siteResourceId, accessibleSiteResourceIds)
|
|
||||||
);
|
|
||||||
|
|
||||||
const baseSelect = () =>
|
|
||||||
db
|
|
||||||
.select({ alias: siteResources.alias })
|
|
||||||
.from(siteResources)
|
|
||||||
.where(whereClause);
|
|
||||||
|
|
||||||
const countQuery = db.$count(baseSelect().as("filtered_aliases"));
|
|
||||||
|
|
||||||
const [rows, totalCount] = await Promise.all([
|
|
||||||
baseSelect()
|
|
||||||
.orderBy(asc(siteResources.alias))
|
|
||||||
.limit(pageSize)
|
|
||||||
.offset(pageSize * (page - 1)),
|
|
||||||
countQuery
|
|
||||||
]);
|
|
||||||
|
|
||||||
const aliases = rows.map((r) => r.alias as string);
|
|
||||||
|
|
||||||
const data: ListUserResourceAliasesResponse = {
|
|
||||||
aliases,
|
|
||||||
pagination: {
|
|
||||||
total: totalCount,
|
|
||||||
pageSize,
|
|
||||||
page
|
|
||||||
}
|
|
||||||
};
|
|
||||||
localCache.set(cacheKey, data, USER_RESOURCE_ALIASES_CACHE_TTL_SEC);
|
|
||||||
|
|
||||||
return response<ListUserResourceAliasesResponse>(res, {
|
|
||||||
data,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "User resource aliases retrieved successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"Internal server error"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, domainNamespaces, loginPage } from "@server/db";
|
import { db, loginPage } from "@server/db";
|
||||||
import {
|
import {
|
||||||
domains,
|
domains,
|
||||||
Org,
|
Org,
|
||||||
@@ -25,7 +25,6 @@ import { validateAndConstructDomain } from "@server/lib/domainUtils";
|
|||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import { isSubscribed } from "#dynamic/lib/isSubscribed";
|
|
||||||
|
|
||||||
const updateResourceParamsSchema = z.strictObject({
|
const updateResourceParamsSchema = z.strictObject({
|
||||||
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
@@ -121,9 +120,7 @@ const updateHttpResourceBodySchema = z
|
|||||||
if (data.headers) {
|
if (data.headers) {
|
||||||
// HTTP header values must be visible ASCII or horizontal whitespace, no control chars (RFC 7230)
|
// HTTP header values must be visible ASCII or horizontal whitespace, no control chars (RFC 7230)
|
||||||
const validHeaderValue = /^[\t\x20-\x7E]*$/;
|
const validHeaderValue = /^[\t\x20-\x7E]*$/;
|
||||||
return data.headers.every((h) =>
|
return data.headers.every((h) => validHeaderValue.test(h.value));
|
||||||
validHeaderValue.test(h.value)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -321,34 +318,6 @@ async function updateHttpResource(
|
|||||||
if (updateData.domainId) {
|
if (updateData.domainId) {
|
||||||
const domainId = updateData.domainId;
|
const domainId = updateData.domainId;
|
||||||
|
|
||||||
if (
|
|
||||||
build == "saas" &&
|
|
||||||
!isSubscribed(resource.orgId, tierMatrix.domainNamespaces)
|
|
||||||
) {
|
|
||||||
// grandfather in existing users
|
|
||||||
const lastAllowedDate = new Date("2026-04-13");
|
|
||||||
const userCreatedDate = new Date(
|
|
||||||
req.user?.dateCreated || new Date()
|
|
||||||
);
|
|
||||||
if (userCreatedDate > lastAllowedDate) {
|
|
||||||
// check if this domain id is a namespace domain and if so, reject
|
|
||||||
const domain = await db
|
|
||||||
.select()
|
|
||||||
.from(domainNamespaces)
|
|
||||||
.where(eq(domainNamespaces.domainId, domainId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (domain.length > 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"Your current subscription does not support custom domain namespaces. Please upgrade to access this feature."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate domain and construct full domain
|
// Validate domain and construct full domain
|
||||||
const domainResult = await validateAndConstructDomain(
|
const domainResult = await validateAndConstructDomain(
|
||||||
domainId,
|
domainId,
|
||||||
@@ -397,7 +366,7 @@ async function updateHttpResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (build != "oss") {
|
if (build != "oss") {
|
||||||
const existingLoginPages = await db
|
const existingLoginPages = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ async function queryUser(userId: string) {
|
|||||||
serverAdmin: users.serverAdmin,
|
serverAdmin: users.serverAdmin,
|
||||||
idpName: idp.name,
|
idpName: idp.name,
|
||||||
idpId: users.idpId,
|
idpId: users.idpId,
|
||||||
locale: users.locale,
|
locale: users.locale
|
||||||
dateCreated: users.dateCreated
|
|
||||||
})
|
})
|
||||||
.from(users)
|
.from(users)
|
||||||
.leftJoin(idp, eq(users.idpId, idp.idpId))
|
.leftJoin(idp, eq(users.idpId, idp.idpId))
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import {
|
import { orgs, roles, userInviteRoles, userInvites, userOrgs, users } from "@server/db";
|
||||||
orgs,
|
|
||||||
roles,
|
|
||||||
userInviteRoles,
|
|
||||||
userInvites,
|
|
||||||
userOrgs,
|
|
||||||
users
|
|
||||||
} from "@server/db";
|
|
||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { and, eq, inArray } 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";
|
||||||
@@ -44,7 +37,8 @@ const inviteUserBodySchema = z
|
|||||||
regenerate: z.boolean().optional()
|
regenerate: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(d) => (d.roleIds != null && d.roleIds.length > 0) || d.roleId != null,
|
(d) =>
|
||||||
|
(d.roleIds != null && d.roleIds.length > 0) || d.roleId != null,
|
||||||
{ message: "roleIds or roleId is required", path: ["roleIds"] }
|
{ message: "roleIds or roleId is required", path: ["roleIds"] }
|
||||||
)
|
)
|
||||||
.transform((data) => ({
|
.transform((data) => ({
|
||||||
@@ -271,7 +265,7 @@ export async function inviteUser(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${email}`;
|
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`;
|
||||||
|
|
||||||
if (doEmail) {
|
if (doEmail) {
|
||||||
await sendEmail(
|
await sendEmail(
|
||||||
@@ -320,12 +314,12 @@ export async function inviteUser(
|
|||||||
expiresAt,
|
expiresAt,
|
||||||
tokenHash
|
tokenHash
|
||||||
});
|
});
|
||||||
await trx
|
await trx.insert(userInviteRoles).values(
|
||||||
.insert(userInviteRoles)
|
uniqueRoleIds.map((roleId) => ({ inviteId, roleId }))
|
||||||
.values(uniqueRoleIds.map((roleId) => ({ inviteId, roleId })));
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${email}`;
|
const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`;
|
||||||
|
|
||||||
if (doEmail) {
|
if (doEmail) {
|
||||||
await sendEmail(
|
await sendEmail(
|
||||||
|
|||||||
@@ -64,8 +64,7 @@ export async function myDevice(
|
|||||||
serverAdmin: users.serverAdmin,
|
serverAdmin: users.serverAdmin,
|
||||||
idpName: idp.name,
|
idpName: idp.name,
|
||||||
idpId: users.idpId,
|
idpId: users.idpId,
|
||||||
locale: users.locale,
|
locale: users.locale
|
||||||
dateCreated: users.dateCreated
|
|
||||||
})
|
})
|
||||||
.from(users)
|
.from(users)
|
||||||
.leftJoin(idp, eq(users.idpId, idp.idpId))
|
.leftJoin(idp, eq(users.idpId, idp.idpId))
|
||||||
|
|||||||
@@ -235,9 +235,7 @@ export default async function migration() {
|
|||||||
for (const row of existingUserInviteRoles) {
|
for (const row of existingUserInviteRoles) {
|
||||||
await db.execute(sql`
|
await db.execute(sql`
|
||||||
INSERT INTO "userInviteRoles" ("inviteId", "roleId")
|
INSERT INTO "userInviteRoles" ("inviteId", "roleId")
|
||||||
SELECT ${row.inviteId}, ${row.roleId}
|
VALUES (${row.inviteId}, ${row.roleId})
|
||||||
WHERE EXISTS (SELECT 1 FROM "userInvites" WHERE "inviteId" = ${row.inviteId})
|
|
||||||
AND EXISTS (SELECT 1 FROM "roles" WHERE "roleId" = ${row.roleId})
|
|
||||||
ON CONFLICT DO NOTHING
|
ON CONFLICT DO NOTHING
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
@@ -260,10 +258,7 @@ export default async function migration() {
|
|||||||
for (const row of existingUserOrgRoles) {
|
for (const row of existingUserOrgRoles) {
|
||||||
await db.execute(sql`
|
await db.execute(sql`
|
||||||
INSERT INTO "userOrgRoles" ("userId", "orgId", "roleId")
|
INSERT INTO "userOrgRoles" ("userId", "orgId", "roleId")
|
||||||
SELECT ${row.userId}, ${row.orgId}, ${row.roleId}
|
VALUES (${row.userId}, ${row.orgId}, ${row.roleId})
|
||||||
WHERE EXISTS (SELECT 1 FROM "user" WHERE "id" = ${row.userId})
|
|
||||||
AND EXISTS (SELECT 1 FROM "orgs" WHERE "orgId" = ${row.orgId})
|
|
||||||
AND EXISTS (SELECT 1 FROM "roles" WHERE "roleId" = ${row.roleId})
|
|
||||||
ON CONFLICT DO NOTHING
|
ON CONFLICT DO NOTHING
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export default async function migration() {
|
|||||||
).run();
|
).run();
|
||||||
|
|
||||||
db.prepare(
|
db.prepare(
|
||||||
`INSERT INTO '__new_userOrgs'("userId", "orgId", "isOwner", "autoProvisioned", "pamUsername") SELECT "userId", "orgId", "isOwner", "autoProvisioned", "pamUsername" FROM 'userOrgs' WHERE EXISTS (SELECT 1 FROM 'user' WHERE id = userOrgs.userId) AND EXISTS (SELECT 1 FROM 'orgs' WHERE orgId = userOrgs.orgId);`
|
`INSERT INTO '__new_userOrgs'("userId", "orgId", "isOwner", "autoProvisioned", "pamUsername") SELECT "userId", "orgId", "isOwner", "autoProvisioned", "pamUsername" FROM 'userOrgs';`
|
||||||
).run();
|
).run();
|
||||||
db.prepare(`DROP TABLE 'userOrgs';`).run();
|
db.prepare(`DROP TABLE 'userOrgs';`).run();
|
||||||
db.prepare(
|
db.prepare(
|
||||||
@@ -246,15 +246,12 @@ export default async function migration() {
|
|||||||
// Re-insert the preserved invite role assignments into the new userInviteRoles table
|
// Re-insert the preserved invite role assignments into the new userInviteRoles table
|
||||||
if (existingUserInviteRoles.length > 0) {
|
if (existingUserInviteRoles.length > 0) {
|
||||||
const insertUserInviteRole = db.prepare(
|
const insertUserInviteRole = db.prepare(
|
||||||
`INSERT OR IGNORE INTO 'userInviteRoles' ("inviteId", "roleId")
|
`INSERT OR IGNORE INTO 'userInviteRoles' ("inviteId", "roleId") VALUES (?, ?)`
|
||||||
SELECT ?, ?
|
|
||||||
WHERE EXISTS (SELECT 1 FROM 'userInvites' WHERE inviteId = ?)
|
|
||||||
AND EXISTS (SELECT 1 FROM 'roles' WHERE roleId = ?)`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const insertAll = db.transaction(() => {
|
const insertAll = db.transaction(() => {
|
||||||
for (const row of existingUserInviteRoles) {
|
for (const row of existingUserInviteRoles) {
|
||||||
insertUserInviteRole.run(row.inviteId, row.roleId, row.inviteId, row.roleId);
|
insertUserInviteRole.run(row.inviteId, row.roleId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -268,16 +265,12 @@ export default async function migration() {
|
|||||||
// Re-insert the preserved role assignments into the new userOrgRoles table
|
// Re-insert the preserved role assignments into the new userOrgRoles table
|
||||||
if (existingUserOrgRoles.length > 0) {
|
if (existingUserOrgRoles.length > 0) {
|
||||||
const insertUserOrgRole = db.prepare(
|
const insertUserOrgRole = db.prepare(
|
||||||
`INSERT OR IGNORE INTO 'userOrgRoles' ("userId", "orgId", "roleId")
|
`INSERT OR IGNORE INTO 'userOrgRoles' ("userId", "orgId", "roleId") VALUES (?, ?, ?)`
|
||||||
SELECT ?, ?, ?
|
|
||||||
WHERE EXISTS (SELECT 1 FROM 'user' WHERE id = ?)
|
|
||||||
AND EXISTS (SELECT 1 FROM 'orgs' WHERE orgId = ?)
|
|
||||||
AND EXISTS (SELECT 1 FROM 'roles' WHERE roleId = ?)`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const insertAll = db.transaction(() => {
|
const insertAll = db.transaction(() => {
|
||||||
for (const row of existingUserOrgRoles) {
|
for (const row of existingUserOrgRoles) {
|
||||||
insertUserOrgRole.run(row.userId, row.orgId, row.roleId, row.userId, row.orgId, row.roleId);
|
insertUserOrgRole.run(row.userId, row.orgId, row.roleId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -491,10 +491,6 @@ export default function BillingPage() {
|
|||||||
|
|
||||||
const currentPlanId = getCurrentPlanId();
|
const currentPlanId = getCurrentPlanId();
|
||||||
|
|
||||||
const visiblePlanOptions = planOptions.filter(
|
|
||||||
(plan) => plan.id !== "home" || currentPlanId === "home"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check if subscription is in a problematic state that requires attention
|
// Check if subscription is in a problematic state that requires attention
|
||||||
const hasProblematicSubscription = (): boolean => {
|
const hasProblematicSubscription = (): boolean => {
|
||||||
if (!tierSubscription?.subscription) return false;
|
if (!tierSubscription?.subscription) return false;
|
||||||
@@ -807,8 +803,8 @@ export default function BillingPage() {
|
|||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
{/* Plan Cards Grid */}
|
{/* Plan Cards Grid */}
|
||||||
<div className={cn("grid grid-cols-1 gap-4", visiblePlanOptions.length === 5 ? "md:grid-cols-5" : "md:grid-cols-4")}>
|
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||||
{visiblePlanOptions.map((plan) => {
|
{planOptions.map((plan) => {
|
||||||
const isCurrentPlan = plan.id === currentPlanId;
|
const isCurrentPlan = plan.id === currentPlanId;
|
||||||
const planAction = getPlanAction(plan);
|
const planAction = getPlanAction(plan);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { authCookieHeader } from "@app/lib/api/cookies";
|
|||||||
import { GetDNSRecordsResponse } from "@server/routers/domain";
|
import { GetDNSRecordsResponse } from "@server/routers/domain";
|
||||||
import DNSRecordsTable from "@app/components/DNSRecordTable";
|
import DNSRecordsTable from "@app/components/DNSRecordTable";
|
||||||
import DomainCertForm from "@app/components/DomainCertForm";
|
import DomainCertForm from "@app/components/DomainCertForm";
|
||||||
import { build } from "@server/build";
|
|
||||||
|
|
||||||
interface DomainSettingsPageProps {
|
interface DomainSettingsPageProps {
|
||||||
params: Promise<{ domainId: string; orgId: string }>;
|
params: Promise<{ domainId: string; orgId: string }>;
|
||||||
@@ -66,14 +65,12 @@ export default async function DomainSettingsPage({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{build != "oss" && env.flags.usePangolinDns ? (
|
<DomainInfoCard
|
||||||
<DomainInfoCard
|
failed={domain.failed}
|
||||||
failed={domain.failed}
|
verified={domain.verified}
|
||||||
verified={domain.verified}
|
type={domain.type}
|
||||||
type={domain.type}
|
errorMessage={domain.errorMessage}
|
||||||
errorMessage={domain.errorMessage}
|
/>
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<DNSRecordsTable records={dnsRecords} type={domain.type} />
|
<DNSRecordsTable records={dnsRecords} type={domain.type} />
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ export default function ResourceAuthenticationPage() {
|
|||||||
...orgQueries.identityProviders({
|
...orgQueries.identityProviders({
|
||||||
orgId: org.org.orgId,
|
orgId: org.org.orgId,
|
||||||
useOrgOnlyIdp: env.app.identityProviderMode === "org"
|
useOrgOnlyIdp: env.app.identityProviderMode === "org"
|
||||||
})
|
}),
|
||||||
|
enabled: isPaidUser(tierMatrix.orgOidc)
|
||||||
});
|
});
|
||||||
|
|
||||||
const pageLoading =
|
const pageLoading =
|
||||||
|
|||||||
@@ -678,7 +678,6 @@ function ProxyResourceTargetsForm({
|
|||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
getRowId: (row) => String(row.targetId),
|
|
||||||
state: {
|
state: {
|
||||||
pagination: {
|
pagination: {
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
|||||||
@@ -999,7 +999,6 @@ export default function Page() {
|
|||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
getRowId: (row) => String(row.targetId),
|
|
||||||
state: {
|
state: {
|
||||||
pagination: {
|
pagination: {
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
|||||||
@@ -95,8 +95,7 @@ export default async function ProxyResourcesPage(
|
|||||||
ip: target.ip,
|
ip: target.ip,
|
||||||
port: target.port,
|
port: target.port,
|
||||||
enabled: target.enabled,
|
enabled: target.enabled,
|
||||||
healthStatus: target.healthStatus,
|
healthStatus: target.healthStatus
|
||||||
siteName: target.siteName
|
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ import {
|
|||||||
SettingsSectionFooter
|
SettingsSectionFooter
|
||||||
} from "@app/components/Settings";
|
} from "@app/components/Settings";
|
||||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||||
import { ArrowRight, Check, ExternalLink, Heart, InfoIcon, TicketCheck } from "lucide-react";
|
import { Check, Heart, InfoIcon } from "lucide-react";
|
||||||
import Link from "next/link";
|
|
||||||
import DismissableBanner from "@app/components/DismissableBanner";
|
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
import CopyTextBox from "@app/components/CopyTextBox";
|
||||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||||
import { SitePriceCalculator } from "@app/components/SitePriceCalculator";
|
import { SitePriceCalculator } from "@app/components/SitePriceCalculator";
|
||||||
@@ -53,10 +51,6 @@ import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
|||||||
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
|
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
const ENTERPRISE_DOCS_URL =
|
|
||||||
"https://docs.pangolin.net/self-host/enterprise-edition";
|
|
||||||
const ENTERPRISE_PRICING_URL = "https://pangolin.net/pricing#Self-Hosted";
|
|
||||||
|
|
||||||
function obfuscateLicenseKey(key: string): string {
|
function obfuscateLicenseKey(key: string): string {
|
||||||
if (key.length <= 8) return key;
|
if (key.length <= 8) return key;
|
||||||
const firstPart = key.substring(0, 4);
|
const firstPart = key.substring(0, 4);
|
||||||
@@ -342,47 +336,6 @@ export default function LicensePage() {
|
|||||||
description={t("licenseTitleDescription")}
|
description={t("licenseTitleDescription")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!licenseStatus?.isLicenseValid && (
|
|
||||||
<DismissableBanner
|
|
||||||
storageKey="license-banner-dismissed"
|
|
||||||
version={1}
|
|
||||||
title={t("licenseBannerTitle")}
|
|
||||||
titleIcon={
|
|
||||||
<TicketCheck className="w-5 h-5 text-primary" />
|
|
||||||
}
|
|
||||||
description={t("licenseBannerDescription")}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
href={ENTERPRISE_PRICING_URL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
className="gap-2"
|
|
||||||
>
|
|
||||||
{t("licenseBannerGetLicense")}
|
|
||||||
<ArrowRight className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href={ENTERPRISE_DOCS_URL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="gap-2 hover:bg-primary/10 hover:border-primary/50 transition-colors"
|
|
||||||
>
|
|
||||||
{t("licenseBannerViewDocs")}
|
|
||||||
<ExternalLink className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</DismissableBanner>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* <Alert variant="neutral" className="mb-6"> */}
|
{/* <Alert variant="neutral" className="mb-6"> */}
|
||||||
{/* <InfoIcon className="h-4 w-4" /> */}
|
{/* <InfoIcon className="h-4 w-4" /> */}
|
||||||
{/* <AlertTitle className="font-semibold"> */}
|
{/* <AlertTitle className="font-semibold"> */}
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export default function CreateDomainForm({
|
|||||||
|
|
||||||
const punycodePreview = useMemo(() => {
|
const punycodePreview = useMemo(() => {
|
||||||
if (!baseDomain) return "";
|
if (!baseDomain) return "";
|
||||||
const punycode = toPunycode(baseDomain.toLowerCase());
|
const punycode = toPunycode(baseDomain);
|
||||||
return punycode !== baseDomain.toLowerCase() ? punycode : "";
|
return punycode !== baseDomain.toLowerCase() ? punycode : "";
|
||||||
}, [baseDomain]);
|
}, [baseDomain]);
|
||||||
|
|
||||||
@@ -239,24 +239,21 @@ export default function CreateDomainForm({
|
|||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
id="create-domain-form"
|
id="create-domain-form"
|
||||||
>
|
>
|
||||||
{build != "oss" && env.flags.usePangolinDns ? (
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="type"
|
||||||
name="type"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<StrategySelect
|
||||||
<StrategySelect
|
options={domainOptions}
|
||||||
options={domainOptions}
|
defaultValue={field.value}
|
||||||
defaultValue={field.value}
|
onChange={field.onChange}
|
||||||
onChange={field.onChange}
|
cols={1}
|
||||||
cols={1}
|
/>
|
||||||
/>
|
<FormMessage />
|
||||||
<FormMessage />
|
</FormItem>
|
||||||
</FormItem>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="baseDomain"
|
name="baseDomain"
|
||||||
|
|||||||
@@ -319,7 +319,6 @@ export default function DeviceLoginForm({
|
|||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<InputOTP
|
<InputOTP
|
||||||
maxLength={9}
|
maxLength={9}
|
||||||
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
|
|
||||||
{...field}
|
{...field}
|
||||||
value={field.value
|
value={field.value
|
||||||
.replace(/-/g, "")
|
.replace(/-/g, "")
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
@@ -41,15 +40,11 @@ import {
|
|||||||
Check,
|
Check,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
ChevronsUpDown,
|
ChevronsUpDown,
|
||||||
KeyRound,
|
|
||||||
Zap
|
Zap
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { usePaidStatus } from "@/hooks/usePaidStatus";
|
|
||||||
import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
|
|
||||||
import { toUnicode } from "punycode";
|
import { toUnicode } from "punycode";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useUserContext } from "@app/hooks/useUserContext";
|
|
||||||
|
|
||||||
type AvailableOption = {
|
type AvailableOption = {
|
||||||
domainNamespaceId: string;
|
domainNamespaceId: string;
|
||||||
@@ -98,15 +93,8 @@ export default function DomainPicker({
|
|||||||
warnOnProvidedDomain = false
|
warnOnProvidedDomain = false
|
||||||
}: DomainPickerProps) {
|
}: DomainPickerProps) {
|
||||||
const { env } = useEnvContext();
|
const { env } = useEnvContext();
|
||||||
const { user } = useUserContext();
|
|
||||||
const api = createApiClient({ env });
|
const api = createApiClient({ env });
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const { hasSaasSubscription } = usePaidStatus();
|
|
||||||
|
|
||||||
const requiresPaywall =
|
|
||||||
build === "saas" &&
|
|
||||||
!hasSaasSubscription(tierMatrix[TierFeature.DomainNamespaces]) &&
|
|
||||||
new Date(user.dateCreated) > new Date("2026-04-13");
|
|
||||||
|
|
||||||
const { data = [], isLoading: loadingDomains } = useQuery(
|
const { data = [], isLoading: loadingDomains } = useQuery(
|
||||||
orgQueries.domains({ orgId })
|
orgQueries.domains({ orgId })
|
||||||
@@ -521,11 +509,9 @@ export default function DomainPicker({
|
|||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{selectedBaseDomain.domain}
|
{selectedBaseDomain.domain}
|
||||||
</span>
|
</span>
|
||||||
{selectedBaseDomain.verified &&
|
{selectedBaseDomain.verified && (
|
||||||
selectedBaseDomain.domainType !==
|
<CheckCircle2 className="h-3 w-3 text-green-500 shrink-0" />
|
||||||
"wildcard" && (
|
)}
|
||||||
<CheckCircle2 className="h-3 w-3 text-green-500 shrink-0" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
t("domainPickerSelectBaseDomain")
|
t("domainPickerSelectBaseDomain")
|
||||||
@@ -588,23 +574,14 @@ export default function DomainPicker({
|
|||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{orgDomain.type ===
|
{orgDomain.type.toUpperCase()}{" "}
|
||||||
"wildcard"
|
•{" "}
|
||||||
|
{orgDomain.verified
|
||||||
? t(
|
? t(
|
||||||
"domainPickerManual"
|
"domainPickerVerified"
|
||||||
)
|
)
|
||||||
: (
|
: t(
|
||||||
<>
|
"domainPickerUnverified"
|
||||||
{orgDomain.type.toUpperCase()}{" "}
|
|
||||||
•{" "}
|
|
||||||
{orgDomain.verified
|
|
||||||
? t(
|
|
||||||
"domainPickerVerified"
|
|
||||||
)
|
|
||||||
: t(
|
|
||||||
"domainPickerUnverified"
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -663,7 +640,6 @@ export default function DomainPicker({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="mx-2 rounded-md"
|
className="mx-2 rounded-md"
|
||||||
disabled={requiresPaywall}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 mr-3">
|
<div className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 mr-3">
|
||||||
<Zap className="h-4 w-4 text-primary" />
|
<Zap className="h-4 w-4 text-primary" />
|
||||||
@@ -704,19 +680,6 @@ export default function DomainPicker({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{requiresPaywall && !hideFreeDomain && (
|
|
||||||
<Card className="mt-3 border-black-500/30 bg-linear-to-br from-black-500/10 via-background to-background overflow-hidden">
|
|
||||||
<CardContent className="py-3 px-4">
|
|
||||||
<div className="flex items-center gap-2.5 text-sm text-muted-foreground">
|
|
||||||
<KeyRound className="size-4 shrink-0 text-black-500" />
|
|
||||||
<span>
|
|
||||||
{t("domainPickerFreeDomainsPaidFeature")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/*showProvidedDomainSearch && build === "saas" && (
|
{/*showProvidedDomainSearch && build === "saas" && (
|
||||||
<Alert>
|
<Alert>
|
||||||
<AlertCircle className="h-4 w-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
|||||||
@@ -39,11 +39,7 @@ export default function InviteStatusCard({
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [type, setType] = useState<
|
const [type, setType] = useState<
|
||||||
| "rejected"
|
"rejected" | "wrong_user" | "user_does_not_exist" | "not_logged_in" | "user_limit_exceeded"
|
||||||
| "wrong_user"
|
|
||||||
| "user_does_not_exist"
|
|
||||||
| "not_logged_in"
|
|
||||||
| "user_limit_exceeded"
|
|
||||||
>("rejected");
|
>("rejected");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -94,12 +90,12 @@ export default function InviteStatusCard({
|
|||||||
|
|
||||||
if (!user && type === "user_does_not_exist") {
|
if (!user && type === "user_does_not_exist") {
|
||||||
const redirectUrl = email
|
const redirectUrl = email
|
||||||
? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${email}`
|
? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||||
: `/auth/signup?redirect=/invite?token=${tokenParam}`;
|
: `/auth/signup?redirect=/invite?token=${tokenParam}`;
|
||||||
router.push(redirectUrl);
|
router.push(redirectUrl);
|
||||||
} else if (!user && type === "not_logged_in") {
|
} else if (!user && type === "not_logged_in") {
|
||||||
const redirectUrl = email
|
const redirectUrl = email
|
||||||
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${email}`
|
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||||
: `/auth/login?redirect=/invite?token=${tokenParam}`;
|
: `/auth/login?redirect=/invite?token=${tokenParam}`;
|
||||||
router.push(redirectUrl);
|
router.push(redirectUrl);
|
||||||
} else {
|
} else {
|
||||||
@@ -113,7 +109,7 @@ export default function InviteStatusCard({
|
|||||||
async function goToLogin() {
|
async function goToLogin() {
|
||||||
await api.post("/auth/logout", {});
|
await api.post("/auth/logout", {});
|
||||||
const redirectUrl = email
|
const redirectUrl = email
|
||||||
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${email}`
|
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||||
: `/auth/login?redirect=/invite?token=${tokenParam}`;
|
: `/auth/login?redirect=/invite?token=${tokenParam}`;
|
||||||
router.push(redirectUrl);
|
router.push(redirectUrl);
|
||||||
}
|
}
|
||||||
@@ -121,7 +117,7 @@ export default function InviteStatusCard({
|
|||||||
async function goToSignup() {
|
async function goToSignup() {
|
||||||
await api.post("/auth/logout", {});
|
await api.post("/auth/logout", {});
|
||||||
const redirectUrl = email
|
const redirectUrl = email
|
||||||
? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${email}`
|
? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||||
: `/auth/signup?redirect=/invite?token=${tokenParam}`;
|
: `/auth/signup?redirect=/invite?token=${tokenParam}`;
|
||||||
router.push(redirectUrl);
|
router.push(redirectUrl);
|
||||||
}
|
}
|
||||||
@@ -161,9 +157,7 @@ export default function InviteStatusCard({
|
|||||||
Cannot Accept Invite
|
Cannot Accept Invite
|
||||||
</p>
|
</p>
|
||||||
<p className="text-center text-sm">
|
<p className="text-center text-sm">
|
||||||
This organization has reached its user limit. Please
|
This organization has reached its user limit. Please contact the organization administrator to upgrade their plan before accepting this invite.
|
||||||
contact the organization administrator to upgrade their
|
|
||||||
plan before accepting this invite.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -333,8 +333,7 @@ export default function PendingSitesTable({
|
|||||||
"jupiter",
|
"jupiter",
|
||||||
"saturn",
|
"saturn",
|
||||||
"uranus",
|
"uranus",
|
||||||
"neptune",
|
"neptune"
|
||||||
"pluto"
|
|
||||||
].includes(originalRow.exitNodeName.toLowerCase());
|
].includes(originalRow.exitNodeName.toLowerCase());
|
||||||
|
|
||||||
if (isCloudNode) {
|
if (isCloudNode) {
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export type TargetHealth = {
|
|||||||
port: number;
|
port: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
healthStatus: "healthy" | "unhealthy" | "unknown" | null;
|
healthStatus: "healthy" | "unhealthy" | "unknown" | null;
|
||||||
siteName: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResourceRow = {
|
export type ResourceRow = {
|
||||||
@@ -275,9 +274,7 @@ export default function ProxyResourcesTable({
|
|||||||
}
|
}
|
||||||
className="h-3 w-3"
|
className="h-3 w-3"
|
||||||
/>
|
/>
|
||||||
{target.siteName
|
{`${target.ip}:${target.port}`}
|
||||||
? `${target.siteName} (${target.ip}:${target.port})`
|
|
||||||
: `${target.ip}:${target.port}`}
|
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={`capitalize ${
|
className={`capitalize ${
|
||||||
@@ -304,9 +301,7 @@ export default function ProxyResourcesTable({
|
|||||||
status="unknown"
|
status="unknown"
|
||||||
className="h-3 w-3"
|
className="h-3 w-3"
|
||||||
/>
|
/>
|
||||||
{target.siteName
|
{`${target.ip}:${target.port}`}
|
||||||
? `${target.siteName} (${target.ip}:${target.port})`
|
|
||||||
: `${target.ip}:${target.port}`}
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
{!target.enabled
|
{!target.enabled
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { Button } from "./ui/button";
|
|||||||
import { TicketCheck } from "lucide-react";
|
import { TicketCheck } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||||
import { useUserContext } from "@app/hooks/useUserContext";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
interface SidebarLicenseButtonProps {
|
interface SidebarLicenseButtonProps {
|
||||||
@@ -21,11 +20,8 @@ export default function SidebarLicenseButton({
|
|||||||
isCollapsed = false
|
isCollapsed = false
|
||||||
}: SidebarLicenseButtonProps) {
|
}: SidebarLicenseButtonProps) {
|
||||||
const { licenseStatus, updateLicenseStatus } = useLicenseStatusContext();
|
const { licenseStatus, updateLicenseStatus } = useLicenseStatusContext();
|
||||||
const { user } = useUserContext();
|
|
||||||
|
|
||||||
const url = user?.serverAdmin
|
const url = "https://docs.pangolin.net/self-host/enterprise-edition";
|
||||||
? "/admin/license"
|
|
||||||
: "https://docs.pangolin.net/self-host/enterprise-edition";
|
|
||||||
|
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
|
|||||||
@@ -342,8 +342,7 @@ export default function SitesTable({
|
|||||||
"jupiter",
|
"jupiter",
|
||||||
"saturn",
|
"saturn",
|
||||||
"uranus",
|
"uranus",
|
||||||
"neptune",
|
"neptune"
|
||||||
"pluto"
|
|
||||||
].includes(originalRow.exitNodeName.toLowerCase());
|
].includes(originalRow.exitNodeName.toLowerCase());
|
||||||
|
|
||||||
if (isCloudNode) {
|
if (isCloudNode) {
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ export default function UserDevicesTable({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "online",
|
accessorKey: "online",
|
||||||
friendlyName: t("connected"),
|
friendlyName: t("online"),
|
||||||
header: () => {
|
header: () => {
|
||||||
return (
|
return (
|
||||||
<ColumnFilterButton
|
<ColumnFilterButton
|
||||||
@@ -410,7 +410,7 @@ export default function UserDevicesTable({
|
|||||||
}
|
}
|
||||||
searchPlaceholder={t("searchPlaceholder")}
|
searchPlaceholder={t("searchPlaceholder")}
|
||||||
emptyMessage={t("emptySearchOptions")}
|
emptyMessage={t("emptySearchOptions")}
|
||||||
label={t("connected")}
|
label={t("online")}
|
||||||
className="p-3"
|
className="p-3"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ const countryClass = cn(
|
|||||||
|
|
||||||
const highlightedCountryClass = cn(
|
const highlightedCountryClass = cn(
|
||||||
sharedCountryClass,
|
sharedCountryClass,
|
||||||
"stroke-[3]",
|
"stroke-2",
|
||||||
"fill-[#f4f4f5]",
|
"fill-[#f4f4f5]",
|
||||||
"stroke-[#f36117]",
|
"stroke-[#f36117]",
|
||||||
"dark:fill-[#3f3f46]"
|
"dark:fill-[#3f3f46]"
|
||||||
@@ -194,20 +194,11 @@ function drawInteractiveCountries(
|
|||||||
const path = setupProjetionPath();
|
const path = setupProjetionPath();
|
||||||
const data = parseWorldTopoJsonToGeoJsonFeatures();
|
const data = parseWorldTopoJsonToGeoJsonFeatures();
|
||||||
const svg = d3.select(element);
|
const svg = d3.select(element);
|
||||||
const countriesLayer = svg.append("g");
|
|
||||||
const hoverLayer = svg.append("g").style("pointer-events", "none");
|
|
||||||
const hoverPath = hoverLayer
|
|
||||||
.append("path")
|
|
||||||
.datum(null)
|
|
||||||
.attr("class", highlightedCountryClass)
|
|
||||||
.style("display", "none");
|
|
||||||
|
|
||||||
countriesLayer
|
svg.selectAll("path")
|
||||||
.selectAll("path")
|
|
||||||
.data(data)
|
.data(data)
|
||||||
.enter()
|
.enter()
|
||||||
.append("path")
|
.append("path")
|
||||||
.attr("data-country-path", "true")
|
|
||||||
.attr("class", countryClass)
|
.attr("class", countryClass)
|
||||||
.attr("d", path as never)
|
.attr("d", path as never)
|
||||||
|
|
||||||
@@ -218,10 +209,9 @@ function drawInteractiveCountries(
|
|||||||
y,
|
y,
|
||||||
hoveredCountryAlpha3Code: country.properties.a3
|
hoveredCountryAlpha3Code: country.properties.a3
|
||||||
});
|
});
|
||||||
hoverPath
|
// brings country to front
|
||||||
.datum(country)
|
this.parentNode?.appendChild(this);
|
||||||
.attr("d", path(country) as string)
|
d3.select(this).attr("class", highlightedCountryClass);
|
||||||
.style("display", null);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
.on("mousemove", function (event) {
|
.on("mousemove", function (event) {
|
||||||
@@ -231,13 +221,13 @@ function drawInteractiveCountries(
|
|||||||
|
|
||||||
.on("mouseout", function () {
|
.on("mouseout", function () {
|
||||||
setTooltip({ x: 0, y: 0, hoveredCountryAlpha3Code: null });
|
setTooltip({ x: 0, y: 0, hoveredCountryAlpha3Code: null });
|
||||||
hoverPath.style("display", "none");
|
d3.select(this).attr("class", countryClass);
|
||||||
});
|
});
|
||||||
|
|
||||||
return svg;
|
return svg;
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorldJsonCountryData = d3.ExtendedFeature<d3.GeoGeometryObjects | null, { name: string; a3: string }>;
|
type WorldJsonCountryData = { properties: { name: string; a3: string } };
|
||||||
|
|
||||||
function parseWorldTopoJsonToGeoJsonFeatures(): Array<WorldJsonCountryData> {
|
function parseWorldTopoJsonToGeoJsonFeatures(): Array<WorldJsonCountryData> {
|
||||||
const collection = topojson.feature(
|
const collection = topojson.feature(
|
||||||
@@ -267,7 +257,7 @@ function colorInCountriesWithValues(
|
|||||||
const svg = d3.select(element);
|
const svg = d3.select(element);
|
||||||
|
|
||||||
return svg
|
return svg
|
||||||
.selectAll('path[data-country-path="true"]')
|
.selectAll("path")
|
||||||
.style("fill", (countryPath) => {
|
.style("fill", (countryPath) => {
|
||||||
const country = getCountryByCountryPath(countryPath);
|
const country = getCountryByCountryPath(countryPath);
|
||||||
if (!country?.count) {
|
if (!country?.count) {
|
||||||
|
|||||||