diff --git a/README.md b/README.md index f11196f77..a4bfb9fe8 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,9 @@ Pangolin is an open-source, identity-based remote access platform built on WireG ## Deployment Options -- **Pangolin Cloud** — Fully managed service - no infrastructure required. -- **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 - no infrastructure required. +- **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. ## Key Features diff --git a/license_header_checker.py b/license_header_checker.py index c173d693b..ab7ddf4d5 100644 --- a/license_header_checker.py +++ b/license_header_checker.py @@ -96,7 +96,7 @@ def process_directory(root_dir): if has_correct_header: print(f"Header up-to-date: {file_path}") else: - # Either no header exists or the header is outdated — write + # Either no header exists or the header is outdated - write # the correct one. action = "Replaced header in" if has_any_header else "Added header to" new_content = HEADER_NORMALIZED + '\n\n' + body @@ -106,7 +106,7 @@ def process_directory(root_dir): files_modified += 1 else: if has_any_header: - # Remove the header — it shouldn't be here. + # Remove the header - it shouldn't be here. with open(file_path, 'w', encoding='utf-8') as f: f.write(body) print(f"Removed header from: {file_path}") @@ -134,4 +134,4 @@ if __name__ == "__main__": print(f"Error: Directory '{target_directory}' not found.") sys.exit(1) - process_directory(os.path.abspath(target_directory)) \ No newline at end of file + process_directory(os.path.abspath(target_directory)) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index a44d1948f..d429f4bd6 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -1993,7 +1993,7 @@ "description": "По-надежден и по-нисък поддръжка на Самостоятелно-хостван Панголиин сървър с допълнителни екстри", "introTitle": "Управлявано Самостоятелно-хостван Панголиин", "introDescription": "е опция за внедряване, предназначена за хора, които искат простота и допълнителна надеждност, като същевременно запазят данните си частни и самостоятелно-хоствани.", - "introDetail": "С тази опция все още управлявате свой собствен Панголиин възел — вашите тунели, SSL терминатора и трафик остават на вашия сървър. Разликата е, че управлението и мониторингът се обработват чрез нашия облачен панел за контрол, който отключва редица предимства:", + "introDetail": "С тази опция все още управлявате свой собствен Панголиин възел - вашите тунели, SSL терминатора и трафик остават на вашия сървър. Разликата е, че управлението и мониторингът се обработват чрез нашия облачен панел за контрол, който отключва редица предимства:", "benefitSimplerOperations": { "title": "По-прости операции", "description": "Няма нужда да управлявате свой собствен имейл сървър или да настройвате сложни аларми. Ще получите проверки и предупреждения при прекъсване от самото начало." @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "Разкриване на употреба", - "description": "Изберете лицензионен клас, който точно отразява вашата целена употреба. Персоналният лиценз позволява безплатно ползване на софтуера за индивидуална, некомерсиална или маломащабна комерсиална дейност с годишен брутен приход под 100,000 USD. Всяко ползване извън тези граници — включително ползване във фирма, организация или друга доходоносна среда — изисква валиден корпоративен лиценз и плащане на съответната лицензионна такса. Всички потребители, независимо дали са лични или корпоративни, трябва да спазват Условията на Fossorial Commercial License." + "description": "Изберете лицензионен клас, който точно отразява вашата целена употреба. Персоналният лиценз позволява безплатно ползване на софтуера за индивидуална, некомерсиална или маломащабна комерсиална дейност с годишен брутен приход под 100,000 USD. Всяко ползване извън тези граници - включително ползване във фирма, организация или друга доходоносна среда - изисква валиден корпоративен лиценз и плащане на съответната лицензионна такса. Всички потребители, независимо дали са лични или корпоративни, трябва да спазват Условията на Fossorial Commercial License." }, "trialPeriodInformation": { "title": "Информация за пробен период", @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "JSON масив", "httpDestFormatJsonArrayDescription": "Една заявка на партида, тялото е JSON масив. Съвместим с повечето общи уеб куки и Datadog.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "Една заявка на партида, тялото е ново линии отделени JSON — един обект на ред, няма външен масив. Изисквано от Splunk HEC, Elastic / OpenSearch и Grafana.", + "httpDestFormatNdjsonDescription": "Една заявка на партида, тялото е ново линии отделени JSON - един обект на ред, няма външен масив. Изисквано от Splunk HEC, Elastic / OpenSearch и Grafana.", "httpDestFormatSingleTitle": "Едно събитие на заявка", "httpDestFormatSingleDescription": "Изпращат се отделни HTTP POST за всяко индивидуално събитие. Използвайте само за крайни точки, които не могат да обработват партиди.", "httpDestLogTypesTitle": "Видове логове", diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 3a797e564..66cee2a8b 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -1993,7 +1993,7 @@ "description": "Spolehlivější a nízko udržovaný Pangolinův server s dalšími zvony a bičkami", "introTitle": "Spravovaný Pangolin", "introDescription": "je možnost nasazení určená pro lidi, kteří chtějí jednoduchost a spolehlivost při zachování soukromých a samoobslužných dat.", - "introDetail": "Pomocí této volby stále provozujete vlastní uzel Pangolin — tunely, SSL terminály a provoz všech pobytů na vašem serveru. Rozdíl spočívá v tom, že řízení a monitorování se řeší prostřednictvím našeho cloudového panelu, který odemkne řadu výhod:", + "introDetail": "Pomocí této volby stále provozujete vlastní uzel Pangolin - tunely, SSL terminály a provoz všech pobytů na vašem serveru. Rozdíl spočívá v tom, že řízení a monitorování se řeší prostřednictvím našeho cloudového panelu, který odemkne řadu výhod:", "benefitSimplerOperations": { "title": "Jednoduchý provoz", "description": "Není třeba spouštět svůj vlastní poštovní server nebo nastavit komplexní upozornění. Ze schránky dostanete upozornění na zdravotní kontrolu a výpadek." diff --git a/messages/de-DE.json b/messages/de-DE.json index 2b5e92865..4ea6c9fe6 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "Verwendungsanzeige", - "description": "Wählen Sie die Lizenz-Ebene, die Ihre beabsichtigte Nutzung genau widerspiegelt. Die Persönliche Lizenz erlaubt die freie Nutzung der Software für individuelle, nicht-kommerzielle oder kleine kommerzielle Aktivitäten mit jährlichen Brutto-Einnahmen von 100.000 USD. Über diese Grenzen hinausgehende Verwendungszwecke – einschließlich der Verwendung innerhalb eines Unternehmens, einer Organisation, oder eine andere umsatzgenerierende Umgebung — erfordert eine gültige Enterprise-Lizenz und die Zahlung der Lizenzgebühr. Alle Benutzer, ob Personal oder Enterprise, müssen die Fossorial Commercial License Bedingungen einhalten." + "description": "Wählen Sie die Lizenz-Ebene, die Ihre beabsichtigte Nutzung genau widerspiegelt. Die Persönliche Lizenz erlaubt die freie Nutzung der Software für individuelle, nicht-kommerzielle oder kleine kommerzielle Aktivitäten mit jährlichen Brutto-Einnahmen von 100.000 USD. Über diese Grenzen hinausgehende Verwendungszwecke – einschließlich der Verwendung innerhalb eines Unternehmens, einer Organisation, oder eine andere umsatzgenerierende Umgebung - erfordert eine gültige Enterprise-Lizenz und die Zahlung der Lizenzgebühr. Alle Benutzer, ob Personal oder Enterprise, müssen die Fossorial Commercial License Bedingungen einhalten." }, "trialPeriodInformation": { "title": "Testperiode Information", @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "JSON Array", "httpDestFormatJsonArrayDescription": "Eine Anfrage pro Stapel ist ein JSON-Array. Kompatibel mit den meisten generischen Webhooks und Datadog.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "Eine Anfrage pro Batch, der Körper ist newline-getrenntes JSON — ein Objekt pro Zeile, kein äußeres Array. Benötigt von Splunk HEC, Elastic / OpenSearch, und Grafana Loki.", + "httpDestFormatNdjsonDescription": "Eine Anfrage pro Batch, der Körper ist newline-getrenntes JSON - ein Objekt pro Zeile, kein äußeres Array. Benötigt von Splunk HEC, Elastic / OpenSearch, und Grafana Loki.", "httpDestFormatSingleTitle": "Ein Ereignis pro Anfrage", "httpDestFormatSingleDescription": "Sendet eine separate HTTP-POST für jedes einzelne Ereignis. Nur für Endpunkte, die Batches nicht handhaben können.", "httpDestLogTypesTitle": "Log-Typen", diff --git a/messages/en-US.json b/messages/en-US.json index 5d53ea03b..9dd4f1262 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1346,7 +1346,7 @@ "sidebarManagement": "Management", "sidebarBillingAndLicenses": "Billing & Licenses", "sidebarLogsAnalytics": "Analytics", - "alertingTitle": "Alerting rules", + "alertingTitle": "Alerting", "alertingDescription": "Define sources, triggers, and actions for notifications.", "alertingRules": "Alert rules", "alertingSearchRules": "Search rules…", @@ -1399,7 +1399,7 @@ "alertingSelectHealthChecks": "Select health checks…", "alertingHealthChecksSelected": "{count} health checks selected", "alertingNoHealthChecks": "No targets with health checks enabled", - "alertingHealthCheckStub": "Health check source selection is not wired up yet — you can still configure triggers and actions.", + "alertingHealthCheckStub": "Health check source selection is not wired up yet - you can still configure triggers and actions.", "alertingSelectUsers": "Select users…", "alertingUsersSelected": "{count} users selected", "alertingSelectRoles": "Select roles…", @@ -1417,7 +1417,7 @@ "alertingConfigureTrigger": "Configure Trigger", "alertingConfigureActions": "Configure Actions", "alertingBackToRules": "Back to Rules", - "alertingDraftBadge": "Draft — save to store this rule", + "alertingDraftBadge": "Draft - save to store this rule", "alertingSidebarHint": "Click a step on the canvas to edit it here.", "alertingGraphCanvasTitle": "Rule Flow", "alertingGraphCanvasDescription": "Visual overview of source, trigger, and actions. Select a node to edit it in the panel.", @@ -2115,7 +2115,7 @@ "description": "More reliable and low-maintenance self-hosted Pangolin server with extra bells and whistles", "introTitle": "Managed Self-Hosted Pangolin", "introDescription": "is a deployment option designed for people who want simplicity and extra reliability while still keeping their data private and self-hosted.", - "introDetail": "With this option, you still run your own Pangolin node — your tunnels, SSL termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:", + "introDetail": "With this option, you still run your own Pangolin node - your tunnels, SSL termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:", "benefitSimplerOperations": { "title": "Simpler operations", "description": "No need to run your own mail server or set up complex alerting. You'll get health checks and downtime alerts out of the box." @@ -2240,7 +2240,7 @@ "selectDomainForOrgAuthPage": "Select a domain for the organization's authentication page", "domainPickerProvidedDomain": "Provided Domain", "domainPickerFreeProvidedDomain": "Provided Domain", - "domainPickerFreeDomainsPaidFeature": "Provided domains are a paid feature. Subscribe to get a domain included with your plan — no need to bring your own.", + "domainPickerFreeDomainsPaidFeature": "Provided domains are a paid feature. Subscribe to get a domain included with your plan - no need to bring your own.", "domainPickerVerified": "Verified", "domainPickerUnverified": "Unverified", "domainPickerManual": "Manual", @@ -2418,7 +2418,7 @@ "alerts": { "commercialUseDisclosure": { "title": "Usage Disclosure", - "description": "Select the license tier that accurately reflects your intended use. The Personal License permits free use of the Software for individual, non-commercial or small-scale commercial activities with annual gross revenue under $100,000 USD. Any use beyond these limits — including use within a business, organization, or other revenue-generating environment — requires a valid Enterprise License and payment of the applicable licensing fee. All users, whether Personal or Enterprise, must comply with the Fossorial Commercial License Terms." + "description": "Select the license tier that accurately reflects your intended use. The Personal License permits free use of the Software for individual, non-commercial or small-scale commercial activities with annual gross revenue under $100,000 USD. Any use beyond these limits - including use within a business, organization, or other revenue-generating environment - requires a valid Enterprise License and payment of the applicable licensing fee. All users, whether Personal or Enterprise, must comply with the Fossorial Commercial License Terms." }, "trialPeriodInformation": { "title": "Trial Period Information", @@ -3010,7 +3010,7 @@ "httpDestFormatJsonArrayTitle": "JSON Array", "httpDestFormatJsonArrayDescription": "One request per batch, body is a JSON array. Compatible with most generic webhooks and Datadog.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "One request per batch, body is newline-delimited JSON — one object per line, no outer array. Required by Splunk HEC, Elastic / OpenSearch, and Grafana Loki.", + "httpDestFormatNdjsonDescription": "One request per batch, body is newline-delimited JSON - one object per line, no outer array. Required by Splunk HEC, Elastic / OpenSearch, and Grafana Loki.", "httpDestFormatSingleTitle": "One Event Per Request", "httpDestFormatSingleDescription": "Sends a separate HTTP POST for each individual event. Use only for endpoints that cannot handle batches.", "httpDestLogTypesTitle": "Log Types", diff --git a/messages/es-ES.json b/messages/es-ES.json index 34c4cc970..0fa9201c8 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2118,7 +2118,7 @@ "selectDomainForOrgAuthPage": "Seleccione un dominio para la página de autenticación de la organización", "domainPickerProvidedDomain": "Dominio proporcionado", "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.", + "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", "domainPickerUnverified": "Sin verificar", "domainPickerManual": "Manual", @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "Divulgación de uso", - "description": "Seleccione el nivel de licencia que refleje con precisión su uso previsto. La Licencia Personal permite el uso libre del Software para actividades comerciales individuales, no comerciales o de pequeña escala con ingresos brutos anuales inferiores a $100,000 USD. Cualquier uso más allá de estos límites — incluyendo el uso dentro de una empresa, organización, u otro entorno de generación de ingresos — requiere una Licencia Empresarial válida y el pago de la cuota de licencia aplicable. Todos los usuarios, ya sean personales o empresariales, deben cumplir con las Condiciones de Licencia Comercial Fossorial." + "description": "Seleccione el nivel de licencia que refleje con precisión su uso previsto. La Licencia Personal permite el uso libre del Software para actividades comerciales individuales, no comerciales o de pequeña escala con ingresos brutos anuales inferiores a $100,000 USD. Cualquier uso más allá de estos límites - incluyendo el uso dentro de una empresa, organización, u otro entorno de generación de ingresos - requiere una Licencia Empresarial válida y el pago de la cuota de licencia aplicable. Todos los usuarios, ya sean personales o empresariales, deben cumplir con las Condiciones de Licencia Comercial Fossorial." }, "trialPeriodInformation": { "title": "Información del período de prueba", @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "Matriz JSON", "httpDestFormatJsonArrayDescription": "Una petición por lote, cuerpo es una matriz JSON. Compatible con la mayoría de los webhooks y Datadog.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "Una petición por lote, el cuerpo es JSON delimitado por línea — un objeto por línea, sin arrays externos. Requerido por Splunk HEC, Elastic / OpenSearch, y Grafana Loki.", + "httpDestFormatNdjsonDescription": "Una petición por lote, el cuerpo es JSON delimitado por línea - un objeto por línea, sin arrays externos. Requerido por Splunk HEC, Elastic / OpenSearch, y Grafana Loki.", "httpDestFormatSingleTitle": "Un evento por solicitud", "httpDestFormatSingleDescription": "Envía un HTTP POST separado para cada evento individual. Úsalo sólo para los extremos que no pueden manejar lotes.", "httpDestLogTypesTitle": "Tipos de Log", diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 6b2efec27..419701b5f 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -1993,7 +1993,7 @@ "description": "Serveur Pangolin auto-hébergé avec des cloches et des sifflets supplémentaires", "introTitle": "Pangolin auto-hébergé géré", "introDescription": "est une option de déploiement conçue pour les personnes qui veulent de la simplicité et de la fiabilité tout en gardant leurs données privées et auto-hébergées.", - "introDetail": "Avec cette option, vous exécutez toujours votre propre nœud Pangolin — vos tunnels, la terminaison SSL et le trafic restent sur votre serveur. La différence est que la gestion et la surveillance sont gérées via notre tableau de bord du cloud, qui déverrouille un certain nombre d'avantages :", + "introDetail": "Avec cette option, vous exécutez toujours votre propre nœud Pangolin - vos tunnels, la terminaison SSL et le trafic restent sur votre serveur. La différence est que la gestion et la surveillance sont gérées via notre tableau de bord du cloud, qui déverrouille un certain nombre d'avantages :", "benefitSimplerOperations": { "title": "Opérations plus simples", "description": "Pas besoin de faire tourner votre propre serveur de messagerie ou de configurer des alertes complexes. Vous obtiendrez des contrôles de santé et des alertes de temps d'arrêt par la suite." @@ -2118,7 +2118,7 @@ "selectDomainForOrgAuthPage": "Sélectionnez un domaine pour la page d'authentification de l'organisation", "domainPickerProvidedDomain": "Domaine fourni", "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.", + "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é", "domainPickerUnverified": "Non vérifié", "domainPickerManual": "Manuel", @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "Divulgation d'utilisation", - "description": "Sélectionnez le niveau de licence qui correspond exactement à votre utilisation prévue. La Licence Personnelle autorise l'utilisation libre du Logiciel pour des activités commerciales individuelles, non commerciales ou à petite échelle avec un revenu annuel brut inférieur à 100 000 USD. Toute utilisation au-delà de ces limites — y compris l'utilisation au sein d'une entreprise, d'une organisation, ou tout autre environnement générateur de revenus — nécessite une licence d’entreprise valide et le paiement des droits de licence applicables. Tous les utilisateurs, qu'ils soient personnels ou d'entreprise, doivent se conformer aux conditions de licence commerciale Fossorial." + "description": "Sélectionnez le niveau de licence qui correspond exactement à votre utilisation prévue. La Licence Personnelle autorise l'utilisation libre du Logiciel pour des activités commerciales individuelles, non commerciales ou à petite échelle avec un revenu annuel brut inférieur à 100 000 USD. Toute utilisation au-delà de ces limites - y compris l'utilisation au sein d'une entreprise, d'une organisation, ou tout autre environnement générateur de revenus - nécessite une licence d’entreprise valide et le paiement des droits de licence applicables. Tous les utilisateurs, qu'ils soient personnels ou d'entreprise, doivent se conformer aux conditions de licence commerciale Fossorial." }, "trialPeriodInformation": { "title": "Informations sur la période d'essai", @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "Tableau JSON", "httpDestFormatJsonArrayDescription": "Une requête par lot, le corps est un tableau JSON. Compatible avec la plupart des webhooks génériques et des datadog.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "Une requête par lot, body est un JSON délimité par une nouvelle ligne — un objet par ligne, pas de tableau extérieur. Requis par Splunk HEC, Elastic / OpenSearch, et Grafana Loki.", + "httpDestFormatNdjsonDescription": "Une requête par lot, body est un JSON délimité par une nouvelle ligne - un objet par ligne, pas de tableau extérieur. Requis par Splunk HEC, Elastic / OpenSearch, et Grafana Loki.", "httpDestFormatSingleTitle": "Un événement par demande", "httpDestFormatSingleDescription": "Envoie un POST HTTP séparé pour chaque événement individuel. Utilisé uniquement pour les terminaux qui ne peuvent pas gérer des lots.", "httpDestLogTypesTitle": "Types de logs", diff --git a/messages/it-IT.json b/messages/it-IT.json index 6a771b5a3..e761ea55f 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -1993,7 +1993,7 @@ "description": "Server Pangolin self-hosted più affidabile e a bassa manutenzione con campanelli e fischietti extra", "introTitle": "Managed Self-Hosted Pangolin", "introDescription": "è un'opzione di distribuzione progettata per le persone che vogliono la semplicità e l'affidabilità extra mantenendo i loro dati privati e self-hosted.", - "introDetail": "Con questa opzione, esegui ancora il tuo nodo Pangolin — i tunnel, la terminazione SSL e il traffico rimangono tutti sul tuo server. La differenza è che la gestione e il monitoraggio sono gestiti attraverso il nostro cruscotto cloud, che sblocca una serie di vantaggi:", + "introDetail": "Con questa opzione, esegui ancora il tuo nodo Pangolin - i tunnel, la terminazione SSL e il traffico rimangono tutti sul tuo server. La differenza è che la gestione e il monitoraggio sono gestiti attraverso il nostro cruscotto cloud, che sblocca una serie di vantaggi:", "benefitSimplerOperations": { "title": "Operazioni più semplici", "description": "Non è necessario eseguire il proprio server di posta o impostare un avviso complesso. Otterrai controlli di salute e avvisi di inattività fuori dalla casella." @@ -2118,7 +2118,7 @@ "selectDomainForOrgAuthPage": "Seleziona un dominio per la pagina di autenticazione dell'organizzazione", "domainPickerProvidedDomain": "Dominio Fornito", "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.", + "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", "domainPickerUnverified": "Non Verificato", "domainPickerManual": "Manuale", @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "Trasparenza Di Utilizzo", - "description": "Seleziona il livello di licenza che rispecchia accuratamente il tuo utilizzo previsto. La Licenza Personale consente l'uso gratuito del Software per le attività commerciali individuali, non commerciali o su piccola scala con entrate lorde annue inferiori a $100.000 USD. Qualsiasi uso oltre questi limiti — compreso l'uso all'interno di un'azienda, organizzazione, o altro ambiente generatore di entrate — richiede una licenza Enterprise valida e il pagamento della tassa di licenza applicabile. Tutti gli utenti, siano essi personali o aziendali, devono rispettare i termini di licenza commerciale Fossorial." + "description": "Seleziona il livello di licenza che rispecchia accuratamente il tuo utilizzo previsto. La Licenza Personale consente l'uso gratuito del Software per le attività commerciali individuali, non commerciali o su piccola scala con entrate lorde annue inferiori a $100.000 USD. Qualsiasi uso oltre questi limiti - compreso l'uso all'interno di un'azienda, organizzazione, o altro ambiente generatore di entrate - richiede una licenza Enterprise valida e il pagamento della tassa di licenza applicabile. Tutti gli utenti, siano essi personali o aziendali, devono rispettare i termini di licenza commerciale Fossorial." }, "trialPeriodInformation": { "title": "Informazioni Periodo Di Prova", @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "JSON Array", "httpDestFormatJsonArrayDescription": "Una richiesta per lotto, corpo è un array JSON. Compatibile con la maggior parte dei webhooks generici e Datadog.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "Una richiesta per lotto, corpo è newline-delimited JSON — un oggetto per linea, nessun array esterno. Richiesto da Splunk HEC, Elastic / OpenSearch, e Grafana Loki.", + "httpDestFormatNdjsonDescription": "Una richiesta per lotto, corpo è newline-delimited JSON - un oggetto per linea, nessun array esterno. Richiesto da Splunk HEC, Elastic / OpenSearch, e Grafana Loki.", "httpDestFormatSingleTitle": "Un Evento Per Richiesta", "httpDestFormatSingleDescription": "Invia un HTTP POST separato per ogni singolo evento. Usa solo per gli endpoint che non possono gestire i batch.", "httpDestLogTypesTitle": "Tipi Di Log", diff --git a/messages/ko-KR.json b/messages/ko-KR.json index b444d9f4d..f394fa2d6 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2118,7 +2118,7 @@ "selectDomainForOrgAuthPage": "조직 인증 페이지에 대한 도메인을 선택하세요.", "domainPickerProvidedDomain": "제공된 도메인", "domainPickerFreeProvidedDomain": "무료 제공된 도메인", - "domainPickerFreeDomainsPaidFeature": "제공된 도메인은 유료 기능입니다. 요금제에 도메인이 포함되도록 구독하세요. — 별도로 도메인을 준비할 필요 없습니다.", + "domainPickerFreeDomainsPaidFeature": "제공된 도메인은 유료 기능입니다. 요금제에 도메인이 포함되도록 구독하세요. - 별도로 도메인을 준비할 필요 없습니다.", "domainPickerVerified": "검증됨", "domainPickerUnverified": "검증되지 않음", "domainPickerManual": "수동", @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "사용 공개", - "description": "당신의 의도된 사용에 정확히 맞는 라이선스 등급을 선택하세요. 개인 라이선스는 연간 총 수익 100,000 USD 이하의 개인, 비상업적 또는 소규모 상업 활동을 위한 소프트웨어의 무료 사용을 허용합니다. 이러한 제한을 넘는 모든 사용 — 비즈니스, 조직 또는 기타 수익 창출 환경 내에서의 사용 — 은 유효한 엔터프라이즈 라이선스 및 해당 라이선스 수수료의 지불이 필요합니다. 개인 또는 기업 사용자는 모두 Fossorial 상용 라이선스 조건을 준수해야 합니다." + "description": "당신의 의도된 사용에 정확히 맞는 라이선스 등급을 선택하세요. 개인 라이선스는 연간 총 수익 100,000 USD 이하의 개인, 비상업적 또는 소규모 상업 활동을 위한 소프트웨어의 무료 사용을 허용합니다. 이러한 제한을 넘는 모든 사용 - 비즈니스, 조직 또는 기타 수익 창출 환경 내에서의 사용 - 은 유효한 엔터프라이즈 라이선스 및 해당 라이선스 수수료의 지불이 필요합니다. 개인 또는 기업 사용자는 모두 Fossorial 상용 라이선스 조건을 준수해야 합니다." }, "trialPeriodInformation": { "title": "시험 기간 정보", @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "JSON 배열", "httpDestFormatJsonArrayDescription": "각 배치마다 요청 하나씩, 본문은 JSON 배열입니다. 대부분의 일반 웹훅 및 Datadog과 호환됩니다.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "각 배치마다 요청 하나씩, 본문은 줄 구분 JSON — 한 라인에 하나의 객체가 있으며 외부 배열이 없습니다. Splunk HEC, Elastic / OpenSearch, Grafana Loki에 필요합니다.", + "httpDestFormatNdjsonDescription": "각 배치마다 요청 하나씩, 본문은 줄 구분 JSON - 한 라인에 하나의 객체가 있으며 외부 배열이 없습니다. Splunk HEC, Elastic / OpenSearch, Grafana Loki에 필요합니다.", "httpDestFormatSingleTitle": "각 요청 당 하나의 이벤트", "httpDestFormatSingleDescription": "각 개별 이벤트에 대해 별도의 HTTP POST를 전송합니다. 배치를 처리할 수 없는 엔드포인트에만 사용하세요.", "httpDestLogTypesTitle": "로그 유형", diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 91593503a..d8bd93680 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "JSON liste", "httpDestFormatJsonArrayDescription": "Én forespørsel per batch, innholdet er en JSON-liste. Kompatibel med de mest generiske webhooks og Datadog.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "Én forespørsel per sats, innholdet er nytt avgrenset JSON — et objekt per linje, ingen ytterarray. Kreves av Splunk HEC, Elastisk/OpenSearch, og Grafana Loki.", + "httpDestFormatNdjsonDescription": "Én forespørsel per sats, innholdet er nytt avgrenset JSON - et objekt per linje, ingen ytterarray. Kreves av Splunk HEC, Elastisk/OpenSearch, og Grafana Loki.", "httpDestFormatSingleTitle": "En hendelse per forespørsel", "httpDestFormatSingleDescription": "Sender en separat HTTP POST for hver enkelt hendelse. Bruk bare for endepunkter som ikke kan håndtere batcher.", "httpDestLogTypesTitle": "Logg typer", diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 987e08419..fce7c836f 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2118,7 +2118,7 @@ "selectDomainForOrgAuthPage": "Selecteer een domein voor de authenticatiepagina van de organisatie", "domainPickerProvidedDomain": "Opgegeven 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.", + "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", "domainPickerUnverified": "Ongeverifieerd", "domainPickerManual": "Handleiding", diff --git a/messages/pl-PL.json b/messages/pl-PL.json index eb4b4af2f..41b10b7fb 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -1993,7 +1993,7 @@ "description": "Większa niezawodność i niska konserwacja serwera Pangolin z dodatkowymi dzwonkami i sygnałami", "introTitle": "Zarządzany samowystarczalny Pangolin", "introDescription": "jest opcją wdrażania zaprojektowaną dla osób, które chcą prostoty i dodatkowej niezawodności, przy jednoczesnym utrzymaniu swoich danych prywatnych i samodzielnych.", - "introDetail": "Z tą opcją nadal obsługujesz swój własny węzeł Pangolin — tunele, zakończenie SSL i ruch na Twoim serwerze. Różnica polega na tym, że zarządzanie i monitorowanie odbywa się za pomocą naszej tablicy rozdzielczej, która odblokowuje szereg korzyści:", + "introDetail": "Z tą opcją nadal obsługujesz swój własny węzeł Pangolin - tunele, zakończenie SSL i ruch na Twoim serwerze. Różnica polega na tym, że zarządzanie i monitorowanie odbywa się za pomocą naszej tablicy rozdzielczej, która odblokowuje szereg korzyści:", "benefitSimplerOperations": { "title": "Uproszczone operacje", "description": "Nie ma potrzeby uruchamiania własnego serwera pocztowego lub ustawiania skomplikowanych powiadomień. Będziesz mieć kontrolę zdrowia i powiadomienia o przestoju." @@ -2118,7 +2118,7 @@ "selectDomainForOrgAuthPage": "Wybierz domenę dla strony uwierzytelniania organizacji", "domainPickerProvidedDomain": "Dostarczona 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.", + "domainPickerFreeDomainsPaidFeature": "Dostarczane domeny to funkcja płatna. Subskrybuj, aby uzyskać domenę w ramach swojego planu - nie ma potrzeby przynoszenia własnej.", "domainPickerVerified": "Zweryfikowano", "domainPickerUnverified": "Niezweryfikowane", "domainPickerManual": "Podręcznik", @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "Tablica JSON", "httpDestFormatJsonArrayDescription": "Jedna prośba na partię, treść jest tablicą JSON. Kompatybilna z najbardziej ogólnymi webhookami i Datadog.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "Jedno żądanie na partię, ciałem jest plik JSON rozdzielony na newline-delimited — jeden obiekt na wiersz, bez tablicy zewnętrznej. Wymagane przez Splunk HEC, Elastic / OpenSesearch i Grafana Loki.", + "httpDestFormatNdjsonDescription": "Jedno żądanie na partię, ciałem jest plik JSON rozdzielony na newline-delimited - jeden obiekt na wiersz, bez tablicy zewnętrznej. Wymagane przez Splunk HEC, Elastic / OpenSesearch i Grafana Loki.", "httpDestFormatSingleTitle": "Jedno wydarzenie na żądanie", "httpDestFormatSingleDescription": "Wysyła oddzielny POST HTTP dla każdego zdarzenia. Użyj tylko dla punktów końcowych, które nie mogą obsługiwać partii.", "httpDestLogTypesTitle": "Typy logów", diff --git a/messages/pt-PT.json b/messages/pt-PT.json index a16101e43..df7ef9f17 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -1993,7 +1993,7 @@ "description": "Servidor Pangolin auto-hospedado mais confiável e com baixa manutenção com sinos extras e assobiamentos", "introTitle": "Pangolin Auto-Hospedado Gerenciado", "introDescription": "é uma opção de implantação projetada para pessoas que querem simplicidade e confiança adicional, mantendo os seus dados privados e auto-hospedados.", - "introDetail": "Com esta opção, você ainda roda seu próprio nó Pangolin — seus túneis, terminação SSL e tráfego todos permanecem no seu servidor. A diferença é que a gestão e a monitorização são geridos através do nosso painel de nuvem, que desbloqueia vários benefícios:", + "introDetail": "Com esta opção, você ainda roda seu próprio nó Pangolin - seus túneis, terminação SSL e tráfego todos permanecem no seu servidor. A diferença é que a gestão e a monitorização são geridos através do nosso painel de nuvem, que desbloqueia vários benefícios:", "benefitSimplerOperations": { "title": "Operações simples", "description": "Não é necessário executar o seu próprio servidor de e-mail ou configurar um alerta complexo. Você receberá fora de caixa verificações de saúde e alertas de tempo de inatividade." @@ -2118,7 +2118,7 @@ "selectDomainForOrgAuthPage": "Selecione um domínio para a página de autenticação da organização", "domainPickerProvidedDomain": "Domínio fornecido", "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.", + "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", "domainPickerUnverified": "Não verificado", "domainPickerManual": "Manual", @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "Divulgação de uso", - "description": "Selecione o nível de licença que reflete corretamente seu uso pretendido. A Licença Pessoal permite o uso livre do Software para atividades comerciais individuais, não comerciais ou em pequena escala com rendimento bruto anual inferior a 100.000 USD. Qualquer uso além destes limites — incluindo uso dentro de um negócio, organização, ou outro ambiente gerador de receitas — requer uma Licença Enterprise válida e o pagamento da taxa aplicável de licenciamento. Todos os usuários, pessoais ou empresariais, devem cumprir os Termos da Licença Comercial Fossorial." + "description": "Selecione o nível de licença que reflete corretamente seu uso pretendido. A Licença Pessoal permite o uso livre do Software para atividades comerciais individuais, não comerciais ou em pequena escala com rendimento bruto anual inferior a 100.000 USD. Qualquer uso além destes limites - incluindo uso dentro de um negócio, organização, ou outro ambiente gerador de receitas - requer uma Licença Enterprise válida e o pagamento da taxa aplicável de licenciamento. Todos os usuários, pessoais ou empresariais, devem cumprir os Termos da Licença Comercial Fossorial." }, "trialPeriodInformation": { "title": "Informações do Período de Avaliação", @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "Matriz JSON", "httpDestFormatJsonArrayDescription": "Um pedido por lote, o corpo é um array JSON. Compatível com a maioria dos webhooks genéricos e Datadog.", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "Um pedido por lote, o corpo é um JSON delimitado por nova-linha — um objeto por linha, sem array exterior. Requerido pelo Splunk HEC, Elástico / OpenSearch, e Grafana Loki.", + "httpDestFormatNdjsonDescription": "Um pedido por lote, o corpo é um JSON delimitado por nova-linha - um objeto por linha, sem array exterior. Requerido pelo Splunk HEC, Elástico / OpenSearch, e Grafana Loki.", "httpDestFormatSingleTitle": "Um Evento por Requisição", "httpDestFormatSingleDescription": "Envia um POST HTTP separado para cada evento. Utilize apenas para endpoints que não podem manipular lotes.", "httpDestLogTypesTitle": "Tipos de log", diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 279f8b1a8..7a8bee8b9 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -56,7 +56,7 @@ "siteManageSites": "Управление сайтами", "siteDescription": "Создание и управление сайтами, чтобы включить подключение к приватным сетям", "sitesBannerTitle": "Подключить любую сеть", - "sitesBannerDescription": "Сайт — это соединение с удаленной сетью, которое позволяет Pangolin предоставлять доступ к ресурсам, будь они общедоступными или частными, пользователям в любом месте. Установите сетевой коннектор сайта (Newt) там, где можно запустить исполняемый файл или контейнер, чтобы установить соединение.", + "sitesBannerDescription": "Сайт - это соединение с удаленной сетью, которое позволяет Pangolin предоставлять доступ к ресурсам, будь они общедоступными или частными, пользователям в любом месте. Установите сетевой коннектор сайта (Newt) там, где можно запустить исполняемый файл или контейнер, чтобы установить соединение.", "sitesBannerButtonText": "Установить сайт", "approvalsBannerTitle": "Одобрить или запретить доступ к устройству", "approvalsBannerDescription": "Просмотрите и подтвердите или отклоните запросы на доступ к устройству от пользователей. Когда требуется подтверждение устройства, пользователи должны получить одобрение администратора, прежде чем их устройства смогут подключиться к ресурсам вашей организации.", @@ -163,7 +163,7 @@ "proxyResourceTitle": "Управление публичными ресурсами", "proxyResourceDescription": "Создание и управление ресурсами, которые доступны через веб-браузер", "proxyResourcesBannerTitle": "Общедоступный доступ через веб", - "proxyResourcesBannerDescription": "Общедоступные ресурсы — это прокси-по HTTPS или TCP/UDP, доступные любому пользователю в Интернете через веб-браузер. В отличие от частных ресурсов, они не требуют программного обеспечения на стороне клиента и могут включать политики доступа на основе идентификации и контекста.", + "proxyResourcesBannerDescription": "Общедоступные ресурсы - это прокси-по HTTPS или TCP/UDP, доступные любому пользователю в Интернете через веб-браузер. В отличие от частных ресурсов, они не требуют программного обеспечения на стороне клиента и могут включать политики доступа на основе идентификации и контекста.", "clientResourceTitle": "Управление приватными ресурсами", "clientResourceDescription": "Создание и управление ресурсами, которые доступны только через подключенный клиент", "privateResourcesBannerTitle": "Частный доступ с нулевым доверием", @@ -371,7 +371,7 @@ "provisioningKeysUpdated": "Ключ подготовки обновлен", "provisioningKeysUpdatedDescription": "Ваши изменения были сохранены.", "provisioningKeysBannerTitle": "Ключи подготовки сайта", - "provisioningKeysBannerDescription": "Создайте ключ настройки и используйте его с соединителем Newt для автоматического создания сайтов при первом запуске — нет необходимости настраивать отдельные учетные данные для каждого сайта.", + "provisioningKeysBannerDescription": "Создайте ключ настройки и используйте его с соединителем Newt для автоматического создания сайтов при первом запуске - нет необходимости настраивать отдельные учетные данные для каждого сайта.", "provisioningKeysBannerButtonText": "Узнать больше", "pendingSitesBannerTitle": "Ожидающие сайты", "pendingSitesBannerDescription": "Сайты, подключающиеся с помощью ключа настройки, отображаются здесь для проверки.", @@ -1993,7 +1993,7 @@ "description": "Более надежный и низко обслуживаемый сервер Pangolin с дополнительными колокольнями и свистками", "introTitle": "Управляемый Само-Хост Панголина", "introDescription": "- это вариант развертывания, предназначенный для людей, которые хотят простоты и надёжности, сохраняя при этом свои данные конфиденциальными и самостоятельными.", - "introDetail": "С помощью этой опции вы по-прежнему используете узел Pangolin — туннели, SSL, и весь остающийся на вашем сервере. Разница заключается в том, что управление и мониторинг осуществляются через нашу панель инструментов из облака, которая открывает ряд преимуществ:", + "introDetail": "С помощью этой опции вы по-прежнему используете узел Pangolin - туннели, SSL, и весь остающийся на вашем сервере. Разница заключается в том, что управление и мониторинг осуществляются через нашу панель инструментов из облака, которая открывает ряд преимуществ:", "benefitSimplerOperations": { "title": "Более простые операции", "description": "Не нужно запускать свой собственный почтовый сервер или настроить комплексное оповещение. Вы будете получать проверки состояния здоровья и оповещения о неисправностях из коробки." @@ -2118,7 +2118,7 @@ "selectDomainForOrgAuthPage": "Выберите домен для страницы аутентификации организации", "domainPickerProvidedDomain": "Домен предоставлен", "domainPickerFreeProvidedDomain": "Бесплатный домен", - "domainPickerFreeDomainsPaidFeature": "Предоставленные домены являются платной функцией. Подпишитесь, чтобы получить домен, включенный в ваш план — не нужно приносить свой собственный.", + "domainPickerFreeDomainsPaidFeature": "Предоставленные домены являются платной функцией. Подпишитесь, чтобы получить домен, включенный в ваш план - не нужно приносить свой собственный.", "domainPickerVerified": "Подтверждено", "domainPickerUnverified": "Не подтверждено", "domainPickerManual": "Ручной", @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "Раскрытие", - "description": "Выберите уровень лицензии, который точно отражает ваше предполагаемое использование. Личная Лицензия разрешает свободное использование Программного Обеспечения для частной, некоммерческой или малой коммерческой деятельности с годовым валовым доходом до $100 000 USD. Любое использование сверх этих пределов — включая использование в бизнесе, организацию, или другой приносящей доход среде — требует действительной лицензии предприятия и уплаты соответствующей лицензионной платы. Все пользователи, будь то Личные или Предприятия, обязаны соблюдать условия коммерческой лицензии Fossoral." + "description": "Выберите уровень лицензии, который точно отражает ваше предполагаемое использование. Личная Лицензия разрешает свободное использование Программного Обеспечения для частной, некоммерческой или малой коммерческой деятельности с годовым валовым доходом до $100 000 USD. Любое использование сверх этих пределов - включая использование в бизнесе, организацию, или другой приносящей доход среде - требует действительной лицензии предприятия и уплаты соответствующей лицензионной платы. Все пользователи, будь то Личные или Предприятия, обязаны соблюдать условия коммерческой лицензии Fossoral." }, "trialPeriodInformation": { "title": "Информация о пробном периоде", diff --git a/messages/tr-TR.json b/messages/tr-TR.json index e38b93ca8..68de3399d 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -1993,7 +1993,7 @@ "description": "Daha güvenilir ve düşük bakım gerektiren, ekstra özelliklere sahip kendi kendine barındırabileceğiniz Pangolin sunucusu", "introTitle": "Yönetilen Kendi Kendine Barındırılan Pangolin", "introDescription": "Bu, basitlik ve ekstra güvenilirlik arayan, ancak verilerini gizli tutmak ve kendi sunucularında barındırmak isteyen kişiler için tasarlanmış bir dağıtım seçeneğidir.", - "introDetail": "Bu seçenekle, kendi Pangolin düğümünüzü çalıştırmaya devam edersiniz — tünelleriniz, SSL bitişiniz ve trafiğiniz tamamen sunucunuzda kalır. Fark, yönetim ve izlemeyi bulut panomuz üzerinden gerçekleştiririz, bu da bir dizi avantaj sağlar:", + "introDetail": "Bu seçenekle, kendi Pangolin düğümünüzü çalıştırmaya devam edersiniz - tünelleriniz, SSL bitişiniz ve trafiğiniz tamamen sunucunuzda kalır. Fark, yönetim ve izlemeyi bulut panomuz üzerinden gerçekleştiririz, bu da bir dizi avantaj sağlar:", "benefitSimplerOperations": { "title": "Daha basit işlemler", "description": "Kendi e-posta sunucunuzu çalıştırmanıza veya karmaşık uyarılar kurmanıza gerek yok. Sağlık kontrolleri ve kesinti uyarılarını kutudan çıktığı gibi alırsınız." @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "Kullanım Açıklaması", - "description": "Kullanım amacınızı doğru bir şekilde yansıtan lisans seviyesini seçin. Kişisel Lisans, yazılımın bireysel, ticari olmayan veya yıllık geliri 100,000 ABD Dolarının altında olan küçük ölçekli ticari faaliyetlerde ücretsiz kullanılmasına izin verir. Bu sınırların ötesinde kullanım — bir işletme, organizasyon veya diğer gelir getirici ortamlarda kullanım dahil olmak üzere — geçerli bir Kurumsal Lisans ve ilgili lisans ücretinin ödenmesini gerektirir. Tüm kullanıcılar, ister Kişisel ister Kurumsal, Fossorial Ticari Lisans Şartlarına uymalıdır." + "description": "Kullanım amacınızı doğru bir şekilde yansıtan lisans seviyesini seçin. Kişisel Lisans, yazılımın bireysel, ticari olmayan veya yıllık geliri 100,000 ABD Dolarının altında olan küçük ölçekli ticari faaliyetlerde ücretsiz kullanılmasına izin verir. Bu sınırların ötesinde kullanım - bir işletme, organizasyon veya diğer gelir getirici ortamlarda kullanım dahil olmak üzere - geçerli bir Kurumsal Lisans ve ilgili lisans ücretinin ödenmesini gerektirir. Tüm kullanıcılar, ister Kişisel ister Kurumsal, Fossorial Ticari Lisans Şartlarına uymalıdır." }, "trialPeriodInformation": { "title": "Deneme Süresi Bilgileri", diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 761f39524..955ad7096 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -1993,7 +1993,7 @@ "description": "更可靠和低维护自我托管的 Pangolin 服务器,带有额外的铃声和告密器", "introTitle": "托管自托管的潘戈林公司", "introDescription": "这是一种部署选择,为那些希望简洁和额外可靠的人设计,同时仍然保持他们的数据的私密性和自我托管性。", - "introDetail": "通过此选项,您仍然运行您自己的 Pangolin 节点 — — 您的隧道、SSL 终止,并且流量在您的服务器上保持所有状态。 不同之处在于,管理和监测是通过我们的云层仪表板进行的,该仪表板开启了一些好处:", + "introDetail": "通过此选项,您仍然运行您自己的 Pangolin 节点 - - 您的隧道、SSL 终止,并且流量在您的服务器上保持所有状态。 不同之处在于,管理和监测是通过我们的云层仪表板进行的,该仪表板开启了一些好处:", "benefitSimplerOperations": { "title": "简单的操作", "description": "无需运行您自己的邮件服务器或设置复杂的警报。您将从方框中获得健康检查和下限提醒。" @@ -2118,7 +2118,7 @@ "selectDomainForOrgAuthPage": "选择组织认证页面的域", "domainPickerProvidedDomain": "提供的域", "domainPickerFreeProvidedDomain": "免费提供的域", - "domainPickerFreeDomainsPaidFeature": "提供的域名是付费功能。订阅即可将域名包含在您的计划中—无需自带域名。", + "domainPickerFreeDomainsPaidFeature": "提供的域名是付费功能。订阅即可将域名包含在您的计划中-无需自带域名。", "domainPickerVerified": "已验证", "domainPickerUnverified": "未验证", "domainPickerManual": "手动", @@ -2296,7 +2296,7 @@ "alerts": { "commercialUseDisclosure": { "title": "使用情况披露", - "description": "选择能准确反映您预定用途的许可等级。 个人许可证允许对个人、非商业性或小型商业活动免费使用软件,年收入毛额不到100 000美元。 超出这些限度的任何用途,包括在企业、组织内的用途。 或其他创收环境——需要有效的企业许可证和支付适用的许可证费用。 所有用户,不论是个人还是企业,都必须遵守寄养商业许可证条款。" + "description": "选择能准确反映您预定用途的许可等级。 个人许可证允许对个人、非商业性或小型商业活动免费使用软件,年收入毛额不到100 000美元。 超出这些限度的任何用途,包括在企业、组织内的用途。 或其他创收环境--需要有效的企业许可证和支付适用的许可证费用。 所有用户,不论是个人还是企业,都必须遵守寄养商业许可证条款。" }, "trialPeriodInformation": { "title": "试用期信息", @@ -2881,7 +2881,7 @@ "httpDestFormatJsonArrayTitle": "JSON 数组", "httpDestFormatJsonArrayDescription": "每批一个请求,实体是一个 JSON 数组。与大多数通用的 Web 钩子和数据兼容。", "httpDestFormatNdjsonTitle": "NDJSON", - "httpDestFormatNdjsonDescription": "每批有一个请求,物体是换行符限制的 JSON ——每行一个对象,不是外部数组。 Sluk HEC、Elastic / OpenSearch和Grafana Loki所需。", + "httpDestFormatNdjsonDescription": "每批有一个请求,物体是换行符限制的 JSON --每行一个对象,不是外部数组。 Sluk HEC、Elastic / OpenSearch和Grafana Loki所需。", "httpDestFormatSingleTitle": "每个请求一个事件", "httpDestFormatSingleDescription": "为每个事件单独发送一个 HTTP POST。仅用于无法处理批量的端点。", "httpDestLogTypesTitle": "日志类型", diff --git a/messages/zh-TW.json b/messages/zh-TW.json index cf7c25ced..1ef8061e2 100644 --- a/messages/zh-TW.json +++ b/messages/zh-TW.json @@ -1763,7 +1763,7 @@ "description": "更可靠、維護成本更低的自架 Pangolin 伺服器,並附帶額外的附加功能", "introTitle": "託管式自架 Pangolin", "introDescription": "這是一種部署選擇,為那些希望簡潔和額外可靠的人設計,同時仍然保持他們的數據的私密性和自我託管性。", - "introDetail": "通過此選項,您仍然運行您自己的 Pangolin 節點 — — 您的隧道、SSL 終止,並且流量在您的伺服器上保持所有狀態。 不同之處在於,管理和監測是通過我們的雲層儀錶板進行的,該儀錶板開啟了一些好處:", + "introDetail": "通過此選項,您仍然運行您自己的 Pangolin 節點 - - 您的隧道、SSL 終止,並且流量在您的伺服器上保持所有狀態。 不同之處在於,管理和監測是通過我們的雲層儀錶板進行的,該儀錶板開啟了一些好處:", "benefitSimplerOperations": { "title": "簡單的操作", "description": "無需運行您自己的郵件伺服器或設置複雜的警報。您將從方框中獲得健康檢查和下限提醒。" @@ -2035,7 +2035,7 @@ "alerts": { "commercialUseDisclosure": { "title": "使用情況披露", - "description": "選擇能準確反映您預定用途的許可等級。 個人許可證允許對個人、非商業性或小型商業活動免費使用軟體,年收入毛額不到 100,000 美元。 超出這些限度的任何用途,包括在企業、組織內的用途。 或其他創收環境——需要有效的企業許可證和支付適用的許可證費用。 所有用戶,不論是個人還是企業,都必須遵守寄養商業許可證條款。" + "description": "選擇能準確反映您預定用途的許可等級。 個人許可證允許對個人、非商業性或小型商業活動免費使用軟體,年收入毛額不到 100,000 美元。 超出這些限度的任何用途,包括在企業、組織內的用途。 或其他創收環境--需要有效的企業許可證和支付適用的許可證費用。 所有用戶,不論是個人還是企業,都必須遵守寄養商業許可證條款。" }, "trialPeriodInformation": { "title": "試用期資訊", diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index f39a125a3..b091eb20b 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -189,9 +189,11 @@ export const targetHealthCheck = pgTable("targetHealthCheck", { targetId: integer("targetId").references(() => targets.targetId, { onDelete: "cascade" }), - orgId: varchar("orgId").references(() => orgs.orgId, { - onDelete: "cascade" - }), + orgId: varchar("orgId") + .references(() => orgs.orgId, { + onDelete: "cascade" + }) + .notNull(), name: varchar("name"), hcEnabled: boolean("hcEnabled").notNull().default(false), hcPath: varchar("hcPath"), diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 22144a2c6..f30331d64 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -209,11 +209,14 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", { targetHealthCheckId: integer("targetHealthCheckId").primaryKey({ autoIncrement: true }), - targetId: integer("targetId") - .references(() => targets.targetId, { onDelete: "cascade" }), - orgId: text("orgId").references(() => orgs.orgId, { + targetId: integer("targetId").references(() => targets.targetId, { onDelete: "cascade" }), + orgId: text("orgId") + .references(() => orgs.orgId, { + onDelete: "cascade" + }) + .notNull(), name: text("name"), hcEnabled: integer("hcEnabled", { mode: "boolean" }) .notNull() @@ -294,7 +297,7 @@ export const siteResources = sqliteTable("siteResources", { onDelete: "set null" }), subdomain: text("subdomain"), - fullDomain: text("fullDomain"), + fullDomain: text("fullDomain") }); export const networks = sqliteTable("networks", { diff --git a/server/emails/templates/AlertNotification.tsx b/server/emails/templates/AlertNotification.tsx index c01a99f3e..8a0cf7631 100644 --- a/server/emails/templates/AlertNotification.tsx +++ b/server/emails/templates/AlertNotification.tsx @@ -83,7 +83,7 @@ function formatDataItems( .replace(/([A-Z])/g, " $1") .replace(/^./, (s) => s.toUpperCase()) .trim(), - value: String(value ?? "—") + value: String(value ?? "-") })); } @@ -137,4 +137,4 @@ export const AlertNotification = ({ eventType, orgId, data }: Props) => { ); }; -export default AlertNotification; \ No newline at end of file +export default AlertNotification; diff --git a/server/emails/templates/EnterpriseEditionKeyGenerated.tsx b/server/emails/templates/EnterpriseEditionKeyGenerated.tsx index 44472c8a6..82154ab7d 100644 --- a/server/emails/templates/EnterpriseEditionKeyGenerated.tsx +++ b/server/emails/templates/EnterpriseEditionKeyGenerated.tsx @@ -32,7 +32,7 @@ export const EnterpriseEditionKeyGenerated = ({ }: EnterpriseEditionKeyGeneratedProps) => { const previewText = personalUseOnly ? "Your Enterprise Edition key for personal use is ready" - : "Thank you for your purchase — your Enterprise Edition key is ready"; + : "Thank you for your purchase - your Enterprise Edition key is ready"; return ( diff --git a/server/lib/rebuildClientAssociations.ts b/server/lib/rebuildClientAssociations.ts index b213ec9be..40c5a5bf7 100644 --- a/server/lib/rebuildClientAssociations.ts +++ b/server/lib/rebuildClientAssociations.ts @@ -825,7 +825,7 @@ async function handleSubnetProxyTargetUpdates( // Check if this client still has access to another resource // on this specific site with the same destination. We scope // by siteId (via siteNetworks) rather than networkId because - // removePeerData operates per-site — a resource on a different + // removePeerData operates per-site - a resource on a different // site sharing the same network should not block removal here. const destinationStillInUse = await trx .select() @@ -980,7 +980,7 @@ export async function rebuildClientAssociationsFromClient( ) : []; - // Group by siteId for site-level associations — look up via siteNetworks since + // Group by siteId for site-level associations - look up via siteNetworks since // siteResources no longer carries a direct siteId column. const networkIds = Array.from( new Set( @@ -1459,7 +1459,7 @@ async function handleMessagesForClientResources( // Check if this client still has access to another resource // on this specific site with the same destination. We scope // by siteId (via siteNetworks) rather than networkId because - // removePeerData operates per-site — a resource on a different + // removePeerData operates per-site - a resource on a different // site sharing the same network should not block removal here. const destinationStillInUse = await trx .select() diff --git a/server/lib/traefik/TraefikConfigManager.ts b/server/lib/traefik/TraefikConfigManager.ts index 6ef3c45b5..c8fcfafdc 100644 --- a/server/lib/traefik/TraefikConfigManager.ts +++ b/server/lib/traefik/TraefikConfigManager.ts @@ -1011,7 +1011,7 @@ export class TraefikConfigManager { ); if (!isUnused) { - // Domain is still active — remove from pending deletion if it was queued + // Domain is still active - remove from pending deletion if it was queued if (this.pendingDeletion.has(dirName)) { logger.info( `Certificate ${dirName} is active again, cancelling pending deletion` @@ -1021,7 +1021,7 @@ export class TraefikConfigManager { continue; } - // Domain is unused — add to pending deletion or decrement its counter + // Domain is unused - add to pending deletion or decrement its counter if (!this.pendingDeletion.has(dirName)) { const graceCycles = 3; logger.info( @@ -1036,7 +1036,7 @@ export class TraefikConfigManager { ); this.pendingDeletion.set(dirName, remaining); } else { - // Grace period expired — actually delete now + // Grace period expired - actually delete now this.pendingDeletion.delete(dirName); const domainDir = path.join(certsPath, dirName); diff --git a/server/lib/traefik/pathEncoding.test.ts b/server/lib/traefik/pathEncoding.test.ts index 83d53a039..f0318807a 100644 --- a/server/lib/traefik/pathEncoding.test.ts +++ b/server/lib/traefik/pathEncoding.test.ts @@ -24,7 +24,7 @@ function encodePath(path: string | null | undefined): string { /** * Exact replica of the OLD key computation from upstream main. - * Uses sanitize() for paths — this is what had the collision bug. + * Uses sanitize() for paths - this is what had the collision bug. */ function oldKeyComputation( resourceId: number, @@ -44,7 +44,7 @@ function oldKeyComputation( /** * Replica of the NEW key computation from our fix. - * Uses encodePath() for paths — collision-free. + * Uses encodePath() for paths - collision-free. */ function newKeyComputation( resourceId: number, @@ -195,11 +195,11 @@ function runTests() { true, "/a/b and /a-b MUST have different keys" ); - console.log(" PASS: collision fix — /a/b vs /a-b have different keys"); + console.log(" PASS: collision fix - /a/b vs /a-b have different keys"); passed++; } - // Test 9: demonstrate the old bug — old code maps /a/b and /a-b to same key + // Test 9: demonstrate the old bug - old code maps /a/b and /a-b to same key { const oldKeyAB = oldKeyComputation(1, "/a/b", "prefix", null, null); const oldKeyDash = oldKeyComputation(1, "/a-b", "prefix", null, null); @@ -208,11 +208,11 @@ function runTests() { oldKeyDash, "old code MUST have this collision (confirms the bug exists)" ); - console.log(" PASS: confirmed old code bug — /a/b and /a-b collided"); + console.log(" PASS: confirmed old code bug - /a/b and /a-b collided"); passed++; } - // Test 10: /api/v1 and /api-v1 — old code collision, new code fixes it + // Test 10: /api/v1 and /api-v1 - old code collision, new code fixes it { const oldKey1 = oldKeyComputation(1, "/api/v1", "prefix", null, null); const oldKey2 = oldKeyComputation(1, "/api-v1", "prefix", null, null); @@ -229,11 +229,11 @@ function runTests() { true, "new code must separate /api/v1 and /api-v1" ); - console.log(" PASS: collision fix — /api/v1 vs /api-v1"); + console.log(" PASS: collision fix - /api/v1 vs /api-v1"); passed++; } - // Test 11: /app.v2 and /app/v2 and /app-v2 — three-way collision fixed + // Test 11: /app.v2 and /app/v2 and /app-v2 - three-way collision fixed { const a = newKeyComputation(1, "/app.v2", "prefix", null, null); const b = newKeyComputation(1, "/app/v2", "prefix", null, null); @@ -245,14 +245,14 @@ function runTests() { "three paths must produce three unique keys" ); console.log( - " PASS: collision fix — three-way /app.v2, /app/v2, /app-v2" + " PASS: collision fix - three-way /app.v2, /app/v2, /app-v2" ); passed++; } // ── Edge cases ─────────────────────────────────────────────────── - // Test 12: same path in different resources — always separate + // Test 12: same path in different resources - always separate { const key1 = newKeyComputation(1, "/api", "prefix", null, null); const key2 = newKeyComputation(2, "/api", "prefix", null, null); @@ -261,11 +261,11 @@ function runTests() { true, "different resources with same path must have different keys" ); - console.log(" PASS: edge case — same path, different resources"); + console.log(" PASS: edge case - same path, different resources"); passed++; } - // Test 13: same resource, different pathMatchType — separate keys + // Test 13: same resource, different pathMatchType - separate keys { const exact = newKeyComputation(1, "/api", "exact", null, null); const prefix = newKeyComputation(1, "/api", "prefix", null, null); @@ -274,11 +274,11 @@ function runTests() { true, "exact vs prefix must have different keys" ); - console.log(" PASS: edge case — same path, different match types"); + console.log(" PASS: edge case - same path, different match types"); passed++; } - // Test 14: same resource and path, different rewrite config — separate keys + // Test 14: same resource and path, different rewrite config - separate keys { const noRewrite = newKeyComputation(1, "/api", "prefix", null, null); const withRewrite = newKeyComputation( @@ -293,7 +293,7 @@ function runTests() { true, "with vs without rewrite must have different keys" ); - console.log(" PASS: edge case — same path, different rewrite config"); + console.log(" PASS: edge case - same path, different rewrite config"); passed++; } @@ -308,7 +308,7 @@ function runTests() { paths.length, "special URL chars must produce unique keys" ); - console.log(" PASS: edge case — special URL characters in paths"); + console.log(" PASS: edge case - special URL characters in paths"); passed++; } diff --git a/server/private/lib/acmeCertSync.ts b/server/private/lib/acmeCertSync.ts index f640a6859..faa45b08e 100644 --- a/server/private/lib/acmeCertSync.ts +++ b/server/private/lib/acmeCertSync.ts @@ -144,7 +144,7 @@ async function pushCertUpdateToAffectedNewts( await cache.del(`cert:${resource.fullDomain}`); } - // Generate target once — same cert applies to all sites for this resource + // Generate target once - same cert applies to all sites for this resource const newTargets = await generateSubnetProxyTargetV2( resource, resourceClients @@ -157,7 +157,7 @@ async function pushCertUpdateToAffectedNewts( continue; } - // Construct the old targets — same routing shape but with the previous cert/key. + // Construct the old targets - same routing shape but with the previous cert/key. // The newt only uses destPrefix/sourcePrefixes for removal, but we keep the // semantics correct so the update message accurately reflects what changed. const oldTargets: SubnetProxyTargetV2[] = newTargets.map((t) => ({ diff --git a/server/private/lib/logConnectionAudit.ts b/server/private/lib/logConnectionAudit.ts index 8cc3a1e52..039b75ec9 100644 --- a/server/private/lib/logConnectionAudit.ts +++ b/server/private/lib/logConnectionAudit.ts @@ -153,7 +153,7 @@ export async function flushConnectionLogToDb(): Promise { ); } - // Stop processing further batches from this snapshot — they will + // Stop processing further batches from this snapshot - they will // be picked up via the re-queued records on the next flush. const remaining = snapshot.slice(i + INSERT_BATCH_SIZE); if (remaining.length > 0) { @@ -180,7 +180,7 @@ const flushTimer = setInterval(async () => { }, FLUSH_INTERVAL_MS); // Calling unref() means this timer will not keep the Node.js event loop alive -// on its own — the process can still exit normally when there is no other work +// on its own - the process can still exit normally when there is no other work // left. The graceful-shutdown path will call flushConnectionLogToDb() explicitly // before process.exit(), so no data is lost. flushTimer.unref(); @@ -223,7 +223,7 @@ export function logConnectionAudit(record: ConnectionLogRecord): void { buffer.push(record); if (buffer.length >= MAX_BUFFERED_RECORDS) { - // Fire and forget — errors are handled inside flushConnectionLogToDb + // Fire and forget - errors are handled inside flushConnectionLogToDb flushConnectionLogToDb().catch((error) => { logger.error( "Unexpected error during size-triggered connection log flush:", @@ -231,4 +231,4 @@ export function logConnectionAudit(record: ConnectionLogRecord): void { ); }); } -} \ No newline at end of file +} diff --git a/server/private/lib/logStreaming/providers/HttpLogDestination.ts b/server/private/lib/logStreaming/providers/HttpLogDestination.ts index dde7bd695..337a58f1f 100644 --- a/server/private/lib/logStreaming/providers/HttpLogDestination.ts +++ b/server/private/lib/logStreaming/providers/HttpLogDestination.ts @@ -37,7 +37,7 @@ const DEFAULT_FORMAT: PayloadFormat = "json_array"; * * **Payload formats** (controlled by `config.format`): * - * - `json_array` (default) — one POST per batch, body is a JSON array: + * - `json_array` (default) - one POST per batch, body is a JSON array: * ```json * [ * { "event": "request", "timestamp": "2024-01-01T00:00:00.000Z", "data": { … } }, @@ -46,7 +46,7 @@ const DEFAULT_FORMAT: PayloadFormat = "json_array"; * ``` * `Content-Type: application/json` * - * - `ndjson` — one POST per batch, body is newline-delimited JSON (one object + * - `ndjson` - one POST per batch, body is newline-delimited JSON (one object * per line, no outer array). Required by Splunk HEC, Elastic/OpenSearch, * and Grafana Loki: * ``` @@ -55,7 +55,7 @@ const DEFAULT_FORMAT: PayloadFormat = "json_array"; * ``` * `Content-Type: application/x-ndjson` * - * - `json_single` — one POST **per event**, body is a plain JSON object. + * - `json_single` - one POST **per event**, body is a plain JSON object. * Use only for endpoints that cannot handle batches at all. * * With a body template each event is rendered through the template before @@ -319,4 +319,4 @@ function epochSecondsToIso(epochSeconds: number): string { function escapeJsonString(value: string): string { // JSON.stringify produces `""` – strip the outer quotes. return JSON.stringify(value).slice(1, -1); -} \ No newline at end of file +} diff --git a/server/private/lib/logStreaming/types.ts b/server/private/lib/logStreaming/types.ts index 5eed79520..1bcd25a66 100644 --- a/server/private/lib/logStreaming/types.ts +++ b/server/private/lib/logStreaming/types.ts @@ -60,9 +60,9 @@ export type AuthType = "none" | "bearer" | "basic" | "custom"; /** * Controls how the batch of events is serialised into the HTTP request body. * - * - `json_array` – `[{…}, {…}]` — default; one POST per batch wrapped in a + * - `json_array` – `[{…}, {…}]` - default; one POST per batch wrapped in a * JSON array. Works with most generic webhooks and Datadog. - * - `ndjson` – `{…}\n{…}` — newline-delimited JSON, one object per + * - `ndjson` – `{…}\n{…}` - newline-delimited JSON, one object per * line. Required by Splunk HEC, Elastic/OpenSearch, Loki. * - `json_single` – one HTTP POST per event, body is a plain JSON object. * Use only for endpoints that cannot handle batches at all. @@ -131,4 +131,4 @@ export interface DestinationFailureState { nextRetryAt: number; /** Date.now() value of the very first failure in the current streak */ firstFailedAt: number; -} \ No newline at end of file +} diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index 404615a86..fb6e176b8 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -267,7 +267,7 @@ export async function getTraefikConfig( }); }); - // Query siteResources in HTTP mode with SSL enabled and aliases — cert generation / HTTPS edge + // Query siteResources in HTTP mode with SSL enabled and aliases - cert generation / HTTPS edge const siteResourcesWithFullDomain = await db .select({ siteResourceId: siteResources.siteResourceId, @@ -1010,7 +1010,7 @@ export async function getTraefikConfig( } } - // HTTPS router — presence of this entry triggers cert generation + // HTTPS router - presence of this entry triggers cert generation config_output.http.routers[siteResourceRouterName] = { entryPoints: [ config.getRawConfig().traefik.https_entrypoint @@ -1022,7 +1022,7 @@ export async function getTraefikConfig( tls }; - // Assets bypass router — lets Next.js static files load without rewrite + // Assets bypass router - lets Next.js static files load without rewrite config_output.http.routers[`${siteResourceRouterName}-assets`] = { entryPoints: [ config.getRawConfig().traefik.https_entrypoint diff --git a/server/private/routers/healthChecks/listHealthChecks.ts b/server/private/routers/healthChecks/listHealthChecks.ts index d5b05ac24..b2e6949a1 100644 --- a/server/private/routers/healthChecks/listHealthChecks.ts +++ b/server/private/routers/healthChecks/listHealthChecks.ts @@ -11,7 +11,7 @@ * This file is not licensed under the AGPLv3. */ -import { db, targetHealthCheck } from "@server/db"; +import { db, targetHealthCheck, targets, resources } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -84,12 +84,36 @@ export async function listHealthChecks( const whereClause = and( eq(targetHealthCheck.orgId, orgId), - isNull(targetHealthCheck.targetId) ); const list = await db - .select() + .select({ + targetHealthCheckId: targetHealthCheck.targetHealthCheckId, + name: targetHealthCheck.name, + hcEnabled: targetHealthCheck.hcEnabled, + hcHealth: targetHealthCheck.hcHealth, + hcMode: targetHealthCheck.hcMode, + hcHostname: targetHealthCheck.hcHostname, + hcPort: targetHealthCheck.hcPort, + hcPath: targetHealthCheck.hcPath, + hcScheme: targetHealthCheck.hcScheme, + hcMethod: targetHealthCheck.hcMethod, + hcInterval: targetHealthCheck.hcInterval, + hcUnhealthyInterval: targetHealthCheck.hcUnhealthyInterval, + hcTimeout: targetHealthCheck.hcTimeout, + hcHeaders: targetHealthCheck.hcHeaders, + hcFollowRedirects: targetHealthCheck.hcFollowRedirects, + hcStatus: targetHealthCheck.hcStatus, + hcTlsServerName: targetHealthCheck.hcTlsServerName, + hcHealthyThreshold: targetHealthCheck.hcHealthyThreshold, + hcUnhealthyThreshold: targetHealthCheck.hcUnhealthyThreshold, + resourceId: resources.resourceId, + resourceName: resources.name, + resourceNiceId: resources.niceId + }) .from(targetHealthCheck) + .leftJoin(targets, eq(targetHealthCheck.targetId, targets.targetId)) + .leftJoin(resources, eq(targets.resourceId, resources.resourceId)) .where(whereClause) .orderBy(sql`${targetHealthCheck.targetHealthCheckId} DESC`) .limit(limit) @@ -124,7 +148,10 @@ export async function listHealthChecks( hcStatus: row.hcStatus ?? null, hcTlsServerName: row.hcTlsServerName ?? null, hcHealthyThreshold: row.hcHealthyThreshold ?? null, - hcUnhealthyThreshold: row.hcUnhealthyThreshold ?? null + hcUnhealthyThreshold: row.hcUnhealthyThreshold ?? null, + resourceId: row.resourceId ?? null, + resourceName: row.resourceName ?? null, + resourceNiceId: row.resourceNiceId ?? null })), pagination: { total: count, diff --git a/server/routers/gerbil/receiveBandwidth.ts b/server/routers/gerbil/receiveBandwidth.ts index dcd897471..eacf3dad4 100644 --- a/server/routers/gerbil/receiveBandwidth.ts +++ b/server/routers/gerbil/receiveBandwidth.ts @@ -88,11 +88,11 @@ async function dbQueryRows>( ): Promise { const anyDb = db as any; if (typeof anyDb.execute === "function") { - // PostgreSQL (node-postgres via Drizzle) — returns { rows: [...] } or an array + // PostgreSQL (node-postgres via Drizzle) - returns { rows: [...] } or an array const result = await anyDb.execute(query); return (Array.isArray(result) ? result : (result.rows ?? [])) as T[]; } - // SQLite (better-sqlite3 via Drizzle) — returns an array directly + // SQLite (better-sqlite3 via Drizzle) - returns an array directly return (await anyDb.all(query)) as T[]; } @@ -106,7 +106,7 @@ function isSQLite(): boolean { * Swaps out the accumulator before writing so that any bandwidth messages * received during the flush are captured in the new accumulator rather than * being lost or causing contention. Sites are updated in chunks via a single - * batch UPDATE per chunk. Failed chunks are discarded — exact per-flush + * batch UPDATE per chunk. Failed chunks are discarded - exact per-flush * accuracy is not critical and re-queuing is not worth the added complexity. * * This function is exported so that the application's graceful-shutdown @@ -125,7 +125,7 @@ export async function flushSiteBandwidthToDb(): Promise { const currentTime = new Date().toISOString(); // Sort by publicKey for consistent lock ordering across concurrent - // writers — deadlock-prevention strategy. + // writers - deadlock-prevention strategy. const sortedEntries = [...snapshot.entries()].sort(([a], [b]) => a.localeCompare(b) ); @@ -150,7 +150,7 @@ export async function flushSiteBandwidthToDb(): Promise { try { rows = await withDeadlockRetry(async () => { if (isSQLite()) { - // SQLite: one UPDATE per row — no need for batch efficiency here. + // SQLite: one UPDATE per row - no need for batch efficiency here. const results: { orgId: string; pubKey: string }[] = []; for (const [publicKey, { bytesIn, bytesOut }] of chunk) { const result = await dbQueryRows<{ @@ -170,7 +170,7 @@ export async function flushSiteBandwidthToDb(): Promise { return results; } - // PostgreSQL: batch UPDATE … FROM (VALUES …) — single round-trip per chunk. + // PostgreSQL: batch UPDATE … FROM (VALUES …) - single round-trip per chunk. const valuesList = chunk.map(([publicKey, { bytesIn, bytesOut }]) => sql`(${publicKey}::text, ${bytesIn}::real, ${bytesOut}::real)` ); @@ -191,7 +191,7 @@ export async function flushSiteBandwidthToDb(): Promise { `Failed to flush bandwidth chunk [${i}–${chunkEnd}], discarding ${chunk.length} site(s):`, error ); - // Discard the chunk — exact per-flush accuracy is not critical. + // Discard the chunk - exact per-flush accuracy is not critical. continue; } @@ -232,7 +232,7 @@ export async function flushSiteBandwidthToDb(): Promise { totalBandwidth ); if (bandwidthUsage) { - // Fire-and-forget — don't block the flush on limit checking. + // Fire-and-forget - don't block the flush on limit checking. usageService .checkLimitSet( orgId, @@ -298,7 +298,7 @@ export async function updateSiteBandwidth( exitNodeId?: number ): Promise { for (const { publicKey, bytesIn, bytesOut } of bandwidthData) { - // Skip peers that haven't transferred any data — writing zeros to the + // Skip peers that haven't transferred any data - writing zeros to the // database would be a no-op anyway. if (bytesIn <= 0 && bytesOut <= 0) { continue; diff --git a/server/routers/healthChecks/types.ts b/server/routers/healthChecks/types.ts index 429da80c0..d8395c593 100644 --- a/server/routers/healthChecks/types.ts +++ b/server/routers/healthChecks/types.ts @@ -19,6 +19,9 @@ export type ListHealthChecksResponse = { hcTlsServerName: string | null; hcHealthyThreshold: number | null; hcUnhealthyThreshold: number | null; + resourceId: number | null; + resourceName: string | null; + resourceNiceId: string | null; }[]; pagination: { total: number; diff --git a/server/routers/newt/handleReceiveBandwidthMessage.ts b/server/routers/newt/handleReceiveBandwidthMessage.ts index f086333e7..2d5d99b09 100644 --- a/server/routers/newt/handleReceiveBandwidthMessage.ts +++ b/server/routers/newt/handleReceiveBandwidthMessage.ts @@ -88,7 +88,7 @@ export async function flushBandwidthToDb(): Promise { const currentTime = new Date().toISOString(); // Sort by publicKey for consistent lock ordering across concurrent - // writers — this is the same deadlock-prevention strategy used in the + // writers - this is the same deadlock-prevention strategy used in the // original per-message implementation. const sortedEntries = [...snapshot.entries()].sort(([a], [b]) => a.localeCompare(b) @@ -143,7 +143,7 @@ const flushTimer = setInterval(async () => { }, FLUSH_INTERVAL_MS); // Calling unref() means this timer will not keep the Node.js event loop alive -// on its own — the process can still exit normally when there is no other work +// on its own - the process can still exit normally when there is no other work // left. The graceful-shutdown path (see server/cleanup.ts) will call // flushBandwidthToDb() explicitly before process.exit(), so no data is lost. flushTimer.unref(); @@ -167,7 +167,7 @@ export const handleReceiveBandwidthMessage: MessageHandler = async ( // Accumulate the incoming data in memory; the periodic timer (and the // shutdown hook) will take care of writing it to the database. for (const { publicKey, bytesIn, bytesOut } of bandwidthData) { - // Skip peers that haven't transferred any data — writing zeros to the + // Skip peers that haven't transferred any data - writing zeros to the // database would be a no-op anyway. if (bytesIn <= 0 && bytesOut <= 0) { continue; diff --git a/server/routers/newt/offlineChecker.ts b/server/routers/newt/offlineChecker.ts index 3343a92fd..4d5fd5de4 100644 --- a/server/routers/newt/offlineChecker.ts +++ b/server/routers/newt/offlineChecker.ts @@ -16,7 +16,7 @@ const OFFLINE_THRESHOLD_BANDWIDTH_MS = 8 * 60 * 1000; // 8 minutes * Starts the background interval that checks for newt sites that haven't * pinged recently and marks them as offline. For backward compatibility, * a site is only marked offline when there is no active WebSocket connection - * either — so older newt versions that don't send pings but remain connected + * either - so older newt versions that don't send pings but remain connected * continue to be treated as online. */ export const startNewtOfflineChecker = (): void => { @@ -63,7 +63,7 @@ export const startNewtOfflineChecker = (): void => { ); if (isConnected) { logger.debug( - `Newt ${staleSite.newtId} has not pinged recently but is still connected via WebSocket — keeping site ${staleSite.siteId} online` + `Newt ${staleSite.newtId} has not pinged recently but is still connected via WebSocket - keeping site ${staleSite.siteId} online` ); continue; } diff --git a/server/routers/newt/pingAccumulator.ts b/server/routers/newt/pingAccumulator.ts index 8f2154c39..56429372d 100644 --- a/server/routers/newt/pingAccumulator.ts +++ b/server/routers/newt/pingAccumulator.ts @@ -112,7 +112,7 @@ async function flushSitePingsToDb(): Promise { try { const newlyOnlineSites = await withRetry(async () => { - // Only update sites that were offline — these are the + // Only update sites that were offline - these are the // offline→online transitions. .returning() gives us exactly // the site IDs that changed state. const transitioned = await db @@ -249,7 +249,7 @@ async function flushClientPingsToDb(): Promise { } /** - * Flush everything — called by the interval timer and during shutdown. + * Flush everything - called by the interval timer and during shutdown. */ export async function flushPingsToDb(): Promise { await flushSitePingsToDb(); @@ -314,7 +314,7 @@ function isTransientError(error: any): boolean { return true; } - // PostgreSQL deadlock detected — always safe to retry (one winner guaranteed) + // PostgreSQL deadlock detected - always safe to retry (one winner guaranteed) if (code === "40P01" || message.includes("deadlock")) { return true; } diff --git a/server/routers/newt/registerNewt.ts b/server/routers/newt/registerNewt.ts index de68ab2de..cc53e48df 100644 --- a/server/routers/newt/registerNewt.ts +++ b/server/routers/newt/registerNewt.ts @@ -249,7 +249,7 @@ export async function registerNewt( dateCreated: moment().toISOString() }); - // Consume the provisioning key — cascade removes siteProvisioningKeyOrg + // Consume the provisioning key - cascade removes siteProvisioningKeyOrg await trx .update(siteProvisioningKeys) .set({ diff --git a/server/routers/olm/handleOlmServerInitAddPeerHandshake.ts b/server/routers/olm/handleOlmServerInitAddPeerHandshake.ts index 0eda41e04..05a83a146 100644 --- a/server/routers/olm/handleOlmServerInitAddPeerHandshake.ts +++ b/server/routers/olm/handleOlmServerInitAddPeerHandshake.ts @@ -211,7 +211,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async ( continue; } - // Trigger the peer add handshake — if the peer was already added this will be a no-op + // Trigger the peer add handshake - if the peer was already added this will be a no-op await initPeerAddHandshake( client.clientId, { @@ -236,4 +236,4 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async ( } return; -}; \ No newline at end of file +}; diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 973155ccc..a31e5179e 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -228,7 +228,7 @@ export async function createTarget( healthCheck = await db .insert(targetHealthCheck) .values({ - name: `${targetData.ip}:${targetData.port}`, + orgId: resource.orgId, targetId: newTarget[0].targetId, hcEnabled: targetData.hcEnabled ?? false, hcPath: targetData.hcPath ?? null, diff --git a/server/routers/ws/messageHandlers.ts b/server/routers/ws/messageHandlers.ts index 2dc09eedc..f89284389 100644 --- a/server/routers/ws/messageHandlers.ts +++ b/server/routers/ws/messageHandlers.ts @@ -47,7 +47,7 @@ export const messageHandlers: Record = { "ws/round-trip/complete": handleRoundTripMessage }; -// Start the ping accumulator for all builds — it batches per-site online/lastPing +// Start the ping accumulator for all builds - it batches per-site online/lastPing // updates into periodic bulk writes, preventing connection pool exhaustion. startPingAccumulator(); diff --git a/src/app/[orgId]/settings/logs/connection/page.tsx b/src/app/[orgId]/settings/logs/connection/page.tsx index 6eaedff5a..0fc8f95b7 100644 --- a/src/app/[orgId]/settings/logs/connection/page.tsx +++ b/src/app/[orgId]/settings/logs/connection/page.tsx @@ -22,7 +22,7 @@ import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState, useTransition } from "react"; function formatBytes(bytes: number | null): string { - if (bytes === null || bytes === undefined) return "—"; + if (bytes === null || bytes === undefined) return "-"; if (bytes === 0) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(1024)); @@ -33,7 +33,7 @@ function formatBytes(bytes: number | null): string { function formatDuration(startedAt: number, endedAt: number | null): string { if (endedAt === null || endedAt === undefined) return "Active"; const durationSec = endedAt - startedAt; - if (durationSec < 0) return "—"; + if (durationSec < 0) return "-"; if (durationSec < 60) return `${durationSec}s`; if (durationSec < 3600) { const m = Math.floor(durationSec / 60); @@ -460,7 +460,7 @@ export default function ConnectionLogsPage() { } return ( - {row.original.resourceName ?? "—"} + {row.original.resourceName ?? "-"} ); } @@ -503,7 +503,7 @@ export default function ConnectionLogsPage() { } return ( - {row.original.clientName ?? "—"} + {row.original.clientName ?? "-"} ); } @@ -538,7 +538,7 @@ export default function ConnectionLogsPage() { ); } - return ; + return -; } }, { @@ -612,23 +612,23 @@ export default function ConnectionLogsPage() {
Session ID:{" "} - {row.sessionId ?? "—"} + {row.sessionId ?? "-"}
Protocol:{" "} - {row.protocol?.toUpperCase() ?? "—"} + {row.protocol?.toUpperCase() ?? "-"}
Source:{" "} - {row.sourceAddr ?? "—"} + {row.sourceAddr ?? "-"}
Destination:{" "} - {row.destAddr ?? "—"} + {row.destAddr ?? "-"}
@@ -638,7 +638,7 @@ export default function ConnectionLogsPage() { */} {/*
Resource:{" "} - {row.resourceName ?? "—"} + {row.resourceName ?? "-"} {row.resourceNiceId && ( ({row.resourceNiceId}) @@ -646,7 +646,7 @@ export default function ConnectionLogsPage() { )}
*/}
- Site: {row.siteName ?? "—"} + Site: {row.siteName ?? "-"} {row.siteNiceId && ( ({row.siteNiceId}) @@ -654,7 +654,7 @@ export default function ConnectionLogsPage() { )}
- Site ID: {row.siteId ?? "—"} + Site ID: {row.siteId ?? "-"}
Started At:{" "} @@ -662,7 +662,7 @@ export default function ConnectionLogsPage() { ? new Date( row.startedAt * 1000 ).toLocaleString() - : "—"} + : "-"}
Ended At:{" "} @@ -676,7 +676,7 @@ export default function ConnectionLogsPage() {
{/*
Resource ID:{" "} - {row.siteResourceId ?? "—"} + {row.siteResourceId ?? "-"}
*/}
diff --git a/src/app/[orgId]/settings/logs/streaming/page.tsx b/src/app/[orgId]/settings/logs/streaming/page.tsx index 7e48d7566..5192a9c9b 100644 --- a/src/app/[orgId]/settings/logs/streaming/page.tsx +++ b/src/app/[orgId]/settings/logs/streaming/page.tsx @@ -434,7 +434,7 @@ export default function StreamingDestinationsPage() { disabled={!isEnterprise} /> ))} - {/* Add card is always clickable — paywall is enforced inside the picker */} + {/* Add card is always clickable - paywall is enforced inside the picker */}
)} diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx index b0e044699..03426ef1f 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx @@ -341,19 +341,6 @@ function ProxyResourceTargetsForm({ header: () => {t("healthCheck")}, cell: ({ row }) => { const status = row.original.hcHealth || "unknown"; - const isEnabled = row.original.hcEnabled; - - const getStatusColor = (status: string) => { - switch (status) { - case "healthy": - return "green"; - case "unhealthy": - return "red"; - case "unknown": - default: - return "secondary"; - } - }; const getStatusText = (status: string) => { switch (status) { @@ -367,19 +354,7 @@ function ProxyResourceTargetsForm({ } }; - const getStatusIcon = (status: string) => { - switch (status) { - case "healthy": - return ; - case "unhealthy": - return ; - case "unknown": - default: - return null; - } - }; - - return ( + return (
{row.original.siteType === "newt" ? ( + ) : ( - )} diff --git a/src/app/[orgId]/settings/sites/create/page.tsx b/src/app/[orgId]/settings/sites/create/page.tsx index b7cff202a..ab97197a3 100644 --- a/src/app/[orgId]/settings/sites/create/page.tsx +++ b/src/app/[orgId]/settings/sites/create/page.tsx @@ -425,7 +425,7 @@ export default function Page() { setRemoteExitNodeOptions(exitNodeOptions); if (exitNodeOptions.length === 0) { - // No remote exit nodes available — remove local option and default to newt + // No remote exit nodes available - remove local option and default to newt setTunnelTypes((prev: any) => prev.filter((item: any) => item.id !== "local") ); @@ -434,7 +434,7 @@ export default function Page() { } } catch (error) { console.error("Failed to fetch remote exit nodes:", error); - // If fetch fails, no remote exit nodes available — remove local option and default to newt + // If fetch fails, no remote exit nodes available - remove local option and default to newt setTunnelTypes((prev: any) => prev.filter((item: any) => item.id !== "local") ); diff --git a/src/components/ClientInfoCard.tsx b/src/components/ClientInfoCard.tsx index ece2309e2..7f55a46cd 100644 --- a/src/components/ClientInfoCard.tsx +++ b/src/components/ClientInfoCard.tsx @@ -49,7 +49,7 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
) : (
-
+
{t("offline")}
)} diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index c32208321..36f8caa78 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -133,7 +133,7 @@ function aggregateStatusDotClass(status: AggregateSitesStatus): string { return "bg-yellow-500"; case "allOffline": default: - return "bg-gray-500"; + return "bg-neutral-500"; } } @@ -188,7 +188,7 @@ function ClientResourceSitesStatusCell({ "h-2 w-2 shrink-0 rounded-full", isOnline ? "bg-green-500" - : "bg-gray-500" + : "bg-neutral-500" )} /> diff --git a/src/components/ExitNodeInfoCard.tsx b/src/components/ExitNodeInfoCard.tsx index 63dff644d..5f50d892a 100644 --- a/src/components/ExitNodeInfoCard.tsx +++ b/src/components/ExitNodeInfoCard.tsx @@ -32,7 +32,7 @@ export default function ExitNodeInfoCard({}: ExitNodeInfoCardProps) { ) : (
-
+
{t("offline")}
)} diff --git a/src/components/ExitNodesTable.tsx b/src/components/ExitNodesTable.tsx index 5c39f409e..67d819a47 100644 --- a/src/components/ExitNodesTable.tsx +++ b/src/components/ExitNodesTable.tsx @@ -146,7 +146,7 @@ export default function ExitNodesTable({ } else { return ( -
+
{t("offline")}
); diff --git a/src/components/HealthCheckCredenza.tsx b/src/components/HealthCheckCredenza.tsx index 597be7c0b..f575784f4 100644 --- a/src/components/HealthCheckCredenza.tsx +++ b/src/components/HealthCheckCredenza.tsx @@ -61,6 +61,9 @@ export type HealthCheckRow = { hcTlsServerName: string | null; hcHealthyThreshold: number | null; hcUnhealthyThreshold: number | null; + resourceId: number | null; + resourceName: string | null; + resourceNiceId: string | null; }; export type HealthCheckCredenzaProps = diff --git a/src/components/HealthCheckFormFields.tsx b/src/components/HealthCheckFormFields.tsx index f6f32dd34..8ee7a82d2 100644 --- a/src/components/HealthCheckFormFields.tsx +++ b/src/components/HealthCheckFormFields.tsx @@ -10,6 +10,7 @@ import { SelectTrigger, SelectValue } from "@/components/ui/select"; +import { StrategySelect } from "@app/components/StrategySelect"; import { Switch } from "@/components/ui/switch"; import { HeadersInput } from "@app/components/HeadersInput"; import { @@ -103,22 +104,27 @@ export function HealthCheckFormFields({ render={({ field }) => ( {t("healthCheckStrategy")} - + + + handleChange("hcMode", value, field.onChange) + } + /> + )} diff --git a/src/components/HealthChecksTable.tsx b/src/components/HealthChecksTable.tsx index eff3a6d22..ed5296c82 100644 --- a/src/components/HealthChecksTable.tsx +++ b/src/components/HealthChecksTable.tsx @@ -19,17 +19,17 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { orgQueries } from "@app/lib/queries"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { ArrowUpDown, MoreHorizontal } from "lucide-react"; +import { ArrowUpDown, ArrowUpRight, MoreHorizontal } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; -import { Span } from "next/dist/trace"; +import Link from "next/link"; type StandaloneHealthChecksTableProps = { orgId: string; }; function formatTarget(row: HealthCheckRow): string { - if (!row.hcHostname) return "—"; + if (!row.hcHostname) return "-"; if (row.hcMode === "tcp") { if (!row.hcPort) return row.hcHostname; return `${row.hcHostname}:${row.hcPort}`; @@ -154,7 +154,7 @@ export default function HealthChecksTable({ ), cell: ({ row }) => ( - {row.original.hcMode?.toUpperCase() ?? "—"} + {row.original.hcMode?.toUpperCase() ?? "-"} ) }, @@ -166,6 +166,27 @@ export default function HealthChecksTable({ ), cell: ({ row }) => {formatTarget(row.original)} }, + { + id: "resource", + friendlyName: "Resource", + header: () => ( + Resource + ), + cell: ({ row }) => { + const r = row.original; + if (!r.resourceId || !r.resourceName || !r.resourceNiceId) { + return -; + } + return ( + + + + ); + } + }, { id: "health", friendlyName: t("standaloneHcColumnHealth"), @@ -191,7 +212,7 @@ export default function HealthChecksTable({ } else { return ( -
+
{healthLabel.unknown}
); diff --git a/src/components/LocaleSwitcherSelect.tsx b/src/components/LocaleSwitcherSelect.tsx index e647f7dd1..5d7ece74e 100644 --- a/src/components/LocaleSwitcherSelect.tsx +++ b/src/components/LocaleSwitcherSelect.tsx @@ -36,7 +36,7 @@ export default function LocaleSwitcherSelect({ }); // Persist locale to the database (fire-and-forget) api.post("/user/locale", { locale }).catch(() => { - // Silently ignore errors — cookie is already set as fallback + // Silently ignore errors - cookie is already set as fallback }); } diff --git a/src/components/MachineClientsTable.tsx b/src/components/MachineClientsTable.tsx index 9c1da5b4d..4ef22c83d 100644 --- a/src/components/MachineClientsTable.tsx +++ b/src/components/MachineClientsTable.tsx @@ -293,7 +293,7 @@ export default function MachineClientsTable({ } else { return ( -
+
{t("disconnected")}
); diff --git a/src/components/PendingSitesTable.tsx b/src/components/PendingSitesTable.tsx index a1ed6f354..a6625037d 100644 --- a/src/components/PendingSitesTable.tsx +++ b/src/components/PendingSitesTable.tsx @@ -204,7 +204,7 @@ export default function PendingSitesTable({ } else { return ( -
+
{t("offline")}
); diff --git a/src/components/ResourceInfoBox.tsx b/src/components/ResourceInfoBox.tsx index 36a507ea9..8006612a9 100644 --- a/src/components/ResourceInfoBox.tsx +++ b/src/components/ResourceInfoBox.tsx @@ -79,7 +79,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
) : ( -
+
Offline
)} diff --git a/src/components/SiteInfoCard.tsx b/src/components/SiteInfoCard.tsx index c0a9b36b1..2b40d379b 100644 --- a/src/components/SiteInfoCard.tsx +++ b/src/components/SiteInfoCard.tsx @@ -52,7 +52,7 @@ export default function SiteInfoCard({}: SiteInfoCardProps) { ) : (
-
+
{t("offline")}
)} diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 606630a50..7fa635b87 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -212,7 +212,7 @@ export default function SitesTable({ } else { return ( -
+
{t("offline")}
); diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx index 5542029a6..88e495406 100644 --- a/src/components/UserDevicesTable.tsx +++ b/src/components/UserDevicesTable.tsx @@ -427,7 +427,7 @@ export default function UserDevicesTable({ } else { return ( -
+
{t("disconnected")}
); diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 17cc10f11..f658f9fee 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -291,6 +291,9 @@ export const orgQueries = { hcTlsServerName: string | null; hcHealthyThreshold: number | null; hcUnhealthyThreshold: number | null; + resourceId: number | null; + resourceName: string | null; + resourceNiceId: string | null; }[]; pagination: { total: number; diff --git a/src/services/locale.ts b/src/services/locale.ts index 81be42bc1..2d3e1d21f 100644 --- a/src/services/locale.ts +++ b/src/services/locale.ts @@ -17,14 +17,14 @@ export async function getUserLocale(): Promise { return cookieLocale as Locale; } - // No cookie found — try to restore from user's saved locale in DB + // No cookie found - try to restore from user's saved locale in DB try { const res = await internal.get("/user", await authCookieHeader()); const userLocale = res.data?.data?.locale; if (userLocale && locales.includes(userLocale as Locale)) { // Try to cache in a cookie so subsequent requests skip the API // call. cookies().set() is only permitted in Server Actions and - // Route Handlers — not during rendering — so we isolate it so + // Route Handlers - not during rendering - so we isolate it so // that a write failure doesn't prevent the locale from being // returned for the current request. try { @@ -40,7 +40,7 @@ export async function getUserLocale(): Promise { return userLocale as Locale; } } catch { - // User not logged in or API unavailable — fall through + // User not logged in or API unavailable - fall through } const headerList = await headers();