mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-16 15:50:09 +02:00
@@ -0,0 +1,4 @@
|
|||||||
|
# typescript-eslint@8.70.0 declares a peer range of typescript "<6.1.0" and
|
||||||
|
# hasn't caught up to typescript@7.x yet, even though it works fine against it
|
||||||
|
# in practice. Without this, `npm install`/`npm ci` fail with ERESOLVE.
|
||||||
|
legacy-peer-deps=true
|
||||||
+1
-1
@@ -5,7 +5,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json .npmrc ./
|
||||||
|
|
||||||
FROM base AS builder-dev
|
FROM base AS builder-dev
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN apk add --no-cache python3 make g++
|
RUN apk add --no-cache python3 make g++
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json .npmrc ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Няма намерени сайтове.",
|
"sitesNotFound": "Няма намерени сайтове.",
|
||||||
"pangolinServerAdmin": "Администратор на сървър - Панголин",
|
"pangolinServerAdmin": "Администратор на сървър - Панголин",
|
||||||
"licenseTierProfessional": "Професионален лиценз",
|
"licenseTierProfessional": "Професионален лиценз",
|
||||||
"licenseTierEnterprise": "Предприятие лиценз",
|
"licenseTierEnterprise": "Предприятие",
|
||||||
"licenseTierPersonal": "Персонален лиценз",
|
"licenseTierPersonal": "Личен",
|
||||||
|
"licenseTierTier1": "Начален",
|
||||||
|
"licenseTierTier2": "Мащаб",
|
||||||
"licensed": "Лицензиран",
|
"licensed": "Лицензиран",
|
||||||
"yes": "Да",
|
"yes": "Да",
|
||||||
"no": "Не",
|
"no": "Не",
|
||||||
|
|||||||
+5
-3
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Nebyly nalezeny žádné stránky.",
|
"sitesNotFound": "Nebyly nalezeny žádné stránky.",
|
||||||
"pangolinServerAdmin": "Správce serveru - Pangolin",
|
"pangolinServerAdmin": "Správce serveru - Pangolin",
|
||||||
"licenseTierProfessional": "Profesionální licence",
|
"licenseTierProfessional": "Profesionální licence",
|
||||||
"licenseTierEnterprise": "Podniková licence",
|
"licenseTierEnterprise": "Podniky",
|
||||||
"licenseTierPersonal": "Osobní licence",
|
"licenseTierPersonal": "Osobní",
|
||||||
|
"licenseTierTier1": "Počáteční",
|
||||||
|
"licenseTierTier2": "Měřítko",
|
||||||
"licensed": "Licencováno",
|
"licensed": "Licencováno",
|
||||||
"yes": "Ano",
|
"yes": "Ano",
|
||||||
"no": "Ne",
|
"no": "Ne",
|
||||||
@@ -3979,7 +3981,7 @@
|
|||||||
"tpmAvailable": "TPM k dispozici",
|
"tpmAvailable": "TPM k dispozici",
|
||||||
"windowsAntivirusEnabled": "Antivirus povolen",
|
"windowsAntivirusEnabled": "Antivirus povolen",
|
||||||
"macosSipEnabled": "Ochrana systémové integrity (SIP)",
|
"macosSipEnabled": "Ochrana systémové integrity (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Strážce",
|
||||||
"macosFirewallStealthMode": "Režim neviditelnosti firewallu",
|
"macosFirewallStealthMode": "Režim neviditelnosti firewallu",
|
||||||
"linuxAppArmorEnabled": "Pancíř aplikace",
|
"linuxAppArmorEnabled": "Pancíř aplikace",
|
||||||
"linuxSELinuxEnabled": "SELinux",
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
|||||||
+5
-3
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Ingen sites fundet.",
|
"sitesNotFound": "Ingen sites fundet.",
|
||||||
"pangolinServerAdmin": "Serveradmin - Pangolin",
|
"pangolinServerAdmin": "Serveradmin - Pangolin",
|
||||||
"licenseTierProfessional": "Professionel licens",
|
"licenseTierProfessional": "Professionel licens",
|
||||||
"licenseTierEnterprise": "Enterprise-licens",
|
"licenseTierEnterprise": "Bedrift",
|
||||||
"licenseTierPersonal": "Personlig licens",
|
"licenseTierPersonal": "Personlig",
|
||||||
|
"licenseTierTier1": "Begynder",
|
||||||
|
"licenseTierTier2": "Skala",
|
||||||
"licensed": "Licenseret",
|
"licensed": "Licenseret",
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"no": "Nej",
|
"no": "Nej",
|
||||||
@@ -2068,7 +2070,7 @@
|
|||||||
"aiUsageFilterNotFound": "Ingen valg fundet",
|
"aiUsageFilterNotFound": "Ingen valg fundet",
|
||||||
"aiUsageResetFilters": "Nulstil Filtre",
|
"aiUsageResetFilters": "Nulstil Filtre",
|
||||||
"aiUsageRefresh": "Opdater",
|
"aiUsageRefresh": "Opdater",
|
||||||
"aiUsageTokenTypePrompt": "Prompt",
|
"aiUsageTokenTypePrompt": "Vis",
|
||||||
"aiUsageTokenTypeCacheRead": "Cache læs",
|
"aiUsageTokenTypeCacheRead": "Cache læs",
|
||||||
"aiUsageTokenTypeCacheWrite": "Cache skriv",
|
"aiUsageTokenTypeCacheWrite": "Cache skriv",
|
||||||
"aiUsageTokenTypeCompletion": "Komplettering",
|
"aiUsageTokenTypeCompletion": "Komplettering",
|
||||||
|
|||||||
+5
-3
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Keine Standorte gefunden.",
|
"sitesNotFound": "Keine Standorte gefunden.",
|
||||||
"pangolinServerAdmin": "Server-Admin - Pangolin",
|
"pangolinServerAdmin": "Server-Admin - Pangolin",
|
||||||
"licenseTierProfessional": "Professional Lizenz",
|
"licenseTierProfessional": "Professional Lizenz",
|
||||||
"licenseTierEnterprise": "Enterprise Lizenz",
|
"licenseTierEnterprise": "Firma",
|
||||||
"licenseTierPersonal": "Persönliche Lizenz",
|
"licenseTierPersonal": "Persönlich",
|
||||||
|
"licenseTierTier1": "Starter",
|
||||||
|
"licenseTierTier2": "Maßstab",
|
||||||
"licensed": "Lizenziert",
|
"licensed": "Lizenziert",
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"no": "Nein",
|
"no": "Nein",
|
||||||
@@ -3205,7 +3207,7 @@
|
|||||||
"idpAzureClientIdDescription2": "Azure App Registration Client ID",
|
"idpAzureClientIdDescription2": "Azure App Registration Client ID",
|
||||||
"idpAzureClientSecretDescription2": "Azure App Registration Client Geheimnis",
|
"idpAzureClientSecretDescription2": "Azure App Registration Client Geheimnis",
|
||||||
"idpGoogleDescription": "Google OAuth2/OIDC Provider",
|
"idpGoogleDescription": "Google OAuth2/OIDC Provider",
|
||||||
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
|
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC-Anbieter",
|
||||||
"subnet": "Subnetz",
|
"subnet": "Subnetz",
|
||||||
"utilitySubnet": "Nutzsubnetz",
|
"utilitySubnet": "Nutzsubnetz",
|
||||||
"subnetDescription": "Das Subnetz für die Netzwerkkonfiguration dieser Organisation.",
|
"subnetDescription": "Das Subnetz für die Netzwerkkonfiguration dieser Organisation.",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "No sites found.",
|
"sitesNotFound": "No sites found.",
|
||||||
"pangolinServerAdmin": "Server Admin - Pangolin",
|
"pangolinServerAdmin": "Server Admin - Pangolin",
|
||||||
"licenseTierProfessional": "Professional License",
|
"licenseTierProfessional": "Professional License",
|
||||||
"licenseTierEnterprise": "Enterprise License",
|
"licenseTierEnterprise": "Enterprise",
|
||||||
"licenseTierPersonal": "Personal License",
|
"licenseTierPersonal": "Personal",
|
||||||
|
"licenseTierTier1": "Starter",
|
||||||
|
"licenseTierTier2": "Scale",
|
||||||
"licensed": "Licensed",
|
"licensed": "Licensed",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Sitios no encontrados.",
|
"sitesNotFound": "Sitios no encontrados.",
|
||||||
"pangolinServerAdmin": "Admin Servidor - Pangolin",
|
"pangolinServerAdmin": "Admin Servidor - Pangolin",
|
||||||
"licenseTierProfessional": "Licencia profesional",
|
"licenseTierProfessional": "Licencia profesional",
|
||||||
"licenseTierEnterprise": "Licencia Enterprise",
|
"licenseTierEnterprise": "Empresa",
|
||||||
"licenseTierPersonal": "Licencia personal",
|
"licenseTierPersonal": "Uso personal",
|
||||||
|
"licenseTierTier1": "Iniciador",
|
||||||
|
"licenseTierTier2": "Escala",
|
||||||
"licensed": "Licenciado",
|
"licensed": "Licenciado",
|
||||||
"yes": "Sí",
|
"yes": "Sí",
|
||||||
"no": "Nu",
|
"no": "Nu",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Aucun site trouvé.",
|
"sitesNotFound": "Aucun site trouvé.",
|
||||||
"pangolinServerAdmin": "Admin Serveur - Pangolin",
|
"pangolinServerAdmin": "Admin Serveur - Pangolin",
|
||||||
"licenseTierProfessional": "Licence Professionnelle",
|
"licenseTierProfessional": "Licence Professionnelle",
|
||||||
"licenseTierEnterprise": "Licence Entreprise",
|
"licenseTierEnterprise": "Entreprise",
|
||||||
"licenseTierPersonal": "Licence personnelle",
|
"licenseTierPersonal": "Personnel",
|
||||||
|
"licenseTierTier1": "Démarrage",
|
||||||
|
"licenseTierTier2": "Échelle",
|
||||||
"licensed": "Sous licence",
|
"licensed": "Sous licence",
|
||||||
"yes": "Oui",
|
"yes": "Oui",
|
||||||
"no": "Non",
|
"no": "Non",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Nessun sito trovato.",
|
"sitesNotFound": "Nessun sito trovato.",
|
||||||
"pangolinServerAdmin": "Server Admin - Pangolina",
|
"pangolinServerAdmin": "Server Admin - Pangolina",
|
||||||
"licenseTierProfessional": "Licenza Professional",
|
"licenseTierProfessional": "Licenza Professional",
|
||||||
"licenseTierEnterprise": "Licenza Enterprise",
|
"licenseTierEnterprise": "Impresa",
|
||||||
"licenseTierPersonal": "Licenza Personale",
|
"licenseTierPersonal": "Personale",
|
||||||
|
"licenseTierTier1": "Avviatore",
|
||||||
|
"licenseTierTier2": "Scala",
|
||||||
"licensed": "Con Licenza",
|
"licensed": "Con Licenza",
|
||||||
"yes": "Sì",
|
"yes": "Sì",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "사이트를 찾을 수 없습니다.",
|
"sitesNotFound": "사이트를 찾을 수 없습니다.",
|
||||||
"pangolinServerAdmin": "서버 관리자 - 판골린",
|
"pangolinServerAdmin": "서버 관리자 - 판골린",
|
||||||
"licenseTierProfessional": "전문 라이센스",
|
"licenseTierProfessional": "전문 라이센스",
|
||||||
"licenseTierEnterprise": "기업 라이선스",
|
"licenseTierEnterprise": "기업",
|
||||||
"licenseTierPersonal": "개인 라이선스",
|
"licenseTierPersonal": "개인",
|
||||||
|
"licenseTierTier1": "스타터",
|
||||||
|
"licenseTierTier2": "스케일",
|
||||||
"licensed": "라이센스",
|
"licensed": "라이센스",
|
||||||
"yes": "예",
|
"yes": "예",
|
||||||
"no": "아니요",
|
"no": "아니요",
|
||||||
|
|||||||
+20
-18
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Ingen områder funnet.",
|
"sitesNotFound": "Ingen områder funnet.",
|
||||||
"pangolinServerAdmin": "Server Admin - Pangolin",
|
"pangolinServerAdmin": "Server Admin - Pangolin",
|
||||||
"licenseTierProfessional": "Profesjonell lisens",
|
"licenseTierProfessional": "Profesjonell lisens",
|
||||||
"licenseTierEnterprise": "Bedriftslisens",
|
"licenseTierEnterprise": "Bedrift",
|
||||||
"licenseTierPersonal": "Personlig lisens",
|
"licenseTierPersonal": "Personlig",
|
||||||
|
"licenseTierTier1": "Begynner",
|
||||||
|
"licenseTierTier2": "Skala",
|
||||||
"licensed": "Lisensiert",
|
"licensed": "Lisensiert",
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"no": "Nei",
|
"no": "Nei",
|
||||||
@@ -2816,7 +2818,7 @@
|
|||||||
"roleTextImportPreview": "Forhåndsvisning",
|
"roleTextImportPreview": "Forhåndsvisning",
|
||||||
"roleTextImportItemCount": "{count, plural, =0 {Ingen elementer å importere} one {ett element å importere} other {# elementer å importere}}",
|
"roleTextImportItemCount": "{count, plural, =0 {Ingen elementer å importere} one {ett element å importere} other {# elementer å importere}}",
|
||||||
"roleTextImportTotalCount": "{existing} eksisterende + {imported} importert = {total} totalt",
|
"roleTextImportTotalCount": "{existing} eksisterende + {imported} importert = {total} totalt",
|
||||||
"roleTextImportConfirm": "Import",
|
"roleTextImportConfirm": "Importer",
|
||||||
"roleTextImportInvalidFile": "Ustøttet filtype",
|
"roleTextImportInvalidFile": "Ustøttet filtype",
|
||||||
"roleTextImportInvalidFileDescription": "Bare .txt og .csv filer er støttet.",
|
"roleTextImportInvalidFileDescription": "Bare .txt og .csv filer er støttet.",
|
||||||
"roleTextImportEmpty": "Ingen elementer funnet i filen",
|
"roleTextImportEmpty": "Ingen elementer funnet i filen",
|
||||||
@@ -3093,7 +3095,7 @@
|
|||||||
"regionAfrica": "Afrika",
|
"regionAfrica": "Afrika",
|
||||||
"regionNorthernAfrica": "[country name] Nord-Afrika",
|
"regionNorthernAfrica": "[country name] Nord-Afrika",
|
||||||
"regionEasternAfrica": "Øst-Afrika",
|
"regionEasternAfrica": "Øst-Afrika",
|
||||||
"regionMiddleAfrica": "Middle Africa",
|
"regionMiddleAfrica": "Midt-Afrika",
|
||||||
"regionSouthernAfrica": "Sør-Afrika",
|
"regionSouthernAfrica": "Sør-Afrika",
|
||||||
"regionWesternAfrica": "[country name] Vest-Afrika",
|
"regionWesternAfrica": "[country name] Vest-Afrika",
|
||||||
"regionAmericas": "Amerika",
|
"regionAmericas": "Amerika",
|
||||||
@@ -3112,10 +3114,10 @@
|
|||||||
"regionNorthernEurope": "Nord-Europa",
|
"regionNorthernEurope": "Nord-Europa",
|
||||||
"regionSouthernEurope": "Sørlige Europa",
|
"regionSouthernEurope": "Sørlige Europa",
|
||||||
"regionWesternEurope": "Vest-Europa",
|
"regionWesternEurope": "Vest-Europa",
|
||||||
"regionOceania": "Oceania",
|
"regionOceania": "Oseania",
|
||||||
"regionAustraliaAndNewZealand": "Australia og New Zealand",
|
"regionAustraliaAndNewZealand": "Australia og New Zealand",
|
||||||
"regionMelanesia": "Melanesia",
|
"regionMelanesia": "Melanesia",
|
||||||
"regionMicronesia": "Micronesia",
|
"regionMicronesia": "Mikronesia",
|
||||||
"regionPolynesia": "Polynesia",
|
"regionPolynesia": "Polynesia",
|
||||||
"managedSelfHosted": {
|
"managedSelfHosted": {
|
||||||
"title": "Administrert selv-hostet",
|
"title": "Administrert selv-hostet",
|
||||||
@@ -3205,7 +3207,7 @@
|
|||||||
"idpAzureClientIdDescription2": "Azure App registrerings klient-ID",
|
"idpAzureClientIdDescription2": "Azure App registrerings klient-ID",
|
||||||
"idpAzureClientSecretDescription2": "Azure App Registrering Klient Hemmelig",
|
"idpAzureClientSecretDescription2": "Azure App Registrering Klient Hemmelig",
|
||||||
"idpGoogleDescription": "Google OAuth2/OIDC leverandør",
|
"idpGoogleDescription": "Google OAuth2/OIDC leverandør",
|
||||||
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
|
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC-leverandør",
|
||||||
"subnet": "Subnett",
|
"subnet": "Subnett",
|
||||||
"utilitySubnet": "Nyttesubnett",
|
"utilitySubnet": "Nyttesubnett",
|
||||||
"subnetDescription": "Undernettverket for denne organisasjonens nettverkskonfigurasjon.",
|
"subnetDescription": "Undernettverket for denne organisasjonens nettverkskonfigurasjon.",
|
||||||
@@ -3220,7 +3222,7 @@
|
|||||||
"authPageBrandingRemoveTitle": "Fjern markedsføring for autentiseringsside",
|
"authPageBrandingRemoveTitle": "Fjern markedsføring for autentiseringsside",
|
||||||
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
|
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
|
||||||
"authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging",
|
"authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging",
|
||||||
"brandingLogoURL": "Logo URL",
|
"brandingLogoURL": "Logo-URL",
|
||||||
"brandingLogoURLOrPath": "Logoen URL eller sti",
|
"brandingLogoURLOrPath": "Logoen URL eller sti",
|
||||||
"brandingLogoPathDescription": "Skriv inn en URL eller en lokal bane.",
|
"brandingLogoPathDescription": "Skriv inn en URL eller en lokal bane.",
|
||||||
"brandingLogoURLDescription": "Skriv inn en offentlig tilgjengelig nettadresse til din logobilde.",
|
"brandingLogoURLDescription": "Skriv inn en offentlig tilgjengelig nettadresse til din logobilde.",
|
||||||
@@ -3360,7 +3362,7 @@
|
|||||||
"resourceHeaderAuthSetupTitleDescription": "Angi grunnleggende auth legitimasjon (brukernavn og passord) for å beskytte denne ressursen med HTTP Header autentisering. Tilgang til det ved hjelp av formatet https://username:password@resource.example.com",
|
"resourceHeaderAuthSetupTitleDescription": "Angi grunnleggende auth legitimasjon (brukernavn og passord) for å beskytte denne ressursen med HTTP Header autentisering. Tilgang til det ved hjelp av formatet https://username:password@resource.example.com",
|
||||||
"resourceHeaderAuthSubmit": "Angi topptekst godkjenning",
|
"resourceHeaderAuthSubmit": "Angi topptekst godkjenning",
|
||||||
"actionSetResourceHeaderAuth": "Angi topptekst godkjenning",
|
"actionSetResourceHeaderAuth": "Angi topptekst godkjenning",
|
||||||
"enterpriseEdition": "Enterprise Edition",
|
"enterpriseEdition": "Enterprise-utgave",
|
||||||
"unlicensed": "Ikke lisensiert",
|
"unlicensed": "Ikke lisensiert",
|
||||||
"beta": "beta",
|
"beta": "beta",
|
||||||
"manageUserDevices": "Bruker Enheter",
|
"manageUserDevices": "Bruker Enheter",
|
||||||
@@ -3501,7 +3503,7 @@
|
|||||||
"priority": "Prioritet",
|
"priority": "Prioritet",
|
||||||
"priorityDescription": "Høyere prioriterte ruter evalueres først. Prioritet = 100 betyr automatisk bestilling (systembeslutninger). Bruk et annet nummer til å håndheve manuell prioritet.",
|
"priorityDescription": "Høyere prioriterte ruter evalueres først. Prioritet = 100 betyr automatisk bestilling (systembeslutninger). Bruk et annet nummer til å håndheve manuell prioritet.",
|
||||||
"instanceName": "Forekomst navn",
|
"instanceName": "Forekomst navn",
|
||||||
"clearInstanceName": "Reset Server Association",
|
"clearInstanceName": "Tilbakestill server-assosiasjon",
|
||||||
"pathMatchModalTitle": "Konfigurere matching av sti",
|
"pathMatchModalTitle": "Konfigurere matching av sti",
|
||||||
"pathMatchModalDescription": "Sett opp hvordan innkommende forespørsler skal matches basert på deres bane.",
|
"pathMatchModalDescription": "Sett opp hvordan innkommende forespørsler skal matches basert på deres bane.",
|
||||||
"pathMatchType": "Trefftype",
|
"pathMatchType": "Trefftype",
|
||||||
@@ -3557,11 +3559,11 @@
|
|||||||
"allowedByRule": "Tillatt etter regel",
|
"allowedByRule": "Tillatt etter regel",
|
||||||
"allowedNoAuth": "Tillatt Ingen Auth",
|
"allowedNoAuth": "Tillatt Ingen Auth",
|
||||||
"validAccessToken": "Gyldig tilgangsnøkkel",
|
"validAccessToken": "Gyldig tilgangsnøkkel",
|
||||||
"validHeaderAuth": "Valid header auth",
|
"validHeaderAuth": "Gyldig header-autentisering",
|
||||||
"validPincode": "Gyldig PIN-kode",
|
"validPincode": "Gyldig PIN-kode",
|
||||||
"validPassword": "Gyldig passord",
|
"validPassword": "Gyldig passord",
|
||||||
"validEmail": "Valid email",
|
"validEmail": "Gyldig e-post",
|
||||||
"validSSO": "Valid SSO",
|
"validSSO": "Gyldig SSO",
|
||||||
"validVirtualAPIKey": "Gyldig Virtuell API-nøkkel",
|
"validVirtualAPIKey": "Gyldig Virtuell API-nøkkel",
|
||||||
"view": "Vis",
|
"view": "Vis",
|
||||||
"configManaged": "Konfigurasjon administrert",
|
"configManaged": "Konfigurasjon administrert",
|
||||||
@@ -3570,7 +3572,7 @@
|
|||||||
"droppedByRule": "Legg i regelen",
|
"droppedByRule": "Legg i regelen",
|
||||||
"noSessions": "Ingen økter",
|
"noSessions": "Ingen økter",
|
||||||
"temporaryRequestToken": "Midlertidig forespørsel Token",
|
"temporaryRequestToken": "Midlertidig forespørsel Token",
|
||||||
"noMoreAuthMethods": "No Valid Auth",
|
"noMoreAuthMethods": "Ingen gyldig autentisering",
|
||||||
"ip": "IP",
|
"ip": "IP",
|
||||||
"reason": "Grunn",
|
"reason": "Grunn",
|
||||||
"requestLogs": "HTTP-forespørselslogger",
|
"requestLogs": "HTTP-forespørselslogger",
|
||||||
@@ -3784,14 +3786,14 @@
|
|||||||
"niceIdUpdateErrorDescription": "Det oppstod en feil under oppdatering av Nice ID.",
|
"niceIdUpdateErrorDescription": "Det oppstod en feil under oppdatering av Nice ID.",
|
||||||
"niceIdCannotBeEmpty": "God ID kan ikke være tom",
|
"niceIdCannotBeEmpty": "God ID kan ikke være tom",
|
||||||
"enterIdentifier": "Angi identifikator",
|
"enterIdentifier": "Angi identifikator",
|
||||||
"identifier": "Identifier",
|
"identifier": "Identifikator",
|
||||||
"deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.",
|
"deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.",
|
"deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.",
|
||||||
"loginSelectAuthenticationMethod": "Velg en autentiseringsmetode for å fortsette.",
|
"loginSelectAuthenticationMethod": "Velg en autentiseringsmetode for å fortsette.",
|
||||||
"noData": "Ingen data",
|
"noData": "Ingen data",
|
||||||
"machineClients": "Maskinklienter",
|
"machineClients": "Maskinklienter",
|
||||||
"install": "Installer",
|
"install": "Installer",
|
||||||
"downloadInstaller": "Download Installer",
|
"downloadInstaller": "Last ned installasjonsprogram",
|
||||||
"run": "Kjør",
|
"run": "Kjør",
|
||||||
"envFile": "Miljøfil",
|
"envFile": "Miljøfil",
|
||||||
"serviceFile": "Tjenestefil",
|
"serviceFile": "Tjenestefil",
|
||||||
@@ -3969,7 +3971,7 @@
|
|||||||
"kernelVersion": "Kjerne versjon",
|
"kernelVersion": "Kjerne versjon",
|
||||||
"deviceModel": "Enhets modell",
|
"deviceModel": "Enhets modell",
|
||||||
"serialNumber": "Serienummer",
|
"serialNumber": "Serienummer",
|
||||||
"hostname": "Hostname",
|
"hostname": "Vertsnavn",
|
||||||
"firstSeen": "Først sett",
|
"firstSeen": "Først sett",
|
||||||
"lastSeen": "Sist sett",
|
"lastSeen": "Sist sett",
|
||||||
"biometricsEnabled": "Biometri aktivert",
|
"biometricsEnabled": "Biometri aktivert",
|
||||||
@@ -3999,7 +4001,7 @@
|
|||||||
"disconnected": "Frakoblet",
|
"disconnected": "Frakoblet",
|
||||||
"approvalsEmptyStateTitle": "Enhetsgodkjenninger er ikke aktivert",
|
"approvalsEmptyStateTitle": "Enhetsgodkjenninger er ikke aktivert",
|
||||||
"approvalsEmptyStateDescription": "Aktivere godkjenninger av enheter for at roller må godkjennes av admin før brukere kan koble til nye enheter.",
|
"approvalsEmptyStateDescription": "Aktivere godkjenninger av enheter for at roller må godkjennes av admin før brukere kan koble til nye enheter.",
|
||||||
"approvalsEmptyStateHowToTitle": "How to Enable",
|
"approvalsEmptyStateHowToTitle": "Hvordan aktivere",
|
||||||
"approvalsEmptyStateStep1Title": "Gå til roller",
|
"approvalsEmptyStateStep1Title": "Gå til roller",
|
||||||
"approvalsEmptyStateStep1Description": "Naviger til organisasjonens roller innstillinger for å konfigurere enhetsgodkjenninger.",
|
"approvalsEmptyStateStep1Description": "Naviger til organisasjonens roller innstillinger for å konfigurere enhetsgodkjenninger.",
|
||||||
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
||||||
|
|||||||
+5
-3
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Geen sites gevonden.",
|
"sitesNotFound": "Geen sites gevonden.",
|
||||||
"pangolinServerAdmin": "Serverbeheer - Pangolin",
|
"pangolinServerAdmin": "Serverbeheer - Pangolin",
|
||||||
"licenseTierProfessional": "Professionele licentie",
|
"licenseTierProfessional": "Professionele licentie",
|
||||||
"licenseTierEnterprise": "Enterprise Licentie",
|
"licenseTierEnterprise": "Onderneming",
|
||||||
"licenseTierPersonal": "Persoonlijke licentie",
|
"licenseTierPersonal": "Persoonlijk",
|
||||||
|
"licenseTierTier1": "Beginner",
|
||||||
|
"licenseTierTier2": "Schaal",
|
||||||
"licensed": "Gelicentieerd",
|
"licensed": "Gelicentieerd",
|
||||||
"yes": "ja",
|
"yes": "ja",
|
||||||
"no": "Neen",
|
"no": "Neen",
|
||||||
@@ -3205,7 +3207,7 @@
|
|||||||
"idpAzureClientIdDescription2": "Azure App registratie Client ID",
|
"idpAzureClientIdDescription2": "Azure App registratie Client ID",
|
||||||
"idpAzureClientSecretDescription2": "Azure App registratie client geheim",
|
"idpAzureClientSecretDescription2": "Azure App registratie client geheim",
|
||||||
"idpGoogleDescription": "Algemene OAuth2/OIDC provider",
|
"idpGoogleDescription": "Algemene OAuth2/OIDC provider",
|
||||||
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
|
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC-provider",
|
||||||
"subnet": "Subnet",
|
"subnet": "Subnet",
|
||||||
"utilitySubnet": "Hulpmiddel Subnet",
|
"utilitySubnet": "Hulpmiddel Subnet",
|
||||||
"subnetDescription": "Het subnet van de netwerkconfiguratie van deze organisatie.",
|
"subnetDescription": "Het subnet van de netwerkconfiguratie van deze organisatie.",
|
||||||
|
|||||||
+6
-4
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Nie znaleziono witryn.",
|
"sitesNotFound": "Nie znaleziono witryn.",
|
||||||
"pangolinServerAdmin": "Administrator serwera - Pangolin",
|
"pangolinServerAdmin": "Administrator serwera - Pangolin",
|
||||||
"licenseTierProfessional": "Licencja Professional",
|
"licenseTierProfessional": "Licencja Professional",
|
||||||
"licenseTierEnterprise": "Licencja Enterprise",
|
"licenseTierEnterprise": "Przedsiębiorstwo",
|
||||||
"licenseTierPersonal": "Licencja osobista",
|
"licenseTierPersonal": "Osobiste",
|
||||||
|
"licenseTierTier1": "Startowy",
|
||||||
|
"licenseTierTier2": "Skala",
|
||||||
"licensed": "Licencjonowany",
|
"licensed": "Licencjonowany",
|
||||||
"yes": "Tak",
|
"yes": "Tak",
|
||||||
"no": "Nie",
|
"no": "Nie",
|
||||||
@@ -3791,7 +3793,7 @@
|
|||||||
"noData": "Brak danych",
|
"noData": "Brak danych",
|
||||||
"machineClients": "Klienci maszyn",
|
"machineClients": "Klienci maszyn",
|
||||||
"install": "Zainstaluj",
|
"install": "Zainstaluj",
|
||||||
"downloadInstaller": "Download Installer",
|
"downloadInstaller": "Pobierz instalator",
|
||||||
"run": "Uruchom",
|
"run": "Uruchom",
|
||||||
"envFile": "Plik środowiska",
|
"envFile": "Plik środowiska",
|
||||||
"serviceFile": "Plik serwisu",
|
"serviceFile": "Plik serwisu",
|
||||||
@@ -3999,7 +4001,7 @@
|
|||||||
"disconnected": "Rozłączony",
|
"disconnected": "Rozłączony",
|
||||||
"approvalsEmptyStateTitle": "Zatwierdzanie urządzenia nie włączone",
|
"approvalsEmptyStateTitle": "Zatwierdzanie urządzenia nie włączone",
|
||||||
"approvalsEmptyStateDescription": "Włącz zatwierdzanie urządzeń dla ról aby wymagać zgody administratora, zanim użytkownicy będą mogli podłączyć nowe urządzenia.",
|
"approvalsEmptyStateDescription": "Włącz zatwierdzanie urządzeń dla ról aby wymagać zgody administratora, zanim użytkownicy będą mogli podłączyć nowe urządzenia.",
|
||||||
"approvalsEmptyStateHowToTitle": "How to Enable",
|
"approvalsEmptyStateHowToTitle": "Jak włączyć",
|
||||||
"approvalsEmptyStateStep1Title": "Przejdź do ról",
|
"approvalsEmptyStateStep1Title": "Przejdź do ról",
|
||||||
"approvalsEmptyStateStep1Description": "Przejdź do ustawień ról swojej organizacji, aby skonfigurować zatwierdzenia urządzenia.",
|
"approvalsEmptyStateStep1Description": "Przejdź do ustawień ról swojej organizacji, aby skonfigurować zatwierdzenia urządzenia.",
|
||||||
"approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia",
|
"approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Nenhum site encontrado.",
|
"sitesNotFound": "Nenhum site encontrado.",
|
||||||
"pangolinServerAdmin": "Administrador do Servidor - Pangolin",
|
"pangolinServerAdmin": "Administrador do Servidor - Pangolin",
|
||||||
"licenseTierProfessional": "Licença Profissional",
|
"licenseTierProfessional": "Licença Profissional",
|
||||||
"licenseTierEnterprise": "Licença Empresarial",
|
"licenseTierEnterprise": "Empresa",
|
||||||
"licenseTierPersonal": "Licença Pessoal",
|
"licenseTierPersonal": "Pessoal",
|
||||||
|
"licenseTierTier1": "Iniciante",
|
||||||
|
"licenseTierTier2": "Escala",
|
||||||
"licensed": "Licenciado",
|
"licensed": "Licenciado",
|
||||||
"yes": "Sim",
|
"yes": "Sim",
|
||||||
"no": "Não",
|
"no": "Não",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Сайты не найдены.",
|
"sitesNotFound": "Сайты не найдены.",
|
||||||
"pangolinServerAdmin": "Администратор сервера - Pangolin",
|
"pangolinServerAdmin": "Администратор сервера - Pangolin",
|
||||||
"licenseTierProfessional": "Профессиональная лицензия",
|
"licenseTierProfessional": "Профессиональная лицензия",
|
||||||
"licenseTierEnterprise": "Корпоративная лицензия",
|
"licenseTierEnterprise": "Предприятие",
|
||||||
"licenseTierPersonal": "Личная лицензия",
|
"licenseTierPersonal": "Личное",
|
||||||
|
"licenseTierTier1": "Старт",
|
||||||
|
"licenseTierTier2": "Масштаб",
|
||||||
"licensed": "Лицензировано",
|
"licensed": "Лицензировано",
|
||||||
"yes": "Да",
|
"yes": "Да",
|
||||||
"no": "Нет",
|
"no": "Нет",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "Site bulunamadı.",
|
"sitesNotFound": "Site bulunamadı.",
|
||||||
"pangolinServerAdmin": "Sunucu Yöneticisi - Pangolin",
|
"pangolinServerAdmin": "Sunucu Yöneticisi - Pangolin",
|
||||||
"licenseTierProfessional": "Profesyonel Lisans",
|
"licenseTierProfessional": "Profesyonel Lisans",
|
||||||
"licenseTierEnterprise": "Kurumsal Lisans",
|
"licenseTierEnterprise": "Kurumsal",
|
||||||
"licenseTierPersonal": "Kişisel Lisans",
|
"licenseTierPersonal": "Kişisel",
|
||||||
|
"licenseTierTier1": "Başlangıç",
|
||||||
|
"licenseTierTier2": "Ölçek",
|
||||||
"licensed": "Lisanslı",
|
"licensed": "Lisanslı",
|
||||||
"yes": "Evet",
|
"yes": "Evet",
|
||||||
"no": "Hayır",
|
"no": "Hayır",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "未找到站点。",
|
"sitesNotFound": "未找到站点。",
|
||||||
"pangolinServerAdmin": "服务器管理 - Pangolin",
|
"pangolinServerAdmin": "服务器管理 - Pangolin",
|
||||||
"licenseTierProfessional": "专业许可证",
|
"licenseTierProfessional": "专业许可证",
|
||||||
"licenseTierEnterprise": "企业许可证",
|
"licenseTierEnterprise": "企业",
|
||||||
"licenseTierPersonal": "个人许可证",
|
"licenseTierPersonal": "个人",
|
||||||
|
"licenseTierTier1": "启动器",
|
||||||
|
"licenseTierTier2": "扩展",
|
||||||
"licensed": "已授权",
|
"licensed": "已授权",
|
||||||
"yes": "是",
|
"yes": "是",
|
||||||
"no": "否",
|
"no": "否",
|
||||||
|
|||||||
Generated
+1530
-1394
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -69,7 +69,7 @@
|
|||||||
"@simplewebauthn/server": "14.0.1",
|
"@simplewebauthn/server": "14.0.1",
|
||||||
"@tailwindcss/forms": "0.5.11",
|
"@tailwindcss/forms": "0.5.11",
|
||||||
"@tanstack/react-query": "5.102.8",
|
"@tanstack/react-query": "5.102.8",
|
||||||
"@tanstack/react-table": "9.2.4",
|
"@tanstack/react-table": "8.21.3",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
"@xterm/addon-web-links": "^0.12.0",
|
"@xterm/addon-web-links": "^0.12.0",
|
||||||
"@xterm/xterm": "^6.0.0",
|
"@xterm/xterm": "^6.0.0",
|
||||||
|
|||||||
+13
-9
@@ -1,5 +1,6 @@
|
|||||||
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
||||||
import { readConfigFile } from "@server/lib/readConfigFile";
|
import { readConfigFile } from "@server/lib/readConfigFile";
|
||||||
|
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||||
import { withReplicas } from "drizzle-orm/pg-core";
|
import { withReplicas } from "drizzle-orm/pg-core";
|
||||||
import { createPool } from "./poolConfig";
|
import { createPool } from "./poolConfig";
|
||||||
|
|
||||||
@@ -7,17 +8,20 @@ function createDb() {
|
|||||||
const config = readConfigFile();
|
const config = readConfigFile();
|
||||||
|
|
||||||
// check the environment variables for postgres config first before the config file
|
// check the environment variables for postgres config first before the config file
|
||||||
if (process.env.POSTGRES_CONNECTION_STRING) {
|
const envConnectionString = readEnvOrFile("POSTGRES_CONNECTION_STRING");
|
||||||
|
if (envConnectionString) {
|
||||||
config.postgres = {
|
config.postgres = {
|
||||||
connection_string: process.env.POSTGRES_CONNECTION_STRING
|
connection_string: envConnectionString
|
||||||
};
|
};
|
||||||
if (process.env.POSTGRES_REPLICA_CONNECTION_STRINGS) {
|
const replicaConnectionStrings = readEnvOrFile(
|
||||||
const replicas =
|
"POSTGRES_REPLICA_CONNECTION_STRINGS"
|
||||||
process.env.POSTGRES_REPLICA_CONNECTION_STRINGS.split(",").map(
|
);
|
||||||
(conn) => ({
|
if (replicaConnectionStrings) {
|
||||||
connection_string: conn.trim()
|
const replicas = replicaConnectionStrings
|
||||||
})
|
.split(",")
|
||||||
);
|
.map((conn) => ({
|
||||||
|
connection_string: conn.trim()
|
||||||
|
}));
|
||||||
config.postgres.replicas = replicas;
|
config.postgres.replicas = replicas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
||||||
import { readConfigFile } from "@server/lib/readConfigFile";
|
import { readConfigFile } from "@server/lib/readConfigFile";
|
||||||
|
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||||
import { withReplicas } from "drizzle-orm/pg-core";
|
import { withReplicas } from "drizzle-orm/pg-core";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { db as mainDb } from "./driver";
|
import { db as mainDb } from "./driver";
|
||||||
@@ -17,7 +18,7 @@ function createLogsDb() {
|
|||||||
const logsConfig = config.postgres_logs;
|
const logsConfig = config.postgres_logs;
|
||||||
|
|
||||||
// Check environment variable first
|
// Check environment variable first
|
||||||
let connectionString = process.env.POSTGRES_LOGS_CONNECTION_STRING;
|
let connectionString = readEnvOrFile("POSTGRES_LOGS_CONNECTION_STRING");
|
||||||
let replicaConnections: Array<{ connection_string: string }> = [];
|
let replicaConnections: Array<{ connection_string: string }> = [];
|
||||||
|
|
||||||
if (!connectionString && logsConfig) {
|
if (!connectionString && logsConfig) {
|
||||||
@@ -26,13 +27,15 @@ function createLogsDb() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it
|
// If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it
|
||||||
if (process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS) {
|
const replicaConnectionStrings = readEnvOrFile(
|
||||||
replicaConnections =
|
"POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS"
|
||||||
process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS.split(",").map(
|
);
|
||||||
(conn) => ({
|
if (replicaConnectionStrings) {
|
||||||
connection_string: conn.trim()
|
replicaConnections = replicaConnectionStrings
|
||||||
})
|
.split(",")
|
||||||
);
|
.map((conn) => ({
|
||||||
|
connection_string: conn.trim()
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no logs database is configured, fall back to main database
|
// If no logs database is configured, fall back to main database
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import fs from "fs";
|
|||||||
import { APP_PATH } from "@server/lib/consts";
|
import { APP_PATH } from "@server/lib/consts";
|
||||||
import { existsSync, mkdirSync } from "fs";
|
import { existsSync, mkdirSync } from "fs";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||||
|
|
||||||
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||||
export const exists = checkFileExists(location);
|
export const exists = checkFileExists(location);
|
||||||
@@ -19,7 +20,7 @@ function createDb() {
|
|||||||
: undefined;
|
: undefined;
|
||||||
const sqlite = new Database(location, { verbose });
|
const sqlite = new Database(location, { verbose });
|
||||||
|
|
||||||
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") {
|
if (readEnvOrFile("ENABLE_SQLITE_WAL_MODE") == "true") {
|
||||||
// Enable WAL mode — allows concurrent readers + single writer, preventing
|
// Enable WAL mode — allows concurrent readers + single writer, preventing
|
||||||
// contention across subsystems (verifySession, Traefik, audit, ping).
|
// contention across subsystems (verifySession, Traefik, audit, ping).
|
||||||
// NOTE: journal_mode persists in the DB file once set; unsetting this
|
// NOTE: journal_mode persists in the DB file once set; unsetting this
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export enum LicenseId {
|
export enum LicenseId {
|
||||||
SMALL_LICENSE = "small_license",
|
TIER1 = "tier1",
|
||||||
BIG_LICENSE = "big_license"
|
TIER2 = "tier2"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicensePriceSet = {
|
export type LicensePriceSet = {
|
||||||
@@ -9,15 +9,15 @@ export type LicensePriceSet = {
|
|||||||
|
|
||||||
export const licensePriceSet: LicensePriceSet = {
|
export const licensePriceSet: LicensePriceSet = {
|
||||||
// Free license matches the freeLimitSet
|
// Free license matches the freeLimitSet
|
||||||
[LicenseId.SMALL_LICENSE]: "price_1TMJzmD3Ee2Ir7Wm05NlGImT",
|
[LicenseId.TIER1]: "price_1TMJzmD3Ee2Ir7Wm05NlGImT",
|
||||||
[LicenseId.BIG_LICENSE]: "price_1TMJzzD3Ee2Ir7WmzJw9TerS"
|
[LicenseId.TIER2]: "price_1TMJzzD3Ee2Ir7WmzJw9TerS"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const licensePriceSetSandbox: LicensePriceSet = {
|
export const licensePriceSetSandbox: LicensePriceSet = {
|
||||||
// Free license matches the freeLimitSet
|
// Free license matches the freeLimitSet
|
||||||
// when matching license the keys closer to 0 index are matched first so list the licenses in descending order of value
|
// when matching license the keys closer to 0 index are matched first so list the licenses in descending order of value
|
||||||
[LicenseId.SMALL_LICENSE]: "price_1SxDwuDCpkOb237Bz0yTiOgN",
|
[LicenseId.TIER1]: "price_1SxDwuDCpkOb237Bz0yTiOgN",
|
||||||
[LicenseId.BIG_LICENSE]: "price_1SxDy0DCpkOb237BWJxrxYkl"
|
[LicenseId.TIER2]: "price_1SxDy0DCpkOb237BWJxrxYkl"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getLicensePriceSet(
|
export function getLicensePriceSet(
|
||||||
|
|||||||
@@ -1,3 +1,37 @@
|
|||||||
export const getEnvOrYaml = (envVar: string) => (valFromYaml: any) => {
|
import fs from "fs";
|
||||||
return process.env[envVar] ?? valFromYaml;
|
|
||||||
|
// Resolves an environment variable, also honoring a `<envVar>_FILE` variant
|
||||||
|
// that points to a file whose (trimmed) contents should be used as the
|
||||||
|
// value. This is the common convention for consuming Docker/Swarm secrets
|
||||||
|
// (e.g. mounted at /run/secrets/...) without putting the raw value in the
|
||||||
|
// container's environment.
|
||||||
|
export const readEnvOrFile = (envVar: string): string | undefined => {
|
||||||
|
const fileEnvVar = `${envVar}_FILE`;
|
||||||
|
const filePath = process.env[fileEnvVar];
|
||||||
|
|
||||||
|
if (filePath) {
|
||||||
|
if (process.env[envVar]) {
|
||||||
|
throw new Error(
|
||||||
|
`Both ${envVar} and ${fileEnvVar} are set. Please set only one.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(filePath, "utf8").trim();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to read ${fileEnvVar} (${filePath}): ${
|
||||||
|
error instanceof Error ? error.message : error
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return process.env[envVar];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getEnvOrYaml =
|
||||||
|
(envVar: string) =>
|
||||||
|
(valFromYaml: string | undefined): string | undefined => {
|
||||||
|
return readEnvOrFile(envVar) ?? valFromYaml;
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as yaml from "js-yaml";
|
|||||||
import { configFilePath1, configFilePath2 } from "./consts";
|
import { configFilePath1, configFilePath2 } from "./consts";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import stoi from "./stoi";
|
import stoi from "./stoi";
|
||||||
import { getEnvOrYaml } from "./getEnvOrYaml";
|
import { getEnvOrYaml, readEnvOrFile } from "./getEnvOrYaml";
|
||||||
|
|
||||||
const portSchema = z.number().positive().gt(0).lte(65535);
|
const portSchema = z.number().positive().gt(0).lte(65535);
|
||||||
|
|
||||||
@@ -26,14 +26,12 @@ export const configSchema = z
|
|||||||
.object({
|
.object({
|
||||||
anonymous_usage: z.boolean().optional().default(true)
|
anonymous_usage: z.boolean().optional().default(true)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
notifications: z
|
notifications: z
|
||||||
.object({
|
.object({
|
||||||
product_updates: z.boolean().optional().default(true),
|
product_updates: z.boolean().optional().default(true),
|
||||||
new_releases: z.boolean().optional().default(true)
|
new_releases: z.boolean().optional().default(true)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
@@ -109,7 +107,6 @@ export const configSchema = z
|
|||||||
id: z.string().optional().default("P-Access-Token-Id"),
|
id: z.string().optional().default("P-Access-Token-Id"),
|
||||||
token: z.string().optional().default("P-Access-Token")
|
token: z.string().optional().default("P-Access-Token")
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
remote_headers: z
|
remote_headers: z
|
||||||
.object({
|
.object({
|
||||||
@@ -126,7 +123,6 @@ export const configSchema = z
|
|||||||
name: z.string().optional().default("Remote-Name"),
|
name: z.string().optional().default("Remote-Name"),
|
||||||
role: z.string().optional().default("Remote-Role")
|
role: z.string().optional().default("Remote-Role")
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
resource_session_request_param: z
|
resource_session_request_param: z
|
||||||
.string()
|
.string()
|
||||||
@@ -164,14 +160,17 @@ export const configSchema = z
|
|||||||
.boolean()
|
.boolean()
|
||||||
.optional()
|
.optional()
|
||||||
.default(false)
|
.default(false)
|
||||||
.transform((val) =>
|
.transform((val) => {
|
||||||
process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER !==
|
const envVal = readEnvOrFile(
|
||||||
undefined
|
"ENABLE_AI_GATEWAY_CLIENT_IP_HEADER"
|
||||||
? process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER ===
|
);
|
||||||
"true"
|
return envVal !== undefined ? envVal === "true" : val;
|
||||||
: val
|
}),
|
||||||
),
|
secret: z
|
||||||
secret: z.string().pipe(z.string().min(8)).optional(),
|
.string()
|
||||||
|
.pipe(z.string().min(8))
|
||||||
|
.optional()
|
||||||
|
.transform(getEnvOrYaml("SERVER_SECRET")),
|
||||||
maxmind_db_path: z.string().optional(),
|
maxmind_db_path: z.string().optional(),
|
||||||
maxmind_asn_path: z.string().optional()
|
maxmind_asn_path: z.string().optional()
|
||||||
})
|
})
|
||||||
@@ -202,7 +201,8 @@ export const configSchema = z
|
|||||||
dashboard_session_length_hours: 720,
|
dashboard_session_length_hours: 720,
|
||||||
resource_session_length_hours: 720,
|
resource_session_length_hours: 720,
|
||||||
trust_proxy: 1,
|
trust_proxy: 1,
|
||||||
enable_ai_gateway_client_ip_header: false
|
enable_ai_gateway_client_ip_header: false,
|
||||||
|
secret: undefined
|
||||||
}),
|
}),
|
||||||
postgres: z
|
postgres: z
|
||||||
.object({
|
.object({
|
||||||
@@ -238,7 +238,6 @@ export const configSchema = z
|
|||||||
.default(5000),
|
.default(5000),
|
||||||
jit_mode: z.boolean().default(true)
|
jit_mode: z.boolean().default(true)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -278,7 +277,6 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(5000)
|
.default(5000)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -325,10 +323,8 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(50)
|
.default(50)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
gerbil: z
|
gerbil: z
|
||||||
.object({
|
.object({
|
||||||
@@ -357,7 +353,6 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(30)
|
.default(30)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
orgs: z
|
orgs: z
|
||||||
.object({
|
.object({
|
||||||
@@ -391,7 +386,6 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(500)
|
.default(500)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
auth: z
|
auth: z
|
||||||
.object({
|
.object({
|
||||||
@@ -408,10 +402,8 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(500)
|
.default(500)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
email: z
|
email: z
|
||||||
.object({
|
.object({
|
||||||
@@ -489,10 +481,8 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(12)
|
.default(12)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
@@ -513,10 +503,7 @@ export const configSchema = z
|
|||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
// If hybrid is not defined, server secret must be defined. If its not defined already then pull it from env
|
// If hybrid is not defined, server secret must be defined
|
||||||
if (data.server?.secret === undefined) {
|
|
||||||
data.server.secret = process.env.SERVER_SECRET;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
data.server?.secret !== undefined &&
|
data.server?.secret !== undefined &&
|
||||||
data.server.secret.length > 0
|
data.server.secret.length > 0
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { getTraefikConfig } from "#dynamic/lib/traefik";
|
|||||||
import { getValidCertificatesForDomains } from "@server/lib/certificates";
|
import { getValidCertificatesForDomains } from "@server/lib/certificates";
|
||||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import license from "#dynamic/license/license";
|
||||||
|
|
||||||
export class TraefikConfigManager {
|
export class TraefikConfigManager {
|
||||||
private intervalId: NodeJS.Timeout | null = null;
|
private intervalId: NodeJS.Timeout | null = null;
|
||||||
@@ -357,7 +358,11 @@ export class TraefikConfigManager {
|
|||||||
this.lastActiveDomains = new Set(domains);
|
this.lastActiveDomains = new Set(domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.CERT_MODE === "pangolin" && build != "oss") {
|
if (
|
||||||
|
process.env.CERT_MODE === "pangolin" &&
|
||||||
|
build != "oss" &&
|
||||||
|
(await license.hasTier(["personal", "tier2", "enterprise"]))
|
||||||
|
) {
|
||||||
// Scan current local certificate state
|
// Scan current local certificate state
|
||||||
this.lastLocalCertificateState =
|
this.lastLocalCertificateState =
|
||||||
await this.scanLocalCertificateState();
|
await this.scanLocalCertificateState();
|
||||||
@@ -717,10 +722,9 @@ export class TraefikConfigManager {
|
|||||||
}
|
}
|
||||||
if (shouldWrite) {
|
if (shouldWrite) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
traefikDynamicConfigPath,
|
traefikDynamicConfigPath,
|
||||||
yaml.dump(traefikConfig, { noRefs: true }),
|
yaml.dump(traefikConfig, { noRefs: true })
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
logger.info("Traefik dynamic config updated");
|
logger.info("Traefik dynamic config updated");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -822,7 +826,7 @@ export class TraefikConfigManager {
|
|||||||
// Only write the config if it has changed
|
// Only write the config if it has changed
|
||||||
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
||||||
if (newConfigYaml !== originalConfigYaml) {
|
if (newConfigYaml !== originalConfigYaml) {
|
||||||
fs.writeFileSync(dynamicConfigPath, newConfigYaml, "utf8");
|
this.atomicWriteFileSync(dynamicConfigPath, newConfigYaml);
|
||||||
logger.info("Dynamic cert config updated from local certificates");
|
logger.info("Dynamic cert config updated from local certificates");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -900,26 +904,23 @@ export class TraefikConfigManager {
|
|||||||
`Processing certificate for domain: ${cert.domain}`
|
`Processing certificate for domain: ${cert.domain}`
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFileSync(certPath, cert.certFile, "utf8");
|
// Write atomically (temp file + rename) so Traefik's
|
||||||
fs.writeFileSync(keyPath, cert.keyFile, "utf8");
|
// file watcher never observes a partially written
|
||||||
|
// cert/key and fails with "failed to find any PEM data".
|
||||||
// Set appropriate permissions (readable by owner only for key file)
|
this.atomicWriteFileSync(certPath, cert.certFile, 0o644);
|
||||||
fs.chmodSync(certPath, 0o644);
|
this.atomicWriteFileSync(keyPath, cert.keyFile, 0o600);
|
||||||
fs.chmodSync(keyPath, 0o600);
|
|
||||||
|
|
||||||
// Write/update .last_update file with current timestamp
|
// Write/update .last_update file with current timestamp
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
lastUpdatePath,
|
lastUpdatePath,
|
||||||
new Date().toISOString(),
|
new Date().toISOString()
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if this is a wildcard certificate and store it
|
// Check if this is a wildcard certificate and store it
|
||||||
const wildcardPath = path.join(domainDir, ".wildcard");
|
const wildcardPath = path.join(domainDir, ".wildcard");
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
wildcardPath,
|
wildcardPath,
|
||||||
cert.wildcard ? "true" : "false",
|
cert.wildcard ? "true" : "false"
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -931,10 +932,9 @@ export class TraefikConfigManager {
|
|||||||
// even if the cert content didn't change
|
// even if the cert content didn't change
|
||||||
if (cert.expiresAt) {
|
if (cert.expiresAt) {
|
||||||
const expiresAtPath = path.join(domainDir, ".expires_at");
|
const expiresAtPath = path.join(domainDir, ".expires_at");
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
expiresAtPath,
|
expiresAtPath,
|
||||||
cert.expiresAt.toString(),
|
cert.expiresAt.toString()
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,7 +970,7 @@ export class TraefikConfigManager {
|
|||||||
// Only write the config if it has changed
|
// Only write the config if it has changed
|
||||||
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
||||||
if (newConfigYaml !== originalConfigYaml) {
|
if (newConfigYaml !== originalConfigYaml) {
|
||||||
fs.writeFileSync(dynamicConfigPath, newConfigYaml, "utf8");
|
this.atomicWriteFileSync(dynamicConfigPath, newConfigYaml);
|
||||||
logger.info("Dynamic cert config updated");
|
logger.info("Dynamic cert config updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1141,10 +1141,9 @@ export class TraefikConfigManager {
|
|||||||
|
|
||||||
if (configChanged) {
|
if (configChanged) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
dynamicConfigPath,
|
dynamicConfigPath,
|
||||||
yaml.dump(dynamicConfig, { noRefs: true }),
|
yaml.dump(dynamicConfig, { noRefs: true })
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
logger.info("Dynamic config updated after cleanup");
|
logger.info("Dynamic config updated after cleanup");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1171,6 +1170,36 @@ export class TraefikConfigManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a file atomically by writing to a temp file in the same
|
||||||
|
* directory and renaming it into place. This avoids Traefik (which
|
||||||
|
* watches these files/directories) picking up a partially written
|
||||||
|
* file and failing to parse it (e.g. "failed to find any PEM data").
|
||||||
|
*/
|
||||||
|
private atomicWriteFileSync(
|
||||||
|
filePath: string,
|
||||||
|
data: string,
|
||||||
|
mode?: number
|
||||||
|
): void {
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
const tmpPath = path.join(
|
||||||
|
dir,
|
||||||
|
`.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(tmpPath, data, "utf8");
|
||||||
|
if (mode !== undefined) {
|
||||||
|
fs.chmodSync(tmpPath, mode);
|
||||||
|
}
|
||||||
|
fs.renameSync(tmpPath, filePath);
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(tmpPath, { force: true });
|
||||||
|
} catch {}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if file exists
|
* Check if file exists
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { setHostMeta } from "@server/lib/hostMeta";
|
|||||||
const keyTypes = ["host"] as const;
|
const keyTypes = ["host"] as const;
|
||||||
export type LicenseKeyType = (typeof keyTypes)[number];
|
export type LicenseKeyType = (typeof keyTypes)[number];
|
||||||
|
|
||||||
const keyTiers = ["personal", "enterprise"] as const;
|
const keyTiers = ["personal", "enterprise", "tier1", "tier2"] as const;
|
||||||
export type LicenseKeyTier = (typeof keyTiers)[number];
|
export type LicenseKeyTier = (typeof keyTiers)[number];
|
||||||
|
|
||||||
export type LicenseStatus = {
|
export type LicenseStatus = {
|
||||||
@@ -33,7 +33,7 @@ export type LicenseKeyCache = {
|
|||||||
export class License {
|
export class License {
|
||||||
private serverSecret!: string;
|
private serverSecret!: string;
|
||||||
|
|
||||||
constructor(private hostMeta: HostMeta) { }
|
constructor(private hostMeta: HostMeta) {}
|
||||||
|
|
||||||
public async check(): Promise<LicenseStatus> {
|
public async check(): Promise<LicenseStatus> {
|
||||||
return {
|
return {
|
||||||
@@ -50,6 +50,10 @@ export class License {
|
|||||||
public async isUnlocked() {
|
public async isUnlocked() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async hasTier(tier: LicenseKeyTier[]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await setHostMeta();
|
await setHostMeta();
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ export class JobScheduler {
|
|||||||
label: string
|
label: string
|
||||||
): () => Promise<void> {
|
): () => Promise<void> {
|
||||||
return async () => {
|
return async () => {
|
||||||
if (!(await license.isUnlocked())) {
|
if (!(await license.hasTier(["personal", "tier2", "enterprise"]))) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Skipping ${label} tick - license is not subscribed`
|
`Skipping ${label} tick - requires a tier2 license`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,10 +63,11 @@ export class AuthoritativeDNSServer {
|
|||||||
private allDomains: Set<string> = new Set();
|
private allDomains: Set<string> = new Set();
|
||||||
private domainRefreshInterval: NodeJS.Timeout | null = null;
|
private domainRefreshInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
// Cached license/subscription status. license.isUnlocked() does a DB
|
// Cached license/plan status - only a tier2 license unlocks the DNS
|
||||||
// round-trip on every call, so it can't be checked per-query on a UDP
|
// server. license.hasPlan() does a DB round-trip on every call, so it
|
||||||
// server that may see very high query volume - instead it's polled on
|
// can't be checked per-query on a UDP server that may see very high
|
||||||
// the same cadence as the domain set refresh and read from memory here.
|
// query volume - instead it's polled on the same cadence as the domain
|
||||||
|
// set refresh and read from memory here.
|
||||||
private isLicensed: boolean = false;
|
private isLicensed: boolean = false;
|
||||||
private licenseRefreshInterval: NodeJS.Timeout | null = null;
|
private licenseRefreshInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ export class AuthoritativeDNSServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isLicensed) {
|
if (!this.isLicensed) {
|
||||||
logger.debug("Refusing DNS query - license is not subscribed");
|
logger.debug("Refusing DNS query - requires a tier2 license");
|
||||||
// REFUSED (rcode=5) indicates a policy refusal by this nameserver.
|
// REFUSED (rcode=5) indicates a policy refusal by this nameserver.
|
||||||
this.sendResponse(packet, [], rinfo, false, 5, []);
|
this.sendResponse(packet, [], rinfo, false, 5, []);
|
||||||
return;
|
return;
|
||||||
@@ -1042,7 +1043,11 @@ export class AuthoritativeDNSServer {
|
|||||||
|
|
||||||
private async refreshLicenseStatus(): Promise<void> {
|
private async refreshLicenseStatus(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.isLicensed = await license.isUnlocked();
|
this.isLicensed = await license.hasTier([
|
||||||
|
"personal",
|
||||||
|
"tier2",
|
||||||
|
"enterprise"
|
||||||
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to refresh license status:", error);
|
logger.error("Failed to refresh license status:", error);
|
||||||
this.isLicensed = false;
|
this.isLicensed = false;
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export const privateConfigSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.transform(getEnvOrYaml("FOSSORIAL_API_KEY"))
|
.transform(getEnvOrYaml("FOSSORIAL_API_KEY"))
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
redis: z
|
redis: z
|
||||||
.object({
|
.object({
|
||||||
@@ -166,7 +165,6 @@ export const privateConfigSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default("http://gerbil:3004")
|
.default("http://gerbil:3004")
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
flags: z
|
flags: z
|
||||||
.object({
|
.object({
|
||||||
@@ -187,7 +185,6 @@ export const privateConfigSchema = z
|
|||||||
// (server/private/lib/config.ts).
|
// (server/private/lib/config.ts).
|
||||||
disable_private_http_placeholder: z.boolean().optional()
|
disable_private_http_placeholder: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
acme: z
|
acme: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import {
|
|||||||
getValidCertificatesForDomains
|
getValidCertificatesForDomains
|
||||||
} from "@server/lib/certificates";
|
} from "@server/lib/certificates";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import license from "#private/license/license";
|
||||||
import regionalCache from "#private/lib/cache";
|
import regionalCache from "#private/lib/cache";
|
||||||
import { TargetWithSite } from "@server/lib/traefik/types";
|
import { TargetWithSite } from "@server/lib/traefik/types";
|
||||||
import { buildWildcardTls } from "@server/lib/traefik/certResolver";
|
import { buildWildcardTls } from "@server/lib/traefik/certResolver";
|
||||||
@@ -395,8 +396,15 @@ export async function getTraefikConfig(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Pangolin-managed DNS-01/ACME cert mode requires either a tier1
|
||||||
|
// license (self-hosted) or a saas build - otherwise fall back to
|
||||||
|
// Traefik's own cert resolvers (buildWildcardTls) throughout.
|
||||||
|
const pangolinCertModeEnabled =
|
||||||
|
privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin" &&
|
||||||
|
(await license.hasTier(["personal", "tier2", "enterprise"]));
|
||||||
|
|
||||||
let validCerts: CertificateResult[] = [];
|
let validCerts: CertificateResult[] = [];
|
||||||
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
|
if (pangolinCertModeEnabled) {
|
||||||
// create a list of all domains to get certs for
|
// create a list of all domains to get certs for
|
||||||
const domains = new Set<string>();
|
const domains = new Set<string>();
|
||||||
for (const resource of resourcesMap.values()) {
|
for (const resource of resourcesMap.values()) {
|
||||||
@@ -522,10 +530,7 @@ export async function getTraefikConfig(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tls = {};
|
let tls = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
tls = buildWildcardTls({
|
tls = buildWildcardTls({
|
||||||
fullDomain,
|
fullDomain,
|
||||||
hasSubdomain: !!resource.subdomain,
|
hasSubdomain: !!resource.subdomain,
|
||||||
@@ -546,6 +551,30 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config_output.http.services![serviceName] = {
|
||||||
|
loadBalancer: {
|
||||||
|
servers: buildHttpLoadBalancerServers(targets),
|
||||||
|
...(resource.stickySession
|
||||||
|
? buildStickySessionCookie(resource.ssl)
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (resource.tlsServerName) {
|
||||||
|
if (!config_output.http.serversTransports) {
|
||||||
|
config_output.http.serversTransports = {};
|
||||||
|
}
|
||||||
|
config_output.http.serversTransports![transportName] = {
|
||||||
|
serverName: resource.tlsServerName,
|
||||||
|
//unfortunately the following needs to be set. traefik doesn't merge the default serverTransport settings
|
||||||
|
// if defined in the static config and here. if not set, self-signed certs won't work
|
||||||
|
insecureSkipVerify: true
|
||||||
|
};
|
||||||
|
config_output.http.services![
|
||||||
|
serviceName
|
||||||
|
].loadBalancer.serversTransport = transportName;
|
||||||
|
}
|
||||||
|
|
||||||
if (resource.ssl) {
|
if (resource.ssl) {
|
||||||
config_output.http.routers![routerName + "-redirect"] = {
|
config_output.http.routers![routerName + "-redirect"] = {
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
@@ -710,31 +739,6 @@ export async function getTraefikConfig(
|
|||||||
priority: priority,
|
priority: priority,
|
||||||
...(resource.ssl ? { tls } : {})
|
...(resource.ssl ? { tls } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
config_output.http.services![serviceName] = {
|
|
||||||
loadBalancer: {
|
|
||||||
servers: buildHttpLoadBalancerServers(targets),
|
|
||||||
...(resource.stickySession
|
|
||||||
? buildStickySessionCookie(resource.ssl)
|
|
||||||
: {})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the serversTransport if TLS server name is provided
|
|
||||||
if (resource.tlsServerName) {
|
|
||||||
if (!config_output.http.serversTransports) {
|
|
||||||
config_output.http.serversTransports = {};
|
|
||||||
}
|
|
||||||
config_output.http.serversTransports![transportName] = {
|
|
||||||
serverName: resource.tlsServerName,
|
|
||||||
//unfortunately the following needs to be set. traefik doesn't merge the default serverTransport settings
|
|
||||||
// if defined in the static config and here. if not set, self-signed certs won't work
|
|
||||||
insecureSkipVerify: true
|
|
||||||
};
|
|
||||||
config_output.http.services![
|
|
||||||
serviceName
|
|
||||||
].loadBalancer.serversTransport = transportName;
|
|
||||||
}
|
|
||||||
} else if (resource.mode == "tcp" || resource.mode == "udp") {
|
} else if (resource.mode == "tcp" || resource.mode == "udp") {
|
||||||
// Non-HTTP (TCP/UDP) configuration
|
// Non-HTTP (TCP/UDP) configuration
|
||||||
if (!resource.enableProxy) {
|
if (!resource.enableProxy) {
|
||||||
@@ -791,10 +795,7 @@ export async function getTraefikConfig(
|
|||||||
domainCertResolver,
|
domainCertResolver,
|
||||||
preferWildcardCert
|
preferWildcardCert
|
||||||
}) => {
|
}) => {
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
return buildWildcardTls({
|
return buildWildcardTls({
|
||||||
fullDomain,
|
fullDomain,
|
||||||
hasSubdomain,
|
hasSubdomain,
|
||||||
@@ -835,10 +836,7 @@ export async function getTraefikConfig(
|
|||||||
maintenancePageUiUrl,
|
maintenancePageUiUrl,
|
||||||
redirectHttpsMiddlewareName,
|
redirectHttpsMiddlewareName,
|
||||||
resolveTls: (fullDomain) => {
|
resolveTls: (fullDomain) => {
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
// siteResource aliases don't have a per-domain cert
|
// siteResource aliases don't have a per-domain cert
|
||||||
// resolver stored, so always fall back to the global
|
// resolver stored, so always fall back to the global
|
||||||
// defaults.
|
// defaults.
|
||||||
@@ -929,10 +927,7 @@ export async function getTraefikConfig(
|
|||||||
const rule = buildHostRule(fullDomain, ir.wildcard);
|
const rule = buildHostRule(fullDomain, ir.wildcard);
|
||||||
|
|
||||||
let tls: any = {};
|
let tls: any = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
tls = buildWildcardTls({
|
tls = buildWildcardTls({
|
||||||
fullDomain,
|
fullDomain,
|
||||||
hasSubdomain: !!ir.subdomain,
|
hasSubdomain: !!ir.subdomain,
|
||||||
@@ -1012,10 +1007,7 @@ export async function getTraefikConfig(
|
|||||||
const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to
|
const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to
|
||||||
|
|
||||||
let tls: any = {};
|
let tls: any = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
// siteResource aliases don't have a per-domain cert
|
// siteResource aliases don't have a per-domain cert
|
||||||
// resolver stored, so always fall back to the global
|
// resolver stored, so always fall back to the global
|
||||||
// defaults.
|
// defaults.
|
||||||
@@ -1089,7 +1081,7 @@ export async function getTraefikConfig(
|
|||||||
.where(eq(exitNodes.exitNodeId, exitNodeId));
|
.where(eq(exitNodes.exitNodeId, exitNodeId));
|
||||||
|
|
||||||
let validCertsLoginPages: CertificateResult[] = [];
|
let validCertsLoginPages: CertificateResult[] = [];
|
||||||
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
|
if (pangolinCertModeEnabled) {
|
||||||
// create a list of all domains to get certs for
|
// create a list of all domains to get certs for
|
||||||
const domains = new Set<string>();
|
const domains = new Set<string>();
|
||||||
for (const lp of exitNodeLoginPages) {
|
for (const lp of exitNodeLoginPages) {
|
||||||
@@ -1134,10 +1126,7 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tls = {};
|
const tls = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
// TODO: we need to add the wildcard logic here too
|
// TODO: we need to add the wildcard logic here too
|
||||||
} else {
|
} else {
|
||||||
// find a cert that matches the full domain, if not continue
|
// find a cert that matches the full domain, if not continue
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
LicenseStatus
|
LicenseStatus
|
||||||
} from "@server/license/license";
|
} from "@server/license/license";
|
||||||
import { setHostMeta } from "@server/lib/hostMeta";
|
import { setHostMeta } from "@server/lib/hostMeta";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
type ActivateLicenseKeyAPIResponse = {
|
type ActivateLicenseKeyAPIResponse = {
|
||||||
data: {
|
data: {
|
||||||
@@ -119,6 +120,9 @@ LQIDAQAB
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async isUnlocked(): Promise<boolean> {
|
public async isUnlocked(): Promise<boolean> {
|
||||||
|
if (build == "saas") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const status = await this.check();
|
const status = await this.check();
|
||||||
if (status.isHostLicensed) {
|
if (status.isHostLicensed) {
|
||||||
if (status.isLicenseValid) {
|
if (status.isLicenseValid) {
|
||||||
@@ -128,6 +132,20 @@ LQIDAQAB
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async hasTier(tier: LicenseKeyTier[]): Promise<boolean> {
|
||||||
|
if (build == "saas") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const status = await this.check();
|
||||||
|
if (status.isHostLicensed && status.isLicenseValid) {
|
||||||
|
return (
|
||||||
|
status.tier !== undefined &&
|
||||||
|
tier.includes(status.tier as LicenseKeyTier)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public async check(): Promise<LicenseStatus> {
|
public async check(): Promise<LicenseStatus> {
|
||||||
// If a check is already in progress, return the last known status
|
// If a check is already in progress, return the last known status
|
||||||
if (this.checkInProgress) {
|
if (this.checkInProgress) {
|
||||||
@@ -135,8 +153,7 @@ LQIDAQAB
|
|||||||
"License check already in progress, returning last known status"
|
"License check already in progress, returning last known status"
|
||||||
);
|
);
|
||||||
const lastStatus = this.statusCache.get(this.statusKey) as
|
const lastStatus = this.statusCache.get(this.statusKey) as
|
||||||
| LicenseStatus
|
LicenseStatus | undefined;
|
||||||
| undefined;
|
|
||||||
if (lastStatus) {
|
if (lastStatus) {
|
||||||
return lastStatus;
|
return lastStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export const queryAccessAuditLogsQuery = z.object({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export const queryActionAuditLogsQuery = z.object({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export const queryConnectionAuditLogsQuery = z.object({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -222,15 +222,16 @@ export async function handleSubscriptionCreated(
|
|||||||
|
|
||||||
let numUsers: number;
|
let numUsers: number;
|
||||||
let numSites: number;
|
let numSites: number;
|
||||||
|
let tier = "enterprise";
|
||||||
|
|
||||||
if (subscriptionPriceId === priceSet[LicenseId.SMALL_LICENSE]) {
|
if (subscriptionPriceId === priceSet[LicenseId.TIER1]) {
|
||||||
numUsers = 25;
|
numUsers = 25;
|
||||||
numSites = 25;
|
numSites = 25;
|
||||||
} else if (
|
tier = "tier1";
|
||||||
subscriptionPriceId === priceSet[LicenseId.BIG_LICENSE]
|
} else if (subscriptionPriceId === priceSet[LicenseId.TIER2]) {
|
||||||
) {
|
|
||||||
numUsers = 50;
|
numUsers = 50;
|
||||||
numSites = 100;
|
numSites = 100;
|
||||||
|
tier = "tier2";
|
||||||
} else {
|
} else {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}`
|
`Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}`
|
||||||
@@ -256,7 +257,8 @@ export async function handleSubscriptionCreated(
|
|||||||
licenseId: parseInt(licenseId),
|
licenseId: parseInt(licenseId),
|
||||||
paidFor: true,
|
paidFor: true,
|
||||||
users: numUsers,
|
users: numUsers,
|
||||||
sites: numSites
|
sites: numSites,
|
||||||
|
tier: tier
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -64,14 +64,11 @@ export async function generateNewEnterpriseLicense(
|
|||||||
|
|
||||||
const licenseData = req.body;
|
const licenseData = req.body;
|
||||||
|
|
||||||
if (
|
if (licenseData.tier != "tier2" && licenseData.tier != "tier1") {
|
||||||
licenseData.tier != "big_license" &&
|
|
||||||
licenseData.tier != "small_license"
|
|
||||||
) {
|
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
"Invalid tier specified. Must be either 'big_license' or 'small_license'."
|
"Invalid tier specified. Must be either 'tier2' or 'tier1'."
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -118,9 +115,7 @@ export async function generateNewEnterpriseLicense(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tier =
|
const tier =
|
||||||
licenseData.tier === "big_license"
|
licenseData.tier === "tier2" ? LicenseId.TIER2 : LicenseId.TIER1;
|
||||||
? LicenseId.BIG_LICENSE
|
|
||||||
: LicenseId.SMALL_LICENSE;
|
|
||||||
const tierPrice = getLicensePriceSet()[tier];
|
const tierPrice = getLicensePriceSet()[tier];
|
||||||
|
|
||||||
const session = await stripe!.checkout.sessions.create({
|
const session = await stripe!.checkout.sessions.create({
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export const queryAiSessionLogsQuery = z.strictObject({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ const queryAccessAuditLogsQuery = z.object({
|
|||||||
error: "timeStart must be a valid ISO date string"
|
error: "timeStart must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => getSevenDaysAgo().toISOString())
|
.prefault(() => getSevenDaysAgo().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -34,7 +33,6 @@ const queryAccessAuditLogsQuery = z.object({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export const queryAccessAuditLogsQuery = z.strictObject({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
import type {
|
import type {
|
||||||
GenerateRegistrationOptionsOpts,
|
GenerateRegistrationOptionsOpts,
|
||||||
GenerateAuthenticationOptionsOpts,
|
GenerateAuthenticationOptionsOpts,
|
||||||
AuthenticatorTransportFuture
|
AuthenticatorTransport
|
||||||
} from "@simplewebauthn/server";
|
} from "@simplewebauthn/server";
|
||||||
import { isoBase64URL } from "@simplewebauthn/server/helpers";
|
import { isoBase64URL } from "@simplewebauthn/server/helpers";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
@@ -221,7 +221,7 @@ export async function startRegistration(
|
|||||||
const excludeCredentials = existingSecurityKeys.map((key) => ({
|
const excludeCredentials = existingSecurityKeys.map((key) => ({
|
||||||
id: key.credentialId,
|
id: key.credentialId,
|
||||||
transports: key.transports
|
transports: key.transports
|
||||||
? (JSON.parse(key.transports) as AuthenticatorTransportFuture[])
|
? (JSON.parse(key.transports) as AuthenticatorTransport[])
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -571,7 +571,7 @@ export async function startAuthentication(
|
|||||||
transports: key.transports
|
transports: key.transports
|
||||||
? (JSON.parse(
|
? (JSON.parse(
|
||||||
key.transports
|
key.transports
|
||||||
) as AuthenticatorTransportFuture[])
|
) as AuthenticatorTransport[])
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -702,7 +702,7 @@ export async function verifyAuthentication(
|
|||||||
transports: securityKey.transports
|
transports: securityKey.transports
|
||||||
? (JSON.parse(
|
? (JSON.parse(
|
||||||
securityKey.transports
|
securityKey.transports
|
||||||
) as AuthenticatorTransportFuture[])
|
) as AuthenticatorTransport[])
|
||||||
: undefined
|
: undefined
|
||||||
},
|
},
|
||||||
requireUserVerification: false
|
requireUserVerification: false
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (
|
|||||||
const { message, client: c } = context;
|
const { message, client: c } = context;
|
||||||
const newt = c as Newt;
|
const newt = c as Newt;
|
||||||
|
|
||||||
logger.info("Handling healthcheck status message");
|
logger.debug("Handling healthcheck status message");
|
||||||
|
|
||||||
if (!newt) {
|
if (!newt) {
|
||||||
logger.warn("Newt not found");
|
logger.warn("Newt not found");
|
||||||
|
|||||||
@@ -252,9 +252,10 @@ export default function GenerateLicenseKeyForm({
|
|||||||
try {
|
try {
|
||||||
// Check if this is a business/enterprise license request
|
// Check if this is a business/enterprise license request
|
||||||
if (payload.useCaseType === "business") {
|
if (payload.useCaseType === "business") {
|
||||||
const response = await api.put<
|
const response = await api.put<AxiosResponse<string>>(
|
||||||
AxiosResponse<string>
|
`/org/${orgId}/license/enterprise`,
|
||||||
>(`/org/${orgId}/license/enterprise`, { ...payload, tier: "big_license" } );
|
{ ...payload, tier: "tier2" }
|
||||||
|
);
|
||||||
|
|
||||||
console.log("Checkout session response:", response.data);
|
console.log("Checkout session response:", response.data);
|
||||||
const checkoutUrl = response.data.data;
|
const checkoutUrl = response.data.data;
|
||||||
@@ -1087,16 +1088,16 @@ export default function GenerateLicenseKeyForm({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!generatedKey && useCaseType === "business" && (
|
{!generatedKey && useCaseType === "business" && (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
form="generate-license-business-form"
|
form="generate-license-business-form"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
{t(
|
{t(
|
||||||
"generateLicenseKeyForm.buttons.generateLicenseKey"
|
"generateLicenseKeyForm.buttons.generateLicenseKey"
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</CredenzaFooter>
|
</CredenzaFooter>
|
||||||
</CredenzaContent>
|
</CredenzaContent>
|
||||||
|
|||||||
@@ -201,9 +201,16 @@ export default function GenerateLicenseKeysTable({
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const tier = row.original.tier;
|
const tier = row.original.tier;
|
||||||
return tier === "enterprise"
|
switch (tier) {
|
||||||
? t("licenseTierEnterprise")
|
case "enterprise":
|
||||||
: t("licenseTierPersonal");
|
return t("licenseTierEnterprise");
|
||||||
|
case "tier1":
|
||||||
|
return t("licenseTierTier1");
|
||||||
|
case "tier2":
|
||||||
|
return t("licenseTierTier2");
|
||||||
|
default:
|
||||||
|
return t("licenseTierPersonal");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -100,9 +100,16 @@ export function LicenseKeysDataTable({
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const tier = row.original.tier;
|
const tier = row.original.tier;
|
||||||
return tier === "enterprise"
|
switch (tier) {
|
||||||
? t("licenseTierEnterprise")
|
case "enterprise":
|
||||||
: t("licenseTierPersonal");
|
return t("licenseTierEnterprise");
|
||||||
|
case "tier1":
|
||||||
|
return t("licenseTierTier1");
|
||||||
|
case "tier2":
|
||||||
|
return t("licenseTierTier2");
|
||||||
|
default:
|
||||||
|
return t("licenseTierPersonal");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ import { InfoIcon } from "lucide-react";
|
|||||||
import { useUserContext } from "@app/hooks/useUserContext";
|
import { useUserContext } from "@app/hooks/useUserContext";
|
||||||
|
|
||||||
const TIER_TO_LICENSE_ID = {
|
const TIER_TO_LICENSE_ID = {
|
||||||
starter: "small_license",
|
starter: "tier1",
|
||||||
scale: "big_license"
|
scale: "tier2"
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type FormProps = {
|
type FormProps = {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ function Calendar({
|
|||||||
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
|
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
|
||||||
defaultClassNames.caption_label
|
defaultClassNames.caption_label
|
||||||
),
|
),
|
||||||
table: "w-full border-collapse",
|
month_grid: "w-full border-collapse",
|
||||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||||
weekday: cn(
|
weekday: cn(
|
||||||
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
|
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
|
||||||
|
|||||||
Reference in New Issue
Block a user