From 47743a5fa84887c9cac22a6957bc7f36b1465168 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 10:26:50 -0800 Subject: [PATCH 001/100] Fix private import -> dynamic --- server/routers/badger/verifySession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 828960d15..c446e0f7f 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -39,7 +39,7 @@ import { import { logRequestAudit } from "./logRequestAudit"; import cache from "@server/lib/cache"; import { APP_VERSION } from "@server/lib/consts"; -import { isSubscribed } from "#private/lib/isSubscribed"; +import { isSubscribed } from "#dynamic/lib/isSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; const verifyResourceSessionSchema = z.object({ From 040a9457741f7dd273f4cd53dde36b3e0d77b3a1 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 10:30:25 -0800 Subject: [PATCH 002/100] Fix dynamic -> private import violations --- server/private/routers/orgIdp/createOrgOidcIdp.ts | 2 +- server/private/routers/orgIdp/updateOrgOidcIdp.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/private/routers/orgIdp/createOrgOidcIdp.ts b/server/private/routers/orgIdp/createOrgOidcIdp.ts index 23fa4b7ce..d18740338 100644 --- a/server/private/routers/orgIdp/createOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/createOrgOidcIdp.ts @@ -25,7 +25,7 @@ import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl"; import { encrypt } from "@server/lib/crypto"; import config from "@server/lib/config"; import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types"; -import { isSubscribed } from "#dynamic/lib/isSubscribed"; +import { isSubscribed } from "#private/lib/isSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; const paramsSchema = z.strictObject({ orgId: z.string().nonempty() }); diff --git a/server/private/routers/orgIdp/updateOrgOidcIdp.ts b/server/private/routers/orgIdp/updateOrgOidcIdp.ts index d83ff569a..c56194606 100644 --- a/server/private/routers/orgIdp/updateOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/updateOrgOidcIdp.ts @@ -24,7 +24,7 @@ import { idp, idpOidcConfig } from "@server/db"; import { eq, and } from "drizzle-orm"; import { encrypt } from "@server/lib/crypto"; import config from "@server/lib/config"; -import { isSubscribed } from "#dynamic/lib/isSubscribed"; +import { isSubscribed } from "#private/lib/isSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; const paramsSchema = z From 6475dceab9e1b5971aa18579c3f1ef00db0345d4 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 12:38:18 -0800 Subject: [PATCH 003/100] Rename tiers in features and fix subscribed logic issue --- server/lib/billing/features.ts | 20 +++++++++---------- server/private/routers/billing/changeTier.ts | 12 +++++------ .../routers/billing/createCheckoutSession.ts | 12 +++++------ .../routers/billing/hooks/getSubType.ts | 12 +++++------ server/routers/idp/validateOidcCallback.ts | 2 +- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/server/lib/billing/features.ts b/server/lib/billing/features.ts index 501fba36f..3fec53b41 100644 --- a/server/lib/billing/features.ts +++ b/server/lib/billing/features.ts @@ -56,22 +56,22 @@ export function getFeatureIdByMetricId( export type FeaturePriceSet = Partial>; -export const homeLabFeaturePriceSet: FeaturePriceSet = { +export const tier1FeaturePriceSet: FeaturePriceSet = { [FeatureId.TIER1]: "price_1SzVE3D3Ee2Ir7Wm6wT5Dl3G" }; -export const homeLabFeaturePriceSetSandbox: FeaturePriceSet = { +export const tier1FeaturePriceSetSandbox: FeaturePriceSet = { [FeatureId.TIER1]: "price_1SxgpPDCpkOb237Bfo4rIsoT" }; -export function getHomeLabFeaturePriceSet(): FeaturePriceSet { +export function getTier1FeaturePriceSet(): FeaturePriceSet { if ( process.env.ENVIRONMENT == "prod" && process.env.SANDBOX_MODE !== "true" ) { - return homeLabFeaturePriceSet; + return tier1FeaturePriceSet; } else { - return homeLabFeaturePriceSetSandbox; + return tier1FeaturePriceSetSandbox; } } @@ -83,7 +83,7 @@ export const tier2FeaturePriceSetSandbox: FeaturePriceSet = { [FeatureId.USERS]: "price_1SxaEHDCpkOb237BD9lBkPiR" }; -export function getStarterFeaturePriceSet(): FeaturePriceSet { +export function getTier2FeaturePriceSet(): FeaturePriceSet { if ( process.env.ENVIRONMENT == "prod" && process.env.SANDBOX_MODE !== "true" @@ -102,7 +102,7 @@ export const tier3FeaturePriceSetSandbox: FeaturePriceSet = { [FeatureId.USERS]: "price_1SxaEODCpkOb237BiXdCBSfs" }; -export function getScaleFeaturePriceSet(): FeaturePriceSet { +export function getTier3FeaturePriceSet(): FeaturePriceSet { if ( process.env.ENVIRONMENT == "prod" && process.env.SANDBOX_MODE !== "true" @@ -116,9 +116,9 @@ export function getScaleFeaturePriceSet(): FeaturePriceSet { export function getFeatureIdByPriceId(priceId: string): FeatureId | undefined { // Check all feature price sets const allPriceSets = [ - getHomeLabFeaturePriceSet(), - getStarterFeaturePriceSet(), - getScaleFeaturePriceSet() + getTier1FeaturePriceSet(), + getTier2FeaturePriceSet(), + getTier3FeaturePriceSet() ]; for (const priceSet of allPriceSets) { diff --git a/server/private/routers/billing/changeTier.ts b/server/private/routers/billing/changeTier.ts index ee60c0ec7..3c9b8e437 100644 --- a/server/private/routers/billing/changeTier.ts +++ b/server/private/routers/billing/changeTier.ts @@ -22,9 +22,9 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import stripe from "#private/lib/stripe"; import { - getHomeLabFeaturePriceSet, - getScaleFeaturePriceSet, - getStarterFeaturePriceSet, + getTier1FeaturePriceSet, + getTier3FeaturePriceSet, + getTier2FeaturePriceSet, FeatureId, type FeaturePriceSet } from "@server/lib/billing"; @@ -113,11 +113,11 @@ export async function changeTier( // Get the target tier's price set let targetPriceSet: FeaturePriceSet; if (tier === "tier1") { - targetPriceSet = getHomeLabFeaturePriceSet(); + targetPriceSet = getTier1FeaturePriceSet(); } else if (tier === "tier2") { - targetPriceSet = getStarterFeaturePriceSet(); + targetPriceSet = getTier2FeaturePriceSet(); } else if (tier === "tier3") { - targetPriceSet = getScaleFeaturePriceSet(); + targetPriceSet = getTier3FeaturePriceSet(); } else { return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid tier")); } diff --git a/server/private/routers/billing/createCheckoutSession.ts b/server/private/routers/billing/createCheckoutSession.ts index e5bb95db2..b35c65329 100644 --- a/server/private/routers/billing/createCheckoutSession.ts +++ b/server/private/routers/billing/createCheckoutSession.ts @@ -23,9 +23,9 @@ import config from "@server/lib/config"; import { fromError } from "zod-validation-error"; import stripe from "#private/lib/stripe"; import { - getHomeLabFeaturePriceSet, - getScaleFeaturePriceSet, - getStarterFeaturePriceSet + getTier1FeaturePriceSet, + getTier3FeaturePriceSet, + getTier2FeaturePriceSet } from "@server/lib/billing"; import { getLineItems } from "@server/lib/billing/getLineItems"; import Stripe from "stripe"; @@ -88,11 +88,11 @@ export async function createCheckoutSession( let lineItems: Stripe.Checkout.SessionCreateParams.LineItem[]; if (tier === "tier1") { - lineItems = await getLineItems(getHomeLabFeaturePriceSet(), orgId); + lineItems = await getLineItems(getTier1FeaturePriceSet(), orgId); } else if (tier === "tier2") { - lineItems = await getLineItems(getStarterFeaturePriceSet(), orgId); + lineItems = await getLineItems(getTier2FeaturePriceSet(), orgId); } else if (tier === "tier3") { - lineItems = await getLineItems(getScaleFeaturePriceSet(), orgId); + lineItems = await getLineItems(getTier3FeaturePriceSet(), orgId); } else { return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid plan")); } diff --git a/server/private/routers/billing/hooks/getSubType.ts b/server/private/routers/billing/hooks/getSubType.ts index a38290ebd..44cfe0026 100644 --- a/server/private/routers/billing/hooks/getSubType.ts +++ b/server/private/routers/billing/hooks/getSubType.ts @@ -15,9 +15,9 @@ import { getLicensePriceSet, } from "@server/lib/billing/licenses"; import { - getHomeLabFeaturePriceSet, - getStarterFeaturePriceSet, - getScaleFeaturePriceSet, + getTier1FeaturePriceSet, + getTier2FeaturePriceSet, + getTier3FeaturePriceSet, } from "@server/lib/billing/features"; import Stripe from "stripe"; import { Tier } from "@server/types/Tiers"; @@ -40,19 +40,19 @@ export function getSubType(fullSubscription: Stripe.Response Date: Wed, 11 Feb 2026 12:41:22 -0800 Subject: [PATCH 004/100] Fix anouther subscribed logic issue --- server/routers/user/createOrgUser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/user/createOrgUser.ts b/server/routers/user/createOrgUser.ts index cac912f97..d0515e71e 100644 --- a/server/routers/user/createOrgUser.ts +++ b/server/routers/user/createOrgUser.ts @@ -132,7 +132,7 @@ export async function createOrgUser( orgId, tierMatrix.orgOidc ); - if (subscribed) { + if (!subscribed) { return next( createHttpError( HttpCode.FORBIDDEN, From eab275095313fd016d6eab9c5c937e98a468e70d Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 17:21:15 -0800 Subject: [PATCH 005/100] Add migrations for 1.15.3 --- server/setup/migrationsPg.ts | 4 ++- server/setup/migrationsSqlite.ts | 4 ++- server/setup/scriptsPg/1.15.3.ts | 39 ++++++++++++++++++++++++++++ server/setup/scriptsSqlite/1.15.3.ts | 28 ++++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 server/setup/scriptsPg/1.15.3.ts create mode 100644 server/setup/scriptsSqlite/1.15.3.ts diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index 7ae218364..c1ebfa09c 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -17,6 +17,7 @@ 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"; +import m13 from "./scriptsPg/1.15.3"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -34,7 +35,8 @@ const migrations = [ { version: "1.12.0", run: m9 }, { version: "1.13.0", run: m10 }, { version: "1.14.0", run: m11 }, - { version: "1.15.0", run: m12 } + { version: "1.15.0", run: m12 }, + { version: "1.15.3", run: m13 } // Add new migrations here as they are created ] as { version: string; diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 0bbc86ee3..170cf93d0 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -35,6 +35,7 @@ import m30 from "./scriptsSqlite/1.12.0"; 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"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -68,7 +69,8 @@ const migrations = [ { version: "1.12.0", run: m30 }, { version: "1.13.0", run: m31 }, { version: "1.14.0", run: m32 }, - { version: "1.15.0", run: m33 } + { version: "1.15.0", run: m33 }, + { version: "1.15.3", run: m34 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scriptsPg/1.15.3.ts b/server/setup/scriptsPg/1.15.3.ts new file mode 100644 index 000000000..80c5f67d3 --- /dev/null +++ b/server/setup/scriptsPg/1.15.3.ts @@ -0,0 +1,39 @@ +import { db } from "@server/db/pg/driver"; +import { sql } from "drizzle-orm"; +import { __DIRNAME } from "@server/lib/consts"; + +const version = "1.15.3"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + await db.execute(sql`BEGIN`); + + await db.execute( + sql`ALTER TABLE "limits" ADD COLUMN "override" boolean DEFAULT false;` + ); + await db.execute( + sql`ALTER TABLE "subscriptionItems" ADD COLUMN "stripeSubscriptionItemId" varchar(255);` + ); + await db.execute( + sql`ALTER TABLE "subscriptionItems" ADD COLUMN "featureId" varchar(255);` + ); + await db.execute( + sql`ALTER TABLE "subscriptions" ADD COLUMN "version" integer;` + ); + await db.execute( + sql`ALTER TABLE "subscriptions" ADD COLUMN "type" varchar(50);` + ); + + 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`); +} diff --git a/server/setup/scriptsSqlite/1.15.3.ts b/server/setup/scriptsSqlite/1.15.3.ts new file mode 100644 index 000000000..614b07598 --- /dev/null +++ b/server/setup/scriptsSqlite/1.15.3.ts @@ -0,0 +1,28 @@ +import { __DIRNAME, APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.15.3"; + +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 'limits' ADD 'override' integer DEFAULT false;`).run(); + db.prepare(`ALTER TABLE 'subscriptionItems' ADD 'featureId' text;`).run(); + db.prepare(`ALTER TABLE 'subscriptions' ADD 'version' integer;`).run(); + db.prepare(`ALTER TABLE 'subscriptions' ADD 'type' text;`).run(); + })(); + + console.log(`Migrated database`); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } + + console.log(`${version} migration complete`); +} From ba7239ac087af70fd554e1bc0d43b59e0a93cca9 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 17:30:21 -0800 Subject: [PATCH 006/100] Verify everything we can --- .github/workflows/cicd.yml | 189 +++++++------ .github/workflows/cicd.yml.backup | 426 ------------------------------ .github/workflows/saas.yml | 2 +- 3 files changed, 112 insertions(+), 505 deletions(-) delete mode 100644 .github/workflows/cicd.yml.backup diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 0e4d9bc6c..2ebb46639 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -1,4 +1,4 @@ -name: CI/CD Pipeline +name: Public 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. @@ -440,6 +440,10 @@ jobs: issuer="https://token.actions.githubusercontent.com" id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs) + # Track failures + FAILED_TAGS=() + SUCCESSFUL_TAGS=() + # Determine if this is an RC release IS_RC="false" if [[ "$TAG" == *"-rc."* ]]; then @@ -471,94 +475,123 @@ jobs: for BASE_IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do for IMAGE_TAG in "${IMAGE_TAGS[@]}"; do echo "Processing ${BASE_IMAGE}:${IMAGE_TAG}" + TAG_FAILED=false - DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')" - REF="${BASE_IMAGE}@${DIGEST}" - echo "Resolved digest: ${REF}" + # Wrap the entire tag processing in error handling + ( + set -e + DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')" + REF="${BASE_IMAGE}@${DIGEST}" + echo "Resolved digest: ${REF}" - echo "==> cosign sign (keyless) --recursive ${REF}" - cosign sign --recursive "${REF}" + echo "==> cosign sign (keyless) --recursive ${REF}" + cosign sign --recursive "${REF}" - echo "==> cosign sign (key) --recursive ${REF}" - cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}" + echo "==> cosign sign (key) --recursive ${REF}" + cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}" - # Retry wrapper for verification to handle registry propagation delays - retry_verify() { - local cmd="$1" - local attempts=6 - local delay=5 - local i=1 - until eval "$cmd"; do - if [ $i -ge $attempts ]; then - echo "Verification failed after $attempts attempts" - return 1 - fi - echo "Verification not yet available. Retry $i/$attempts after ${delay}s..." - sleep $delay - i=$((i+1)) - delay=$((delay*2)) - # Cap the delay to avoid very long waits - if [ $delay -gt 60 ]; then delay=60; fi - done - return 0 - } - - echo "==> cosign verify (public key) ${REF}" - if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${REF}' -o text"; then - VERIFIED_INDEX=true - else - VERIFIED_INDEX=false - fi - - echo "==> cosign verify (keyless policy) ${REF}" - if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then - VERIFIED_INDEX_KEYLESS=true - else - VERIFIED_INDEX_KEYLESS=false - fi - - # If index verification fails, attempt to verify child platform manifests - if [ "${VERIFIED_INDEX}" != "true" ] || [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then - echo "Index verification not available; attempting child manifest verification for ${BASE_IMAGE}:${IMAGE_TAG}" - CHILD_VERIFIED=false - - for ARCH in arm64 amd64; do - CHILD_TAG="${IMAGE_TAG}-${ARCH}" - echo "Resolving child digest for ${BASE_IMAGE}:${CHILD_TAG}" - CHILD_DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${CHILD_TAG} | jq -r '.Digest' || true)" - if [ -n "${CHILD_DIGEST}" ] && [ "${CHILD_DIGEST}" != "null" ]; then - CHILD_REF="${BASE_IMAGE}@${CHILD_DIGEST}" - echo "==> cosign verify (public key) child ${CHILD_REF}" - if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${CHILD_REF}' -o text"; then - CHILD_VERIFIED=true - echo "Public key verification succeeded for child ${CHILD_REF}" - else - echo "Public key verification failed for child ${CHILD_REF}" + # Retry wrapper for verification to handle registry propagation delays + retry_verify() { + local cmd="$1" + local attempts=6 + local delay=5 + local i=1 + until eval "$cmd"; do + if [ $i -ge $attempts ]; then + echo "Verification failed after $attempts attempts" + return 1 fi + echo "Verification not yet available. Retry $i/$attempts after ${delay}s..." + sleep $delay + i=$((i+1)) + delay=$((delay*2)) + # Cap the delay to avoid very long waits + if [ $delay -gt 60 ]; then delay=60; fi + done + return 0 + } - echo "==> cosign verify (keyless policy) child ${CHILD_REF}" - if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${CHILD_REF}' -o text"; then - CHILD_VERIFIED=true - echo "Keyless verification succeeded for child ${CHILD_REF}" - else - echo "Keyless verification failed for child ${CHILD_REF}" - fi - else - echo "No child digest found for ${BASE_IMAGE}:${CHILD_TAG}; skipping" - fi - done - - if [ "${CHILD_VERIFIED}" != "true" ]; then - echo "Failed to verify index and no child manifests verified for ${BASE_IMAGE}:${IMAGE_TAG}" - exit 10 + echo "==> cosign verify (public key) ${REF}" + if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${REF}' -o text"; then + VERIFIED_INDEX=true + else + VERIFIED_INDEX=false fi - fi - echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}" + echo "==> cosign verify (keyless policy) ${REF}" + if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then + VERIFIED_INDEX_KEYLESS=true + else + VERIFIED_INDEX_KEYLESS=false + fi + + # If index verification fails, attempt to verify child platform manifests + if [ "${VERIFIED_INDEX}" != "true" ] || [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then + echo "Index verification not available; attempting child manifest verification for ${BASE_IMAGE}:${IMAGE_TAG}" + CHILD_VERIFIED=false + + for ARCH in arm64 amd64; do + CHILD_TAG="${IMAGE_TAG}-${ARCH}" + echo "Resolving child digest for ${BASE_IMAGE}:${CHILD_TAG}" + CHILD_DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${CHILD_TAG} | jq -r '.Digest' || true)" + if [ -n "${CHILD_DIGEST}" ] && [ "${CHILD_DIGEST}" != "null" ]; then + CHILD_REF="${BASE_IMAGE}@${CHILD_DIGEST}" + echo "==> cosign verify (public key) child ${CHILD_REF}" + if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${CHILD_REF}' -o text"; then + CHILD_VERIFIED=true + echo "Public key verification succeeded for child ${CHILD_REF}" + else + echo "Public key verification failed for child ${CHILD_REF}" + fi + + echo "==> cosign verify (keyless policy) child ${CHILD_REF}" + if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${CHILD_REF}' -o text"; then + CHILD_VERIFIED=true + echo "Keyless verification succeeded for child ${CHILD_REF}" + else + echo "Keyless verification failed for child ${CHILD_REF}" + fi + else + echo "No child digest found for ${BASE_IMAGE}:${CHILD_TAG}; skipping" + fi + done + + if [ "${CHILD_VERIFIED}" != "true" ]; then + echo "Failed to verify index and no child manifests verified for ${BASE_IMAGE}:${IMAGE_TAG}" + exit 1 + fi + fi + ) || TAG_FAILED=true + + if [ "$TAG_FAILED" = "true" ]; then + echo "⚠️ WARNING: Failed to sign/verify ${BASE_IMAGE}:${IMAGE_TAG}" + FAILED_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}") + else + echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}" + SUCCESSFUL_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}") + fi done done - echo "All images signed and verified successfully!" + # Report summary + echo "" + echo "==========================================" + echo "Sign and Verify Summary" + echo "==========================================" + echo "Successful: ${#SUCCESSFUL_TAGS[@]}" + echo "Failed: ${#FAILED_TAGS[@]}" + echo "" + + if [ ${#FAILED_TAGS[@]} -gt 0 ]; then + echo "Failed tags:" + for tag in "${FAILED_TAGS[@]}"; do + echo " - $tag" + done + echo "" + echo "⚠️ WARNING: Some tags failed to sign/verify, but continuing anyway" + else + echo "✓ All images signed and verified successfully!" + fi shell: bash post-run: diff --git a/.github/workflows/cicd.yml.backup b/.github/workflows/cicd.yml.backup deleted file mode 100644 index 09e406ad6..000000000 --- a/.github/workflows/cicd.yml.backup +++ /dev/null @@ -1,426 +0,0 @@ -name: CI/CD 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. - -permissions: - contents: read - packages: write # for GHCR push - id-token: write # for Cosign Keyless (OIDC) Signing - -# Required secrets: -# - DOCKER_HUB_USERNAME / DOCKER_HUB_ACCESS_TOKEN: push to Docker Hub -# - GITHUB_TOKEN: used for GHCR login and OIDC keyless signing -# - COSIGN_PRIVATE_KEY / COSIGN_PASSWORD / COSIGN_PUBLIC_KEY: for key-based signing - -on: - push: - tags: - - "[0-9]+.[0-9]+.[0-9]+" - - "[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+" - -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - -jobs: - pre-run: - runs-on: ubuntu-latest - permissions: write-all - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} - role-duration-seconds: 3600 - aws-region: ${{ secrets.AWS_REGION }} - - - name: Verify AWS identity - run: aws sts get-caller-identity - - - name: Start EC2 instances - run: | - aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }} - aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }} - echo "EC2 instances started" - - - release-arm: - name: Build and Release (ARM64) - runs-on: [self-hosted, linux, arm64, us-east-1] - needs: [pre-run] - if: >- - ${{ - needs.pre-run.result == 'success' - }} - # Job-level timeout to avoid runaway or stuck runs - timeout-minutes: 120 - env: - # Target images - DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }} - GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }} - - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Monitor storage space - run: | - THRESHOLD=75 - USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g') - echo "Used space: $USED_SPACE%" - if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then - echo "Used space is below the threshold of 75% free. Running Docker system prune." - echo y | docker system prune -a - else - echo "Storage space is above the threshold. No action needed." - fi - - - name: Log in to Docker Hub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - registry: docker.io - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Extract tag name - id: get-tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - shell: bash - - - name: Update version in package.json - run: | - TAG=${{ env.TAG }} - sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts - cat server/lib/consts.ts - shell: bash - - - name: Check if release candidate - id: check-rc - run: | - TAG=${{ env.TAG }} - if [[ "$TAG" == *"-rc."* ]]; then - echo "IS_RC=true" >> $GITHUB_ENV - else - echo "IS_RC=false" >> $GITHUB_ENV - fi - shell: bash - - - name: Build and push Docker images (Docker Hub - ARM64) - run: | - TAG=${{ env.TAG }} - if [ "$IS_RC" = "true" ]; then - make build-rc-arm tag=$TAG - else - make build-release-arm tag=$TAG - fi - echo "Built & pushed ARM64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}" - shell: bash - - release-amd: - name: Build and Release (AMD64) - runs-on: [self-hosted, linux, x64, us-east-1] - needs: [pre-run] - if: >- - ${{ - needs.pre-run.result == 'success' - }} - # Job-level timeout to avoid runaway or stuck runs - timeout-minutes: 120 - env: - # Target images - DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }} - GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }} - - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Monitor storage space - run: | - THRESHOLD=75 - USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g') - echo "Used space: $USED_SPACE%" - if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then - echo "Used space is below the threshold of 75% free. Running Docker system prune." - echo y | docker system prune -a - else - echo "Storage space is above the threshold. No action needed." - fi - - - name: Log in to Docker Hub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - registry: docker.io - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Extract tag name - id: get-tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - shell: bash - - - name: Update version in package.json - run: | - TAG=${{ env.TAG }} - sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts - cat server/lib/consts.ts - shell: bash - - - name: Check if release candidate - id: check-rc - run: | - TAG=${{ env.TAG }} - if [[ "$TAG" == *"-rc."* ]]; then - echo "IS_RC=true" >> $GITHUB_ENV - else - echo "IS_RC=false" >> $GITHUB_ENV - fi - shell: bash - - - name: Build and push Docker images (Docker Hub - AMD64) - run: | - TAG=${{ env.TAG }} - if [ "$IS_RC" = "true" ]; then - make build-rc-amd tag=$TAG - else - make build-release-amd tag=$TAG - fi - echo "Built & pushed AMD64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}" - shell: bash - - create-manifest: - name: Create Multi-Arch Manifests - runs-on: [self-hosted, linux, x64, us-east-1] - needs: [release-arm, release-amd] - if: >- - ${{ - needs.release-arm.result == 'success' && - needs.release-amd.result == 'success' - }} - timeout-minutes: 30 - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Log in to Docker Hub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - registry: docker.io - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Extract tag name - id: get-tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - shell: bash - - - name: Check if release candidate - id: check-rc - run: | - TAG=${{ env.TAG }} - if [[ "$TAG" == *"-rc."* ]]; then - echo "IS_RC=true" >> $GITHUB_ENV - else - echo "IS_RC=false" >> $GITHUB_ENV - fi - shell: bash - - - name: Create multi-arch manifests - run: | - TAG=${{ env.TAG }} - if [ "$IS_RC" = "true" ]; then - make create-manifests-rc tag=$TAG - else - make create-manifests tag=$TAG - fi - echo "Created multi-arch manifests for tag: ${TAG}" - shell: bash - - sign-and-package: - name: Sign and Package - runs-on: [self-hosted, linux, x64, us-east-1] - needs: [release-arm, release-amd, create-manifest] - if: >- - ${{ - needs.release-arm.result == 'success' && - needs.release-amd.result == 'success' && - needs.create-manifest.result == 'success' - }} - # Job-level timeout to avoid runaway or stuck runs - timeout-minutes: 120 - env: - # Target images - DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }} - GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }} - - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Extract tag name - id: get-tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - shell: bash - - - name: Install Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 - with: - go-version: 1.24 - - - name: Update version in package.json - run: | - TAG=${{ env.TAG }} - sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts - cat server/lib/consts.ts - shell: bash - - - name: Pull latest Gerbil version - id: get-gerbil-tag - run: | - LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name') - echo "LATEST_GERBIL_TAG=$LATEST_TAG" >> $GITHUB_ENV - shell: bash - - - name: Pull latest Badger version - id: get-badger-tag - run: | - LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name') - echo "LATEST_BADGER_TAG=$LATEST_TAG" >> $GITHUB_ENV - shell: bash - - - name: Update install/main.go - run: | - PANGOLIN_VERSION=${{ env.TAG }} - GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }} - BADGER_VERSION=${{ env.LATEST_BADGER_TAG }} - sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$PANGOLIN_VERSION\"/" install/main.go - sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$GERBIL_VERSION\"/" install/main.go - sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$BADGER_VERSION\"/" install/main.go - echo "Updated install/main.go with Pangolin version $PANGOLIN_VERSION, Gerbil version $GERBIL_VERSION, and Badger version $BADGER_VERSION" - cat install/main.go - shell: bash - - - name: Build installer - working-directory: install - run: | - make go-build-release - - - name: Upload artifacts from /install/bin - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: install-bin - path: install/bin/ - - - name: Install skopeo + jq - # skopeo: copy/inspect images between registries - # jq: JSON parsing tool used to extract digest values - run: | - sudo apt-get update -y - sudo apt-get install -y skopeo jq - skopeo --version - shell: bash - - - name: Login to GHCR - env: - REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json - run: | - mkdir -p "$(dirname "$REGISTRY_AUTH_FILE")" - skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}" - shell: bash - - - name: Copy tag from Docker Hub to GHCR - # Mirror the already-built image (all architectures) to GHCR so we can sign it - # Wait a bit for both architectures to be available in Docker Hub manifest - env: - REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json - run: | - set -euo pipefail - TAG=${{ env.TAG }} - echo "Waiting for multi-arch manifest to be ready..." - sleep 30 - echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}" - skopeo copy --all --retry-times 3 \ - docker://$DOCKERHUB_IMAGE:$TAG \ - docker://$GHCR_IMAGE:$TAG - shell: bash - - - name: Login to GitHub Container Registry (for cosign) - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Install cosign - # cosign is used to sign and verify container images (key and keyless) - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - - - name: Dual-sign and verify (GHCR & Docker Hub) - # Sign each image by digest using keyless (OIDC) and key-based signing, - # then verify both the public key signature and the keyless OIDC signature. - env: - TAG: ${{ env.TAG }} - COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} - COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} - COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }} - COSIGN_YES: "true" - run: | - set -euo pipefail - - issuer="https://token.actions.githubusercontent.com" - id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs) - - for IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do - echo "Processing ${IMAGE}:${TAG}" - - DIGEST="$(skopeo inspect --retry-times 3 docker://${IMAGE}:${TAG} | jq -r '.Digest')" - REF="${IMAGE}@${DIGEST}" - echo "Resolved digest: ${REF}" - - echo "==> cosign sign (keyless) --recursive ${REF}" - cosign sign --recursive "${REF}" - - echo "==> cosign sign (key) --recursive ${REF}" - cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}" - - echo "==> cosign verify (public key) ${REF}" - cosign verify --key env://COSIGN_PUBLIC_KEY "${REF}" -o text - - echo "==> cosign verify (keyless policy) ${REF}" - cosign verify \ - --certificate-oidc-issuer "${issuer}" \ - --certificate-identity-regexp "${id_regex}" \ - "${REF}" -o text - done - shell: bash - - post-run: - needs: [pre-run, release-arm, release-amd, create-manifest, sign-and-package] - if: >- - ${{ - always() && - needs.pre-run.result == 'success' && - (needs.release-arm.result == 'success' || needs.release-arm.result == 'skipped' || needs.release-arm.result == 'failure') && - (needs.release-amd.result == 'success' || needs.release-amd.result == 'skipped' || needs.release-amd.result == 'failure') && - (needs.create-manifest.result == 'success' || needs.create-manifest.result == 'skipped' || needs.create-manifest.result == 'failure') && - (needs.sign-and-package.result == 'success' || needs.sign-and-package.result == 'skipped' || needs.sign-and-package.result == 'failure') - }} - runs-on: ubuntu-latest - permissions: write-all - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} - role-duration-seconds: 3600 - aws-region: ${{ secrets.AWS_REGION }} - - - name: Verify AWS identity - run: aws sts get-caller-identity - - - name: Stop EC2 instances - run: | - aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }} - aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }} - echo "EC2 instances stopped" diff --git a/.github/workflows/saas.yml b/.github/workflows/saas.yml index 4ceabfea0..5db7aa2f4 100644 --- a/.github/workflows/saas.yml +++ b/.github/workflows/saas.yml @@ -1,4 +1,4 @@ -name: CI/CD Pipeline +name: SAAS 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. From 937f6fdae82292e41b85bc188a713ce14bf307ce Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 17:56:58 -0800 Subject: [PATCH 007/100] Add better error message --- server/routers/client/createClient.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/routers/client/createClient.ts b/server/routers/client/createClient.ts index 78764eaca..4eafb0616 100644 --- a/server/routers/client/createClient.ts +++ b/server/routers/client/createClient.ts @@ -26,6 +26,7 @@ import { generateId } from "@server/auth/sessions/app"; import { OpenAPITags, registry } from "@server/openApi"; import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; import { getUniqueClientName } from "@server/db/names"; +import { build } from "@server/build"; const createClientParamsSchema = z.strictObject({ orgId: z.string() @@ -195,6 +196,12 @@ export async function createClient( const randomExitNode = exitNodesList[Math.floor(Math.random() * exitNodesList.length)]; + if (!randomExitNode) { + return next( + createHttpError(HttpCode.NOT_FOUND, `No exit nodes available. ${build == "saas" ? "Please contact support." : "You need to install gerbil to use the clients."}`) + ); + } + const [adminRole] = await trx .select() .from(roles) From 143acbae48b871da7a93ef7e3093d3fffdeb0ebc Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 11 Feb 2026 18:04:56 -0800 Subject: [PATCH 008/100] add identity provider mode setting --- server/db/sqlite/schema/privateSchema.ts | 1 + server/private/lib/config.ts | 11 +- server/private/lib/readConfigFile.ts | 29 +- .../routers/orgIdp/createOrgOidcIdp.ts | 13 + server/private/routers/orgIdp/deleteOrgIdp.ts | 13 + .../routers/orgIdp/updateOrgOidcIdp.ts | 13 + .../proxy/[niceId]/authentication/page.tsx | 2 +- src/app/auth/login/page.tsx | 11 +- src/app/auth/org/[orgId]/page.tsx | 2 +- src/app/auth/org/page.tsx | 2 +- src/app/auth/resource/[resourceGuid]/page.tsx | 2 +- src/app/navigation.tsx | 7 +- src/components/AuthPageBrandingForm.tsx | 2 +- src/components/PermissionsSelectBox.tsx | 2 +- src/components/SignupForm.tsx | 756 +++++++++--------- src/lib/pullEnv.ts | 8 +- src/lib/types/env.ts | 2 +- 17 files changed, 481 insertions(+), 395 deletions(-) diff --git a/server/db/sqlite/schema/privateSchema.ts b/server/db/sqlite/schema/privateSchema.ts index 265cee10d..40f6d7134 100644 --- a/server/db/sqlite/schema/privateSchema.ts +++ b/server/db/sqlite/schema/privateSchema.ts @@ -79,6 +79,7 @@ export const subscriptionItems = sqliteTable("subscriptionItems", { subscriptionItemId: integer("subscriptionItemId").primaryKey({ autoIncrement: true }), + stripeSubscriptionItemId: text("stripeSubscriptionItemId"), subscriptionId: text("subscriptionId") .notNull() .references(() => subscriptions.subscriptionId, { diff --git a/server/private/lib/config.ts b/server/private/lib/config.ts index a1baf5a62..2c3490baf 100644 --- a/server/private/lib/config.ts +++ b/server/private/lib/config.ts @@ -65,6 +65,11 @@ export class PrivateConfig { this.rawPrivateConfig.branding?.logo?.dark_path || undefined; } + if (this.rawPrivateConfig.app.identity_provider_mode) { + process.env.IDENTITY_PROVIDER_MODE = + this.rawPrivateConfig.app.identity_provider_mode; + } + process.env.BRANDING_LOGO_AUTH_WIDTH = this.rawPrivateConfig.branding ?.logo?.auth_page?.width ? this.rawPrivateConfig.branding?.logo?.auth_page?.width.toString() @@ -129,10 +134,8 @@ export class PrivateConfig { process.env.USE_PANGOLIN_DNS = this.rawPrivateConfig.flags.use_pangolin_dns.toString(); } - if (this.rawPrivateConfig.flags.use_org_only_idp) { - process.env.USE_ORG_ONLY_IDP = - this.rawPrivateConfig.flags.use_org_only_idp.toString(); - } + + console.log(this.rawPrivateConfig.app.identity_provider_mode); } public getRawPrivateConfig() { diff --git a/server/private/lib/readConfigFile.ts b/server/private/lib/readConfigFile.ts index ac528e736..e5efa4985 100644 --- a/server/private/lib/readConfigFile.ts +++ b/server/private/lib/readConfigFile.ts @@ -25,7 +25,8 @@ export const privateConfigSchema = z.object({ app: z .object({ region: z.string().optional().default("default"), - base_domain: z.string().optional() + base_domain: z.string().optional(), + identity_provider_mode: z.enum(["global", "org"]).optional() }) .optional() .default({ @@ -95,7 +96,7 @@ export const privateConfigSchema = z.object({ .object({ enable_redis: z.boolean().optional().default(false), use_pangolin_dns: z.boolean().optional().default(false), - use_org_only_idp: z.boolean().optional().default(false), + use_org_only_idp: z.boolean().optional() }) .optional() .prefault({}), @@ -181,7 +182,29 @@ export const privateConfigSchema = z.object({ // localFilePath: z.string().optional() }) .optional() -}); +}) + .transform((data) => { + // this to maintain backwards compatibility with the old config file + const identityProviderMode = data.app?.identity_provider_mode; + const useOrgOnlyIdp = data.flags?.use_org_only_idp; + + if (identityProviderMode !== undefined) { + return data; + } + if (useOrgOnlyIdp === true) { + return { + ...data, + app: { ...data.app, identity_provider_mode: "org" as const } + }; + } + if (useOrgOnlyIdp === false) { + return { + ...data, + app: { ...data.app, identity_provider_mode: "global" as const } + }; + } + return data; + }); export function readPrivateConfigFile() { if (build == "oss") { diff --git a/server/private/routers/orgIdp/createOrgOidcIdp.ts b/server/private/routers/orgIdp/createOrgOidcIdp.ts index d18740338..77346fd93 100644 --- a/server/private/routers/orgIdp/createOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/createOrgOidcIdp.ts @@ -27,6 +27,7 @@ import config from "@server/lib/config"; import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types"; import { isSubscribed } from "#private/lib/isSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import privateConfig from "#private/lib/config"; const paramsSchema = z.strictObject({ orgId: z.string().nonempty() }); @@ -92,6 +93,18 @@ export async function createOrgOidcIdp( ); } + if ( + privateConfig.getRawPrivateConfig().app.identity_provider_mode !== + "org" + ) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature." + ) + ); + } + const { clientId, clientSecret, diff --git a/server/private/routers/orgIdp/deleteOrgIdp.ts b/server/private/routers/orgIdp/deleteOrgIdp.ts index 176f4238c..2d6b0899b 100644 --- a/server/private/routers/orgIdp/deleteOrgIdp.ts +++ b/server/private/routers/orgIdp/deleteOrgIdp.ts @@ -22,6 +22,7 @@ import { fromError } from "zod-validation-error"; import { idp, idpOidcConfig, idpOrg } from "@server/db"; import { eq } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; +import privateConfig from "#private/lib/config"; const paramsSchema = z .object({ @@ -59,6 +60,18 @@ export async function deleteOrgIdp( const { idpId } = parsedParams.data; + if ( + privateConfig.getRawPrivateConfig().app.identity_provider_mode !== + "org" + ) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature." + ) + ); + } + // Check if IDP exists const [existingIdp] = await db .select() diff --git a/server/private/routers/orgIdp/updateOrgOidcIdp.ts b/server/private/routers/orgIdp/updateOrgOidcIdp.ts index c56194606..804afbe63 100644 --- a/server/private/routers/orgIdp/updateOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/updateOrgOidcIdp.ts @@ -26,6 +26,7 @@ import { encrypt } from "@server/lib/crypto"; import config from "@server/lib/config"; import { isSubscribed } from "#private/lib/isSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import privateConfig from "#private/lib/config"; const paramsSchema = z .object({ @@ -97,6 +98,18 @@ export async function updateOrgOidcIdp( ); } + if ( + privateConfig.getRawPrivateConfig().app.identity_provider_mode !== + "org" + ) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature." + ) + ); + } + const { idpId, orgId } = parsedParams.data; const { clientId, diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx index 121e71961..b00ce1eeb 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx @@ -132,7 +132,7 @@ export default function ResourceAuthenticationPage() { const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery( orgQueries.identityProviders({ orgId: org.org.orgId, - useOrgOnlyIdp: env.flags.useOrgOnlyIdp + useOrgOnlyIdp: env.app.identityProviderMode === "org" }) ); diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 2ba4d7f81..bfb552df4 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -76,12 +76,13 @@ export default async function Page(props: { // Only use SmartLoginForm if NOT (OSS build OR org-only IdP enabled) const useSmartLogin = - build === "saas" || (build === "enterprise" && env.flags.useOrgOnlyIdp); + build === "saas" || + (build === "enterprise" && env.app.identityProviderMode === "org"); let loginIdps: LoginFormIDP[] = []; if (!useSmartLogin) { // Load IdPs for DashboardLoginForm (OSS or org-only IdP mode) - if (build === "oss" || !env.flags.useOrgOnlyIdp) { + if (build === "oss" || env.app.identityProviderMode !== "org") { const idpsRes = await cache( async () => await priv.get>("/idp") @@ -165,7 +166,8 @@ export default async function Page(props: { forceLogin={forceLogin} showOrgLogin={ !isInvite && - (build === "saas" || env.flags.useOrgOnlyIdp) + (build === "saas" || + env.app.identityProviderMode === "org") } searchParams={searchParams} defaultUser={defaultUser} @@ -188,7 +190,8 @@ export default async function Page(props: {

)} - {!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? ( + {!isInvite && + (build === "saas" || env.app.identityProviderMode === "org") ? ( diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index d74ef30b5..cb95099e6 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -124,7 +124,8 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [ // PaidFeaturesAlert ...((build === "oss" && !env?.flags.disableEnterpriseFeatures) || build === "saas" || - env?.flags.useOrgOnlyIdp + env?.app.identityProviderMode === "org" || + env?.app.identityProviderMode === undefined ? [ { title: "sidebarIdentityProviders", @@ -251,7 +252,9 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [ href: "/admin/api-keys", icon: }, - ...(build === "oss" || !env?.flags.useOrgOnlyIdp + ...(build === "oss" || + env?.app.identityProviderMode === "global" || + env?.app.identityProviderMode === undefined ? [ { title: "sidebarIdentityProviders", diff --git a/src/components/AuthPageBrandingForm.tsx b/src/components/AuthPageBrandingForm.tsx index daceb2faa..a19980627 100644 --- a/src/components/AuthPageBrandingForm.tsx +++ b/src/components/AuthPageBrandingForm.tsx @@ -322,7 +322,7 @@ export default function AuthPageBrandingForm({ {build === "saas" || - env.env.flags.useOrgOnlyIdp ? ( + env.env.app.identityProviderMode === "org" ? ( <>
diff --git a/src/components/PermissionsSelectBox.tsx b/src/components/PermissionsSelectBox.tsx index 73f8a2120..b11c635a6 100644 --- a/src/components/PermissionsSelectBox.tsx +++ b/src/components/PermissionsSelectBox.tsx @@ -118,7 +118,7 @@ function getActionsCategories(root: boolean) { } }; - if (root || build === "saas" || env.flags.useOrgOnlyIdp) { + if (root || build === "saas" || env.app.identityProviderMode === "org") { actionsByCategory["Identity Provider (IDP)"] = { [t("actionCreateIdp")]: "createIdp", [t("actionUpdateIdp")]: "updateIdp", diff --git a/src/components/SignupForm.tsx b/src/components/SignupForm.tsx index 9cb93757c..a54b1c238 100644 --- a/src/components/SignupForm.tsx +++ b/src/components/SignupForm.tsx @@ -204,7 +204,9 @@ export default function SignupForm({ ? env.branding.logo?.authPage?.height || 44 : 44; - const showOrgBanner = fromSmartLogin && (build === "saas" || env.flags.useOrgOnlyIdp); + const showOrgBanner = + fromSmartLogin && + (build === "saas" || env.app.identityProviderMode === "org"); const orgBannerHref = redirect ? `/auth/org?redirect=${encodeURIComponent(redirect)}` : "/auth/org"; @@ -226,388 +228,398 @@ export default function SignupForm({ )} - -
- -
-
-

{getSubtitle()}

-
-
- -
- - ( - - {t("email")} - - - - - - )} - /> - ( - -
- {t("password")} - {passwordStrength.strength === - "strong" && ( - - )} -
- -
+ +
+ +
+
+

{getSubtitle()}

+
+
+ + + + ( + + {t("email")} + { - field.onChange(e); - setPasswordValue( - e.target.value - ); - }} - className={cn( - passwordStrength.strength === - "strong" && - "border-green-500 focus-visible:ring-green-500", - passwordStrength.strength === - "medium" && - "border-yellow-500 focus-visible:ring-yellow-500", - passwordStrength.strength === - "weak" && - passwordValue.length > - 0 && - "border-red-500 focus-visible:ring-red-500" - )} - autoComplete="new-password" + disabled={!!emailParam} /> -
-
- - {passwordValue.length > 0 && ( -
- {/* Password Strength Meter */} -
-
- - {t("passwordStrength")} - - - {t( - `passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}` - )} - -
- -
- - {/* Requirements Checklist */} -
-
- {t("passwordRequirements")} -
-
-
- {passwordStrength - .requirements - .length ? ( - - ) : ( - - )} - - {t( - "passwordRequirementLengthText" - )} - -
-
- {passwordStrength - .requirements - .uppercase ? ( - - ) : ( - - )} - - {t( - "passwordRequirementUppercaseText" - )} - -
-
- {passwordStrength - .requirements - .lowercase ? ( - - ) : ( - - )} - - {t( - "passwordRequirementLowercaseText" - )} - -
-
- {passwordStrength - .requirements - .number ? ( - - ) : ( - - )} - - {t( - "passwordRequirementNumberText" - )} - -
-
- {passwordStrength - .requirements - .special ? ( - - ) : ( - - )} - - {t( - "passwordRequirementSpecialText" - )} - -
-
-
-
- )} - - {/* Only show FormMessage when not showing our custom requirements */} - {passwordValue.length === 0 && ( + - )} -
- )} - /> - ( - -
- - {t("confirmPassword")} - - {doPasswordsMatch && ( - - )} -
- -
- { - field.onChange(e); - setConfirmPasswordValue( - e.target.value - ); - }} - className={cn( - doPasswordsMatch && - "border-green-500 focus-visible:ring-green-500", - confirmPasswordValue.length > - 0 && - !doPasswordsMatch && - "border-red-500 focus-visible:ring-red-500" - )} - autoComplete="new-password" - /> + + )} + /> + ( + +
+ + {t("password")} + + {passwordStrength.strength === + "strong" && ( + + )}
- - {confirmPasswordValue.length > 0 && - !doPasswordsMatch && ( -

- {t("passwordsDoNotMatch")} -

- )} - {/* Only show FormMessage when field is empty */} - {confirmPasswordValue.length === 0 && ( - - )} -
- )} - /> - {build === "saas" && ( - <> - ( - - - { - field.onChange(checked); - handleTermsChange( - checked as boolean + +
+ { + field.onChange(e); + setPasswordValue( + e.target.value ); }} - /> - -
- -
- {t( - "signUpTerms.IAgreeToThe" - )}{" "} - - {t( - "signUpTerms.termsOfService" - )}{" "} - - {t("signUpTerms.and")}{" "} - - {t( - "signUpTerms.privacyPolicy" - )} - -
-
- -
- - )} - /> - ( - - - - -
- - {t( - "signUpMarketing.keepMeInTheLoop" + className={cn( + passwordStrength.strength === + "strong" && + "border-green-500 focus-visible:ring-green-500", + passwordStrength.strength === + "medium" && + "border-yellow-500 focus-visible:ring-yellow-500", + passwordStrength.strength === + "weak" && + passwordValue.length > + 0 && + "border-red-500 focus-visible:ring-red-500" )} - - + autoComplete="new-password" + />
-
- )} - /> - - )} + - {error && ( - - {error} - - )} + {passwordValue.length > 0 && ( +
+ {/* Password Strength Meter */} +
+
+ + {t( + "passwordStrength" + )} + + + {t( + `passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}` + )} + +
+ +
- - - - - + {/* Requirements Checklist */} +
+
+ {t( + "passwordRequirements" + )} +
+
+
+ {passwordStrength + .requirements + .length ? ( + + ) : ( + + )} + + {t( + "passwordRequirementLengthText" + )} + +
+
+ {passwordStrength + .requirements + .uppercase ? ( + + ) : ( + + )} + + {t( + "passwordRequirementUppercaseText" + )} + +
+
+ {passwordStrength + .requirements + .lowercase ? ( + + ) : ( + + )} + + {t( + "passwordRequirementLowercaseText" + )} + +
+
+ {passwordStrength + .requirements + .number ? ( + + ) : ( + + )} + + {t( + "passwordRequirementNumberText" + )} + +
+
+ {passwordStrength + .requirements + .special ? ( + + ) : ( + + )} + + {t( + "passwordRequirementSpecialText" + )} + +
+
+
+
+ )} + + {/* Only show FormMessage when not showing our custom requirements */} + {passwordValue.length === 0 && ( + + )} + + )} + /> + ( + +
+ + {t("confirmPassword")} + + {doPasswordsMatch && ( + + )} +
+ +
+ { + field.onChange(e); + setConfirmPasswordValue( + e.target.value + ); + }} + className={cn( + doPasswordsMatch && + "border-green-500 focus-visible:ring-green-500", + confirmPasswordValue.length > + 0 && + !doPasswordsMatch && + "border-red-500 focus-visible:ring-red-500" + )} + autoComplete="new-password" + /> +
+
+ {confirmPasswordValue.length > 0 && + !doPasswordsMatch && ( +

+ {t("passwordsDoNotMatch")} +

+ )} + {/* Only show FormMessage when field is empty */} + {confirmPasswordValue.length === 0 && ( + + )} +
+ )} + /> + {build === "saas" && ( + <> + ( + + + { + field.onChange( + checked + ); + handleTermsChange( + checked as boolean + ); + }} + /> + +
+ +
+ {t( + "signUpTerms.IAgreeToThe" + )}{" "} + + {t( + "signUpTerms.termsOfService" + )}{" "} + + {t( + "signUpTerms.and" + )}{" "} + + {t( + "signUpTerms.privacyPolicy" + )} + +
+
+ +
+
+ )} + /> + ( + + + + +
+ + {t( + "signUpMarketing.keepMeInTheLoop" + )} + + +
+
+ )} + /> + + )} + + {error && ( + + {error} + + )} + + + + + + ); } diff --git a/src/lib/pullEnv.ts b/src/lib/pullEnv.ts index 298745b9b..ddbd42c26 100644 --- a/src/lib/pullEnv.ts +++ b/src/lib/pullEnv.ts @@ -32,7 +32,11 @@ export function pullEnv(): Env { process.env.NEW_RELEASES_NOTIFICATION_ENABLED === "true" ? true : false - } + }, + identityProviderMode: process.env.IDENTITY_PROVIDER_MODE as + | "org" + | "global" + | undefined }, email: { emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false @@ -64,8 +68,6 @@ export function pullEnv(): Env { process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true" ? true : false, - useOrgOnlyIdp: - process.env.USE_ORG_ONLY_IDP === "true" ? true : false, disableEnterpriseFeatures: process.env.DISABLE_ENTERPRISE_FEATURES === "true" ? true diff --git a/src/lib/types/env.ts b/src/lib/types/env.ts index 925e4348d..46513ae52 100644 --- a/src/lib/types/env.ts +++ b/src/lib/types/env.ts @@ -8,6 +8,7 @@ export type Env = { product_updates: boolean; new_releases: boolean; }; + identityProviderMode?: "global" | "org"; }; server: { externalPort: string; @@ -34,7 +35,6 @@ export type Env = { hideSupporterKey: boolean; usePangolinDns: boolean; disableProductHelpBanners: boolean; - useOrgOnlyIdp: boolean; disableEnterpriseFeatures: boolean; }; branding: { From c8a4ac1ed4d54f60a72f9d3a41f915d0cf14a07f Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 11 Feb 2026 19:01:06 -0800 Subject: [PATCH 009/100] add global/org idp banner --- messages/en-US.json | 3 + .../[orgId]/settings/(private)/idp/page.tsx | 3 + src/app/admin/users/page.tsx | 2 +- src/app/navigation.tsx | 2 +- src/components/IdpGlobalModeBanner.tsx | 65 +++++++++++++++++++ 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/components/IdpGlobalModeBanner.tsx diff --git a/messages/en-US.json b/messages/en-US.json index 685c37cdf..8ec0a97cd 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Increase site count", "idpManage": "Manage Identity Providers", "idpManageDescription": "View and manage identity providers in the system", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Identity provider deleted successfully", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Are you sure you want to permanently delete the identity provider?", diff --git a/src/app/[orgId]/settings/(private)/idp/page.tsx b/src/app/[orgId]/settings/(private)/idp/page.tsx index 6d75c0a5a..cd0bc5566 100644 --- a/src/app/[orgId]/settings/(private)/idp/page.tsx +++ b/src/app/[orgId]/settings/(private)/idp/page.tsx @@ -5,6 +5,7 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import IdpTable, { IdpRow } from "@app/components/OrgIdpTable"; import { getTranslations } from "next-intl/server"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; +import { IdpGlobalModeBanner } from "@app/components/IdpGlobalModeBanner"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; type OrgIdpPageProps = { @@ -36,6 +37,8 @@ export default async function OrgIdpPage(props: OrgIdpPageProps) { description={t("idpManageDescription")} /> + + diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index c84a077fe..7368cb253 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -50,7 +50,7 @@ export default async function UsersPage(props: PageProps) { title={t("userTitle")} description={t("userDescription")} /> - + {t("userAbount")} diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index cb95099e6..7df4364a5 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -125,7 +125,7 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [ ...((build === "oss" && !env?.flags.disableEnterpriseFeatures) || build === "saas" || env?.app.identityProviderMode === "org" || - env?.app.identityProviderMode === undefined + (env?.app.identityProviderMode === undefined && build !== "oss") ? [ { title: "sidebarIdentityProviders", diff --git a/src/components/IdpGlobalModeBanner.tsx b/src/components/IdpGlobalModeBanner.tsx new file mode 100644 index 000000000..9f864b36d --- /dev/null +++ b/src/components/IdpGlobalModeBanner.tsx @@ -0,0 +1,65 @@ +"use client"; + +import Link from "next/link"; +import { useTranslations } from "next-intl"; +import { Alert, AlertDescription } from "@app/components/ui/alert"; +import { Info } from "lucide-react"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { usePaidStatus } from "@app/hooks/usePaidStatus"; +import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import { build } from "@server/build"; + +export function IdpGlobalModeBanner() { + const t = useTranslations(); + const { env } = useEnvContext(); + const { isPaidUser, hasEnterpriseLicense } = usePaidStatus(); + + const identityProviderModeUndefined = + env.app.identityProviderMode === undefined; + const paidUserForOrgOidc = isPaidUser(tierMatrix.orgOidc); + const enterpriseUnlicensed = + build === "enterprise" && !hasEnterpriseLicense; + + if (build === "saas") { + return null; + } + + if (!identityProviderModeUndefined) { + return null; + } + + const adminPanelLinkRenderer = (chunks: React.ReactNode) => ( + + {chunks} + + ); + + return ( + + + + {paidUserForOrgOidc + ? t.rich("idpGlobalModeBanner", { + adminPanelLink: adminPanelLinkRenderer, + configDocsLink: (chunks) => ( + + {chunks} + + ) + }) + : enterpriseUnlicensed + ? t.rich("idpGlobalModeBannerLicenseRequired", { + adminPanelLink: adminPanelLinkRenderer + }) + : t.rich("idpGlobalModeBannerUpgradeRequired", { + adminPanelLink: adminPanelLinkRenderer + })} + + + ); +} From ba06c8928d0f079752c102c895e35645caca1396 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 19:01:25 -0800 Subject: [PATCH 010/100] Dont restrict numbers outside of the cloud --- src/app/[orgId]/settings/general/security/page.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/app/[orgId]/settings/general/security/page.tsx b/src/app/[orgId]/settings/general/security/page.tsx index a7b5da039..2c51e9ecb 100644 --- a/src/app/[orgId]/settings/general/security/page.tsx +++ b/src/app/[orgId]/settings/general/security/page.tsx @@ -44,7 +44,6 @@ import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; import type { OrgContextType } from "@app/contexts/orgContext"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; -import { isAppPageRouteDefinition } from "next/dist/server/route-definitions/app-page-route-definition"; // Session length options in hours const SESSION_LENGTH_OPTIONS = [ @@ -219,6 +218,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) { {LOG_RETENTION_OPTIONS.filter( (option) => { + if (build != "saas") { + return true; + } + let maxDays: number; if (!subscriptionTier) { @@ -314,6 +317,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) { {LOG_RETENTION_OPTIONS.filter( (option) => { + if (build != "saas") { + return true; + } + let maxDays: number; if (!subscriptionTier) { @@ -411,6 +418,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) { {LOG_RETENTION_OPTIONS.filter( (option) => { + if (build != "saas") { + return true; + } + let maxDays: number; if (!subscriptionTier) { From 54b77523c55293e2e31b3a3953011144c0d36d72 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 11 Feb 2026 19:02:16 -0800 Subject: [PATCH 011/100] remove console.log --- server/private/lib/config.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/private/lib/config.ts b/server/private/lib/config.ts index 2c3490baf..8e635c93c 100644 --- a/server/private/lib/config.ts +++ b/server/private/lib/config.ts @@ -134,8 +134,6 @@ export class PrivateConfig { process.env.USE_PANGOLIN_DNS = this.rawPrivateConfig.flags.use_pangolin_dns.toString(); } - - console.log(this.rawPrivateConfig.app.identity_provider_mode); } public getRawPrivateConfig() { From a86cfa59344c9dbe3b5130580c4c66e2e0deaa44 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 19:02:56 -0800 Subject: [PATCH 012/100] Add missing col in migration --- server/setup/scriptsSqlite/1.15.3.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/setup/scriptsSqlite/1.15.3.ts b/server/setup/scriptsSqlite/1.15.3.ts index 614b07598..3ba8fb099 100644 --- a/server/setup/scriptsSqlite/1.15.3.ts +++ b/server/setup/scriptsSqlite/1.15.3.ts @@ -14,6 +14,7 @@ export default async function migration() { db.transaction(() => { db.prepare(`ALTER TABLE 'limits' ADD 'override' integer DEFAULT false;`).run(); db.prepare(`ALTER TABLE 'subscriptionItems' ADD 'featureId' text;`).run(); + db.prepare(`ALTER TABLE 'subscriptionItems' ADD 'stripeSubscriptionItemId' text;`).run(); db.prepare(`ALTER TABLE 'subscriptions' ADD 'version' integer;`).run(); db.prepare(`ALTER TABLE 'subscriptions' ADD 'type' text;`).run(); })(); From 2a0655e9de0559bcb3db3e9371801ce380de1e1c Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 19:08:11 -0800 Subject: [PATCH 013/100] Bump version --- server/lib/consts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/consts.ts b/server/lib/consts.ts index b6758c156..018e017e0 100644 --- a/server/lib/consts.ts +++ b/server/lib/consts.ts @@ -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.0"; +export const APP_VERSION = "1.15.3"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); From 0bfce87dc670fc202c912131ad4c4fce4221986d Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 19:25:00 -0800 Subject: [PATCH 014/100] Ignore migrations --- .dockerignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index ecd919cd5..df875b66e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -31,4 +31,5 @@ dist migrations/ config/ build.ts -tsconfig.json \ No newline at end of file +tsconfig.json +migrations/ From 40f49bf6da543a6673812f683f8c41ef2007763e Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 11 Feb 2026 19:46:12 -0800 Subject: [PATCH 015/100] add pangolin cloud link --- messages/en-US.json | 4 +- src/components/PaidFeaturesAlert.tsx | 60 +++++++++++++++++++++------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 8ec0a97cd..aa9cb2b09 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "End of following year", "actionLogsDescription": "View a history of actions performed in this organization", "accessLogsDescription": "View access auth requests for resources in this organization", - "licenseRequiredToUse": "An Enterprise license is required to use this feature.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Certificate Resolver", "certResolverDescription": "Select the certificate resolver to use for this resource.", "selectCertResolver": "Select Certificate Resolver", diff --git a/src/components/PaidFeaturesAlert.tsx b/src/components/PaidFeaturesAlert.tsx index f8a4223b1..f4bc528d1 100644 --- a/src/components/PaidFeaturesAlert.tsx +++ b/src/components/PaidFeaturesAlert.tsx @@ -38,15 +38,49 @@ const bannerContentClassName = "py-3 px-4"; const bannerRowClassName = "flex items-center gap-2.5 text-sm text-muted-foreground"; const bannerIconClassName = "size-4 shrink-0 text-purple-500"; +const docsLinkClassName = + "inline-flex items-center gap-1 font-medium text-purple-600 underline"; +const PANGOLIN_CLOUD_SIGNUP_URL = "https://app.pangolin.net/auth/signup/"; +const ENTERPRISE_DOCS_URL = + "https://docs.pangolin.net/self-host/enterprise-edition"; function getTierLinkRenderer(billingHref: string) { return function tierLinkRenderer(chunks: React.ReactNode) { + return ( + + {chunks} + + ); + }; +} + +function getPangolinCloudLinkRenderer() { + return function pangolinCloudLinkRenderer(chunks: React.ReactNode) { return ( {chunks} + + + ); + }; +} + +function getDocsLinkRenderer(href: string) { + return function docsLinkRenderer(chunks: React.ReactNode) { + return ( + + {chunks} + ); }; @@ -66,6 +100,8 @@ export function PaidFeaturesAlert({ tiers }: Props) { const requiredTierName = requiredTier ? t(TIER_TRANSLATION_KEYS[requiredTier]) : null; const billingHref = orgId ? `/${orgId}/settings/billing` : "https://pangolin.net/pricing"; const tierLinkRenderer = getTierLinkRenderer(billingHref); + const pangolinCloudLinkRenderer = getPangolinCloudLinkRenderer(); + const enterpriseDocsLinkRenderer = getDocsLinkRenderer(ENTERPRISE_DOCS_URL); if (env.flags.disableEnterpriseFeatures) { return null; @@ -103,7 +139,12 @@ export function PaidFeaturesAlert({ tiers }: Props) {
- {t("licenseRequiredToUse")} + + {t.rich("licenseRequiredToUse", { + enterpriseLicenseLink: enterpriseDocsLinkRenderer, + pangolinCloudLink: pangolinCloudLinkRenderer + })} +
@@ -116,17 +157,8 @@ export function PaidFeaturesAlert({ tiers }: Props) { {t.rich("ossEnterpriseEditionRequired", { - enterpriseEditionLink: (chunks) => ( - - {chunks} - - - ) + enterpriseEditionLink: enterpriseDocsLinkRenderer, + pangolinCloudLink: pangolinCloudLinkRenderer })}
From c0d25aeb0268d4ad4580042d6d1523733cecd126 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:02:51 -0800 Subject: [PATCH 016/100] New translations en-us.json (French) --- messages/fr-FR.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index f7914e65e..1bc3a760c 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Augmenter le nombre de sites", "idpManage": "Gérer les fournisseurs d'identité", "idpManageDescription": "Voir et gérer les fournisseurs d'identité dans le système", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Fournisseur d'identité supprimé avec succès", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Êtes-vous sûr de vouloir supprimer définitivement le fournisseur d'identité?", From a4cbfc74e4de2f0373c4d5878a1f138066e6a08c Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:02:53 -0800 Subject: [PATCH 017/100] New translations en-us.json (Spanish) --- messages/es-ES.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/es-ES.json b/messages/es-ES.json index 55148ff0e..1e34a524d 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Aumentar el número de sitios", "idpManage": "Administrar proveedores de identidad", "idpManageDescription": "Ver y administrar proveedores de identidad en el sistema", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Proveedor de identidad eliminado correctamente", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "¿Está seguro que desea eliminar permanentemente el proveedor de identidad?", From 1edc33148a1fdaa26f9a73c933247350841d2f51 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:02:54 -0800 Subject: [PATCH 018/100] New translations en-us.json (Bulgarian) --- messages/bg-BG.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index a2568ee47..118900457 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Увеличаване на броя на сайтовете", "idpManage": "Управление на доставчици на идентичност", "idpManageDescription": "Прегледайте и управлявайте доставчици на идентичност в системата", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Доставчик на идентичност успешно изтрит", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Сигурни ли сте, че искате да изтриете доставчика за идентичност?", From 53e14c2ad74eaa46f0591dd2ce42ac6b30dc1943 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:02:56 -0800 Subject: [PATCH 019/100] New translations en-us.json (Czech) --- messages/cs-CZ.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 0b29fe475..3fe3eff76 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Zvýšit počet stránek", "idpManage": "Spravovat poskytovatele identity", "idpManageDescription": "Zobrazit a spravovat poskytovatele identity v systému", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Poskytovatel identity byl úspěšně odstraněn", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Jste si jisti, že chcete trvale odstranit poskytovatele identity?", From 69475a0ae70a442d701cf03320d88e04aaac2a57 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:02:57 -0800 Subject: [PATCH 020/100] New translations en-us.json (German) --- messages/de-DE.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/de-DE.json b/messages/de-DE.json index b1b59e65e..0dc9e1905 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Anzahl der Standorte erhöhen", "idpManage": "Identitätsanbieter verwalten", "idpManageDescription": "Identitätsanbieter im System anzeigen und verwalten", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Identitätsanbieter erfolgreich gelöscht", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Sind Sie sicher, dass Sie den Identitätsanbieter dauerhaft löschen möchten?", From e763e001e5639b4608fd10ff2410c23d6fe46ff1 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:02:58 -0800 Subject: [PATCH 021/100] New translations en-us.json (Italian) --- messages/it-IT.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/it-IT.json b/messages/it-IT.json index 8f9d0352b..a50b37661 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Aumenta conteggio siti", "idpManage": "Gestisci Provider di Identità", "idpManageDescription": "Visualizza e gestisci i provider di identità nel sistema", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Provider di identità eliminato con successo", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Sei sicuro di voler eliminare definitivamente il provider di identità?", From 75a4362ce3992d769013225dfbe084cd9996119e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:03:00 -0800 Subject: [PATCH 022/100] New translations en-us.json (Korean) --- messages/ko-KR.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 9d2ccad61..cdefc3c20 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "사이트 수 증가", "idpManage": "아이덴티티 공급자 관리", "idpManageDescription": "시스템에서 ID 제공자를 보고 관리합니다", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "신원 공급자가 성공적으로 삭제되었습니다", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "아이덴티티 공급자를 영구적으로 삭제하시겠습니까?", From a841f588dd6a7fb9fd61b43dcf6a7db9043e608c Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:03:02 -0800 Subject: [PATCH 023/100] New translations en-us.json (Dutch) --- messages/nl-NL.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 76c08e810..20de20c78 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Toename van site vergroten", "idpManage": "Identiteitsaanbieders beheren", "idpManageDescription": "Identiteitsaanbieders in het systeem bekijken en beheren", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Identity provider succesvol verwijderd", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Weet u zeker dat u de identiteitsprovider permanent wilt verwijderen?", From bd8da25a462cfc0d3813b2cf99c0f141f135a10b Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:03:03 -0800 Subject: [PATCH 024/100] New translations en-us.json (Polish) --- messages/pl-PL.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index d2cf4ac87..4e61241db 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Zwiększ liczbę witryn", "idpManage": "Zarządzaj dostawcami tożsamości", "idpManageDescription": "Wyświetl i zarządzaj dostawcami tożsamości w systemie", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Dostawca tożsamości został pomyślnie usunięty", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Czy na pewno chcesz trwale usunąć dostawcę tożsamości?", From 0a8565f5e85f39411665adaed0fe53465fdf180a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:03:04 -0800 Subject: [PATCH 025/100] New translations en-us.json (Portuguese) --- messages/pt-PT.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 6dd068f2c..15a751b6a 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Aumentar contagem de sites", "idpManage": "Gerir Provedores de Identidade", "idpManageDescription": "Visualizar e gerir provedores de identidade no sistema", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Provedor de identidade eliminado com sucesso", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Tem certeza que deseja eliminar permanentemente o provedor de identidade?", From 84f5d6137a58dbee1fd02d0cae4c259be03110af Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:03:06 -0800 Subject: [PATCH 026/100] New translations en-us.json (Russian) --- messages/ru-RU.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index b54bcc948..e36977295 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Увеличить количество сайтов", "idpManage": "Управление поставщиками удостоверений", "idpManageDescription": "Просмотр и управление поставщиками удостоверений в системе", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Поставщик удостоверений успешно удалён", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Вы уверены, что хотите навсегда удалить поставщика удостоверений?", From 8e063506e0967f88fcab841e4d184f75024668fe Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:03:08 -0800 Subject: [PATCH 027/100] New translations en-us.json (Turkish) --- messages/tr-TR.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 207de8aaf..f0a016e3c 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Site sayısını artır", "idpManage": "Kimlik Sağlayıcılarını Yönet", "idpManageDescription": "Sistem içindeki kimlik sağlayıcıları görün ve yönetin", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Kimlik sağlayıcı başarıyla silindi", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Kimlik sağlayıcısını kalıcı olarak silmek istediğinizden emin misiniz?", From bcd01badaf34d58f79c421d7b7f39c7d159ad2a9 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:03:09 -0800 Subject: [PATCH 028/100] New translations en-us.json (Chinese Simplified) --- messages/zh-CN.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index c36433b32..d633c4aca 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "增加站点数量", "idpManage": "管理身份提供商", "idpManageDescription": "查看和管理系统中的身份提供商", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "身份提供商删除成功", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "您确定要永久删除身份提供者吗?", From e88a21d6dbe05b04a4ea7307b606671d8ad42104 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 19:03:11 -0800 Subject: [PATCH 029/100] New translations en-us.json (Norwegian Bokmal) --- messages/nb-NO.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index ca6fa2cb7..dbf652190 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Øk antall områder", "idpManage": "Administrer Identitetsleverandører", "idpManageDescription": "Vis og administrer identitetsleverandører i systemet", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Identitetsleverandør slettet vellykket", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Er du sikker på at du vil slette identitetsleverandøren permanent?", From 24654af63514e484af7c76876eb533860f99c1de Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:12 -0800 Subject: [PATCH 030/100] New translations en-us.json (French) --- messages/fr-FR.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 1bc3a760c..567ca0086 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fin de l'année suivante", "actionLogsDescription": "Voir l'historique des actions effectuées dans cette organisation", "accessLogsDescription": "Voir les demandes d'authentification d'accès aux ressources de cette organisation", - "licenseRequiredToUse": "Une licence Entreprise est nécessaire pour utiliser cette fonctionnalité.", - "ossEnterpriseEditionRequired": "La version Enterprise Edition est requise pour utiliser cette fonctionnalité.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Résolveur de certificat", "certResolverDescription": "Sélectionnez le solveur de certificat à utiliser pour cette ressource.", "selectCertResolver": "Sélectionnez le résolveur de certificat", From aeaa8ba13386511284f22add7cc5ec800755487c Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:14 -0800 Subject: [PATCH 031/100] New translations en-us.json (Spanish) --- messages/es-ES.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index 1e34a524d..adc7dadec 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fin del año siguiente", "actionLogsDescription": "Ver un historial de acciones realizadas en esta organización", "accessLogsDescription": "Ver solicitudes de acceso a los recursos de esta organización", - "licenseRequiredToUse": "Se requiere una licencia Enterprise para utilizar esta función.", - "ossEnterpriseEditionRequired": "La Enterprise Edition es necesaria para utilizar esta función.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Resolver certificado", "certResolverDescription": "Seleccione la resolución de certificados a utilizar para este recurso.", "selectCertResolver": "Seleccionar Resolver Certificado", From 1e9dbead3b494a418d8987be039caf6d51af4dfe Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:15 -0800 Subject: [PATCH 032/100] New translations en-us.json (Bulgarian) --- messages/bg-BG.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 118900457..3cacd312b 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Край на следващата година", "actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация", "accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация", - "licenseRequiredToUse": "Необходим е лиценз Enterprise, за да се използва тази функция.", - "ossEnterpriseEditionRequired": "Enterprise Edition се изисква за използване на тази функция.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Решавач на сертификати", "certResolverDescription": "Изберете решавач на сертификати за използване за този ресурс.", "selectCertResolver": "Изберете решавач на сертификати", From 273848ca184e57a3160a044a36c4cf2a5aa92f30 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:17 -0800 Subject: [PATCH 033/100] New translations en-us.json (Czech) --- messages/cs-CZ.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 3fe3eff76..e35f68393 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Konec následujícího roku", "actionLogsDescription": "Zobrazit historii akcí provedených v této organizaci", "accessLogsDescription": "Zobrazit žádosti o ověření přístupu pro zdroje v této organizaci", - "licenseRequiredToUse": "Pro použití této funkce je vyžadována licence pro podnikání.", - "ossEnterpriseEditionRequired": "Enterprise Edition je vyžadována pro použití této funkce.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Oddělovač certifikátů", "certResolverDescription": "Vyberte řešitele certifikátů pro tento dokument.", "selectCertResolver": "Vyberte řešič certifikátů", From be41c094dc7dc6fcfa97c96f46ee0579518e7a8e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:18 -0800 Subject: [PATCH 034/100] New translations en-us.json (German) --- messages/de-DE.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 0dc9e1905..dced1447f 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Ende des folgenden Jahres", "actionLogsDescription": "Verlauf der in dieser Organisation durchgeführten Aktionen anzeigen", "accessLogsDescription": "Zugriffsauth-Anfragen für Ressourcen in dieser Organisation anzeigen", - "licenseRequiredToUse": "Um diese Funktion nutzen zu können, ist eine Enterprise-Lizenz erforderlich.", - "ossEnterpriseEditionRequired": "Die Enterprise Edition wird benötigt, um diese Funktion nutzen zu können.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Zertifikatsauflöser", "certResolverDescription": "Wählen Sie den Zertifikatslöser aus, der für diese Ressource verwendet werden soll.", "selectCertResolver": "Zertifikatsauflöser auswählen", From 66adff44bb8954f6b09155be6210d3c4aa7e2302 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:20 -0800 Subject: [PATCH 035/100] New translations en-us.json (Italian) --- messages/it-IT.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index a50b37661..be3d34402 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fine dell'anno successivo", "actionLogsDescription": "Visualizza una cronologia delle azioni eseguite in questa organizzazione", "accessLogsDescription": "Visualizza le richieste di autenticazione di accesso per le risorse in questa organizzazione", - "licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza Enterprise.", - "ossEnterpriseEditionRequired": "L' Enterprise Edition è necessaria per utilizzare questa funzione.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Risolutore Di Certificato", "certResolverDescription": "Selezionare il risolutore di certificati da usare per questa risorsa.", "selectCertResolver": "Seleziona Risolutore Di Certificato", From 8f4cecd9634c0e4dc357f7055ca0aa55126240b4 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:21 -0800 Subject: [PATCH 036/100] New translations en-us.json (Korean) --- messages/ko-KR.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index cdefc3c20..59e3fe035 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "다음 연도 말", "actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다", "accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다", - "licenseRequiredToUse": "이 기능을 사용하려면 Enterprise 라이선스가 필요합니다.", - "ossEnterpriseEditionRequired": "이 기능을 사용하려면 Enterprise Edition이 필요합니다.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "인증서 해결사", "certResolverDescription": "이 리소스에 사용할 인증서 해결사를 선택하세요.", "selectCertResolver": "인증서 해결사 선택", From d228cf56dde1e599ec6d6dd598d6151125ef8845 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:23 -0800 Subject: [PATCH 037/100] New translations en-us.json (Dutch) --- messages/nl-NL.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 20de20c78..1ddfa8584 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Einde van volgend jaar", "actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie", "accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken", - "licenseRequiredToUse": "Een Enterprise-licentie is vereist om deze functie te gebruiken.", - "ossEnterpriseEditionRequired": "De Enterprise Edition is vereist om deze functie te gebruiken.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Certificaat Resolver", "certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.", "selectCertResolver": "Certificaat Resolver selecteren", From c3540da2e3c64896173876da43e984ba37f4b884 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:24 -0800 Subject: [PATCH 038/100] New translations en-us.json (Polish) --- messages/pl-PL.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 4e61241db..1d26c0eb0 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Koniec następnego roku", "actionLogsDescription": "Zobacz historię działań wykonywanych w tej organizacji", "accessLogsDescription": "Wyświetl prośby o autoryzację dostępu do zasobów w tej organizacji", - "licenseRequiredToUse": "Licencja Enterprise jest wymagana do korzystania z tej funkcji.", - "ossEnterpriseEditionRequired": "Enterprise Edition jest wymagany do korzystania z tej funkcji.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Rozwiązywanie certyfikatów", "certResolverDescription": "Wybierz resolver certyfikatów do użycia dla tego zasobu.", "selectCertResolver": "Wybierz Resolver certyfikatów", From e9e20932209ac9f0e1bebd517a2e4b7125bbc84f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:25 -0800 Subject: [PATCH 039/100] New translations en-us.json (Portuguese) --- messages/pt-PT.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 15a751b6a..e8a570a29 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fim do ano seguinte", "actionLogsDescription": "Visualizar histórico de ações realizadas nesta organização", "accessLogsDescription": "Ver solicitações de autenticação de recursos nesta organização", - "licenseRequiredToUse": "É necessária uma licença empresarial para usar esse recurso.", - "ossEnterpriseEditionRequired": "O Enterprise Edition é necessário para usar este recurso.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Resolvedor de Certificado", "certResolverDescription": "Selecione o resolvedor de certificados para este recurso.", "selectCertResolver": "Selecionar solucionador de certificado", From bf57a9783379ea67ac4548d2ff27f96d2d19f1b3 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:27 -0800 Subject: [PATCH 040/100] New translations en-us.json (Russian) --- messages/ru-RU.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index e36977295..0c79a6236 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Конец следующего года", "actionLogsDescription": "Просмотр истории действий, выполненных в этой организации", "accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации", - "licenseRequiredToUse": "Для использования этой функции требуется лицензия предприятия.", - "ossEnterpriseEditionRequired": "Для использования этой функции требуется корпоративная версия .", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Резольвер сертификата", "certResolverDescription": "Выберите резолвер сертификата, который будет использоваться для этого ресурса.", "selectCertResolver": "Выберите резолвер сертификата", From 6eb82a807b2c2a52a30920ebcd1a4c3ed65af6b0 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:28 -0800 Subject: [PATCH 041/100] New translations en-us.json (Turkish) --- messages/tr-TR.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index f0a016e3c..d8518d75d 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Bir sonraki yılın sonu", "actionLogsDescription": "Bu organizasyondaki eylemler geçmişini görüntüleyin", "accessLogsDescription": "Bu organizasyondaki kaynaklar için erişim kimlik doğrulama isteklerini görüntüleyin", - "licenseRequiredToUse": "Bu özelliği kullanmak için bir kurumsal lisans gereklidir.", - "ossEnterpriseEditionRequired": "Bu özelliği kullanmak için Kurumsal Sürüm gereklidir.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Sertifika Çözücü", "certResolverDescription": "Bu kaynak için kullanılacak sertifika çözücüsünü seçin.", "selectCertResolver": "Sertifika Çözücü Seçin", From 18f9157169787ea7bbf396e41b5bfb440bd242f8 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:30 -0800 Subject: [PATCH 042/100] New translations en-us.json (Chinese Simplified) --- messages/zh-CN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index d633c4aca..4bb87bd0f 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "下一年结束", "actionLogsDescription": "查看此机构执行的操作历史", "accessLogsDescription": "查看此机构资源的访问认证请求", - "licenseRequiredToUse": "需要企业许可证才能使用此功能。", - "ossEnterpriseEditionRequired": "需要 Enterprise Edition 才能使用此功能。", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "证书解决器", "certResolverDescription": "选择用于此资源的证书解析器。", "selectCertResolver": "选择证书解析", From f66b88490f517f12be37c8539501aa3b9cfd058d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 20:36:31 -0800 Subject: [PATCH 043/100] New translations en-us.json (Norwegian Bokmal) --- messages/nb-NO.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index dbf652190..149116d90 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Slutt på neste år", "actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen", "accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen", - "licenseRequiredToUse": "En Enterprise lisens er påkrevd for å bruke denne funksjonen.", - "ossEnterpriseEditionRequired": "Enterprise Edition er nødvendig for å bruke denne funksjonen.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Sertifikat løser", "certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.", "selectCertResolver": "Velg sertifikatløser", From 4b1b772098577c0876b2175348c81f3616bfffd3 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:34 -0800 Subject: [PATCH 044/100] New translations en-us.json (French) --- messages/fr-FR.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 567ca0086..9b2e24d2e 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Augmenter le nombre de sites", "idpManage": "Gérer les fournisseurs d'identité", "idpManageDescription": "Voir et gérer les fournisseurs d'identité dans le système", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérez les IdPs globaux dans le panneau d'administration . Pour activer les IdPs par organisation, éditez la configuration du serveur et réglez le mode IdP sur org. Voir la documentation. Si vous voulez continuer à utiliser les IdPs globaux et faire disparaître cela des paramètres de l'organisation, définissez explicitement le mode à global dans la configuration.", + "idpGlobalModeBannerUpgradeRequired": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérer les IdPs globaux dans le panneau d'administration . Pour utiliser les fournisseurs d'identité par organisation, vous devez passer à l'édition Entreprise.", + "idpGlobalModeBannerLicenseRequired": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérer les IdPs globaux dans le panneau d'administration . Pour utiliser les fournisseurs d'identité par organisation, une licence d'entreprise est requise.", "idpDeletedDescription": "Fournisseur d'identité supprimé avec succès", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Êtes-vous sûr de vouloir supprimer définitivement le fournisseur d'identité?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fin de l'année suivante", "actionLogsDescription": "Voir l'historique des actions effectuées dans cette organisation", "accessLogsDescription": "Voir les demandes d'authentification d'accès aux ressources de cette organisation", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Une licence Enterprise Edition est nécessaire pour utiliser cette fonctionnalité. Cette fonctionnalité est également disponible dans Pangolin Cloud.", + "ossEnterpriseEditionRequired": "La version Enterprise Edition est requise pour utiliser cette fonctionnalité. Cette fonctionnalité est également disponible dans Pangolin Cloud.", "certResolver": "Résolveur de certificat", "certResolverDescription": "Sélectionnez le solveur de certificat à utiliser pour cette ressource.", "selectCertResolver": "Sélectionnez le résolveur de certificat", From 7f7f3d43b2718783909d830030d110a9b68f1948 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:35 -0800 Subject: [PATCH 045/100] New translations en-us.json (Spanish) --- messages/es-ES.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index adc7dadec..28c0e7795 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Aumentar el número de sitios", "idpManage": "Administrar proveedores de identidad", "idpManageDescription": "Ver y administrar proveedores de identidad en el sistema", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el panel de administración. Para habilitar los IdPs por organización, edita la configuración del servidor y establece el modo de IdP en org. Consulta la documentación. Si deseas seguir utilizando IdPs globales y hacer que esto desaparezca de las configuraciones de la organización, establece explícitamente el modo en global en la configuración.", + "idpGlobalModeBannerUpgradeRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el panel de administración. Para usar proveedores de identidad por organización, debes actualizar a la edición Empresarial.", + "idpGlobalModeBannerLicenseRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando identificadores globales (compartidos en todas las organizaciones). Gestionar identificaciones globales en el panel de administración. Para utilizar proveedores de identidad por organización, se requiere una licencia de empresa.", "idpDeletedDescription": "Proveedor de identidad eliminado correctamente", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "¿Está seguro que desea eliminar permanentemente el proveedor de identidad?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fin del año siguiente", "actionLogsDescription": "Ver un historial de acciones realizadas en esta organización", "accessLogsDescription": "Ver solicitudes de acceso a los recursos de esta organización", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Se requiere una licencia Enterprise Edition para utilizar esta función. Esta característica también está disponible en Pangolin Cloud.", + "ossEnterpriseEditionRequired": "La versión Enterprise es necesaria para utilizar esta función. Esta función también está disponible en Pangolin Cloud.", "certResolver": "Resolver certificado", "certResolverDescription": "Seleccione la resolución de certificados a utilizar para este recurso.", "selectCertResolver": "Seleccionar Resolver Certificado", From e27c81eea6d4a8c208e6c914cdb69aec35b35487 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:37 -0800 Subject: [PATCH 046/100] New translations en-us.json (Bulgarian) --- messages/bg-BG.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 3cacd312b..3f48085ac 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Увеличаване на броя на сайтовете", "idpManage": "Управление на доставчици на идентичност", "idpManageDescription": "Прегледайте и управлявайте доставчици на идентичност в системата", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в администраторския панел. За да активирате IdPs за всяка организация, редактирайте конфигурацията на сървъра и задайте режима на IdP към org. Вижте документацията. Ако желаете да продължите да използвате глобалните IdPs и да премахнете това от настройките на организацията, изрично задайте режима на global в конфигурацията.", + "idpGlobalModeBannerUpgradeRequired": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в администраторския панел. За да използвате доставчици на идентичност за всяка организация, трябва да надстроите до изданието Enterprise.", + "idpGlobalModeBannerLicenseRequired": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в администраторския панел. За да използвате доставчици на идентичност за всяка организация, е необходим лиценз за изданието Enterprise.", "idpDeletedDescription": "Доставчик на идентичност успешно изтрит", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Сигурни ли сте, че искате да изтриете доставчика за идентичност?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Край на следващата година", "actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация", "accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Изисква се лиценз за Enterprise Edition, за да използвате тази функция. Тази функция е също достъпна в Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Необходимо е изданието Enterprise, за да използвате тази функция. Тази функция е също достъпна в Pangolin Cloud.", "certResolver": "Решавач на сертификати", "certResolverDescription": "Изберете решавач на сертификати за използване за този ресурс.", "selectCertResolver": "Изберете решавач на сертификати", From e97340ed52e7b78ab194bc2d96ce35476895a605 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:38 -0800 Subject: [PATCH 047/100] New translations en-us.json (Czech) --- messages/cs-CZ.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index e35f68393..c35488cdd 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Zvýšit počet stránek", "idpManage": "Spravovat poskytovatele identity", "idpManageDescription": "Zobrazit a spravovat poskytovatele identity v systému", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Správa globálních IdP v admin panelu. Chcete-li povolit IdP pro každou organizaci, upravte konfiguraci serveru a nastavte IdP režim na org. Viz dokumentace. Pokud chcete pokračovat v používání globálních IdP a zmizet z nastavení organizace, explicitně nastavte režim na globální v konfiguraci.", + "idpGlobalModeBannerUpgradeRequired": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Spravujte globální IdP v admin panelu. Chcete-li použít poskytovatele identity pro každou organizaci, musíte přejít na Enterprise vydání.", + "idpGlobalModeBannerLicenseRequired": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Správa globálních IdP v admin panelu. Chcete-li použít poskytovatele identity pro každou organizaci, je vyžadována Enterprise licence.", "idpDeletedDescription": "Poskytovatel identity byl úspěšně odstraněn", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Jste si jisti, že chcete trvale odstranit poskytovatele identity?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Konec následujícího roku", "actionLogsDescription": "Zobrazit historii akcí provedených v této organizaci", "accessLogsDescription": "Zobrazit žádosti o ověření přístupu pro zdroje v této organizaci", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Pro použití této funkce je vyžadována licence Enterprise Edition . Tato funkce je také dostupná v Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Enterprise Edition je vyžadována pro použití této funkce. Tato funkce je také k dispozici v Pangolin Cloud.", "certResolver": "Oddělovač certifikátů", "certResolverDescription": "Vyberte řešitele certifikátů pro tento dokument.", "selectCertResolver": "Vyberte řešič certifikátů", From d0862a2d269bd21e68eb83823a76884f027fa551 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:39 -0800 Subject: [PATCH 048/100] New translations en-us.json (German) --- messages/de-DE.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index dced1447f..a38e9ac40 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Anzahl der Standorte erhöhen", "idpManage": "Identitätsanbieter verwalten", "idpManageDescription": "Identitätsanbieter im System anzeigen und verwalten", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt über alle Organisationen). Verwalten Sie globale IdPs im Admin-Panel. Um IdPs pro Organisation zu aktivieren, bearbeiten Sie die Server-Konfiguration und setzen Sie den IdP-Modus auf org. Siehe Dokumentation. Wenn Sie weiterhin globale IdPs verwenden und diese in den Organisationseinstellungen verschwinden lassen wollen, setzen Sie den Modus explizit auf global in der Konfiguration.", + "idpGlobalModeBannerUpgradeRequired": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt in allen Organisationen). Globale IdPs im Admin-Panelverwalten. Um Identitätsanbieter pro Organisation nutzen zu können, müssen Sie zur Enterprise Edition upgraden.", + "idpGlobalModeBannerLicenseRequired": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt in allen Organisationen). Globale IdPs im Admin-Panelverwalten. Um Identitätsanbieter pro Organisation zu verwenden, ist eine Enterprise-Lizenz erforderlich.", "idpDeletedDescription": "Identitätsanbieter erfolgreich gelöscht", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Sind Sie sicher, dass Sie den Identitätsanbieter dauerhaft löschen möchten?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Ende des folgenden Jahres", "actionLogsDescription": "Verlauf der in dieser Organisation durchgeführten Aktionen anzeigen", "accessLogsDescription": "Zugriffsauth-Anfragen für Ressourcen in dieser Organisation anzeigen", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Um diese Funktion nutzen zu können, ist eine Enterprise Edition Lizenz erforderlich. Diese Funktion ist auch in der Pangolin Cloud verfügbar.", + "ossEnterpriseEditionRequired": "Um diese Funktion nutzen zu können, ist die Enterprise Edition erforderlich. Diese Funktion ist auch in der Pangolin Cloud verfügbar.", "certResolver": "Zertifikatsauflöser", "certResolverDescription": "Wählen Sie den Zertifikatslöser aus, der für diese Ressource verwendet werden soll.", "selectCertResolver": "Zertifikatsauflöser auswählen", From b08c5f5c67450ca2cbf191bf925f80c966a82e3a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:41 -0800 Subject: [PATCH 049/100] New translations en-us.json (Italian) --- messages/it-IT.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index be3d34402..30443e98f 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Aumenta conteggio siti", "idpManage": "Gestisci Provider di Identità", "idpManageDescription": "Visualizza e gestisci i provider di identità nel sistema", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Sta utilizzando IdP globali (condivisi in tutte le organizzazioni). Gestisci IdP globali nel pannello di amministrazione . Per abilitare IdP per organizzazione, modificare la configurazione del server e impostare la modalità IdP su org. Vedere i documenti. Se si desidera continuare a utilizzare IdP globali e far sparire questo dalle impostazioni dell'organizzazione, impostare esplicitamente la modalità globale nella configurazione.", + "idpGlobalModeBannerUpgradeRequired": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Utilizza IdP globali (condivisi tra tutte le organizzazioni). Gestisci gli IdP globali nel pannello di amministrazione . Per utilizzare i provider di identità per organizzazione, è necessario aggiornare all'edizione Enterprise.", + "idpGlobalModeBannerLicenseRequired": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Utilizza IdP globali (condivisi tra tutte le organizzazioni). Gestisci IdP globali nel pannello di amministrazione . Per utilizzare provider di identità per organizzazione, è richiesta una licenza Enterprise.", "idpDeletedDescription": "Provider di identità eliminato con successo", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Sei sicuro di voler eliminare definitivamente il provider di identità?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fine dell'anno successivo", "actionLogsDescription": "Visualizza una cronologia delle azioni eseguite in questa organizzazione", "accessLogsDescription": "Visualizza le richieste di autenticazione di accesso per le risorse in questa organizzazione", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza Enterprise Edition . Questa funzionalità è disponibile anche in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "L' Enterprise Edition è necessaria per utilizzare questa funzione. Questa funzionalità è disponibile anche in Pangolin Cloud.", "certResolver": "Risolutore Di Certificato", "certResolverDescription": "Selezionare il risolutore di certificati da usare per questa risorsa.", "selectCertResolver": "Seleziona Risolutore Di Certificato", From 256fa880dd1c96297b64dfdae73bd3da9eb5131d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:42 -0800 Subject: [PATCH 050/100] New translations en-us.json (Korean) --- messages/ko-KR.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 59e3fe035..13f0a22ad 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "사이트 수 증가", "idpManage": "아이덴티티 공급자 관리", "idpManageDescription": "시스템에서 ID 제공자를 보고 관리합니다", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. 관리자 패널에서 글로벌 IdP를 관리하십시오. 조직별 IdP를 활성화하려면 서버 설정을 편집하고 IdP 모드를 조직으로 설정하십시오. 문서 보기. 글로벌 IdP 사용을 계속하고 조직 설정에서 이 항목을 제거하려면 설정에서 모드를 글로벌로 명시적으로 설정하십시오.", + "idpGlobalModeBannerUpgradeRequired": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. 관리자 패널에서 글로벌 IdP를 관리하십시오. 조직별 신원 제공자를 사용하려면 Enterprise 에디션으로 업그레이드해야 합니다.", + "idpGlobalModeBannerLicenseRequired": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. 관리자 패널에서 글로벌 IdP를 관리하십시오. 조직별 신원 제공자를 사용하려면 엔터프라이즈 라이선스가 필요합니다.", "idpDeletedDescription": "신원 공급자가 성공적으로 삭제되었습니다", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "아이덴티티 공급자를 영구적으로 삭제하시겠습니까?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "다음 연도 말", "actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다", "accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "이 기능을 사용하려면 엔터프라이즈 에디션 라이선스가 필요합니다. 이 기능은 판골린 클라우드에서도 사용할 수 있습니다.", + "ossEnterpriseEditionRequired": "이 기능을 사용하려면 엔터프라이즈 에디션이 필요합니다. 이 기능은 판골린 클라우드에서도 사용할 수 있습니다.", "certResolver": "인증서 해결사", "certResolverDescription": "이 리소스에 사용할 인증서 해결사를 선택하세요.", "selectCertResolver": "인증서 해결사 선택", From 2d83160b1667c19bdce91b7c19d2b5a3634b0d66 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:43 -0800 Subject: [PATCH 051/100] New translations en-us.json (Dutch) --- messages/nl-NL.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 1ddfa8584..78783d922 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Toename van site vergroten", "idpManage": "Identiteitsaanbieders beheren", "idpManageDescription": "Identiteitsaanbieders in het systeem bekijken en beheren", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Identiteitsaanbieders (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld tussen alle organisaties). Beheer globale IdPs in het beheerderspaneel. Om IdPs per organisatie in te schakelen, bewerk de server configuratie en zet IdP modus op org. Zie de documenten. Als je globale IdPs wilt blijven gebruiken en dit uit de organisatie-instellingen wilt laten verdwijnen, zet dan expliciet de modus naar globaal in de config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld in alle organisaties) Beheer globale IdPs in het beheerderspaneel. Om identiteitsproviders per organisatie te gebruiken, moet u upgraden naar de Enterprise editie.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld in alle organisaties) Beheer globale IdPs in het beheerderspaneel. Om identiteitsaanbieders per organisatie te gebruiken, is een Enterprise-licentie vereist.", "idpDeletedDescription": "Identity provider succesvol verwijderd", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Weet u zeker dat u de identiteitsprovider permanent wilt verwijderen?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Einde van volgend jaar", "actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie", "accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Een Enterprise Edition licentie is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "De Enterprise Edition is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in Pangolin Cloud.", "certResolver": "Certificaat Resolver", "certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.", "selectCertResolver": "Certificaat Resolver selecteren", From 733f6692c6d55439aa96542b23ed8f69c6724adc Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:45 -0800 Subject: [PATCH 052/100] New translations en-us.json (Polish) --- messages/pl-PL.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 1d26c0eb0..b3d3cd94f 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Zwiększ liczbę witryn", "idpManage": "Zarządzaj dostawcami tożsamości", "idpManageDescription": "Wyświetl i zarządzaj dostawcami tożsamości w systemie", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używa globalnych IdP (współdzielonych ze wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym . Aby włączyć IdP na organizację, edytuj konfigurację serwera i ustaw tryb IdP na org. Zobacz dokumentację. Jeśli chcesz nadal używać globalnych IdP i sprawić, że zniknie to z ustawień organizacji, wyraźnie ustaw tryb globalny w konfiguracji.", + "idpGlobalModeBannerUpgradeRequired": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używają globalnych IdP (współdzielonych między wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym . Aby korzystać z dostawców tożsamości na organizację, musisz zaktualizować do edycji Enterprise.", + "idpGlobalModeBannerLicenseRequired": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używają globalnych IdP (współdzielonych między wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym . Aby korzystać z dostawców tożsamości na organizację, wymagana jest licencja Enterprise.", "idpDeletedDescription": "Dostawca tożsamości został pomyślnie usunięty", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Czy na pewno chcesz trwale usunąć dostawcę tożsamości?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Koniec następnego roku", "actionLogsDescription": "Zobacz historię działań wykonywanych w tej organizacji", "accessLogsDescription": "Wyświetl prośby o autoryzację dostępu do zasobów w tej organizacji", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Do korzystania z tej funkcji wymagana jest licencja Enterprise Edition . Ta funkcja jest również dostępna w Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Enterprise Edition jest wymagany do korzystania z tej funkcji. Ta funkcja jest również dostępna w Pangolin Cloud.", "certResolver": "Rozwiązywanie certyfikatów", "certResolverDescription": "Wybierz resolver certyfikatów do użycia dla tego zasobu.", "selectCertResolver": "Wybierz Resolver certyfikatów", From 3547450b035cf2d099a368523751a393211d99ad Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:46 -0800 Subject: [PATCH 053/100] New translations en-us.json (Portuguese) --- messages/pt-PT.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index e8a570a29..e79851388 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Aumentar contagem de sites", "idpManage": "Gerir Provedores de Identidade", "idpManageDescription": "Visualizar e gerir provedores de identidade no sistema", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Provedores de identidade (Pds) por organização estão desabilitados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie IdPs no painel admin. Para habilitar IdPs por organização, edite a configuração do servidor e defina o modo IdP como org. Veja a documentação. Se quiser continuar usando IdPs globais e fazer isso desaparecer das configurações da organização, defina explicitamente o modo como global na configuração.", + "idpGlobalModeBannerUpgradeRequired": "Os provedores de identidade (IdPs) por organização estão desativados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie os IdPs globais no painel administrativo. Para usar provedores de identidade por organização, você deve atualizar para a edição Enterprise.", + "idpGlobalModeBannerLicenseRequired": "Os provedores de identidade (IdPs) por organização estão desativados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie os IdPs globais no painel administrativo. Para usar provedores de identidade por organização, é necessário uma licença Enterprise.", "idpDeletedDescription": "Provedor de identidade eliminado com sucesso", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Tem certeza que deseja eliminar permanentemente o provedor de identidade?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fim do ano seguinte", "actionLogsDescription": "Visualizar histórico de ações realizadas nesta organização", "accessLogsDescription": "Ver solicitações de autenticação de recursos nesta organização", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Uma licença Enterprise Edition é necessária para usar este recurso. Este recurso também está disponível no Pangolin Cloud.", + "ossEnterpriseEditionRequired": "O Enterprise Edition é necessário para usar este recurso. Este recurso também está disponível no Pangolin Cloud.", "certResolver": "Resolvedor de Certificado", "certResolverDescription": "Selecione o resolvedor de certificados para este recurso.", "selectCertResolver": "Selecionar solucionador de certificado", From e756fad5735ba995ec07ea2845ea460ca07d39e7 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:47 -0800 Subject: [PATCH 054/100] New translations en-us.json (Russian) --- messages/ru-RU.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 0c79a6236..4891e0a9f 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Увеличить количество сайтов", "idpManage": "Управление поставщиками удостоверений", "idpManageDescription": "Просмотр и управление поставщиками удостоверений в системе", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в админ-панели. Чтобы включить IdP для каждой организации, отредактируйте конфигурацию сервера и установите режим IdP в org. См. документацию. Если вы хотите продолжать использовать глобальные IdP и скрыть это из настроек организации, явно установите режим в глобальном конфиге.", + "idpGlobalModeBannerUpgradeRequired": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в админ-панели. Чтобы использовать поставщиков удостоверений для каждой организации, необходимо обновить систему до версии Enterprise.", + "idpGlobalModeBannerLicenseRequired": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в админ-панели. Для использования поставщиков удостоверений на организацию требуется лицензия Enterprise.", "idpDeletedDescription": "Поставщик удостоверений успешно удалён", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Вы уверены, что хотите навсегда удалить поставщика удостоверений?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Конец следующего года", "actionLogsDescription": "Просмотр истории действий, выполненных в этой организации", "accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Лицензия на Enterprise Edition требуется для использования этой функции. Эта функция также доступна в Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Для использования этой функции требуется Enterprise Edition. Эта функция также доступна в Pangolin Cloud.", "certResolver": "Резольвер сертификата", "certResolverDescription": "Выберите резолвер сертификата, который будет использоваться для этого ресурса.", "selectCertResolver": "Выберите резолвер сертификата", From 758b03ab25690d7c9b849dad3d978236cbe5b8e1 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:49 -0800 Subject: [PATCH 055/100] New translations en-us.json (Turkish) --- messages/tr-TR.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index d8518d75d..0c4c921d1 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Site sayısını artır", "idpManage": "Kimlik Sağlayıcılarını Yönet", "idpManageDescription": "Sistem içindeki kimlik sağlayıcıları görün ve yönetin", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri yönetici panelinde yönetin. Örgüt başına IdP'leri etkinleştirmek için, sunucu yapılandırmasını düzenleyin ve IdP modunu 'org' olarak ayarlayın. Belgeleri inceleyin . Küresel IdP'leri kullanmaya devam etmek istiyorsanız ve bunun örgüt ayarlarından kaybolmasını istiyorsanız, yapılandırmada modu otomatik olarak 'global' olarak ayarlayın.", + "idpGlobalModeBannerUpgradeRequired": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri yönetici panelinde yönetin. Örgüt başına kimlik sağlayıcılar kullanmak için, Enterprise sürümüne yükseltmeniz gerekmektedir.", + "idpGlobalModeBannerLicenseRequired": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri yönetici panelinde yönetin. Örgüt başına kimlik sağlayıcılar kullanmak için Enterprise lisansı gereklidir.", "idpDeletedDescription": "Kimlik sağlayıcı başarıyla silindi", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Kimlik sağlayıcısını kalıcı olarak silmek istediğinizden emin misiniz?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Bir sonraki yılın sonu", "actionLogsDescription": "Bu organizasyondaki eylemler geçmişini görüntüleyin", "accessLogsDescription": "Bu organizasyondaki kaynaklar için erişim kimlik doğrulama isteklerini görüntüleyin", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "Bu özelliği kullanmak için bir Enterprise Edition lisansı gereklidir. Bu özellik ayrıca Pangolin Cloud'da da mevcuttur.", + "ossEnterpriseEditionRequired": "Bu özelliği kullanmak için Enterprise Edition gereklidir. Bu özellik ayrıca Pangolin Cloud'da da mevcuttur.", "certResolver": "Sertifika Çözücü", "certResolverDescription": "Bu kaynak için kullanılacak sertifika çözücüsünü seçin.", "selectCertResolver": "Sertifika Çözücü Seçin", From 1ae315e3031ebce0f932b185b92faa241a642102 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:50 -0800 Subject: [PATCH 056/100] New translations en-us.json (Chinese Simplified) --- messages/zh-CN.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 4bb87bd0f..7312ba32c 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "增加站点数量", "idpManage": "管理身份提供商", "idpManageDescription": "查看和管理系统中的身份提供商", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "此服务器上禁用了每个组织的身份提供商(Idps)。 它正在使用全局IdP(所有组织共享)。在 管理面板中管理全局IdP。 要启用每个组织的 IdP,请编辑服务器配置并将 IdP 模式设置为 org。 请参阅文档。 如果您想要继续使用全局IdP并使其从组织设置中消失,请在配置中将模式设置为全局模式。", + "idpGlobalModeBannerUpgradeRequired": "此服务器上禁用了每个组织的身份提供商(Idps)。它正在使用全局身份提供商(所有组织共享)。 在 管理面板管理全局身份。要使用每个组织的身份提供者,您必须升级到企业版本。", + "idpGlobalModeBannerLicenseRequired": "此服务器上禁用了每个组织的身份提供商(Idps)。它正在使用全局身份提供商(所有组织共享)。 在 管理面板管理全局身份。要使用每个组织的身份提供者,需要企业许可证。", "idpDeletedDescription": "身份提供商删除成功", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "您确定要永久删除身份提供者吗?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "下一年结束", "actionLogsDescription": "查看此机构执行的操作历史", "accessLogsDescription": "查看此机构资源的访问认证请求", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "需要 Enterprise Edition 许可才能使用此功能。此功能也可在 Pangolin Cloud 中使用。", + "ossEnterpriseEditionRequired": "Enterprise Edition 需要使用此功能。此功能也可在 Pangolin Cloud 中使用。", "certResolver": "证书解决器", "certResolverDescription": "选择用于此资源的证书解析器。", "selectCertResolver": "选择证书解析", From 5e117465498651dc1cb6b693c90837e1755f5b2b Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 11 Feb 2026 21:39:51 -0800 Subject: [PATCH 057/100] New translations en-us.json (Norwegian Bokmal) --- messages/nb-NO.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 149116d90..afb9af386 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -791,9 +791,9 @@ "sitestCountIncrease": "Øk antall områder", "idpManage": "Administrer Identitetsleverandører", "idpManageDescription": "Vis og administrer identitetsleverandører i systemet", - "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", - "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", - "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", + "idpGlobalModeBanner": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdP (delt over alle organisasjoner). Administrer globale IdP'er i admin-panelet. For å aktivere IdP per organisasjon, rediger serverkonfigurasjonen og sett IdP-modus til org. Se dokumentasjonen. Hvis du vil fortsette å bruke globale IdPs og få denne til å forsvinne fra organisasjonens innstillinger, satt eksplisitt modusen til global i konfigurasjonen.", + "idpGlobalModeBannerUpgradeRequired": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdPs (delt på tvers av alle organisasjoner). Administrer globale IdPs i administrasjons-panelet. For å bruke identitetsleverandører per organisasjon, må du oppgradere til Enterprise-utgaven.", + "idpGlobalModeBannerLicenseRequired": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdPs (delt på tvers av alle organisasjoner). Administrer globale IdPs i administrasjons-panelet. For å bruke identitetsleverandører per organisasjon, kreves en Enterprise-lisens.", "idpDeletedDescription": "Identitetsleverandør slettet vellykket", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Er du sikker på at du vil slette identitetsleverandøren permanent?", @@ -2283,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Slutt på neste år", "actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen", "accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen", - "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", + "licenseRequiredToUse": "En Enterprise Edition lisens er påkrevd for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Enterprise Edition er nødvendig for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i Pangolin Cloud.", "certResolver": "Sertifikat løser", "certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.", "selectCertResolver": "Velg sertifikatløser", From bff9d33ee64353c4c7d053618a1dbea30a3466a4 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Feb 2026 21:47:10 -0800 Subject: [PATCH 058/100] Move back to db:sqlite:generate --- .github/workflows/test.yml | 2 +- package.json | 2 +- .../routers/generatedLicense/generateNewEnterpriseLicense.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82e3686a2..e6af8d798 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: run: npm run set:oss - name: Generate database migrations - run: npm run db:generate + run: npm run db:sqlite:generate - name: Apply database migrations run: npm run db:sqlite:push diff --git a/package.json b/package.json index f60d52e5f..93e3c171e 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "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", + "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: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", diff --git a/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts b/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts index fc9978b4d..422544170 100644 --- a/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts +++ b/server/private/routers/generatedLicense/generateNewEnterpriseLicense.ts @@ -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(), From 69ecc223184c80a28fd0ae37acc9af9b9cf71176 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 01:39:38 -0800 Subject: [PATCH 059/100] New translations en-us.json (German) --- messages/de-DE.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index a38e9ac40..ee3d65ea1 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -1154,7 +1154,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", @@ -2532,10 +2532,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", From 52484c774e96d7f3a306042ac789829364967d9d Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 12:05:15 -0800 Subject: [PATCH 060/100] Setting up drizzle and fix site not showing in private resource --- .gitignore | 3 +- drizzle.config.ts | 7 +- server/setup/.gitignore | 1 + server/setup/migrations.ts | 150 ++++++++++++------ .../CreateInternalResourceDialog.tsx | 2 +- src/components/EditInternalResourceDialog.tsx | 2 +- 6 files changed, 107 insertions(+), 58 deletions(-) create mode 100644 server/setup/.gitignore diff --git a/.gitignore b/.gitignore index df9179a43..1067dc2ce 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ dynamic/ scratch/ tsconfig.json hydrateSaas.ts -CLAUDE.md \ No newline at end of file +CLAUDE.md +drizzle.config.ts diff --git a/drizzle.config.ts b/drizzle.config.ts index ba4ca8fe5..d8344f942 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,14 +1,15 @@ +import { APP_PATH } from "@server/lib/consts"; import { defineConfig } from "drizzle-kit"; import path from "path"; -const schema = [path.join("server", "db", "pg", "schema")]; +const schema = [path.join("server", "db", "sqlite", "schema")]; export default defineConfig({ - dialect: "postgresql", + dialect: "sqlite", schema: schema, out: path.join("server", "migrations"), verbose: true, dbCredentials: { - url: process.env.DATABASE_URL as string + url: path.join(APP_PATH, "db", "db.sqlite") } }); diff --git a/server/setup/.gitignore b/server/setup/.gitignore new file mode 100644 index 000000000..a61cfd647 --- /dev/null +++ b/server/setup/.gitignore @@ -0,0 +1 @@ +migrations.ts diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts index 7ae218364..170cf93d0 100644 --- a/server/setup/migrations.ts +++ b/server/setup/migrations.ts @@ -1,45 +1,78 @@ #! /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 { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { db, exists } from "../db/sqlite"; 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"; +import semver from "semver"; +import { versionMigrations } from "../db/sqlite"; +import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; +import { SqliteError } from "better-sqlite3"; +import fs from "fs"; +import m1 from "./scriptsSqlite/1.0.0-beta1"; +import m2 from "./scriptsSqlite/1.0.0-beta2"; +import m3 from "./scriptsSqlite/1.0.0-beta3"; +import m4 from "./scriptsSqlite/1.0.0-beta5"; +import m5 from "./scriptsSqlite/1.0.0-beta6"; +import m6 from "./scriptsSqlite/1.0.0-beta9"; +import m7 from "./scriptsSqlite/1.0.0-beta10"; +import m8 from "./scriptsSqlite/1.0.0-beta12"; +import m13 from "./scriptsSqlite/1.0.0-beta13"; +import m15 from "./scriptsSqlite/1.0.0-beta15"; +import m16 from "./scriptsSqlite/1.0.0"; +import m17 from "./scriptsSqlite/1.1.0"; +import m18 from "./scriptsSqlite/1.2.0"; +import m19 from "./scriptsSqlite/1.3.0"; +import m20 from "./scriptsSqlite/1.5.0"; +import m21 from "./scriptsSqlite/1.6.0"; +import m22 from "./scriptsSqlite/1.7.0"; +import m23 from "./scriptsSqlite/1.8.0"; +import m24 from "./scriptsSqlite/1.9.0"; +import m25 from "./scriptsSqlite/1.10.0"; +import m26 from "./scriptsSqlite/1.10.1"; +import m27 from "./scriptsSqlite/1.10.2"; +import m28 from "./scriptsSqlite/1.11.0"; +import m29 from "./scriptsSqlite/1.11.1"; +import m30 from "./scriptsSqlite/1.12.0"; +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"; // 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 } + { version: "1.0.0-beta.1", run: m1 }, + { version: "1.0.0-beta.2", run: m2 }, + { version: "1.0.0-beta.3", run: m3 }, + { version: "1.0.0-beta.5", run: m4 }, + { version: "1.0.0-beta.6", run: m5 }, + { version: "1.0.0-beta.9", run: m6 }, + { version: "1.0.0-beta.10", run: m7 }, + { version: "1.0.0-beta.12", run: m8 }, + { version: "1.0.0-beta.13", run: m13 }, + { version: "1.0.0-beta.15", run: m15 }, + { version: "1.0.0", run: m16 }, + { version: "1.1.0", run: m17 }, + { version: "1.2.0", run: m18 }, + { version: "1.3.0", run: m19 }, + { version: "1.5.0", run: m20 }, + { version: "1.6.0", run: m21 }, + { version: "1.7.0", run: m22 }, + { version: "1.8.0", run: m23 }, + { version: "1.9.0", run: m24 }, + { version: "1.10.0", run: m25 }, + { version: "1.10.1", run: m26 }, + { version: "1.10.2", run: m27 }, + { version: "1.11.0", run: m28 }, + { version: "1.11.1", run: m29 }, + { version: "1.12.0", run: m30 }, + { version: "1.13.0", run: m31 }, + { version: "1.14.0", run: m32 }, + { version: "1.15.0", run: m33 }, + { version: "1.15.3", run: m34 } // Add new migrations here as they are created -] as { - version: string; - run: () => Promise; -}[]; +] as const; await run(); @@ -48,6 +81,27 @@ async function run() { await runMigrations(); } +function backupDb() { + // make dir config/db/backups + const appPath = APP_PATH; + const dbDir = path.join(appPath, "db"); + + const backupsDir = path.join(dbDir, "backups"); + + // check if the backups directory exists and create it if it doesn't + if (!fs.existsSync(backupsDir)) { + fs.mkdirSync(backupsDir, { recursive: true }); + } + + // copy the db.sqlite file to backups + // add the date to the filename + const date = new Date(); + const dateString = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`; + const dbPath = path.join(dbDir, "db.sqlite"); + const backupPath = path.join(backupsDir, `db_${dateString}.sqlite`); + fs.copyFileSync(dbPath, backupPath); +} + export async function runMigrations() { if (process.env.DISABLE_MIGRATIONS) { console.log("Migrations are disabled. Skipping..."); @@ -56,23 +110,12 @@ export async function runMigrations() { 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, { + migrate(db, { migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build }); console.log("Migrations completed successfully."); @@ -105,7 +148,7 @@ async function executeScripts() { const pendingMigrations = lastExecuted .map((m) => m) .sort((a, b) => semver.compare(b.version, a.version)); - const startVersion = pendingMigrations[0]?.version ?? "0.0.0"; + const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; console.log(`Starting migrations from version ${startVersion}`); const migrationsToRun = migrations.filter((migration) => @@ -122,6 +165,11 @@ async function executeScripts() { console.log(`Running migration ${migration.version}`); try { + if (!process.env.DISABLE_BACKUP_ON_MIGRATION) { + // Backup the database before running the migration + backupDb(); + } + await migration.run(); // Update version in database @@ -138,19 +186,17 @@ async function executeScripts() { ); } catch (e) { if ( - e instanceof Error && - typeof (e as any).code === "string" && - (e as any).code === "23505" + e instanceof SqliteError && + e.code === "SQLITE_CONSTRAINT_UNIQUE" ) { console.error("Migration has already run! Skipping..."); - continue; // or return, depending on context + continue; } - console.error( `Failed to run migration ${migration.version}:`, e ); - throw e; + throw e; // Re-throw to stop migration process } } diff --git a/src/components/CreateInternalResourceDialog.tsx b/src/components/CreateInternalResourceDialog.tsx index 4c8d63250..34e8f55e2 100644 --- a/src/components/CreateInternalResourceDialog.tsx +++ b/src/components/CreateInternalResourceDialog.tsx @@ -303,7 +303,7 @@ export default function CreateInternalResourceDialog({ const [udpCustomPorts, setUdpCustomPorts] = useState(""); const availableSites = sites.filter( - (site) => site.type === "newt" && site.subnet + (site) => site.type === "newt" ); const form = useForm({ diff --git a/src/components/EditInternalResourceDialog.tsx b/src/components/EditInternalResourceDialog.tsx index 4b5030be8..4c1176e5c 100644 --- a/src/components/EditInternalResourceDialog.tsx +++ b/src/components/EditInternalResourceDialog.tsx @@ -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({ From bc7bc8da66451d2ea994f4fa2a71ba31b896b473 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 12:07:57 -0800 Subject: [PATCH 061/100] Stop tracking files that should be ignored --- drizzle.config.ts | 15 --- server/setup/migrations.ts | 208 ------------------------------------- 2 files changed, 223 deletions(-) delete mode 100644 drizzle.config.ts delete mode 100644 server/setup/migrations.ts diff --git a/drizzle.config.ts b/drizzle.config.ts deleted file mode 100644 index d8344f942..000000000 --- a/drizzle.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import { defineConfig } from "drizzle-kit"; -import path from "path"; - -const schema = [path.join("server", "db", "sqlite", "schema")]; - -export default defineConfig({ - dialect: "sqlite", - schema: schema, - out: path.join("server", "migrations"), - verbose: true, - dbCredentials: { - url: path.join(APP_PATH, "db", "db.sqlite") - } -}); diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts deleted file mode 100644 index 170cf93d0..000000000 --- a/server/setup/migrations.ts +++ /dev/null @@ -1,208 +0,0 @@ -#! /usr/bin/env node -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; -import { db, exists } from "../db/sqlite"; -import path from "path"; -import semver from "semver"; -import { versionMigrations } from "../db/sqlite"; -import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; -import { SqliteError } from "better-sqlite3"; -import fs from "fs"; -import m1 from "./scriptsSqlite/1.0.0-beta1"; -import m2 from "./scriptsSqlite/1.0.0-beta2"; -import m3 from "./scriptsSqlite/1.0.0-beta3"; -import m4 from "./scriptsSqlite/1.0.0-beta5"; -import m5 from "./scriptsSqlite/1.0.0-beta6"; -import m6 from "./scriptsSqlite/1.0.0-beta9"; -import m7 from "./scriptsSqlite/1.0.0-beta10"; -import m8 from "./scriptsSqlite/1.0.0-beta12"; -import m13 from "./scriptsSqlite/1.0.0-beta13"; -import m15 from "./scriptsSqlite/1.0.0-beta15"; -import m16 from "./scriptsSqlite/1.0.0"; -import m17 from "./scriptsSqlite/1.1.0"; -import m18 from "./scriptsSqlite/1.2.0"; -import m19 from "./scriptsSqlite/1.3.0"; -import m20 from "./scriptsSqlite/1.5.0"; -import m21 from "./scriptsSqlite/1.6.0"; -import m22 from "./scriptsSqlite/1.7.0"; -import m23 from "./scriptsSqlite/1.8.0"; -import m24 from "./scriptsSqlite/1.9.0"; -import m25 from "./scriptsSqlite/1.10.0"; -import m26 from "./scriptsSqlite/1.10.1"; -import m27 from "./scriptsSqlite/1.10.2"; -import m28 from "./scriptsSqlite/1.11.0"; -import m29 from "./scriptsSqlite/1.11.1"; -import m30 from "./scriptsSqlite/1.12.0"; -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"; - -// 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.0.0-beta.1", run: m1 }, - { version: "1.0.0-beta.2", run: m2 }, - { version: "1.0.0-beta.3", run: m3 }, - { version: "1.0.0-beta.5", run: m4 }, - { version: "1.0.0-beta.6", run: m5 }, - { version: "1.0.0-beta.9", run: m6 }, - { version: "1.0.0-beta.10", run: m7 }, - { version: "1.0.0-beta.12", run: m8 }, - { version: "1.0.0-beta.13", run: m13 }, - { version: "1.0.0-beta.15", run: m15 }, - { version: "1.0.0", run: m16 }, - { version: "1.1.0", run: m17 }, - { version: "1.2.0", run: m18 }, - { version: "1.3.0", run: m19 }, - { version: "1.5.0", run: m20 }, - { version: "1.6.0", run: m21 }, - { version: "1.7.0", run: m22 }, - { version: "1.8.0", run: m23 }, - { version: "1.9.0", run: m24 }, - { version: "1.10.0", run: m25 }, - { version: "1.10.1", run: m26 }, - { version: "1.10.2", run: m27 }, - { version: "1.11.0", run: m28 }, - { version: "1.11.1", run: m29 }, - { version: "1.12.0", run: m30 }, - { version: "1.13.0", run: m31 }, - { version: "1.14.0", run: m32 }, - { version: "1.15.0", run: m33 }, - { version: "1.15.3", run: m34 } - // Add new migrations here as they are created -] as const; - -await run(); - -async function run() { - // run the migrations - await runMigrations(); -} - -function backupDb() { - // make dir config/db/backups - const appPath = APP_PATH; - const dbDir = path.join(appPath, "db"); - - const backupsDir = path.join(dbDir, "backups"); - - // check if the backups directory exists and create it if it doesn't - if (!fs.existsSync(backupsDir)) { - fs.mkdirSync(backupsDir, { recursive: true }); - } - - // copy the db.sqlite file to backups - // add the date to the filename - const date = new Date(); - const dateString = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`; - const dbPath = path.join(dbDir, "db.sqlite"); - const backupPath = path.join(backupsDir, `db_${dateString}.sqlite`); - fs.copyFileSync(dbPath, backupPath); -} - -export async function runMigrations() { - if (process.env.DISABLE_MIGRATIONS) { - console.log("Migrations are disabled. Skipping..."); - return; - } - try { - const appVersion = APP_VERSION; - - if (exists) { - await executeScripts(); - } else { - console.log("Running migrations..."); - try { - 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 ?? APP_VERSION; - 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 { - if (!process.env.DISABLE_BACKUP_ON_MIGRATION) { - // Backup the database before running the migration - backupDb(); - } - - 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 SqliteError && - e.code === "SQLITE_CONSTRAINT_UNIQUE" - ) { - console.error("Migration has already run! Skipping..."); - continue; - } - console.error( - `Failed to run migration ${migration.version}:`, - e - ); - throw e; // Re-throw to stop migration process - } - } - - console.log("All migrations completed successfully"); - } catch (error) { - console.error("Migration process failed:", error); - throw error; - } -} From a409ec269b01586d87f80a65c3991e7530ec486e Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 12:13:13 -0800 Subject: [PATCH 062/100] Change back to lokowitz db method --- .github/workflows/test.yml | 4 ++-- Dockerfile | 2 +- package.json | 9 +++------ server/db/README.md | 8 ++++---- server/db/migrate.ts | 3 +++ server/db/pg/index.ts | 1 + server/db/pg/migrate.ts | 4 +--- server/db/sqlite/index.ts | 1 + server/db/sqlite/migrate.ts | 4 +--- 9 files changed, 17 insertions(+), 19 deletions(-) create mode 100644 server/db/migrate.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6af8d798..eec4ff339 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,10 +34,10 @@ jobs: run: npm run set:oss - name: Generate database migrations - run: npm run db:sqlite:generate + 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 diff --git a/Dockerfile b/Dockerfile index 197a10329..f82719a67 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/package.json b/package.json index 93e3c171e..61dc023e6 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,9 @@ "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:sqlite: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", + "db:generate": "drizzle-kit generate --config=./drizzle.config.ts", + "db:push": "npx tsx server/db/pg/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", diff --git a/server/db/README.md b/server/db/README.md index 36c3730b7..1f7d57d1b 100644 --- a/server/db/README.md +++ b/server/db/README.md @@ -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 diff --git a/server/db/migrate.ts b/server/db/migrate.ts new file mode 100644 index 000000000..67ff15ec9 --- /dev/null +++ b/server/db/migrate.ts @@ -0,0 +1,3 @@ +import { runMigrations } from "./"; + +await runMigrations(); diff --git a/server/db/pg/index.ts b/server/db/pg/index.ts index 6e2c79f50..43e2650f0 100644 --- a/server/db/pg/index.ts +++ b/server/db/pg/index.ts @@ -1,3 +1,4 @@ export * from "./driver"; export * from "./schema/schema"; export * from "./schema/privateSchema"; +export * from "./migrate"; diff --git a/server/db/pg/migrate.ts b/server/db/pg/migrate.ts index 2d2abca34..d84cc010f 100644 --- a/server/db/pg/migrate.ts +++ b/server/db/pg/migrate.ts @@ -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(); diff --git a/server/db/sqlite/index.ts b/server/db/sqlite/index.ts index 6e2c79f50..43e2650f0 100644 --- a/server/db/sqlite/index.ts +++ b/server/db/sqlite/index.ts @@ -1,3 +1,4 @@ export * from "./driver"; export * from "./schema/schema"; export * from "./schema/privateSchema"; +export * from "./migrate"; diff --git a/server/db/sqlite/migrate.ts b/server/db/sqlite/migrate.ts index 7c337ae2d..79a3d8c73 100644 --- a/server/db/sqlite/migrate.ts +++ b/server/db/sqlite/migrate.ts @@ -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(); From 6496763aae52cf00f1d9a046c0dd2771c496eb89 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 12:18:42 -0800 Subject: [PATCH 063/100] Cap retention days --- .github/workflows/cicd.yml | 2 +- .../routers/billing/featureLifecycle.ts | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 2ebb46639..1d066d846 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -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. diff --git a/server/private/routers/billing/featureLifecycle.ts b/server/private/routers/billing/featureLifecycle.ts index 46337fed0..35345444a 100644 --- a/server/private/routers/billing/featureLifecycle.ts +++ b/server/private/routers/billing/featureLifecycle.ts @@ -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 { + 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 = {}; + 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; From 94e70219cfdff06513cf3769d276857aea921b76 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 14:12:45 -0800 Subject: [PATCH 064/100] Make install sudo because run is sudo --- src/components/olm-install-commands.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/olm-install-commands.tsx b/src/components/olm-install-commands.tsx index 1728f528e..b8ad8e7df 100644 --- a/src/components/olm-install-commands.tsx +++ b/src/components/olm-install-commands.tsx @@ -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"), From f527c3092370ca01cdd367bf7af3d8cf50a41a16 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 14:21:43 -0800 Subject: [PATCH 065/100] add post auth url --- package.json | 2 +- server/db/pg/schema/schema.ts | 3 ++- server/db/sqlite/schema/schema.ts | 3 ++- server/lib/normalizePostAuthPath.ts | 18 ++++++++++++++++++ server/routers/resource/authWithAccessToken.ts | 9 ++++++++- server/routers/resource/createResource.ts | 8 +++++--- server/routers/resource/getResourceAuthInfo.ts | 4 +++- server/routers/resource/updateResource.ts | 3 ++- src/app/auth/resource/[resourceGuid]/page.tsx | 6 ++++++ 9 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 server/lib/normalizePostAuthPath.ts diff --git a/package.json b/package.json index 61dc023e6..5cb205ceb 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "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:sqlite:generate && npm run db:sqlite:push", "db:generate": "drizzle-kit generate --config=./drizzle.config.ts", - "db:push": "npx tsx server/db/pg/migrate.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", diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 3c9574704..dc6f37587 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -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", { diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 4137db3cb..42b2309b1 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -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", { diff --git a/server/lib/normalizePostAuthPath.ts b/server/lib/normalizePostAuthPath.ts new file mode 100644 index 000000000..7291f1842 --- /dev/null +++ b/server/lib/normalizePostAuthPath.ts @@ -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}`; +} diff --git a/server/routers/resource/authWithAccessToken.ts b/server/routers/resource/authWithAccessToken.ts index 53f72cb21..a580ee40b 100644 --- a/server/routers/resource/authWithAccessToken.ts +++ b/server/routers/resource/authWithAccessToken.ts @@ -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(res, { data: { session: token, - redirectUrl: `${resource.ssl ? "https" : "http"}://${resource.fullDomain}` + redirectUrl }, success: true, error: false, diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index ba1fdba23..232cea266 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -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(); diff --git a/server/routers/resource/getResourceAuthInfo.ts b/server/routers/resource/getResourceAuthInfo.ts index 7959bff52..7def75d5b 100644 --- a/server/routers/resource/getResourceAuthInfo.ts +++ b/server/routers/resource/getResourceAuthInfo.ts @@ -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, diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 79b59a2a5..84b4f5388 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -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" diff --git a/src/app/auth/resource/[resourceGuid]/page.tsx b/src/app/auth/resource/[resourceGuid]/page.tsx index 5bb431a8a..919dfbd81 100644 --- a/src/app/auth/resource/[resourceGuid]/page.tsx +++ b/src/app/auth/resource/[resourceGuid]/page.tsx @@ -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 || From a35586f762cc346d95134c5585d7979e02abd986 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 14:47:32 -0800 Subject: [PATCH 066/100] Add sudo --- src/components/olm-install-commands.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/olm-install-commands.tsx b/src/components/olm-install-commands.tsx index b8ad8e7df..5418c8b93 100644 --- a/src/components/olm-install-commands.tsx +++ b/src/components/olm-install-commands.tsx @@ -55,7 +55,8 @@ export function OlmInstallCommands({ x64: [ { title: t("install"), - command: `curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"` + 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"), From e9d0ad6e371a5ecf8730d319ad6b1f950df744a5 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 14:51:11 -0800 Subject: [PATCH 067/100] use pangolin cli for container --- messages/en-US.json | 2 +- src/components/newt-install-commands.tsx | 4 +-- src/components/olm-install-commands.tsx | 44 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index aa9cb2b09..68f9640b2 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -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", diff --git a/src/components/newt-install-commands.tsx b/src/components/newt-install-commands.tsx index b814fdaea..3561cd6b6 100644 --- a/src/components/newt-install-commands.tsx +++ b/src/components/newt-install-commands.tsx @@ -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]; diff --git a/src/components/olm-install-commands.tsx b/src/components/olm-install-commands.tsx index 5418c8b93..9ff9970f4 100644 --- a/src/components/olm-install-commands.tsx +++ b/src/components/olm-install-commands.tsx @@ -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]; @@ -51,6 +51,27 @@ export function OlmInstallCommands({ } ] }, + docker: { + "Docker Compose": [ + `services: + pangolin-cli: + image: fosrl/pangolin-cli + container_name: pangolin-cli + restart: unless-stopped + network_mode: host + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + environment: + - PANGOLIN_ENDPOINT=${endpoint} + - 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/pangolin-cli up client --id ${id} --secret ${secret} --endpoint ${endpoint} --attach` + ] + }, windows: { x64: [ { @@ -63,27 +84,6 @@ export function OlmInstallCommands({ command: `olm.exe --id ${id} --secret ${secret} --endpoint ${endpoint}` } ] - }, - docker: { - "Docker Compose": [ - `services: - olm: - image: fosrl/olm - container_name: olm - restart: unless-stopped - network_mode: host - cap_add: - - NET_ADMIN - devices: - - /dev/net/tun:/dev/net/tun - environment: - - PANGOLIN_ENDPOINT=${endpoint} - - OLM_ID=${id} - - OLM_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}` - ] } }; From c73d70933b408690b45fc8e9e0559483afa4492f Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 14:52:29 -0800 Subject: [PATCH 068/100] bump version --- server/lib/consts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/consts.ts b/server/lib/consts.ts index 018e017e0..4f7e4d62c 100644 --- a/server/lib/consts.ts +++ b/server/lib/consts.ts @@ -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); From fdce016921b41b6355971b19133e9e1ac01df739 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 15:00:12 -0800 Subject: [PATCH 069/100] add 1.15.4 migration --- server/setup/migrationsPg.ts | 4 +++- server/setup/migrationsSqlite.ts | 4 +++- server/setup/scriptsPg/1.15.4.ts | 30 ++++++++++++++++++++++++++++ server/setup/scriptsSqlite/1.15.4.ts | 27 +++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 server/setup/scriptsPg/1.15.4.ts create mode 100644 server/setup/scriptsSqlite/1.15.4.ts diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index c1ebfa09c..fd28644cb 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -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; diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 170cf93d0..39c133bfa 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -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; diff --git a/server/setup/scriptsPg/1.15.4.ts b/server/setup/scriptsPg/1.15.4.ts new file mode 100644 index 000000000..8c6cfe898 --- /dev/null +++ b/server/setup/scriptsPg/1.15.4.ts @@ -0,0 +1,30 @@ +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 "subscriptions" ADD COLUMN "type" varchar(50);` + ); + 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`); +} diff --git a/server/setup/scriptsSqlite/1.15.4.ts b/server/setup/scriptsSqlite/1.15.4.ts new file mode 100644 index 000000000..35a51024a --- /dev/null +++ b/server/setup/scriptsSqlite/1.15.4.ts @@ -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`); +} From d998a8087fc082eaadb7ba9b95cb8f089eb8ce5e Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 15:06:24 -0800 Subject: [PATCH 070/100] fix pg migration --- server/setup/scriptsPg/1.15.4.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/setup/scriptsPg/1.15.4.ts b/server/setup/scriptsPg/1.15.4.ts index 8c6cfe898..cec04a1a8 100644 --- a/server/setup/scriptsPg/1.15.4.ts +++ b/server/setup/scriptsPg/1.15.4.ts @@ -10,9 +10,6 @@ export default async function migration() { try { await db.execute(sql`BEGIN`); - await db.execute( - sql`ALTER TABLE "subscriptions" ADD COLUMN "type" varchar(50);` - ); await db.execute( sql`ALTER TABLE "resources" ADD COLUMN "postAuthPath" text;` ); From 2767ee9e803d2e7d8d60879792a9c54a73d8a438 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 12 Feb 2026 15:29:08 -0800 Subject: [PATCH 071/100] update pangolin cli links --- src/components/MachineClientsBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MachineClientsBanner.tsx b/src/components/MachineClientsBanner.tsx index 151397c2c..cd32e91fd 100644 --- a/src/components/MachineClientsBanner.tsx +++ b/src/components/MachineClientsBanner.tsx @@ -37,7 +37,7 @@ export const MachineClientsBanner = ({ orgId }: MachineClientsBannerProps) => { From c2e95a0607cbc7a22c15ea8b4501ca967472850e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:06 -0800 Subject: [PATCH 072/100] New translations en-us.json (French) --- messages/fr-FR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 9b2e24d2e..3ca7df2c5 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Les clients de machine sont conçus pour les serveurs et les systèmes automatisés qui ne sont pas associés à un utilisateur spécifique. Ils s'authentifient avec un identifiant et une clé secrète, et peuvent être exécutés avec Pangolin CLI, Olm CLI ou Olm en tant que conteneur.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Conteneur Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Utilisateur", "clientsTableMachineClients": "Machine", "licenseTableValidUntil": "Valable jusqu'au", From ca9c7ce555e5ba231dcad4e9cd235686404cb687 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:08 -0800 Subject: [PATCH 073/100] New translations en-us.json (Spanish) --- messages/es-ES.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index 28c0e7795..13401b406 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Los clientes de máquinas son para servidores y sistemas automatizados que no están asociados con un usuario específico. Se autentican con una ID y un secreto, y pueden ejecutarse con Pangolin CLI, Olm CLI o Olm como un contenedor.", "machineClientsBannerPangolinCLI": "CLI de Pangolin", "machineClientsBannerOlmCLI": "CLI de Olm", - "machineClientsBannerOlmContainer": "Contenedor de Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Usuario", "clientsTableMachineClients": "Maquina", "licenseTableValidUntil": "Válido hasta", From 3c2f930e6b7663739aa769a027b804b18663db81 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:10 -0800 Subject: [PATCH 074/100] New translations en-us.json (Bulgarian) --- messages/bg-BG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 3f48085ac..eb45f4dac 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Машинните клиенти са за сървъри и автоматизирани системи, които не са свързани с конкретен потребител. Те се автентифицират с ID и секретен ключ и могат да работят с Pangolin CLI, Olm CLI или Olm като контейнер.", "machineClientsBannerPangolinCLI": "Pangolin CLI.", "machineClientsBannerOlmCLI": "Olm CLI.", - "machineClientsBannerOlmContainer": "Olm Контейнер.", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Потребител", "clientsTableMachineClients": "Машина", "licenseTableValidUntil": "Валиден до", From b14b68d83c8a1d9fc39f224a2a8b32a20a287d93 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:11 -0800 Subject: [PATCH 075/100] New translations en-us.json (Czech) --- messages/cs-CZ.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index c35488cdd..3b2a25214 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Klientské stroje jsou určeny pro servery a automatizované systémy, které nejsou přiřazeny k žádnému specifickému uživateli. Autentizují se pomocí ID a tajemství, a mohou běžet s Pangolin CLI, Olm CLI nebo Olm jako kontejner.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm kontejner", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Uživatel", "clientsTableMachineClients": "Stroj", "licenseTableValidUntil": "Platná do", From cdb6813384f21021954ead13cc737f4102152769 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:13 -0800 Subject: [PATCH 076/100] New translations en-us.json (German) --- messages/de-DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index ee3d65ea1..4dc8610b5 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Maschinelle Clients sind für Server und automatisierte Systeme, die nicht einem bestimmten Benutzer zugeordnet sind. Sie authentifizieren sich mit einer ID und einem Geheimnis und können mit Pangolin CLI, Olm CLI oder Olm als Container laufen.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm Container", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Benutzer", "clientsTableMachineClients": "Maschine", "licenseTableValidUntil": "Gültig bis", From c4b1831cfee393690ab37621868e63f45f816376 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:14 -0800 Subject: [PATCH 077/100] New translations en-us.json (Italian) --- messages/it-IT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index 30443e98f..1fd7a22b6 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "I client macchina sono destinati ai server e ai sistemi automatizzati che non sono associati a un utente specifico. Si autenticano con un ID e un segreto e possono funzionare con la CLI di Pangolin, la CLI di Olm o Olm come container.", "machineClientsBannerPangolinCLI": "CLI Pangolin", "machineClientsBannerOlmCLI": "CLI Olm", - "machineClientsBannerOlmContainer": "Container Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Utente", "clientsTableMachineClients": "Macchina", "licenseTableValidUntil": "Valido Fino A", From 22c3b8f116a470d5e8aec857ddc8d469d38c4fa1 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:15 -0800 Subject: [PATCH 078/100] New translations en-us.json (Korean) --- messages/ko-KR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 13f0a22ad..ddaff7f1f 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "머신 클라이언트는 특정 사용자와 연결되지 않은 서버 및 자동화된 시스템을 위한 것입니다. 이들은 ID와 비밀을 통해 인증하며, Pangolin CLI, Olm CLI, 또는 Olm 컨테이너로 실행될 수 있습니다.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm 컨테이너", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "사용자", "clientsTableMachineClients": "기계", "licenseTableValidUntil": "유효 기한", From a964a80d85f5532cf43121650c18ed12881cdd80 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:17 -0800 Subject: [PATCH 079/100] New translations en-us.json (Dutch) --- messages/nl-NL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 78783d922..64bfe66d3 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Machineclients zijn bedoeld voor servers en geautomatiseerde systemen die niet aan een specifieke gebruiker zijn gekoppeld. Ze verifiëren met een ID en geheim, en kunnen draaien met Pangolin CLI, Olm CLI, of Olm als een container.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm-container", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Gebruiker", "clientsTableMachineClients": "Machine", "licenseTableValidUntil": "Geldig tot", From 975550c755ca7166a567b65c491f7ab1ef616232 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:18 -0800 Subject: [PATCH 080/100] New translations en-us.json (Polish) --- messages/pl-PL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index b3d3cd94f..62f1adf68 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Klienci maszyn służą dla serwerów i systemów zautomatyzowanych, które nie są powiązane z konkretnym użytkownikiem. Uwierzytelniają się za pomocą identyfikatora i sekretu i mogą działać z Pangolin CLI, Olm CLI lub Olm jako kontener.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Kontener Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Użytkownik", "clientsTableMachineClients": "Maszyna", "licenseTableValidUntil": "Ważny do", From 04dcf57ff32e4f9bb844fc0a054aaad3d45d158d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:20 -0800 Subject: [PATCH 081/100] New translations en-us.json (Portuguese) --- messages/pt-PT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index e79851388..f1606de94 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Clientes de máquina são para servidores e sistemas automatizados que não estão associados a um usuário específico. Eles autenticam com um ID e segredo, e podem ser executados com CLI Pangolin, CLI Olm, ou Olm como um contêiner.", "machineClientsBannerPangolinCLI": "CLI de Pangolin", "machineClientsBannerOlmCLI": "CLI Olm", - "machineClientsBannerOlmContainer": "Contêiner Olm", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Utilizador", "clientsTableMachineClients": "Máquina", "licenseTableValidUntil": "Válido até", From 4d142b93ddac3ec8c49937f94297a081bb05bc4b Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:21 -0800 Subject: [PATCH 082/100] New translations en-us.json (Russian) --- messages/ru-RU.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 4891e0a9f..34b573052 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Клиенты для машин предназначены для серверов и автоматизированных систем, которые не связаны с конкретным пользователем. Они аутентифицируются по ID и секрету и могут работать с Pangolin CLI, Olm CLI или Olm как с контейнером.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm как контейнер", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Пользователь", "clientsTableMachineClients": "Машина", "licenseTableValidUntil": "Действителен до", From a91c002274c622278d7e58a72be9808166dbbea5 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:22 -0800 Subject: [PATCH 083/100] New translations en-us.json (Turkish) --- messages/tr-TR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 0c4c921d1..4a4ea855a 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Makine müşterileri, belirli bir kullanıcı ile ilişkilendirilmemiş sunucular ve otomatik sistemler içindir. Kimlik ve şifreyle doğrulama yaparlar ve Pangolin CLI, Olm CLI veya Olm'yi bir konteyner olarak çalıştırabilirler.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm Konteyner", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Kullanıcı", "clientsTableMachineClients": "Makine", "licenseTableValidUntil": "Geçerli İki Tarih Kadar", From d5820c49023d1fe8b883ace9ab0c681b6ff2b03e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:24 -0800 Subject: [PATCH 084/100] New translations en-us.json (Chinese Simplified) --- messages/zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 7312ba32c..7b90278bf 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "机器客户端适用于不与特定用户关联的服务器与自动化系统。它们使用ID和密钥进行身份验证,并可以与Pangolin CLI、Olm CLI或作为容器运行。", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm 容器", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "用户", "clientsTableMachineClients": "机", "licenseTableValidUntil": "有效期至", From 899e5aa3955f9ba06d188e34d4c4c3236f24262e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 15:41:25 -0800 Subject: [PATCH 085/100] New translations en-us.json (Norwegian Bokmal) --- messages/nb-NO.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index afb9af386..3bc7ae06d 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Maskinklienter er for servere og automatiserte systemer som ikke er tilknyttet en spesifikk bruker. De autentiserer med en ID og et hemmelighetsnummer, og kan kjøre med Pangolin CLI, Olm CLI eller Olm som en container.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Olm Container", + "machineClientsBannerOlmContainer": "Container", "clientsTableUserClients": "Bruker", "clientsTableMachineClients": "Maskin", "licenseTableValidUntil": "Gyldig til", From 2800655e331c563017e962e9129764345c7e918f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:45 -0800 Subject: [PATCH 086/100] New translations en-us.json (French) --- messages/fr-FR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 3ca7df2c5..9b2e24d2e 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Les clients de machine sont conçus pour les serveurs et les systèmes automatisés qui ne sont pas associés à un utilisateur spécifique. Ils s'authentifient avec un identifiant et une clé secrète, et peuvent être exécutés avec Pangolin CLI, Olm CLI ou Olm en tant que conteneur.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Conteneur Olm", "clientsTableUserClients": "Utilisateur", "clientsTableMachineClients": "Machine", "licenseTableValidUntil": "Valable jusqu'au", From 9d4ace9b3e926d7f6aa8a82510cf9e233e2da02a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:47 -0800 Subject: [PATCH 087/100] New translations en-us.json (Spanish) --- messages/es-ES.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index 13401b406..28c0e7795 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Los clientes de máquinas son para servidores y sistemas automatizados que no están asociados con un usuario específico. Se autentican con una ID y un secreto, y pueden ejecutarse con Pangolin CLI, Olm CLI o Olm como un contenedor.", "machineClientsBannerPangolinCLI": "CLI de Pangolin", "machineClientsBannerOlmCLI": "CLI de Olm", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Contenedor de Olm", "clientsTableUserClients": "Usuario", "clientsTableMachineClients": "Maquina", "licenseTableValidUntil": "Válido hasta", From 8d6700d49368d96cbb5ba0eecda37615919fe5ba Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:49 -0800 Subject: [PATCH 088/100] New translations en-us.json (Bulgarian) --- messages/bg-BG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index eb45f4dac..3f48085ac 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Машинните клиенти са за сървъри и автоматизирани системи, които не са свързани с конкретен потребител. Те се автентифицират с ID и секретен ключ и могат да работят с Pangolin CLI, Olm CLI или Olm като контейнер.", "machineClientsBannerPangolinCLI": "Pangolin CLI.", "machineClientsBannerOlmCLI": "Olm CLI.", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm Контейнер.", "clientsTableUserClients": "Потребител", "clientsTableMachineClients": "Машина", "licenseTableValidUntil": "Валиден до", From 516fd0ee8fdd56dc7bba4539ae611a4929d2efca Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:50 -0800 Subject: [PATCH 089/100] New translations en-us.json (Czech) --- messages/cs-CZ.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 3b2a25214..c35488cdd 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Klientské stroje jsou určeny pro servery a automatizované systémy, které nejsou přiřazeny k žádnému specifickému uživateli. Autentizují se pomocí ID a tajemství, a mohou běžet s Pangolin CLI, Olm CLI nebo Olm jako kontejner.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm kontejner", "clientsTableUserClients": "Uživatel", "clientsTableMachineClients": "Stroj", "licenseTableValidUntil": "Platná do", From f3f8bd3125bd5ffe5f3d1c78b8beea5092693680 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:51 -0800 Subject: [PATCH 090/100] New translations en-us.json (German) --- messages/de-DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 4dc8610b5..ee3d65ea1 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Maschinelle Clients sind für Server und automatisierte Systeme, die nicht einem bestimmten Benutzer zugeordnet sind. Sie authentifizieren sich mit einer ID und einem Geheimnis und können mit Pangolin CLI, Olm CLI oder Olm als Container laufen.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm Container", "clientsTableUserClients": "Benutzer", "clientsTableMachineClients": "Maschine", "licenseTableValidUntil": "Gültig bis", From db7971d2f7a4b932b0040c912a5016c703e5dd13 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:53 -0800 Subject: [PATCH 091/100] New translations en-us.json (Italian) --- messages/it-IT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index 1fd7a22b6..30443e98f 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "I client macchina sono destinati ai server e ai sistemi automatizzati che non sono associati a un utente specifico. Si autenticano con un ID e un segreto e possono funzionare con la CLI di Pangolin, la CLI di Olm o Olm come container.", "machineClientsBannerPangolinCLI": "CLI Pangolin", "machineClientsBannerOlmCLI": "CLI Olm", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Container Olm", "clientsTableUserClients": "Utente", "clientsTableMachineClients": "Macchina", "licenseTableValidUntil": "Valido Fino A", From 16ad60b89ad8b6ba699882105db2cfd2456b9061 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:54 -0800 Subject: [PATCH 092/100] New translations en-us.json (Korean) --- messages/ko-KR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index ddaff7f1f..13f0a22ad 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "머신 클라이언트는 특정 사용자와 연결되지 않은 서버 및 자동화된 시스템을 위한 것입니다. 이들은 ID와 비밀을 통해 인증하며, Pangolin CLI, Olm CLI, 또는 Olm 컨테이너로 실행될 수 있습니다.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm 컨테이너", "clientsTableUserClients": "사용자", "clientsTableMachineClients": "기계", "licenseTableValidUntil": "유효 기한", From b7616026dded36d30ba8dc49db6986a059361840 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:55 -0800 Subject: [PATCH 093/100] New translations en-us.json (Dutch) --- messages/nl-NL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 64bfe66d3..78783d922 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Machineclients zijn bedoeld voor servers en geautomatiseerde systemen die niet aan een specifieke gebruiker zijn gekoppeld. Ze verifiëren met een ID en geheim, en kunnen draaien met Pangolin CLI, Olm CLI, of Olm als een container.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm-container", "clientsTableUserClients": "Gebruiker", "clientsTableMachineClients": "Machine", "licenseTableValidUntil": "Geldig tot", From 3b0fd5c592769b8304b397631dd45f322b015dd6 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:57 -0800 Subject: [PATCH 094/100] New translations en-us.json (Polish) --- messages/pl-PL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 62f1adf68..b3d3cd94f 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Klienci maszyn służą dla serwerów i systemów zautomatyzowanych, które nie są powiązane z konkretnym użytkownikiem. Uwierzytelniają się za pomocą identyfikatora i sekretu i mogą działać z Pangolin CLI, Olm CLI lub Olm jako kontener.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Kontener Olm", "clientsTableUserClients": "Użytkownik", "clientsTableMachineClients": "Maszyna", "licenseTableValidUntil": "Ważny do", From 62ea1b40e1e28c3832f38a97c3bcc433c33fb15a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:13:58 -0800 Subject: [PATCH 095/100] New translations en-us.json (Portuguese) --- messages/pt-PT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index f1606de94..e79851388 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Clientes de máquina são para servidores e sistemas automatizados que não estão associados a um usuário específico. Eles autenticam com um ID e segredo, e podem ser executados com CLI Pangolin, CLI Olm, ou Olm como um contêiner.", "machineClientsBannerPangolinCLI": "CLI de Pangolin", "machineClientsBannerOlmCLI": "CLI Olm", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Contêiner Olm", "clientsTableUserClients": "Utilizador", "clientsTableMachineClients": "Máquina", "licenseTableValidUntil": "Válido até", From 970ecb52f0c032f1a5e93193950dff437ec70019 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:14:00 -0800 Subject: [PATCH 096/100] New translations en-us.json (Russian) --- messages/ru-RU.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 34b573052..4891e0a9f 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Клиенты для машин предназначены для серверов и автоматизированных систем, которые не связаны с конкретным пользователем. Они аутентифицируются по ID и секрету и могут работать с Pangolin CLI, Olm CLI или Olm как с контейнером.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm как контейнер", "clientsTableUserClients": "Пользователь", "clientsTableMachineClients": "Машина", "licenseTableValidUntil": "Действителен до", From dd5e834db00503d8e9810ad7845ee6206b43c45f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:14:01 -0800 Subject: [PATCH 097/100] New translations en-us.json (Turkish) --- messages/tr-TR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 4a4ea855a..0c4c921d1 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Makine müşterileri, belirli bir kullanıcı ile ilişkilendirilmemiş sunucular ve otomatik sistemler içindir. Kimlik ve şifreyle doğrulama yaparlar ve Pangolin CLI, Olm CLI veya Olm'yi bir konteyner olarak çalıştırabilirler.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm Konteyner", "clientsTableUserClients": "Kullanıcı", "clientsTableMachineClients": "Makine", "licenseTableValidUntil": "Geçerli İki Tarih Kadar", From 71f63d8e6fea6fce27e4e4a17434d18535dbf901 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:14:03 -0800 Subject: [PATCH 098/100] New translations en-us.json (Chinese Simplified) --- messages/zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 7b90278bf..7312ba32c 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "机器客户端适用于不与特定用户关联的服务器与自动化系统。它们使用ID和密钥进行身份验证,并可以与Pangolin CLI、Olm CLI或作为容器运行。", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm 容器", "clientsTableUserClients": "用户", "clientsTableMachineClients": "机", "licenseTableValidUntil": "有效期至", From 8250946325b9c9bf2eda6e4090500ec401d1aa07 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 12 Feb 2026 16:14:04 -0800 Subject: [PATCH 099/100] New translations en-us.json (Norwegian Bokmal) --- messages/nb-NO.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 3bc7ae06d..afb9af386 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2060,7 +2060,7 @@ "machineClientsBannerDescription": "Maskinklienter er for servere og automatiserte systemer som ikke er tilknyttet en spesifikk bruker. De autentiserer med en ID og et hemmelighetsnummer, og kan kjøre med Pangolin CLI, Olm CLI eller Olm som en container.", "machineClientsBannerPangolinCLI": "Pangolin CLI", "machineClientsBannerOlmCLI": "Olm CLI", - "machineClientsBannerOlmContainer": "Container", + "machineClientsBannerOlmContainer": "Olm Container", "clientsTableUserClients": "Bruker", "clientsTableMachineClients": "Maskin", "licenseTableValidUntil": "Gyldig til", From dbfd7153814c6f30cb4f2c507f1f5b7a13e0b692 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 16:27:51 -0800 Subject: [PATCH 100/100] Fix windows formatting --- src/components/olm-install-commands.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/olm-install-commands.tsx b/src/components/olm-install-commands.tsx index 9ff9970f4..c613d6989 100644 --- a/src/components/olm-install-commands.tsx +++ b/src/components/olm-install-commands.tsx @@ -77,7 +77,7 @@ export function OlmInstallCommands({ { 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"` +curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"` }, { title: t("run"),