Compare commits

...

88 Commits

Author SHA1 Message Date
Owen Schwartz d2d30cd82e New translations en-us.json (Norwegian Bokmal) 2026-02-14 12:34:30 -08:00
Owen Schwartz 4613d90eaf New translations en-us.json (Chinese Simplified) 2026-02-14 12:34:29 -08:00
Owen Schwartz 4b3a172720 New translations en-us.json (Turkish) 2026-02-14 12:34:27 -08:00
Owen Schwartz 715654dd96 New translations en-us.json (Russian) 2026-02-14 12:34:26 -08:00
Owen Schwartz fb1a9f5849 New translations en-us.json (Portuguese) 2026-02-14 12:34:25 -08:00
Owen Schwartz b783dc62c0 New translations en-us.json (Polish) 2026-02-14 12:34:23 -08:00
Owen Schwartz 83b5e20b1b New translations en-us.json (Dutch) 2026-02-14 12:34:22 -08:00
Owen Schwartz ab2ec7129c New translations en-us.json (Korean) 2026-02-14 12:34:21 -08:00
Owen Schwartz 9d31147750 New translations en-us.json (Italian) 2026-02-14 12:34:20 -08:00
Owen Schwartz 38ae15bad5 New translations en-us.json (German) 2026-02-14 12:34:18 -08:00
Owen Schwartz 2d52748fbc New translations en-us.json (Czech) 2026-02-14 12:34:17 -08:00
Owen Schwartz 5e14b57f90 New translations en-us.json (Bulgarian) 2026-02-14 12:34:16 -08:00
Owen Schwartz a2528fe8f9 New translations en-us.json (Spanish) 2026-02-14 12:34:14 -08:00
Owen Schwartz 6068d8b554 New translations en-us.json (French) 2026-02-14 12:34:13 -08:00
Owen Schwartz 4ca54a6c5b New translations en-us.json (Norwegian Bokmal) 2026-02-13 18:31:27 -08:00
Owen Schwartz 274faded45 New translations en-us.json (Chinese Simplified) 2026-02-13 18:31:26 -08:00
Owen Schwartz 7eebd77aa3 New translations en-us.json (Turkish) 2026-02-13 18:31:25 -08:00
Owen Schwartz 99278f9961 New translations en-us.json (Russian) 2026-02-13 18:31:23 -08:00
Owen Schwartz ceb9cd38eb New translations en-us.json (Portuguese) 2026-02-13 18:31:22 -08:00
Owen Schwartz 687eda132a New translations en-us.json (Polish) 2026-02-13 18:31:21 -08:00
Owen Schwartz 3ebd7bee63 New translations en-us.json (Dutch) 2026-02-13 18:31:19 -08:00
Owen Schwartz 4584d9ddb1 New translations en-us.json (Korean) 2026-02-13 18:31:18 -08:00
Owen Schwartz 97a0c65137 New translations en-us.json (Italian) 2026-02-13 18:31:17 -08:00
Owen Schwartz f28925347c New translations en-us.json (German) 2026-02-13 18:31:15 -08:00
Owen Schwartz 220e6c0aca New translations en-us.json (Czech) 2026-02-13 18:31:14 -08:00
Owen Schwartz dd56706624 New translations en-us.json (Bulgarian) 2026-02-13 18:31:12 -08:00
Owen Schwartz 597ef725c4 New translations en-us.json (Spanish) 2026-02-13 18:31:10 -08:00
Owen Schwartz 39e7655327 New translations en-us.json (French) 2026-02-13 18:31:09 -08:00
Owen Schwartz 829e324217 New translations en-us.json (Norwegian Bokmal) 2026-02-13 17:18:44 -08:00
Owen Schwartz 59ce52ce83 New translations en-us.json (Chinese Simplified) 2026-02-13 17:18:43 -08:00
Owen Schwartz 43f602a005 New translations en-us.json (Turkish) 2026-02-13 17:18:41 -08:00
Owen Schwartz c4643d9bcb New translations en-us.json (Russian) 2026-02-13 17:18:40 -08:00
Owen Schwartz ea5fa998c1 New translations en-us.json (Portuguese) 2026-02-13 17:18:38 -08:00
Owen Schwartz 9258ffda77 New translations en-us.json (Polish) 2026-02-13 17:18:37 -08:00
Owen Schwartz 103b89a171 New translations en-us.json (Dutch) 2026-02-13 17:18:35 -08:00
Owen Schwartz 336d94bc43 New translations en-us.json (Korean) 2026-02-13 17:18:34 -08:00
Owen Schwartz f1dfac1192 New translations en-us.json (Italian) 2026-02-13 17:18:33 -08:00
Owen Schwartz 93f09b9c1b New translations en-us.json (German) 2026-02-13 17:18:31 -08:00
Owen Schwartz e2d940e477 New translations en-us.json (Czech) 2026-02-13 17:18:30 -08:00
Owen Schwartz 2a99bdadd8 New translations en-us.json (Bulgarian) 2026-02-13 17:18:29 -08:00
Owen Schwartz b2af60055b New translations en-us.json (Spanish) 2026-02-13 17:18:27 -08:00
Owen Schwartz 540bafb730 New translations en-us.json (French) 2026-02-13 17:18:26 -08:00
Owen dbfd715381 Fix windows formatting 2026-02-12 16:27:51 -08:00
Owen Schwartz f1d989964e Merge pull request #2471 from fosrl/dev
Update translations
2026-02-12 16:14:41 -08:00
Owen Schwartz b701629498 Merge pull request #2465 from fosrl/crowdin_dev
New Crowdin updates
2026-02-12 16:14:17 -08:00
Owen Schwartz 8250946325 New translations en-us.json (Norwegian Bokmal) 2026-02-12 16:14:04 -08:00
Owen Schwartz 71f63d8e6f New translations en-us.json (Chinese Simplified) 2026-02-12 16:14:03 -08:00
Owen Schwartz dd5e834db0 New translations en-us.json (Turkish) 2026-02-12 16:14:01 -08:00
Owen Schwartz 970ecb52f0 New translations en-us.json (Russian) 2026-02-12 16:14:00 -08:00
Owen Schwartz 62ea1b40e1 New translations en-us.json (Portuguese) 2026-02-12 16:13:58 -08:00
Owen Schwartz 3b0fd5c592 New translations en-us.json (Polish) 2026-02-12 16:13:57 -08:00
Owen Schwartz b7616026dd New translations en-us.json (Dutch) 2026-02-12 16:13:55 -08:00
Owen Schwartz 16ad60b89a New translations en-us.json (Korean) 2026-02-12 16:13:54 -08:00
Owen Schwartz db7971d2f7 New translations en-us.json (Italian) 2026-02-12 16:13:53 -08:00
Owen Schwartz f3f8bd3125 New translations en-us.json (German) 2026-02-12 16:13:51 -08:00
Owen Schwartz 516fd0ee8f New translations en-us.json (Czech) 2026-02-12 16:13:50 -08:00
Owen Schwartz 8d6700d493 New translations en-us.json (Bulgarian) 2026-02-12 16:13:49 -08:00
Owen Schwartz 9d4ace9b3e New translations en-us.json (Spanish) 2026-02-12 16:13:47 -08:00
Owen Schwartz 2800655e33 New translations en-us.json (French) 2026-02-12 16:13:45 -08:00
Owen Schwartz 91eecee11d Merge pull request #2469 from fosrl/dev
1.15.4
2026-02-12 16:10:44 -08:00
Owen Schwartz 899e5aa395 New translations en-us.json (Norwegian Bokmal) 2026-02-12 15:41:25 -08:00
Owen Schwartz d5820c4902 New translations en-us.json (Chinese Simplified) 2026-02-12 15:41:24 -08:00
Owen Schwartz a91c002274 New translations en-us.json (Turkish) 2026-02-12 15:41:22 -08:00
Owen Schwartz 4d142b93dd New translations en-us.json (Russian) 2026-02-12 15:41:21 -08:00
Owen Schwartz 04dcf57ff3 New translations en-us.json (Portuguese) 2026-02-12 15:41:20 -08:00
Owen Schwartz 975550c755 New translations en-us.json (Polish) 2026-02-12 15:41:18 -08:00
Owen Schwartz a964a80d85 New translations en-us.json (Dutch) 2026-02-12 15:41:17 -08:00
Owen Schwartz 22c3b8f116 New translations en-us.json (Korean) 2026-02-12 15:41:15 -08:00
Owen Schwartz c4b1831cfe New translations en-us.json (Italian) 2026-02-12 15:41:14 -08:00
Owen Schwartz cdb6813384 New translations en-us.json (German) 2026-02-12 15:41:13 -08:00
Owen Schwartz b14b68d83c New translations en-us.json (Czech) 2026-02-12 15:41:11 -08:00
Owen Schwartz 3c2f930e6b New translations en-us.json (Bulgarian) 2026-02-12 15:41:10 -08:00
Owen Schwartz ca9c7ce555 New translations en-us.json (Spanish) 2026-02-12 15:41:08 -08:00
Owen Schwartz c2e95a0607 New translations en-us.json (French) 2026-02-12 15:41:06 -08:00
miloschwartz 2767ee9e80 update pangolin cli links 2026-02-12 15:29:08 -08:00
miloschwartz d998a8087f fix pg migration 2026-02-12 15:06:24 -08:00
miloschwartz fdce016921 add 1.15.4 migration 2026-02-12 15:00:12 -08:00
miloschwartz c73d70933b bump version 2026-02-12 14:52:29 -08:00
miloschwartz e9d0ad6e37 use pangolin cli for container 2026-02-12 14:51:25 -08:00
Owen a35586f762 Add sudo 2026-02-12 14:47:55 -08:00
miloschwartz f527c30923 add post auth url 2026-02-12 14:21:50 -08:00
Owen 94e70219cf Make install sudo because run is sudo 2026-02-12 14:12:45 -08:00
Owen 6496763aae Cap retention days 2026-02-12 12:18:42 -08:00
Owen a409ec269b Change back to lokowitz db method 2026-02-12 12:13:13 -08:00
Owen bc7bc8da66 Stop tracking files that should be ignored 2026-02-12 12:07:57 -08:00
Owen 52484c774e Setting up drizzle and fix site not showing in private resource 2026-02-12 12:05:15 -08:00
Owen Schwartz 69ecc22318 New translations en-us.json (German) 2026-02-12 01:39:38 -08:00
Owen bff9d33ee6 Move back to db:sqlite:generate 2026-02-11 21:47:10 -08:00
49 changed files with 370 additions and 252 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
name: Public Pipeline
name: Public CICD Pipeline
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
run: npm run db:generate
- name: Apply database migrations
run: npm run db:sqlite:push
run: npm run db:push
- name: Test with tsc
run: npx tsc --noEmit
+2 -1
View File
@@ -51,4 +51,5 @@ dynamic/
scratch/
tsconfig.json
hydrateSaas.ts
CLAUDE.md
CLAUDE.md
drizzle.config.ts
+1 -1
View File
@@ -16,7 +16,7 @@ COPY . .
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \
npm run set:$DATABASE && \
npm run set:$BUILD && \
npm run db:$DATABASE:generate && \
npm run db:generate && \
npm run build && \
npm run build:cli
-14
View File
@@ -1,14 +0,0 @@
import { defineConfig } from "drizzle-kit";
import path from "path";
const schema = [path.join("server", "db", "pg", "schema")];
export default defineConfig({
dialect: "postgresql",
schema: schema,
out: path.join("server", "migrations"),
verbose: true,
dbCredentials: {
url: process.env.DATABASE_URL as string
}
});
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Филтрирайте по състояние на одобрение",
"approvalListEmpty": "Няма одобрения",
"approvalState": "Състояние на одобрение",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Одобряване",
"approved": "Одобрен",
"denied": "Отказан",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Преглед на дневници",
"noneSelected": "Нищо не е избрано",
"orgNotFound2": "Няма намерени организации.",
"searchProgress": "Търсене...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Създаване",
"orgs": "Организации",
"loginError": "Възникна неочаквана грешка. Моля, опитайте отново.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Сигурни ли сте, че искате да премахнете брандинга за страниците за автентификация?",
"authPageBrandingDeleteConfirm": "Потвърждение на изтриване на брандинга.",
"brandingLogoURL": "URL адрес на логото.",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Основен цвят.",
"brandingLogoWidth": "Ширина (px).",
"brandingLogoHeight": "Височина (px).",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Filtrovat podle státu schválení",
"approvalListEmpty": "Žádná schválení",
"approvalState": "Země schválení",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Schválit",
"approved": "Schváleno",
"denied": "Zamítnuto",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Zobrazit logy",
"noneSelected": "Není vybráno",
"orgNotFound2": "Nebyly nalezeny žádné organizace.",
"searchProgress": "Hledat...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Vytvořit",
"orgs": "Organizace",
"loginError": "Došlo k neočekávané chybě. Zkuste to prosím znovu.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Jste si jisti, že chcete odstranit branding autentizačních stránek?",
"authPageBrandingDeleteConfirm": "Potvrzení odstranění brandingu",
"brandingLogoURL": "URL loga",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Primární barva",
"brandingLogoWidth": "Šířka (px)",
"brandingLogoHeight": "Výška (px)",
+10 -4
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Filtern nach Genehmigungsstatus",
"approvalListEmpty": "Keine Genehmigungen",
"approvalState": "Genehmigungsstatus",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Bestätigen",
"approved": "Genehmigt",
"denied": "Verweigert",
@@ -1154,7 +1156,7 @@
"actionDeleteClient": "Client löschen",
"actionArchiveClient": "Client archivieren",
"actionUnarchiveClient": "Client dearchivieren",
"actionBlockClient": "Klient sperren",
"actionBlockClient": "Client sperren",
"actionUnblockClient": "Client entsperren",
"actionUpdateClient": "Client aktualisieren",
"actionListClients": "Clients auflisten",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Logs anzeigen",
"noneSelected": "Keine ausgewählt",
"orgNotFound2": "Keine Organisationen gefunden.",
"searchProgress": "Suche...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Erstellen",
"orgs": "Organisationen",
"loginError": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Sind Sie sicher, dass Sie das Branding für Authentifizierungsseiten entfernen möchten?",
"authPageBrandingDeleteConfirm": "Branding löschen bestätigen",
"brandingLogoURL": "Logo URL",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Primär-Farbe",
"brandingLogoWidth": "Breite (px)",
"brandingLogoHeight": "Höhe (px)",
@@ -2532,10 +2538,10 @@
"archiveClientQuestion": "Sind Sie sicher, dass Sie diesen Client archivieren möchten?",
"archiveClientMessage": "Der Client wird archiviert und aus der Liste Ihrer aktiven Clients entfernt.",
"archiveClientConfirm": "Client archivieren",
"blockClient": "Klient sperren",
"blockClient": "Client sperren",
"blockClientQuestion": "Sind Sie sicher, dass Sie diesen Client blockieren möchten?",
"blockClientMessage": "Das Gerät wird gezwungen, die Verbindung zu trennen, wenn es gerade verbunden ist. Sie können das Gerät später entsperren.",
"blockClientConfirm": "Klient sperren",
"blockClientConfirm": "Client sperren",
"active": "Aktiv",
"usernameOrEmail": "Benutzername oder E-Mail",
"selectYourOrganization": "Wählen Sie Ihre Organisation",
+1 -1
View File
@@ -2060,7 +2060,7 @@
"machineClientsBannerDescription": "Machine clients are for servers and automated systems that are not associated with a specific user. They authenticate with an ID and secret, and can run with Pangolin CLI, Olm CLI, or Olm as a container.",
"machineClientsBannerPangolinCLI": "Pangolin CLI",
"machineClientsBannerOlmCLI": "Olm CLI",
"machineClientsBannerOlmContainer": "Olm Container",
"machineClientsBannerOlmContainer": "Container",
"clientsTableUserClients": "User",
"clientsTableMachineClients": "Machine",
"licenseTableValidUntil": "Valid Until",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Filtrar por estado de aprobación",
"approvalListEmpty": "No hay aprobaciones",
"approvalState": "Estado de aprobación",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Aprobar",
"approved": "Aprobado",
"denied": "Denegado",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Ver registros",
"noneSelected": "Ninguno seleccionado",
"orgNotFound2": "No se encontraron organizaciones.",
"searchProgress": "Buscar...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Crear",
"orgs": "Organizaciones",
"loginError": "Ocurrió un error inesperado. Por favor, inténtelo de nuevo.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "¿Está seguro de que desea eliminar la marca de las páginas de autenticación?",
"authPageBrandingDeleteConfirm": "Confirmar eliminación de la marca",
"brandingLogoURL": "URL del logotipo",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Color primario",
"brandingLogoWidth": "Ancho (px)",
"brandingLogoHeight": "Altura (px)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Filtrer par État d'Approbation",
"approvalListEmpty": "Aucune approbation",
"approvalState": "État d'approbation",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Approuver",
"approved": "Approuvé",
"denied": "Refusé",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Voir les logs",
"noneSelected": "Aucune sélection",
"orgNotFound2": "Aucune organisation trouvée.",
"searchProgress": "Rechercher...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Créer",
"orgs": "Organisations",
"loginError": "Une erreur inattendue s'est produite. Veuillez réessayer.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Êtes-vous sûr de vouloir supprimer la marque des pages d'authentification ?",
"authPageBrandingDeleteConfirm": "Confirmer la suppression de la marque",
"brandingLogoURL": "URL du logo",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Couleur principale",
"brandingLogoWidth": "Largeur (px)",
"brandingLogoHeight": "Hauteur (px)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Filtra Per Stato Di Approvazione",
"approvalListEmpty": "Nessuna approvazione",
"approvalState": "Stato Di Approvazione",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Approva",
"approved": "Approvato",
"denied": "Negato",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Visualizza Log",
"noneSelected": "Nessuna selezione",
"orgNotFound2": "Nessuna organizzazione trovata.",
"searchProgress": "Ricerca...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Crea",
"orgs": "Organizzazioni",
"loginError": "Si è verificato un errore imprevisto. Riprova.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Sei sicuro di voler rimuovere il branding per le pagine di autenticazione?",
"authPageBrandingDeleteConfirm": "Conferma Eliminazione Branding",
"brandingLogoURL": "URL Logo",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Colore Primario",
"brandingLogoWidth": "Larghezza (px)",
"brandingLogoHeight": "Altezza (px)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "승인 상태로 필터링",
"approvalListEmpty": "승인이 없습니다.",
"approvalState": "승인 상태",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "승인",
"approved": "승인됨",
"denied": "거부됨",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "로그 보기",
"noneSelected": "선택된 항목 없음",
"orgNotFound2": "조직이 없습니다.",
"searchProgress": "검색...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "생성",
"orgs": "조직",
"loginError": "예기치 않은 오류가 발생했습니다. 다시 시도해주세요.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "인증 페이지의 브랜딩을 제거하시겠습니까?",
"authPageBrandingDeleteConfirm": "브랜딩 삭제 확인",
"brandingLogoURL": "로고 URL",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "기본 색상",
"brandingLogoWidth": "너비(px)",
"brandingLogoHeight": "높이(px)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Filtrer etter godkjenningsstatus",
"approvalListEmpty": "Ingen godkjenninger",
"approvalState": "Godkjennings tilstand",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Godkjenn",
"approved": "Godkjent",
"denied": "Avvist",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Vis logger",
"noneSelected": "Ingen valgt",
"orgNotFound2": "Ingen organisasjoner funnet.",
"searchProgress": "Søker...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Opprett",
"orgs": "Organisasjoner",
"loginError": "En uventet feil oppstod. Vennligst prøv igjen.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
"authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging",
"brandingLogoURL": "Logo URL",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Primærfarge",
"brandingLogoWidth": "Bredde (px)",
"brandingLogoHeight": "Høyde (px)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Filter op goedkeuringsstatus",
"approvalListEmpty": "Geen goedkeuringen",
"approvalState": "Goedkeuring status",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Goedkeuren",
"approved": "Goedgekeurd",
"denied": "Geweigerd",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Logboeken bekijken",
"noneSelected": "Niet geselecteerd",
"orgNotFound2": "Geen organisaties gevonden.",
"searchProgress": "Zoeken...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Aanmaken",
"orgs": "Organisaties",
"loginError": "Er is een onverwachte fout opgetreden. Probeer het opnieuw.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Weet u zeker dat u de branding voor Auth-pagina's wilt verwijderen?",
"authPageBrandingDeleteConfirm": "Bevestig verwijder Branding",
"brandingLogoURL": "Het logo-URL",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Primaire kleur",
"brandingLogoWidth": "Breedte (px)",
"brandingLogoHeight": "Hoogte (px)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Filtruj według państwa zatwierdzenia",
"approvalListEmpty": "Brak zatwierdzeń",
"approvalState": "Państwo zatwierdzające",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Zatwierdź",
"approved": "Zatwierdzone",
"denied": "Odmowa",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Zobacz dzienniki",
"noneSelected": "Nie wybrano",
"orgNotFound2": "Nie znaleziono organizacji.",
"searchProgress": "Szukaj...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Utwórz",
"orgs": "Organizacje",
"loginError": "Wystąpił nieoczekiwany błąd. Spróbuj ponownie.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Czy na pewno chcesz usunąć branding dla stron uwierzytelniania?",
"authPageBrandingDeleteConfirm": "Potwierdź usunięcie brandingu",
"brandingLogoURL": "URL logo",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Główny kolor",
"brandingLogoWidth": "Szerokość (piksele)",
"brandingLogoHeight": "Wysokość (piksele)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Filtrar por estado de aprovação",
"approvalListEmpty": "Sem aprovações",
"approvalState": "Estado de aprovação",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Aprovar",
"approved": "Aceito",
"denied": "Negado",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Visualizar registros",
"noneSelected": "Nenhum selecionado",
"orgNotFound2": "Nenhuma organização encontrada.",
"searchProgress": "Pesquisar...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Criar",
"orgs": "Organizações",
"loginError": "Ocorreu um erro inesperado. Por favor, tente novamente.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Tem certeza de que deseja remover a marcação das Páginas de Autenticação?",
"authPageBrandingDeleteConfirm": "Confirmar Exclusão de Marca",
"brandingLogoURL": "URL do Logo",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Cor Primária",
"brandingLogoWidth": "Largura (px)",
"brandingLogoHeight": "Altura (px)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Фильтр по состоянию утверждения",
"approvalListEmpty": "Нет утверждений",
"approvalState": "Состояние одобрения",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Одобрить",
"approved": "Одобрено",
"denied": "Отказано",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Просмотр журналов",
"noneSelected": "Ничего не выбрано",
"orgNotFound2": "Организации не найдены.",
"searchProgress": "Поиск...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Создать",
"orgs": "Организации",
"loginError": "Произошла непредвиденная ошибка. Пожалуйста, попробуйте еще раз.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Вы уверены, что хотите удалить брендирование для страниц аутентификации?",
"authPageBrandingDeleteConfirm": "Подтвердить удаление брендирования",
"brandingLogoURL": "URL логотипа",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Основной цвет",
"brandingLogoWidth": "Ширина (px)",
"brandingLogoHeight": "Высота (px)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "Onay Durumuna Göre Filtrele",
"approvalListEmpty": "Onay yok",
"approvalState": "Onay Durumu",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "Onayla",
"approved": "Onaylandı",
"denied": "Reddedildi",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "Kayıtları Görüntüle",
"noneSelected": "Hiçbiri seçili değil",
"orgNotFound2": "Hiçbir organizasyon bulunamadı.",
"searchProgress": "Ara...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "Oluştur",
"orgs": "Organizasyonlar",
"loginError": "Beklenmeyen bir hata oluştu. Lütfen tekrar deneyin.",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "Kimlik Sayfaları için markayı kaldırmak istediğinizden emin misiniz?",
"authPageBrandingDeleteConfirm": "Markayı Silmeyi Onayla",
"brandingLogoURL": "Logo URL",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "Ana Renk",
"brandingLogoWidth": "Genişlik (px)",
"brandingLogoHeight": "Yükseklik (px)",
+7 -1
View File
@@ -461,6 +461,8 @@
"filterByApprovalState": "按批准状态过滤",
"approvalListEmpty": "无批准",
"approvalState": "审批状态",
"approvalLoadMore": "Load more",
"loadingApprovals": "Loading Approvals",
"approve": "批准",
"approved": "已批准",
"denied": "被拒绝",
@@ -1169,7 +1171,8 @@
"actionViewLogs": "查看日志",
"noneSelected": "未选择",
"orgNotFound2": "未找到组织。",
"searchProgress": "搜索中...",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"create": "创建",
"orgs": "组织",
"loginError": "发生意外错误。请重试。",
@@ -1916,6 +1919,9 @@
"authPageBrandingQuestionRemove": "您确定要移除授权页面的品牌吗?",
"authPageBrandingDeleteConfirm": "确认删除品牌",
"brandingLogoURL": "Logo URL",
"brandingLogoURLOrPath": "Logo URL or Path",
"brandingLogoPathDescription": "Enter a URL or a local path.",
"brandingLogoURLDescription": "Enter a publicly accessible URL to your logo image.",
"brandingPrimaryColor": "主要颜色",
"brandingLogoWidth": "宽度(px",
"brandingLogoHeight": "高度(px",
+4 -7
View File
@@ -13,13 +13,10 @@
"scripts": {
"dev": "NODE_ENV=development ENVIRONMENT=dev tsx watch server/index.ts",
"dev:check": "npx tsc --noEmit && npm run format:check",
"dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:generate && npm run db:sqlite:push",
"db:pg:generate": "drizzle-kit generate --config=./drizzle.pg.config.ts",
"db:sqlite:generate": "drizzle-kit generate --config=./drizzle.sqlite.config.ts",
"db:pg:push": "npx tsx server/db/pg/migrate.ts",
"db:sqlite:push": "npx tsx server/db/sqlite/migrate.ts",
"db:pg:studio": "drizzle-kit studio --config=./drizzle.pg.config.ts",
"db:sqlite:studio": "drizzle-kit studio --config=./drizzle.sqlite.config.ts",
"dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:sqlite:generate && npm run db:sqlite:push",
"db:generate": "drizzle-kit generate --config=./drizzle.config.ts",
"db:push": "npx tsx server/db/migrate.ts",
"db:studio": "drizzle-kit studio --config=./drizzle.config.ts",
"db:clear-migrations": "rm -rf server/migrations",
"set:oss": "echo 'export const build = \"oss\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.oss.json tsconfig.json",
"set:saas": "echo 'export const build = \"saas\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.saas.json tsconfig.json",
+4 -4
View File
@@ -56,15 +56,15 @@ Ensure drizzle-kit is installed.
You must have a connection string in your config file, as shown above.
```bash
npm run db:pg:generate
npm run db:pg:push
npm run db:generate
npm run db:push
```
### SQLite
```bash
npm run db:sqlite:generate
npm run db:sqlite:push
npm run db:generate
npm run db:push
```
## Build Time
+3
View File
@@ -0,0 +1,3 @@
import { runMigrations } from "./";
await runMigrations();
+1
View File
@@ -1,3 +1,4 @@
export * from "./driver";
export * from "./schema/schema";
export * from "./schema/privateSchema";
export * from "./migrate";
+1 -3
View File
@@ -4,7 +4,7 @@ import path from "path";
const migrationsFolder = path.join("server/migrations");
const runMigrations = async () => {
export const runMigrations = async () => {
console.log("Running migrations...");
try {
await migrate(db as any, {
@@ -17,5 +17,3 @@ const runMigrations = async () => {
process.exit(1);
}
};
runMigrations();
+2 -1
View File
@@ -142,7 +142,8 @@ export const resources = pgTable("resources", {
}).default("forced"), // "forced" = always show, "automatic" = only when down
maintenanceTitle: text("maintenanceTitle"),
maintenanceMessage: text("maintenanceMessage"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime")
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
postAuthPath: text("postAuthPath")
});
export const targets = pgTable("targets", {
+1
View File
@@ -1,3 +1,4 @@
export * from "./driver";
export * from "./schema/schema";
export * from "./schema/privateSchema";
export * from "./migrate";
+1 -3
View File
@@ -4,7 +4,7 @@ import path from "path";
const migrationsFolder = path.join("server/migrations");
const runMigrations = async () => {
export const runMigrations = async () => {
console.log("Running migrations...");
try {
migrate(db as any, {
@@ -16,5 +16,3 @@ const runMigrations = async () => {
process.exit(1);
}
};
runMigrations();
+2 -1
View File
@@ -162,7 +162,8 @@ export const resources = sqliteTable("resources", {
}).default("forced"), // "forced" = always show, "automatic" = only when down
maintenanceTitle: text("maintenanceTitle"),
maintenanceMessage: text("maintenanceMessage"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime")
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
postAuthPath: text("postAuthPath")
});
export const targets = sqliteTable("targets", {
+1 -1
View File
@@ -2,7 +2,7 @@ import path from "path";
import { fileURLToPath } from "url";
// This is a placeholder value replaced by the build process
export const APP_VERSION = "1.15.3";
export const APP_VERSION = "1.15.4";
export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME);
+18
View File
@@ -0,0 +1,18 @@
/**
* Normalizes a post-authentication path for safe use when building redirect URLs.
* Returns a path that starts with / and does not allow open redirects (no //, no :).
*/
export function normalizePostAuthPath(path: string | null | undefined): string | null {
if (path == null || typeof path !== "string") {
return null;
}
const trimmed = path.trim();
if (trimmed === "") {
return null;
}
// Reject protocol-relative (//) or scheme (:) to avoid open redirect
if (trimmed.includes("//") || trimmed.includes(":")) {
return null;
}
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
}
@@ -18,6 +18,113 @@ import logger from "@server/logger";
import { db, idp, idpOrg, loginPage, loginPageBranding, loginPageBrandingOrg, loginPageOrg, orgs, resources, roles } from "@server/db";
import { eq } from "drizzle-orm";
/**
* Get the maximum allowed retention days for a given tier
* Returns null for enterprise tier (unlimited)
*/
function getMaxRetentionDaysForTier(tier: Tier | null): number | null {
if (!tier) {
return 3; // Free tier
}
switch (tier) {
case "tier1":
return 7;
case "tier2":
return 30;
case "tier3":
return 90;
case "enterprise":
return null; // No limit
default:
return 3; // Default to free tier limit
}
}
/**
* Cap retention days to the maximum allowed for the given tier
*/
async function capRetentionDays(
orgId: string,
tier: Tier | null
): Promise<void> {
const maxRetentionDays = getMaxRetentionDaysForTier(tier);
// If there's no limit (enterprise tier), no capping needed
if (maxRetentionDays === null) {
logger.debug(
`No retention day limit for org ${orgId} on tier ${tier || "free"}`
);
return;
}
// Get current org settings
const [org] = await db
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
if (!org) {
logger.warn(`Org ${orgId} not found when capping retention days`);
return;
}
const updates: Partial<typeof orgs.$inferInsert> = {};
let needsUpdate = false;
// Cap request log retention if it exceeds the limit
if (
org.settingsLogRetentionDaysRequest !== null &&
org.settingsLogRetentionDaysRequest > maxRetentionDays
) {
updates.settingsLogRetentionDaysRequest = maxRetentionDays;
needsUpdate = true;
logger.info(
`Capping request log retention from ${org.settingsLogRetentionDaysRequest} to ${maxRetentionDays} days for org ${orgId}`
);
}
// Cap access log retention if it exceeds the limit
if (
org.settingsLogRetentionDaysAccess !== null &&
org.settingsLogRetentionDaysAccess > maxRetentionDays
) {
updates.settingsLogRetentionDaysAccess = maxRetentionDays;
needsUpdate = true;
logger.info(
`Capping access log retention from ${org.settingsLogRetentionDaysAccess} to ${maxRetentionDays} days for org ${orgId}`
);
}
// Cap action log retention if it exceeds the limit
if (
org.settingsLogRetentionDaysAction !== null &&
org.settingsLogRetentionDaysAction > maxRetentionDays
) {
updates.settingsLogRetentionDaysAction = maxRetentionDays;
needsUpdate = true;
logger.info(
`Capping action log retention from ${org.settingsLogRetentionDaysAction} to ${maxRetentionDays} days for org ${orgId}`
);
}
// Apply updates if needed
if (needsUpdate) {
await db
.update(orgs)
.set(updates)
.where(eq(orgs.orgId, orgId));
logger.info(
`Successfully capped retention days for org ${orgId} to max ${maxRetentionDays} days`
);
} else {
logger.debug(
`No retention day capping needed for org ${orgId}`
);
}
}
export async function handleTierChange(
orgId: string,
newTier: SubscriptionType | null,
@@ -40,6 +147,9 @@ export async function handleTierChange(
logger.info(
`Org ${orgId} is reverting to free tier, disabling all paid features`
);
// Cap retention days to free tier limits
await capRetentionDays(orgId, null);
// Disable all features in the tier matrix
for (const [featureKey] of Object.entries(tierMatrix)) {
const feature = featureKey as TierFeature;
@@ -57,6 +167,9 @@ export async function handleTierChange(
// Get the tier (cast as Tier since we've ruled out "license" and null)
const tier = newTier as Tier;
// Cap retention days to the new tier's limits
await capRetentionDays(orgId, tier);
// Check each feature in the tier matrix
for (const [featureKey, allowedTiers] of Object.entries(tierMatrix)) {
const feature = featureKey as TierFeature;
@@ -113,7 +113,7 @@ export async function generateNewEnterpriseLicense(
}
const tier = licenseData.tier === "big_license" ? LicenseId.BIG_LICENSE : LicenseId.SMALL_LICENSE;
const tierPrice = getLicensePriceSet()[tier]
const tierPrice = getLicensePriceSet()[tier];
const session = await stripe!.checkout.sessions.create({
client_reference_id: keyId.toString(),
@@ -14,6 +14,7 @@ import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToke
import config from "@server/lib/config";
import stoi from "@server/lib/stoi";
import { logAccessAudit } from "#dynamic/lib/logAccessAudit";
import { normalizePostAuthPath } from "@server/lib/normalizePostAuthPath";
const authWithAccessTokenBodySchema = z.strictObject({
accessToken: z.string(),
@@ -164,10 +165,16 @@ export async function authWithAccessToken(
requestIp: req.ip
});
let redirectUrl = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
const postAuthPath = normalizePostAuthPath(resource.postAuthPath);
if (postAuthPath) {
redirectUrl = redirectUrl + postAuthPath;
}
return response<AuthWithAccessTokenResponse>(res, {
data: {
session: token,
redirectUrl: `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`
redirectUrl
},
success: true,
error: false,
+5 -3
View File
@@ -36,7 +36,8 @@ const createHttpResourceSchema = z
http: z.boolean(),
protocol: z.enum(["tcp", "udp"]),
domainId: z.string(),
stickySession: z.boolean().optional()
stickySession: z.boolean().optional(),
postAuthPath: z.string().nullable().optional()
})
.refine(
(data) => {
@@ -188,7 +189,7 @@ async function createHttpResource(
);
}
const { name, domainId } = parsedBody.data;
const { name, domainId, postAuthPath } = parsedBody.data;
const subdomain = parsedBody.data.subdomain;
const stickySession = parsedBody.data.stickySession;
@@ -255,7 +256,8 @@ async function createHttpResource(
http: true,
protocol: "tcp",
ssl: true,
stickySession: stickySession
stickySession: stickySession,
postAuthPath: postAuthPath
})
.returning();
@@ -35,6 +35,7 @@ export type GetResourceAuthInfoResponse = {
whitelist: boolean;
skipToIdpId: number | null;
orgId: string;
postAuthPath: string | null;
};
export async function getResourceAuthInfo(
@@ -147,7 +148,8 @@ export async function getResourceAuthInfo(
url,
whitelist: resource.emailWhitelistEnabled,
skipToIdpId: resource.skipToIdpId,
orgId: resource.orgId
orgId: resource.orgId,
postAuthPath: resource.postAuthPath ?? null
},
success: true,
error: false,
+2 -1
View File
@@ -55,7 +55,8 @@ const updateHttpResourceBodySchema = z
maintenanceModeType: z.enum(["forced", "automatic"]).optional(),
maintenanceTitle: z.string().max(255).nullable().optional(),
maintenanceMessage: z.string().max(2000).nullable().optional(),
maintenanceEstimatedTime: z.string().max(100).nullable().optional()
maintenanceEstimatedTime: z.string().max(100).nullable().optional(),
postAuthPath: z.string().nullable().optional()
})
.refine((data) => Object.keys(data).length > 0, {
error: "At least one field must be provided for update"
+1
View File
@@ -0,0 +1 @@
migrations.ts
-162
View File
@@ -1,162 +0,0 @@
#! /usr/bin/env node
import { migrate } from "drizzle-orm/node-postgres/migrator";
import { db } from "../db/pg";
import semver from "semver";
import { versionMigrations } from "../db/pg";
import { __DIRNAME, APP_VERSION } from "@server/lib/consts";
import path from "path";
import m1 from "./scriptsPg/1.6.0";
import m2 from "./scriptsPg/1.7.0";
import m3 from "./scriptsPg/1.8.0";
import m4 from "./scriptsPg/1.9.0";
import m5 from "./scriptsPg/1.10.0";
import m6 from "./scriptsPg/1.10.2";
import m7 from "./scriptsPg/1.11.0";
import m8 from "./scriptsPg/1.11.1";
import m9 from "./scriptsPg/1.12.0";
import m10 from "./scriptsPg/1.13.0";
import m11 from "./scriptsPg/1.14.0";
import m12 from "./scriptsPg/1.15.0";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
// Define the migration list with versions and their corresponding functions
const migrations = [
{ version: "1.6.0", run: m1 },
{ version: "1.7.0", run: m2 },
{ version: "1.8.0", run: m3 },
{ version: "1.9.0", run: m4 },
{ version: "1.10.0", run: m5 },
{ version: "1.10.2", run: m6 },
{ version: "1.11.0", run: m7 },
{ version: "1.11.1", run: m8 },
{ version: "1.12.0", run: m9 },
{ version: "1.13.0", run: m10 },
{ version: "1.14.0", run: m11 },
{ version: "1.15.0", run: m12 }
// Add new migrations here as they are created
] as {
version: string;
run: () => Promise<void>;
}[];
await run();
async function run() {
// run the migrations
await runMigrations();
}
export async function runMigrations() {
if (process.env.DISABLE_MIGRATIONS) {
console.log("Migrations are disabled. Skipping...");
return;
}
try {
const appVersion = APP_VERSION;
// determine if the migrations table exists
const exists = await db
.select()
.from(versionMigrations)
.limit(1)
.execute()
.then((res) => res.length > 0)
.catch(() => false);
if (exists) {
console.log("Migrations table exists, running scripts...");
await executeScripts();
} else {
console.log("Migrations table does not exist, creating it...");
console.log("Running migrations...");
try {
await migrate(db, {
migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build
});
console.log("Migrations completed successfully.");
} catch (error) {
console.error("Error running migrations:", error);
}
await db
.insert(versionMigrations)
.values({
version: appVersion,
executedAt: Date.now()
})
.execute();
}
} catch (e) {
console.error("Error running migrations:", e);
await new Promise((resolve) =>
setTimeout(resolve, 1000 * 60 * 60 * 24 * 1)
);
}
}
async function executeScripts() {
try {
// Get the last executed version from the database
const lastExecuted = await db.select().from(versionMigrations);
// Filter and sort migrations
const pendingMigrations = lastExecuted
.map((m) => m)
.sort((a, b) => semver.compare(b.version, a.version));
const startVersion = pendingMigrations[0]?.version ?? "0.0.0";
console.log(`Starting migrations from version ${startVersion}`);
const migrationsToRun = migrations.filter((migration) =>
semver.gt(migration.version, startVersion)
);
console.log(
"Migrations to run:",
migrationsToRun.map((m) => m.version).join(", ")
);
// Run migrations in order
for (const migration of migrationsToRun) {
console.log(`Running migration ${migration.version}`);
try {
await migration.run();
// Update version in database
await db
.insert(versionMigrations)
.values({
version: migration.version,
executedAt: Date.now()
})
.execute();
console.log(
`Successfully completed migration ${migration.version}`
);
} catch (e) {
if (
e instanceof Error &&
typeof (e as any).code === "string" &&
(e as any).code === "23505"
) {
console.error("Migration has already run! Skipping...");
continue; // or return, depending on context
}
console.error(
`Failed to run migration ${migration.version}:`,
e
);
throw e;
}
}
console.log("All migrations completed successfully");
} catch (error) {
console.error("Migration process failed:", error);
throw error;
}
}
+3 -1
View File
@@ -18,6 +18,7 @@ import m10 from "./scriptsPg/1.13.0";
import m11 from "./scriptsPg/1.14.0";
import m12 from "./scriptsPg/1.15.0";
import m13 from "./scriptsPg/1.15.3";
import m14 from "./scriptsPg/1.15.4";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -36,7 +37,8 @@ const migrations = [
{ version: "1.13.0", run: m10 },
{ version: "1.14.0", run: m11 },
{ version: "1.15.0", run: m12 },
{ version: "1.15.3", run: m13 }
{ version: "1.15.3", run: m13 },
{ version: "1.15.4", run: m14 }
// Add new migrations here as they are created
] as {
version: string;
+3 -1
View File
@@ -36,6 +36,7 @@ import m31 from "./scriptsSqlite/1.13.0";
import m32 from "./scriptsSqlite/1.14.0";
import m33 from "./scriptsSqlite/1.15.0";
import m34 from "./scriptsSqlite/1.15.3";
import m35 from "./scriptsSqlite/1.15.4";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -70,7 +71,8 @@ const migrations = [
{ version: "1.13.0", run: m31 },
{ version: "1.14.0", run: m32 },
{ version: "1.15.0", run: m33 },
{ version: "1.15.3", run: m34 }
{ version: "1.15.3", run: m34 },
{ version: "1.15.4", run: m35 }
// Add new migrations here as they are created
] as const;
+27
View File
@@ -0,0 +1,27 @@
import { db } from "@server/db/pg/driver";
import { sql } from "drizzle-orm";
import { __DIRNAME } from "@server/lib/consts";
const version = "1.15.4";
export default async function migration() {
console.log(`Running setup script ${version}...`);
try {
await db.execute(sql`BEGIN`);
await db.execute(
sql`ALTER TABLE "resources" ADD COLUMN "postAuthPath" text;`
);
await db.execute(sql`COMMIT`);
console.log("Migrated database");
} catch (e) {
await db.execute(sql`ROLLBACK`);
console.log("Unable to migrate database");
console.log(e);
throw e;
}
console.log(`${version} migration complete`);
}
+27
View File
@@ -0,0 +1,27 @@
import { __DIRNAME, APP_PATH } from "@server/lib/consts";
import Database from "better-sqlite3";
import path from "path";
const version = "1.15.4";
export default async function migration() {
console.log(`Running setup script ${version}...`);
const location = path.join(APP_PATH, "db", "db.sqlite");
const db = new Database(location);
try {
db.transaction(() => {
db.prepare(
`ALTER TABLE 'resources' ADD 'postAuthPath' text;`
).run();
})();
console.log(`Migrated database`);
} catch (e) {
console.log("Failed to migrate db:", e);
throw e;
}
console.log(`${version} migration complete`);
}
@@ -26,6 +26,7 @@ import type {
import { CheckOrgUserAccessResponse } from "@server/routers/org";
import OrgPolicyRequired from "@app/components/OrgPolicyRequired";
import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed";
import { normalizePostAuthPath } from "@server/lib/normalizePostAuthPath";
export const dynamic = "force-dynamic";
@@ -108,6 +109,11 @@ export default async function ResourceAuthPage(props: {
} catch (e) {}
}
const normalizedPostAuthPath = normalizePostAuthPath(authInfo.postAuthPath);
if (normalizedPostAuthPath) {
redirectUrl = new URL(authInfo.url).origin + normalizedPostAuthPath;
}
const hasAuth =
authInfo.password ||
authInfo.pincode ||
@@ -303,7 +303,7 @@ export default function CreateInternalResourceDialog({
const [udpCustomPorts, setUdpCustomPorts] = useState<string>("");
const availableSites = sites.filter(
(site) => site.type === "newt" && site.subnet
(site) => site.type === "newt"
);
const form = useForm<FormData>({
@@ -397,7 +397,7 @@ export default function EditInternalResourceDialog({
);
const availableSites = sites.filter(
(site) => site.type === "newt" && site.subnet
(site) => site.type === "newt"
);
const form = useForm<FormData>({
+1 -1
View File
@@ -37,7 +37,7 @@ export const MachineClientsBanner = ({ orgId }: MachineClientsBannerProps) => {
</Button>
</Link>
<Link
href="https://docs.pangolin.net/manage/clients/install-client#docker"
href="https://docs.pangolin.net/manage/clients/install-client#docker-pangolin-cli"
target="_blank"
rel="noopener noreferrer"
>
+2 -2
View File
@@ -18,11 +18,11 @@ export type CommandItem = string | { title: string; command: string };
const PLATFORMS = [
"unix",
"windows",
"docker",
"kubernetes",
"podman",
"nixos"
"nixos",
"windows"
] as const;
type Platform = (typeof PLATFORMS)[number];
+21 -20
View File
@@ -14,7 +14,7 @@ import { Button } from "./ui/button";
export type CommandItem = string | { title: string; command: string };
const PLATFORMS = ["unix", "windows", "docker"] as const;
const PLATFORMS = ["unix", "docker", "windows"] as const;
type Platform = (typeof PLATFORMS)[number];
@@ -43,7 +43,7 @@ export function OlmInstallCommands({
All: [
{
title: t("install"),
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | sudo bash`
},
{
title: t("run"),
@@ -51,24 +51,12 @@ export function OlmInstallCommands({
}
]
},
windows: {
x64: [
{
title: t("install"),
command: `curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"`
},
{
title: t("run"),
command: `olm.exe --id ${id} --secret ${secret} --endpoint ${endpoint}`
}
]
},
docker: {
"Docker Compose": [
`services:
olm:
image: fosrl/olm
container_name: olm
pangolin-cli:
image: fosrl/pangolin-cli
container_name: pangolin-cli
restart: unless-stopped
network_mode: host
cap_add:
@@ -77,11 +65,24 @@ export function OlmInstallCommands({
- /dev/net/tun:/dev/net/tun
environment:
- PANGOLIN_ENDPOINT=${endpoint}
- OLM_ID=${id}
- OLM_SECRET=${secret}`
- CLIENT_ID=${id}
- CLIENT_SECRET=${secret}`
],
"Docker Run": [
`docker run -dit --network host --cap-add NET_ADMIN --device /dev/net/tun:/dev/net/tun fosrl/olm --id ${id} --secret ${secret} --endpoint ${endpoint}`
`docker run -dit --network host --cap-add NET_ADMIN --device /dev/net/tun:/dev/net/tun fosrl/pangolin-cli up client --id ${id} --secret ${secret} --endpoint ${endpoint} --attach`
]
},
windows: {
x64: [
{
title: t("install"),
command: `# Download and run the installer to install Olm first\n
curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"`
},
{
title: t("run"),
command: `olm.exe --id ${id} --secret ${secret} --endpoint ${endpoint}`
}
]
}
};