From 7d67fb998470f6c3b368907f474077fdca39ffcc Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 5 May 2026 20:07:06 -0700 Subject: [PATCH 001/139] Make sure the domain is defined on a http resource --- .../siteResource/createSiteResource.ts | 36 ++++++++----------- .../siteResource/updateSiteResource.ts | 26 ++++++-------- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index 01f7a0d9c..1cf9c38d5 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -74,16 +74,14 @@ const createSiteResourceSchema = z .refine( (data) => { if (data.mode === "host") { - if (data.mode == "host") { - // Check if it's a valid IP address using zod (v4 or v6) - const isValidIP = z - // .union([z.ipv4(), z.ipv6()]) - .union([z.ipv4()]) // for now lets just do ipv4 until we verify ipv6 works everywhere - .safeParse(data.destination).success; + // Check if it's a valid IP address using zod (v4 or v6) + const isValidIP = z + // .union([z.ipv4(), z.ipv6()]) + .union([z.ipv4()]) // for now lets just do ipv4 until we verify ipv6 works everywhere + .safeParse(data.destination).success; - if (isValidIP) { - return true; - } + if (isValidIP) { + return true; } // Check if it's a valid domain (hostname pattern, TLD not required) @@ -96,17 +94,12 @@ const createSiteResourceSchema = z data.alias.trim() !== ""; return isValidDomain && isValidAlias; // require the alias to be set in the case of domain - } - return true; - }, - { - message: - "Destination must be a valid IPV4 address or valid domain AND alias is required" - } - ) - .refine( - (data) => { - if (data.mode === "cidr") { + } else if (data.mode === "http") { + // we have to have a domainId defined + if (!data.domainId) { + return false; + } + } else if (data.mode === "cidr") { // Check if it's a valid CIDR (v4 or v6) const isValidCIDR = z .union([z.cidrv4(), z.cidrv6()]) @@ -116,7 +109,8 @@ const createSiteResourceSchema = z return true; }, { - message: "Destination must be a valid CIDR notation for cidr mode" + message: + "Destination must be a valid IPV4 address or valid domain AND alias is required" } ) .refine( diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index 8a3f93326..7f8ef3e25 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -104,6 +104,17 @@ const updateSiteResourceSchema = z data.alias.trim() !== ""; return isValidDomain && isValidAlias; // require the alias to be set in the case of domain + } else if (data.mode === "cidr" && data.destination) { + // Check if it's a valid CIDR (v4 or v6) + const isValidCIDR = z + .union([z.cidrv4(), z.cidrv6()]) + .safeParse(data.destination).success; + return isValidCIDR; + } else if (data.mode === "http") { + // we have to have a domainId defined + if (!data.domainId) { + return false; + } } return true; }, @@ -112,21 +123,6 @@ const updateSiteResourceSchema = z "Destination must be a valid IP address or valid domain AND alias is required" } ) - .refine( - (data) => { - if (data.mode === "cidr" && data.destination) { - // Check if it's a valid CIDR (v4 or v6) - const isValidCIDR = z - .union([z.cidrv4(), z.cidrv6()]) - .safeParse(data.destination).success; - return isValidCIDR; - } - return true; - }, - { - message: "Destination must be a valid CIDR notation for cidr mode" - } - ) .refine( (data) => { if (data.mode !== "http") return true; From 62e19a2f4e12d1e4c583a63d4832dcd216960450 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 5 May 2026 20:10:14 -0700 Subject: [PATCH 002/139] Remove the hover effect --- src/components/ShowTrialCard.tsx | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/ShowTrialCard.tsx b/src/components/ShowTrialCard.tsx index dc58483f8..e0d05d392 100644 --- a/src/components/ShowTrialCard.tsx +++ b/src/components/ShowTrialCard.tsx @@ -32,9 +32,15 @@ export default function ShowTrialCard({ const now = Date.now(); const remainingMs = trialExpiresAt - now; - const remainingDays = Math.max(0, Math.ceil(remainingMs / (1000 * 60 * 60 * 24))); + const remainingDays = Math.max( + 0, + Math.ceil(remainingMs / (1000 * 60 * 60 * 24)) + ); const totalMs = TRIAL_DURATION_DAYS * 24 * 60 * 60 * 1000; - const progressPct = Math.min(100, Math.max(0, ((now - (trialExpiresAt - totalMs)) / totalMs) * 100)); + const progressPct = Math.min( + 100, + Math.max(0, ((now - (trialExpiresAt - totalMs)) / totalMs) * 100) + ); // Inverted: full bar at start, drains to empty as trial ends const displayPct = 100 - progressPct; @@ -47,7 +53,7 @@ export default function ShowTrialCard({ @@ -56,7 +62,9 @@ export default function ShowTrialCard({

{remainingDays === 0 ? t("trialExpired") - : t("trialDaysLeftShort", { days: remainingDays })} + : t("trialDaysLeftShort", { + days: remainingDays + })}

@@ -69,16 +77,13 @@ export default function ShowTrialCard({ href={billingHref} className={cn( "group cursor-pointer block", - "rounded-md border bg-secondary p-2 py-3 w-full flex flex-col gap-2 text-sm", - "transition duration-200 ease-in-out hover:bg-secondary/80 dark:hover:bg-secondary/60" + "rounded-md border bg-secondary p-2 py-3 w-full flex flex-col gap-2 text-sm" )} >

- {remainingDays === 0 - ? t("trialExpired") - : t("trialActive")} + {remainingDays === 0 ? t("trialExpired") : t("trialActive")}

@@ -88,7 +93,7 @@ export default function ShowTrialCard({ ? t("trialHasEnded") : t("trialDaysRemaining", { count: remainingDays })} -
+
{t("trialGoToBilling")}
From fab53ba26a65d16feb065bbee36060f376a151d3 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 5 May 2026 20:40:59 -0700 Subject: [PATCH 003/139] Dont show the link if not the org owner --- src/components/LayoutSidebar.tsx | 13 ++++--- src/components/ShowTrialCard.tsx | 65 ++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/components/LayoutSidebar.tsx b/src/components/LayoutSidebar.tsx index 69149c9f4..9ca7c4cc4 100644 --- a/src/components/LayoutSidebar.tsx +++ b/src/components/LayoutSidebar.tsx @@ -129,9 +129,7 @@ export function LayoutSidebar({ user.serverAdmin || Boolean(currentOrg?.isOwner || currentOrg?.isAdmin); const showTrial = - build === "saas" && - Boolean(orgId) && - subscriptionContext?.isTrial; + build === "saas" && Boolean(orgId) && subscriptionContext?.isTrial; return (
- ) :
} + ) : ( +
+ )} {showTrial && (
- +
)} diff --git a/src/components/ShowTrialCard.tsx b/src/components/ShowTrialCard.tsx index e0d05d392..a8133de19 100644 --- a/src/components/ShowTrialCard.tsx +++ b/src/components/ShowTrialCard.tsx @@ -17,9 +17,11 @@ import { useTranslations } from "next-intl"; const TRIAL_DURATION_DAYS = 10; export default function ShowTrialCard({ - isCollapsed + isCollapsed, + isOwner = false }: { isCollapsed?: boolean; + isOwner?: boolean; }) { const context = useSubscriptionStatusContext(); const params = useParams(); @@ -47,16 +49,13 @@ export default function ShowTrialCard({ const billingHref = orgId ? `/${orgId}/settings/billing` : "/"; if (isCollapsed) { - return ( + const icon = ( - + - +

@@ -70,16 +69,16 @@ export default function ShowTrialCard({ ); + + if (isOwner) { + return {icon}; + } + + return icon; } - return ( - + const cardContent = ( + <>

@@ -93,11 +92,37 @@ export default function ShowTrialCard({ ? t("trialHasEnded") : t("trialDaysRemaining", { count: remainingDays })} -

- {t("trialGoToBilling")} - -
+ {isOwner && ( +
+ {t("trialGoToBilling")} + +
+ )}
- + + ); + + if (isOwner) { + return ( + + {cardContent} + + ); + } + + return ( +
+ {cardContent} +
); } From 0c643e91a6c39a50219ed63310a30ba6fa5dddc7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 16:52:05 +0000 Subject: [PATCH 004/139] Initial plan From 1b183d32c0bd2875a7d17274a77a09e9c42e3589 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 16:54:58 +0000 Subject: [PATCH 005/139] Hide alerting features when disable_enterprise_features is set Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/4337e8e4-2110-45ae-bbf9-63f273d2a9a3 Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com> --- .../[orgId]/settings/alerting/create/page.tsx | 16 +++++++++++++++- src/app/navigation.tsx | 14 +++++++++----- src/components/AlertingRulesTable.tsx | 16 ++++++++++++---- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/app/[orgId]/settings/alerting/create/page.tsx b/src/app/[orgId]/settings/alerting/create/page.tsx index 9f3f20611..e6bd0ff13 100644 --- a/src/app/[orgId]/settings/alerting/create/page.tsx +++ b/src/app/[orgId]/settings/alerting/create/page.tsx @@ -3,10 +3,12 @@ import AlertRuleGraphEditor from "@app/components/alert-rule-editor/AlertRuleGraphEditor"; import HeaderTitle from "@app/components/SettingsSectionTitle"; import { defaultFormValues } from "@app/lib/alertRuleForm"; +import { useEnvContext } from "@app/hooks/useEnvContext"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; -import { useParams } from "next/navigation"; +import { useParams, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; +import { useEffect } from "react"; export default function NewAlertRulePage() { const params = useParams(); @@ -14,6 +16,18 @@ export default function NewAlertRulePage() { const t = useTranslations(); const { isPaidUser } = usePaidStatus(); const isPaid = isPaidUser(tierMatrix.alertingRules); + const { env } = useEnvContext(); + const router = useRouter(); + + useEffect(() => { + if (env.flags.disableEnterpriseFeatures) { + router.replace(`/${orgId}/settings/alerting/rules`); + } + }, [env.flags.disableEnterpriseFeatures, orgId, router]); + + if (env.flags.disableEnterpriseFeatures) { + return null; + } return ( <> diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 24dc02a19..1989161a9 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -212,11 +212,15 @@ export const orgNavSections = ( title: "sidebarManagement", icon: , items: [ - { - title: "sidebarAlerting", - href: "/{orgId}/settings/alerting", - icon: - }, + ...(!env?.flags.disableEnterpriseFeatures + ? [ + { + title: "sidebarAlerting", + href: "/{orgId}/settings/alerting", + icon: + } + ] + : []), { title: "sidebarProvisioning", href: "/{orgId}/settings/provisioning", diff --git a/src/components/AlertingRulesTable.tsx b/src/components/AlertingRulesTable.tsx index f8fcf468d..ae0d564e8 100644 --- a/src/components/AlertingRulesTable.tsx +++ b/src/components/AlertingRulesTable.tsx @@ -134,7 +134,9 @@ export default function AlertingRulesTable({ }: AlertingRulesTableProps) { const router = useRouter(); const t = useTranslations(); - const api = createApiClient(useEnvContext()); + const envContext = useEnvContext(); + const api = createApiClient(envContext); + const { env } = envContext; const [isRefreshing, startRefresh] = useTransition(); const { isPaidUser } = usePaidStatus(); const isPaid = isPaidUser(tierMatrix.alertingRules); @@ -426,9 +428,15 @@ export default function AlertingRulesTable({ searchQuery={query} manualFiltering manualSorting - onAdd={() => { - router.push(`/${orgId}/settings/alerting/create`); - }} + onAdd={ + !env.flags.disableEnterpriseFeatures + ? () => { + router.push( + `/${orgId}/settings/alerting/create` + ); + } + : undefined + } onRefresh={refreshList} isRefreshing={isRefreshing || isFiltering} addButtonText={t("alertingAddRule")} From 3ac315b52ed7ced3841c23095e21a46d5cfffb48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 16:55:38 +0000 Subject: [PATCH 006/139] Fix useEffect dependency array in create alert page Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/4337e8e4-2110-45ae-bbf9-63f273d2a9a3 Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com> --- src/app/[orgId]/settings/alerting/create/page.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/[orgId]/settings/alerting/create/page.tsx b/src/app/[orgId]/settings/alerting/create/page.tsx index e6bd0ff13..f431e9f68 100644 --- a/src/app/[orgId]/settings/alerting/create/page.tsx +++ b/src/app/[orgId]/settings/alerting/create/page.tsx @@ -18,14 +18,15 @@ export default function NewAlertRulePage() { const isPaid = isPaidUser(tierMatrix.alertingRules); const { env } = useEnvContext(); const router = useRouter(); + const disableEnterpriseFeatures = env.flags.disableEnterpriseFeatures; useEffect(() => { - if (env.flags.disableEnterpriseFeatures) { + if (disableEnterpriseFeatures) { router.replace(`/${orgId}/settings/alerting/rules`); } - }, [env.flags.disableEnterpriseFeatures, orgId, router]); + }, [disableEnterpriseFeatures, orgId, router]); - if (env.flags.disableEnterpriseFeatures) { + if (disableEnterpriseFeatures) { return null; } From 780feba19c26f01274e457744f099480668ed763 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 10:26:20 -0700 Subject: [PATCH 007/139] Translate the member page --- messages/en-US.json | 45 +++++- src/components/MemberResourcesPortal.tsx | 170 +++++++++++++++-------- 2 files changed, 154 insertions(+), 61 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index ee4ef143d..3e1b29d88 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Wildcard resources may require additional configuration to work properly.", "domainPickerWildcardCertWarningLink": "Learn more", "health": "Health", - "domainPendingErrorTitle": "Verification Issue" + "domainPendingErrorTitle": "Verification Issue", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } diff --git a/src/components/MemberResourcesPortal.tsx b/src/components/MemberResourcesPortal.tsx index 0ca6c550b..b350be06b 100644 --- a/src/components/MemberResourcesPortal.tsx +++ b/src/components/MemberResourcesPortal.tsx @@ -123,6 +123,7 @@ const ResourceFavicon = ({ // Resource Info component const ResourceInfo = ({ resource }: { resource: Resource }) => { + const t = useTranslations(); const hasAuthMethods = resource.sso || resource.password || @@ -141,7 +142,9 @@ const ResourceInfo = ({ resource }: { resource: Resource }) => { {/* Site Information */} {resource.siteName && (
-
Site
+
+ {t("site")} +
{resource.siteName} @@ -157,7 +160,7 @@ const ResourceInfo = ({ resource }: { resource: Resource }) => { } >
- Authentication Methods + {t("memberPortalAuthMethods")}
{resource.sso && ( @@ -166,7 +169,7 @@ const ResourceInfo = ({ resource }: { resource: Resource }) => {
- Single Sign-On (SSO) + {t("memberPortalSso")}
)} @@ -176,7 +179,7 @@ const ResourceInfo = ({ resource }: { resource: Resource }) => {
- Password Protected + {t("memberPortalPasswordProtected")}
)} @@ -185,7 +188,9 @@ const ResourceInfo = ({ resource }: { resource: Resource }) => {
- PIN Code + + {t("memberPortalPinCode")} +
)} {resource.whitelist && ( @@ -193,7 +198,9 @@ const ResourceInfo = ({ resource }: { resource: Resource }) => {
- Email Whitelist + + {t("memberPortalEmailWhitelist")} + )} @@ -208,7 +215,7 @@ const ResourceInfo = ({ resource }: { resource: Resource }) => {
- Resource Disabled + {t("memberPortalResourceDisabled")}
@@ -233,6 +240,7 @@ const PaginationControls = ({ totalItems: number; itemsPerPage: number; }) => { + const t = useTranslations(); const startItem = (currentPage - 1) * itemsPerPage + 1; const endItem = Math.min(currentPage * itemsPerPage, totalItems); @@ -241,7 +249,11 @@ const PaginationControls = ({ return (
- Showing {startItem}-{endItem} of {totalItems} resources + {t("memberPortalShowingResources", { + start: startItem, + end: endItem, + total: totalItems + })}
@@ -253,7 +265,7 @@ const PaginationControls = ({ className="gap-1" > - Previous + {t("memberPortalPrevious")}
@@ -309,7 +321,7 @@ const PaginationControls = ({ disabled={currentPage === totalPages} className="gap-1" > - Next + {t("memberPortalNext")}
@@ -389,13 +401,11 @@ export default function MemberResourcesPortal({ response.data.data.siteResources || [] ); } else { - setError("Failed to load resources"); + setError(t("memberPortalFailedToLoad")); } } catch (err) { console.error("Error fetching user resources:", err); - setError( - "Failed to load resources. Please check your connection and try again." - ); + setError(t("memberPortalFailedToLoadDescription")); } finally { setLoading(false); setRefreshing(false); @@ -526,8 +536,8 @@ export default function MemberResourcesPortal({ return (
{/* Search and Sort Controls - Skeleton */} @@ -554,8 +564,8 @@ export default function MemberResourcesPortal({ return (
@@ -563,7 +573,7 @@ export default function MemberResourcesPortal({

- Unable to Load Resources + {t("memberPortalUnableToLoad")}

{error} @@ -574,7 +584,7 @@ export default function MemberResourcesPortal({ className="gap-2" > - Try Again + {t("memberPortalTryAgain")} @@ -585,8 +595,8 @@ export default function MemberResourcesPortal({ return (

{/* Search and Sort Controls with Refresh */} @@ -595,7 +605,7 @@ export default function MemberResourcesPortal({ {/* Search */}
setSearchQuery(e.target.value)} className="w-full pl-8 bg-card" @@ -607,26 +617,28 @@ export default function MemberResourcesPortal({
@@ -644,7 +656,7 @@ export default function MemberResourcesPortal({ - Refresh + {t("memberPortalRefresh")}
@@ -663,13 +675,15 @@ export default function MemberResourcesPortal({

{searchQuery - ? "No Resources Found" - : "No Resources Available"} + ? t("memberPortalNoResourcesFound") + : t("memberPortalNoResourcesAvailable")}

{searchQuery - ? `No resources match "${searchQuery}". Try adjusting your search terms or clearing the search to see all resources.` - : "You don't have access to any resources yet. Contact your administrator to get access to resources you need."} + ? t("memberPortalNoResourcesMatchSearch", { + query: searchQuery + }) + : t("memberPortalNoResourcesAccess")}

{searchQuery ? ( @@ -678,7 +692,7 @@ export default function MemberResourcesPortal({ variant="outline" className="gap-2" > - Clear Search + {t("memberPortalClearSearch")} ) : ( )}
@@ -704,11 +718,12 @@ export default function MemberResourcesPortal({

- Public Resources + {t("memberPortalPublicResources")}

- Web applications and services accessible via - browser + {t( + "memberPortalPublicResourcesDescription" + )}

@@ -768,9 +783,12 @@ export default function MemberResourcesPortal({ resource.domain ); toast({ - title: "Copied to clipboard", - description: - "Resource URL has been copied to your clipboard.", + title: t( + "memberPortalCopiedToClipboard" + ), + description: t( + "memberPortalCopiedUrlDescription" + ), duration: 2000 }); }} @@ -791,7 +809,7 @@ export default function MemberResourcesPortal({ disabled={!resource.enabled} > - Open Resource + {t("memberPortalOpenResource")}
@@ -806,11 +824,12 @@ export default function MemberResourcesPortal({

- Private Resources + {t("memberPortalPrivateResources")}

- Internal network resources accessible via - client + {t( + "memberPortalPrivateResourcesDescription" + )}

@@ -843,11 +862,16 @@ export default function MemberResourcesPortal({
- Resource Details + {t( + "memberPortalResourceDetails" + )}
- Mode: + {t( + "memberPortalMode" + )} + : { @@ -858,7 +882,10 @@ export default function MemberResourcesPortal({ {siteResource.protocol && (
- Protocol: + {t( + "protocol" + )} + : { @@ -869,7 +896,10 @@ export default function MemberResourcesPortal({ )}
- Destination: + {t( + "memberPortalDestination" + )} + : { @@ -880,7 +910,10 @@ export default function MemberResourcesPortal({ {siteResource.alias && (
- Alias: + {t( + "memberPortalAlias" + )} + : { @@ -891,14 +924,21 @@ export default function MemberResourcesPortal({ )}
- Status: + {t( + "status" + )} + : {siteResource.enabled - ? "Enabled" - : "Disabled"} + ? t( + "enabled" + ) + : t( + "disabled" + )}
@@ -925,9 +965,13 @@ export default function MemberResourcesPortal({ siteResource.alias! ); toast({ - title: "Copied to clipboard", + title: t( + "memberPortalCopiedToClipboard" + ), description: - "Resource alias has been copied to your clipboard.", + t( + "memberPortalCopiedAliasDescription" + ), duration: 2000 }); }} @@ -959,9 +1003,13 @@ export default function MemberResourcesPortal({ siteResource.destination ); toast({ - title: "Copied to clipboard", + title: t( + "memberPortalCopiedToClipboard" + ), description: - "Resource destination has been copied to your clipboard.", + t( + "memberPortalCopiedDestinationDescription" + ), duration: 2000 }); }} @@ -976,7 +1024,9 @@ export default function MemberResourcesPortal({
- Requires Client Connection + {t( + "memberPortalRequiresClientConnection" + )}
From adf4a1ffda5a7398594b1b5500886c4d3d941155 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 11:03:38 -0700 Subject: [PATCH 008/139] Link to http private resources --- server/routers/resource/getUserResources.ts | 8 +++++ src/components/MemberResourcesPortal.tsx | 36 +++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/server/routers/resource/getUserResources.ts b/server/routers/resource/getUserResources.ts index c0f21a440..7b82870ea 100644 --- a/server/routers/resource/getUserResources.ts +++ b/server/routers/resource/getUserResources.ts @@ -151,6 +151,8 @@ export async function getUserResources( destination: string; mode: string; scheme: string | null; + ssl: boolean; + fullDomain: string | null; enabled: boolean; alias: string | null; aliasAddress: string | null; @@ -164,6 +166,8 @@ export async function getUserResources( destination: siteResources.destination, mode: siteResources.mode, scheme: siteResources.scheme, + ssl: siteResources.ssl, + fullDomain: siteResources.fullDomain, enabled: siteResources.enabled, alias: siteResources.alias, aliasAddress: siteResources.aliasAddress @@ -251,6 +255,8 @@ export async function getUserResources( destination: siteResource.destination, mode: siteResource.mode, protocol: siteResource.scheme, + ssl: siteResource.ssl, + fullDomain: siteResource.fullDomain, enabled: siteResource.enabled, alias: siteResource.alias, aliasAddress: siteResource.aliasAddress, @@ -296,6 +302,8 @@ export type GetUserResourcesResponse = { destination: string; mode: string; protocol: string | null; + ssl: boolean; + fullDomain: string | null; enabled: boolean; alias: string | null; aliasAddress: string | null; diff --git a/src/components/MemberResourcesPortal.tsx b/src/components/MemberResourcesPortal.tsx index b350be06b..87d527e11 100644 --- a/src/components/MemberResourcesPortal.tsx +++ b/src/components/MemberResourcesPortal.tsx @@ -40,6 +40,7 @@ import { TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import CopyToClipboard from "@app/components/CopyToClipboard"; // Update Resource type to include site information type Resource = { @@ -64,6 +65,8 @@ type SiteResource = { destination: string; mode: string; protocol: string | null; + ssl: boolean; + fullDomain: string | null; enabled: boolean; alias: string | null; aliasAddress: string | null; @@ -947,7 +950,14 @@ export default function MemberResourcesPortal({
- {siteResource.alias ? ( + {siteResource.mode === "http" && + siteResource.fullDomain ? ( + /* HTTP mode - show as clickable link */ + + ) : siteResource.alias ? ( <> {/* Alias as primary */}
@@ -1021,7 +1031,29 @@ export default function MemberResourcesPortal({
-
+
+ {siteResource.mode === "http" && + siteResource.fullDomain ? ( + + ) : null}
{t( From 5426031cd41c724a9dab656358714833a953544d Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 11:05:08 -0700 Subject: [PATCH 009/139] Remove duplicate ssl toggle --- src/components/InternalResourceForm.tsx | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/components/InternalResourceForm.tsx b/src/components/InternalResourceForm.tsx index bdf8dab48..3d4febe44 100644 --- a/src/components/InternalResourceForm.tsx +++ b/src/components/InternalResourceForm.tsx @@ -1125,30 +1125,6 @@ export function InternalResourceForm({ }} />
- ( - - - - - - )} - />
Date: Wed, 6 May 2026 11:09:14 -0700 Subject: [PATCH 010/139] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 58eb7d628..8d119e2ea 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Les ressources Joker peuvent nécessiter une configuration supplémentaire pour fonctionner correctement.", "domainPickerWildcardCertWarningLink": "En savoir plus", "health": "Santé", - "domainPendingErrorTitle": "Problème de vérification" + "domainPendingErrorTitle": "Problème de vérification", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From 65908fa00f91ac4409974311cc8a88709912a02f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:16 -0700 Subject: [PATCH 011/139] New translations en-us.json (Bulgarian) [ci skip] --- messages/bg-BG.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index e9ca96733..4373a1bfd 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Ресурсите с уайлдкард може да изискват допълнителна конфигурация за правилна работа.", "domainPickerWildcardCertWarningLink": "Научете повече", "health": "Здраве", - "domainPendingErrorTitle": "Проблем при проверка" + "domainPendingErrorTitle": "Проблем при проверка", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From 342b4aeddf5263d24b12123d713aea00cadebc71 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:18 -0700 Subject: [PATCH 012/139] New translations en-us.json (Czech) [ci skip] --- messages/cs-CZ.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 2a1803486..5b8730010 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Zástupné zdroje mohou vyžadovat dodatečnou konfiguraci pro správnou funkci.", "domainPickerWildcardCertWarningLink": "Zjistit více", "health": "Zdraví", - "domainPendingErrorTitle": "Problém s ověřením" + "domainPendingErrorTitle": "Problém s ověřením", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From 7dd50f65fcea511001941f1d58907f4101c9b49f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:20 -0700 Subject: [PATCH 013/139] New translations en-us.json (German) [ci skip] --- messages/de-DE.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 376e934c9..530e113bb 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Wildcard-Ressourcen erfordern möglicherweise zusätzliche Konfigurationen, um ordnungsgemäß zu funktionieren.", "domainPickerWildcardCertWarningLink": "Mehr erfahren", "health": "Gesundheit", - "domainPendingErrorTitle": "Verifizierungsproblem" + "domainPendingErrorTitle": "Verifizierungsproblem", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From 3c6b1781bc9ff0b92abb173e66682a564e044564 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:23 -0700 Subject: [PATCH 014/139] New translations en-us.json (Italian) [ci skip] --- messages/it-IT.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index d98df3cc2..24a7cbab4 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Le risorse wildcard potrebbero richiedere configurazioni aggiuntive per funzionare correttamente.", "domainPickerWildcardCertWarningLink": "Scopri di più", "health": "Salute", - "domainPendingErrorTitle": "Problema di Verifica" + "domainPendingErrorTitle": "Problema di Verifica", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From d649a8353597cd6b59d8204a2105fd366c8ee219 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:25 -0700 Subject: [PATCH 015/139] New translations en-us.json (Korean) [ci skip] --- messages/ko-KR.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index e82112f10..8835dc46b 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "와일드카드 리소스는 올바르게 작동하려면 추가 구성이 필요할 수 있습니다.", "domainPickerWildcardCertWarningLink": "자세히 알아보기", "health": "건강", - "domainPendingErrorTitle": "확인 문제" + "domainPendingErrorTitle": "확인 문제", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From 3bce57c65cb5cccc57202890c393898c12b22713 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:27 -0700 Subject: [PATCH 016/139] New translations en-us.json (Dutch) [ci skip] --- messages/nl-NL.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 60a35cc5d..e4359dd62 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Wildcard-bronnen hebben mogelijk extra configuratie nodig om correct te werken.", "domainPickerWildcardCertWarningLink": "Meer informatie", "health": "Gezondheid", - "domainPendingErrorTitle": "Verificatieprobleem" + "domainPendingErrorTitle": "Verificatieprobleem", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From 31d3b314e9110776a013a766b4509938bf7908f7 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:29 -0700 Subject: [PATCH 017/139] New translations en-us.json (Polish) [ci skip] --- messages/pl-PL.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 45046ce79..fd21f786a 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Uniwersalne zasoby mogą wymagać dodatkowej konfiguracji, aby działać poprawnie.", "domainPickerWildcardCertWarningLink": "Dowiedz się więcej", "health": "Zdrowie", - "domainPendingErrorTitle": "Problem z weryfikacją" + "domainPendingErrorTitle": "Problem z weryfikacją", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From e11527b43030ccd9f461ee4bf55e683d9b8603a7 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:31 -0700 Subject: [PATCH 018/139] New translations en-us.json (Portuguese) [ci skip] --- messages/pt-PT.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 691e25e6d..a7c60c38c 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Recursos curinga podem exigir configurações adicionais para funcionarem corretamente.", "domainPickerWildcardCertWarningLink": "Saiba mais", "health": "Saúde", - "domainPendingErrorTitle": "Problema de Verificação" + "domainPendingErrorTitle": "Problema de Verificação", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From f1bdb25497a53c19699f533ce902539b6b7b8d64 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:33 -0700 Subject: [PATCH 019/139] New translations en-us.json (Russian) [ci skip] --- messages/ru-RU.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index c036dde3a..73fb6de94 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Wildcard ресурсы могут потребовать дополнительной настройки для правильной работы.", "domainPickerWildcardCertWarningLink": "Узнать больше", "health": "Состояние", - "domainPendingErrorTitle": "Проблема с подтверждением" + "domainPendingErrorTitle": "Проблема с подтверждением", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From b17ba96235626c03924c7eadc16ace8525dc2b46 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:35 -0700 Subject: [PATCH 020/139] New translations en-us.json (Turkish) [ci skip] --- messages/tr-TR.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index a7c63be56..9684557e7 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Genel kaynaklar düzgün çalışmak için ek yapılandırma gerektirebilir.", "domainPickerWildcardCertWarningLink": "Daha fazla bilgi", "health": "Sağlık", - "domainPendingErrorTitle": "Doğrulama Sorunu" + "domainPendingErrorTitle": "Doğrulama Sorunu", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From 35ce947e199925f0b71cdd256f5a22d1881cbac9 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:37 -0700 Subject: [PATCH 021/139] New translations en-us.json (Chinese Simplified) [ci skip] --- messages/zh-CN.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 47beca7dd..24f2e1f26 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "通配符资源可能需要额外配置才能正常工作。", "domainPickerWildcardCertWarningLink": "了解更多", "health": "健康", - "domainPendingErrorTitle": "验证问题" + "domainPendingErrorTitle": "验证问题", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From 6d9bfbf08f732c9a06458279ca928a62b3f7a4e5 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:39 -0700 Subject: [PATCH 022/139] New translations en-us.json (Norwegian Bokmal) [ci skip] --- messages/nb-NO.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index b33c457f7..7f428a59f 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Jokertegnressurser kan kreve ekstra konfigurasjon for å fungere skikkelig.", "domainPickerWildcardCertWarningLink": "Lær mer", "health": "Helse", - "domainPendingErrorTitle": "Verifiseringsproblem" + "domainPendingErrorTitle": "Verifiseringsproblem", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From b8e942478d17f61b387f399afbffdd9dd6d3e8c0 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:09:41 -0700 Subject: [PATCH 023/139] New translations en-us.json (Spanish) [ci skip] --- messages/es-ES.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index e93aff379..f5b81e55c 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -3208,5 +3208,48 @@ "domainPickerWildcardCertWarning": "Los recursos comodín pueden requerir configuración adicional para funcionar correctamente.", "domainPickerWildcardCertWarningLink": "Más información", "health": "Salud", - "domainPendingErrorTitle": "Problema de verificación" + "domainPendingErrorTitle": "Problema de verificación", + "memberPortalTitle": "Resources", + "memberPortalDescription": "Resources you have access to in this organization", + "memberPortalSortBy": "Sort by...", + "memberPortalSortNameAsc": "Name A-Z", + "memberPortalSortNameDesc": "Name Z-A", + "memberPortalSortDomainAsc": "Domain A-Z", + "memberPortalSortDomainDesc": "Domain Z-A", + "memberPortalSortEnabledFirst": "Enabled First", + "memberPortalSortDisabledFirst": "Disabled First", + "memberPortalRefresh": "Refresh", + "memberPortalRefreshResources": "Refresh Resources", + "memberPortalFailedToLoad": "Failed to load resources", + "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", + "memberPortalUnableToLoad": "Unable to Load Resources", + "memberPortalTryAgain": "Try Again", + "memberPortalNoResourcesFound": "No Resources Found", + "memberPortalNoResourcesAvailable": "No Resources Available", + "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", + "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", + "memberPortalClearSearch": "Clear Search", + "memberPortalPublicResources": "Public Resources", + "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", + "memberPortalCopiedToClipboard": "Copied to clipboard", + "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", + "memberPortalOpenResource": "Open Resource", + "memberPortalPrivateResources": "Private Resources", + "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", + "memberPortalResourceDetails": "Resource Details", + "memberPortalMode": "Mode", + "memberPortalDestination": "Destination", + "memberPortalAlias": "Alias", + "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", + "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", + "memberPortalRequiresClientConnection": "Requires Client Connection", + "memberPortalAuthMethods": "Authentication Methods", + "memberPortalSso": "Single Sign-On (SSO)", + "memberPortalPasswordProtected": "Password Protected", + "memberPortalPinCode": "PIN Code", + "memberPortalEmailWhitelist": "Email Whitelist", + "memberPortalResourceDisabled": "Resource Disabled", + "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", + "memberPortalPrevious": "Previous", + "memberPortalNext": "Next" } From b63eda64f4855c9dc44f81c77efe11a0db70a4ad Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:26 -0700 Subject: [PATCH 024/139] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 80 ++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 8d119e2ea..dfbee6967 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "En savoir plus", "health": "Santé", "domainPendingErrorTitle": "Problème de vérification", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", + "memberPortalTitle": "Ressources", + "memberPortalDescription": "Ressources auxquelles vous avez accès dans cette organisation", + "memberPortalSortBy": "Trier par...", + "memberPortalSortNameAsc": "Nom A-Z", + "memberPortalSortNameDesc": "Nom Z-A", + "memberPortalSortDomainAsc": "Domaine A-Z", + "memberPortalSortDomainDesc": "Domaine Z-A", + "memberPortalSortEnabledFirst": "Activé en premier", + "memberPortalSortDisabledFirst": "Désactivé en premier", + "memberPortalRefresh": "Actualiser", + "memberPortalRefreshResources": "Actualiser les ressources", + "memberPortalFailedToLoad": "Échec du chargement des ressources", + "memberPortalFailedToLoadDescription": "Échec du chargement des ressources. Veuillez vérifier votre connexion et réessayer.", + "memberPortalUnableToLoad": "Impossible de charger les ressources", + "memberPortalTryAgain": "Réessayer", + "memberPortalNoResourcesFound": "Aucune ressource trouvée", + "memberPortalNoResourcesAvailable": "Aucune ressource disponible", + "memberPortalNoResourcesMatchSearch": "Aucune ressource ne correspond à \"{query}\". Essayez d'ajuster vos termes de recherche ou de vider la recherche pour voir toutes les ressources.", + "memberPortalNoResourcesAccess": "Vous n'avez encore accès à aucune ressource. Contactez votre administrateur pour obtenir l'accès aux ressources dont vous avez besoin.", + "memberPortalClearSearch": "Effacer la recherche", + "memberPortalPublicResources": "Ressources publiques", + "memberPortalPublicResourcesDescription": "Applications et services web accessibles via un navigateur", + "memberPortalCopiedToClipboard": "Copié dans le presse-papiers", + "memberPortalCopiedUrlDescription": "L'URL de la ressource a été copiée dans votre presse-papiers.", + "memberPortalOpenResource": "Ouvrir la ressource", + "memberPortalPrivateResources": "Ressources privées", + "memberPortalPrivateResourcesDescription": "Ressources réseau internes accessibles via un client", + "memberPortalResourceDetails": "Détails de la ressource", "memberPortalMode": "Mode", "memberPortalDestination": "Destination", "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalCopiedAliasDescription": "L'alias de la ressource a été copié dans votre presse-papiers.", + "memberPortalCopiedDestinationDescription": "La destination de la ressource a été copiée dans votre presse-papiers.", + "memberPortalRequiresClientConnection": "Nécessite une connexion client", + "memberPortalAuthMethods": "Méthodes d'authentification", + "memberPortalSso": "Authentification unique (SSO)", + "memberPortalPasswordProtected": "Protégé par un mot de passe", + "memberPortalPinCode": "Code PIN", + "memberPortalEmailWhitelist": "Liste blanche des e-mails", + "memberPortalResourceDisabled": "Ressource désactivée", + "memberPortalShowingResources": "Affichage de {start}-{end} sur {total} ressources", + "memberPortalPrevious": "Précédent", + "memberPortalNext": "Suivant" } From 864d1d5cc42c86b9a68ec631238f8164ff1eb937 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:28 -0700 Subject: [PATCH 025/139] New translations en-us.json (Bulgarian) [ci skip] --- messages/bg-BG.json | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 4373a1bfd..11fc53fe5 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Научете повече", "health": "Здраве", "domainPendingErrorTitle": "Проблем при проверка", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", - "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalTitle": "Ресурси", + "memberPortalDescription": "Ресурси, до които имате достъп в тази организация", + "memberPortalSortBy": "Сортиране по...", + "memberPortalSortNameAsc": "Име А-Я", + "memberPortalSortNameDesc": "Име Я-А", + "memberPortalSortDomainAsc": "Домен А-Я", + "memberPortalSortDomainDesc": "Домен Я-А", + "memberPortalSortEnabledFirst": "Активирани Първи", + "memberPortalSortDisabledFirst": "Деактивирани Първи", + "memberPortalRefresh": "Обнови", + "memberPortalRefreshResources": "Обнови ресурсите", + "memberPortalFailedToLoad": "Грешка при зареждане на ресурсите", + "memberPortalFailedToLoadDescription": "Грешка при зареждане на ресурсите. Моля, проверете връзката си и опитайте отново.", + "memberPortalUnableToLoad": "Неуспешно зареждане на ресурси", + "memberPortalTryAgain": "Опитай отново", + "memberPortalNoResourcesFound": "Няма намерени ресурси", + "memberPortalNoResourcesAvailable": "Няма налични ресурси", + "memberPortalNoResourcesMatchSearch": "Няма ресурси, съвпадащи с \"{query}\". Опитайте да промените търсените условия или нулирайте търсенето, за да видите всички ресурси.", + "memberPortalNoResourcesAccess": "Още нямате достъп до ресурси. Свържете се с вашия администратор, за да получите достъп до нужните ресурси.", + "memberPortalClearSearch": "Изчисти търсенето", + "memberPortalPublicResources": "Публични ресурси", + "memberPortalPublicResourcesDescription": "Уеб приложения и услуги, достъпни през браузър", + "memberPortalCopiedToClipboard": "Копирано в клипборда", + "memberPortalCopiedUrlDescription": "URL адресът на ресурса е копиран в клипборда.", + "memberPortalOpenResource": "Отвори ресурса", + "memberPortalPrivateResources": "Частни ресурси", + "memberPortalPrivateResourcesDescription": "Ресурси на вътрешната мрежа, достъпни чрез клиент", + "memberPortalResourceDetails": "Детайли за ресурса", + "memberPortalMode": "Режим", + "memberPortalDestination": "Дестинация", + "memberPortalAlias": "Алиас", + "memberPortalCopiedAliasDescription": "Алиасът на ресурса е копиран в клипборда.", + "memberPortalCopiedDestinationDescription": "Дестинацията на ресурса е копирана в клипборда.", + "memberPortalRequiresClientConnection": "Изисква връзка с клиента", + "memberPortalAuthMethods": "Методи на удостоверяване", + "memberPortalSso": "Единно вход (SSO)", + "memberPortalPasswordProtected": "Защитено с парола", + "memberPortalPinCode": "ПИН код", + "memberPortalEmailWhitelist": "Бял списък на имейли", + "memberPortalResourceDisabled": "Ресурсът е деактивиран", + "memberPortalShowingResources": "Показва {start}-{end} от {total} ресурси", + "memberPortalPrevious": "Предишен", + "memberPortalNext": "Следващ" } From 6270dce86af28e911751fe8300667d54cb34f3dd Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:30 -0700 Subject: [PATCH 026/139] New translations en-us.json (Czech) [ci skip] --- messages/cs-CZ.json | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 5b8730010..86571a022 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Zjistit více", "health": "Zdraví", "domainPendingErrorTitle": "Problém s ověřením", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", - "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalTitle": "Zdroje", + "memberPortalDescription": "Zdroje, ke kterým máte v této organizaci přístup", + "memberPortalSortBy": "Řadit podle...", + "memberPortalSortNameAsc": "Názvu A-Z", + "memberPortalSortNameDesc": "Názvu Z-A", + "memberPortalSortDomainAsc": "Domény A-Z", + "memberPortalSortDomainDesc": "Domény Z-A", + "memberPortalSortEnabledFirst": "Nejprve povoleno", + "memberPortalSortDisabledFirst": "Nejprve zakázáno", + "memberPortalRefresh": "Aktualizovat", + "memberPortalRefreshResources": "Aktualizovat zdroje", + "memberPortalFailedToLoad": "Nepodařilo se načíst zdroje", + "memberPortalFailedToLoadDescription": "Nepodařilo se načíst zdroje. Zkontrolujte prosím své připojení a zkuste to znovu.", + "memberPortalUnableToLoad": "Nelze načíst zdroje", + "memberPortalTryAgain": "Zkusit znovu", + "memberPortalNoResourcesFound": "Žádné zdroje nebyly nalezeny", + "memberPortalNoResourcesAvailable": "Žádné zdroje nejsou k dispozici", + "memberPortalNoResourcesMatchSearch": "Žádné zdroje neodpovídají \"{query}\". Zkuste přizpůsobit své vyhledávací termíny nebo vyčistit hledání, abyste viděli všechny zdroje.", + "memberPortalNoResourcesAccess": "Zatím nemáte přístup k žádným zdrojům. Kontaktujte svého správce, aby vám poskytl přístup k potřebným zdrojům.", + "memberPortalClearSearch": "Vymazat hledání", + "memberPortalPublicResources": "Veřejné zdroje", + "memberPortalPublicResourcesDescription": "Webové aplikace a služby přístupné přes prohlížeč", + "memberPortalCopiedToClipboard": "Zkopírováno do schránky", + "memberPortalCopiedUrlDescription": "URL zdroje byla zkopírována do vaší schránky.", + "memberPortalOpenResource": "Otevřít zdroj", + "memberPortalPrivateResources": "Soukromé zdroje", + "memberPortalPrivateResourcesDescription": "Interní síťové zdroje přístupné přes klienta", + "memberPortalResourceDetails": "Podrobnosti o zdroji", + "memberPortalMode": "Režim", + "memberPortalDestination": "Cíl", + "memberPortalAlias": "Přezdívka", + "memberPortalCopiedAliasDescription": "Alias zdroje byl zkopírován do vaší schránky.", + "memberPortalCopiedDestinationDescription": "Cíl zdroje byl zkopírován do vaší schránky.", + "memberPortalRequiresClientConnection": "Vyžaduje klientské připojení", + "memberPortalAuthMethods": "Metody ověřování", + "memberPortalSso": "Jedno přihlášení (SSO)", + "memberPortalPasswordProtected": "Heslo chráněno", + "memberPortalPinCode": "PIN kód", + "memberPortalEmailWhitelist": "Seznam povolených emailů", + "memberPortalResourceDisabled": "Zdroj je zakázán", + "memberPortalShowingResources": "Zobrazeny {start}-{end} z {total} zdrojů", + "memberPortalPrevious": "Předchozí", + "memberPortalNext": "Následující" } From 47b3d26d0e60da9128790f249b8de661fdf29646 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:32 -0700 Subject: [PATCH 027/139] New translations en-us.json (German) [ci skip] --- messages/de-DE.json | 74 ++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 530e113bb..d0b8e9115 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Mehr erfahren", "health": "Gesundheit", "domainPendingErrorTitle": "Verifizierungsproblem", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", + "memberPortalTitle": "Ressourcen", + "memberPortalDescription": "Ressourcen, auf die Sie in dieser Organisation Zugriff haben", + "memberPortalSortBy": "Sortieren nach...", "memberPortalSortNameAsc": "Name A-Z", "memberPortalSortNameDesc": "Name Z-A", "memberPortalSortDomainAsc": "Domain A-Z", "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", + "memberPortalSortEnabledFirst": "Zuerst aktiviert", + "memberPortalSortDisabledFirst": "Zuerst deaktiviert", + "memberPortalRefresh": "Aktualisieren", + "memberPortalRefreshResources": "Ressourcen aktualisieren", + "memberPortalFailedToLoad": "Fehler beim Laden der Ressourcen", + "memberPortalFailedToLoadDescription": "Fehler beim Laden der Ressourcen. Bitte überprüfen Sie Ihre Verbindung und versuchen Sie es erneut.", + "memberPortalUnableToLoad": "Ressourcen konnten nicht geladen werden", + "memberPortalTryAgain": "Nochmal versuchen", + "memberPortalNoResourcesFound": "Keine Ressourcen gefunden", + "memberPortalNoResourcesAvailable": "Keine Ressourcen verfügbar", + "memberPortalNoResourcesMatchSearch": "Keine Ressourcen passen zu \"{query}\". Versuchen Sie, Ihre Suchbegriffe anzupassen oder die Suche zu löschen, um alle Ressourcen anzuzeigen.", + "memberPortalNoResourcesAccess": "Sie haben noch keinen Zugriff auf Ressourcen. Wenden Sie sich an Ihren Administrator, um Zugriff auf die benötigten Ressourcen zu erhalten.", + "memberPortalClearSearch": "Suchverlauf löschen", + "memberPortalPublicResources": "Öffentliche Ressourcen", + "memberPortalPublicResourcesDescription": "Webanwendungen und Dienste, die über den Browser zugänglich sind", + "memberPortalCopiedToClipboard": "In die Zwischenablage kopiert", + "memberPortalCopiedUrlDescription": "Ressourcen-URL wurde in Ihre Zwischenablage kopiert.", + "memberPortalOpenResource": "Ressource öffnen", + "memberPortalPrivateResources": "Private Ressourcen", + "memberPortalPrivateResourcesDescription": "Interne Netzwerkressourcen, die über den Client zugänglich sind", + "memberPortalResourceDetails": "Ressourcendetails", + "memberPortalMode": "Modus", + "memberPortalDestination": "Ziel", "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", + "memberPortalCopiedAliasDescription": "Ressourcenalias wurde in Ihre Zwischenablage kopiert.", + "memberPortalCopiedDestinationDescription": "Ressourcenziel wurde in Ihre Zwischenablage kopiert.", + "memberPortalRequiresClientConnection": "Erfordert Client-Verbindung", + "memberPortalAuthMethods": "Authentifizierungsmethoden", "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalPasswordProtected": "Passwortgeschützt", + "memberPortalPinCode": "PIN-Code", + "memberPortalEmailWhitelist": "E-Mail-Whitelist", + "memberPortalResourceDisabled": "Ressource deaktiviert", + "memberPortalShowingResources": "Zeige {start}-{end} von {total} Ressourcen", + "memberPortalPrevious": "Vorherige", + "memberPortalNext": "Nächste" } From ff928b846db7cfb35319ed954176adb466583d20 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:35 -0700 Subject: [PATCH 028/139] New translations en-us.json (Italian) [ci skip] --- messages/it-IT.json | 84 ++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index 24a7cbab4..17040fa30 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Scopri di più", "health": "Salute", "domainPendingErrorTitle": "Problema di Verifica", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", + "memberPortalTitle": "Risorse", + "memberPortalDescription": "Risorse a cui hai accesso in questa organizzazione", + "memberPortalSortBy": "Ordina per...", + "memberPortalSortNameAsc": "Nome A-Z", + "memberPortalSortNameDesc": "Nome Z-A", + "memberPortalSortDomainAsc": "Dominio A-Z", + "memberPortalSortDomainDesc": "Dominio Z-A", + "memberPortalSortEnabledFirst": "Abilitati per primi", + "memberPortalSortDisabledFirst": "Disabilitati per primi", + "memberPortalRefresh": "Aggiorna", + "memberPortalRefreshResources": "Aggiorna Risorse", + "memberPortalFailedToLoad": "Caricamento delle risorse non riuscito", + "memberPortalFailedToLoadDescription": "Caricamento delle risorse non riuscito. Controlla la tua connessione e riprova.", + "memberPortalUnableToLoad": "Impossibile caricare le risorse", + "memberPortalTryAgain": "Riprova", + "memberPortalNoResourcesFound": "Nessuna risorsa trovata", + "memberPortalNoResourcesAvailable": "Nessuna risorsa disponibile", + "memberPortalNoResourcesMatchSearch": "Nessuna risorsa corrisponde a \"{query}\". Prova ad aggiustare i termini di ricerca o a cancellare la ricerca per vedere tutte le risorse.", + "memberPortalNoResourcesAccess": "Non hai ancora accesso a nessuna risorsa. Contatta il tuo amministratore per ottenere l'accesso alle risorse di cui hai bisogno.", + "memberPortalClearSearch": "Cancella Ricerca", + "memberPortalPublicResources": "Risorse Pubbliche", + "memberPortalPublicResourcesDescription": "Applicazioni web e servizi accessibili tramite browser", + "memberPortalCopiedToClipboard": "Copiato negli appunti", + "memberPortalCopiedUrlDescription": "L'URL della risorsa è stato copiato negli appunti.", + "memberPortalOpenResource": "Apri Risorsa", + "memberPortalPrivateResources": "Risorse Private", + "memberPortalPrivateResourcesDescription": "Risorse di rete interne accessibili tramite client", + "memberPortalResourceDetails": "Dettagli della Risorsa", + "memberPortalMode": "Modalità", + "memberPortalDestination": "Destinazione", "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalCopiedAliasDescription": "L'alias della risorsa è stato copiato negli appunti.", + "memberPortalCopiedDestinationDescription": "La destinazione della risorsa è stata copiata negli appunti.", + "memberPortalRequiresClientConnection": "Richiede Connessione Client", + "memberPortalAuthMethods": "Metodi di Autenticazione", + "memberPortalSso": "Accesso unico (Single Sign-On, SSO)", + "memberPortalPasswordProtected": "Protetto da password", + "memberPortalPinCode": "Codice PIN", + "memberPortalEmailWhitelist": "Lista Autorizzazioni Email", + "memberPortalResourceDisabled": "Risorsa Disabilitata", + "memberPortalShowingResources": "Mostrando {start}-{end} di {total} risorse", + "memberPortalPrevious": "Precedente", + "memberPortalNext": "Successivo" } From 08a5785cc5bb3409f60890eecf0c821fe6e4b13a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:37 -0700 Subject: [PATCH 029/139] New translations en-us.json (Korean) [ci skip] --- messages/ko-KR.json | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 8835dc46b..2fd449a63 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "자세히 알아보기", "health": "건강", "domainPendingErrorTitle": "확인 문제", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", - "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalTitle": "리소스", + "memberPortalDescription": "이 조직에서 접근할 수 있는 리소스", + "memberPortalSortBy": "정렬 기준...", + "memberPortalSortNameAsc": "이름 A-Z", + "memberPortalSortNameDesc": "이름 Z-A", + "memberPortalSortDomainAsc": "도메인 A-Z", + "memberPortalSortDomainDesc": "도메인 Z-A", + "memberPortalSortEnabledFirst": "사용 활성화 우선", + "memberPortalSortDisabledFirst": "사용 비활성화 우선", + "memberPortalRefresh": "새로 고침", + "memberPortalRefreshResources": "리소스 새로 고침", + "memberPortalFailedToLoad": "리소스를 불러오는 데 실패했습니다", + "memberPortalFailedToLoadDescription": "리소스를 불러오는 데 실패했습니다. 연결을 확인하고 다시 시도해 주십시오.", + "memberPortalUnableToLoad": "리소스를 가져오는 데 실패했습니다", + "memberPortalTryAgain": "다시 시도", + "memberPortalNoResourcesFound": "리소스를 발견하지 못했습니다", + "memberPortalNoResourcesAvailable": "사용 가능한 리소스가 없습니다", + "memberPortalNoResourcesMatchSearch": "\"{query}\"와 일치하는 리소스가 없습니다. 검색어를 수정하거나 검색을 초기화하여 모든 리소스를 확인하십시오.", + "memberPortalNoResourcesAccess": "아직 접근할 수 있는 리소스가 없습니다. 필요한 리소스 접근을 위해 관리자에게 문의하세요.", + "memberPortalClearSearch": "검색 초기화", + "memberPortalPublicResources": "공공 리소스", + "memberPortalPublicResourcesDescription": "브라우저를 통해 접근 가능한 웹 애플리케이션 및 서비스", + "memberPortalCopiedToClipboard": "클립보드에 복사됨", + "memberPortalCopiedUrlDescription": "리소스 URL이 클립보드에 복사되었습니다.", + "memberPortalOpenResource": "리소스 열기", + "memberPortalPrivateResources": "비공개 리소스", + "memberPortalPrivateResourcesDescription": "클라이언트를 통해 접근 가능한 내부 네트워크 리소스", + "memberPortalResourceDetails": "리소스 세부 정보", + "memberPortalMode": "모드", + "memberPortalDestination": "대상지", + "memberPortalAlias": "별칭", + "memberPortalCopiedAliasDescription": "리소스 별칭이 클립보드에 복사되었습니다.", + "memberPortalCopiedDestinationDescription": "리소스 대상지가 클립보드에 복사되었습니다.", + "memberPortalRequiresClientConnection": "클라이언트 연결 필요", + "memberPortalAuthMethods": "인증 방법", + "memberPortalSso": "싱글 사인온 (SSO)", + "memberPortalPasswordProtected": "비밀번호 보호", + "memberPortalPinCode": "PIN 코드", + "memberPortalEmailWhitelist": "이메일 화이트리스트", + "memberPortalResourceDisabled": "리소스 비활성화됨", + "memberPortalShowingResources": "{start}-{end} 중 {total}개의 리소스를 표시 중", + "memberPortalPrevious": "이전", + "memberPortalNext": "다음" } From a53da85fb493fe661ffeb50c86c21168f46dd4f1 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:39 -0700 Subject: [PATCH 030/139] New translations en-us.json (Dutch) [ci skip] --- messages/nl-NL.json | 82 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index e4359dd62..e91c84276 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Meer informatie", "health": "Gezondheid", "domainPendingErrorTitle": "Verificatieprobleem", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", + "memberPortalTitle": "Bronnen", + "memberPortalDescription": "Bronnen waartoe je toegang hebt binnen deze organisatie", + "memberPortalSortBy": "Sorteren op...", + "memberPortalSortNameAsc": "Naam A-Z", + "memberPortalSortNameDesc": "Naam Z-A", + "memberPortalSortDomainAsc": "Domein A-Z", + "memberPortalSortDomainDesc": "Domein Z-A", + "memberPortalSortEnabledFirst": "Ingeschakeld Eerst", + "memberPortalSortDisabledFirst": "Uitgeschakeld Eerst", + "memberPortalRefresh": "Vernieuwen", + "memberPortalRefreshResources": "Bronnen Vernieuwen", + "memberPortalFailedToLoad": "Fout bij het laden van bronnen", + "memberPortalFailedToLoadDescription": "Fout bij het laden van bronnen. Controleer uw verbinding en probeer het opnieuw.", + "memberPortalUnableToLoad": "Niet in staat om bronnen te laden", + "memberPortalTryAgain": "Probeer Opnieuw", + "memberPortalNoResourcesFound": "Geen Bronnen Gevonden", + "memberPortalNoResourcesAvailable": "Geen Bronnen Beschikbaar", + "memberPortalNoResourcesMatchSearch": "Geen bronnen komen overeen met \"{query}\". Probeer uw zoektermen aan te passen of wis de zoekopdracht om alle bronnen te zien.", + "memberPortalNoResourcesAccess": "Je hebt nog geen toegang tot bronnen. Neem contact op met je beheerder om toegang te krijgen tot de benodigde bronnen.", + "memberPortalClearSearch": "Zoekopdracht Wissen", + "memberPortalPublicResources": "Publieke Bronnen", + "memberPortalPublicResourcesDescription": "Webapplicaties en services toegankelijk via browser", + "memberPortalCopiedToClipboard": "Gekopieerd naar klembord", + "memberPortalCopiedUrlDescription": "Bron URL is naar uw klembord gekopieerd.", + "memberPortalOpenResource": "Bron Openen", + "memberPortalPrivateResources": "Privé Bronnen", + "memberPortalPrivateResourcesDescription": "Interne netwerkbronnen toegankelijk via client", + "memberPortalResourceDetails": "Bron Details", + "memberPortalMode": "Modus", + "memberPortalDestination": "Bestemming", "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", + "memberPortalCopiedAliasDescription": "Bron alias is naar uw klembord gekopieerd.", + "memberPortalCopiedDestinationDescription": "Bron bestemming is naar uw klembord gekopieerd.", + "memberPortalRequiresClientConnection": "Clientverbinding Vereist", + "memberPortalAuthMethods": "Authenticatiemethoden", "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalPasswordProtected": "Wachtwoord Beveiligd", + "memberPortalPinCode": "Pincode", + "memberPortalEmailWhitelist": "E-mail whitelist", + "memberPortalResourceDisabled": "Bron Uitgeschakeld", + "memberPortalShowingResources": "Toont {start}-{end} van {total} bronnen", + "memberPortalPrevious": "Vorige", + "memberPortalNext": "Volgende" } From 8f52a48937254b62a6babb0c56c5ad827bb245af Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:41 -0700 Subject: [PATCH 031/139] New translations en-us.json (Polish) [ci skip] --- messages/pl-PL.json | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index fd21f786a..4b69190c5 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Dowiedz się więcej", "health": "Zdrowie", "domainPendingErrorTitle": "Problem z weryfikacją", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", - "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalTitle": "Zasoby", + "memberPortalDescription": "Zasoby, do których masz dostęp w tej organizacji", + "memberPortalSortBy": "Sortuj według...", + "memberPortalSortNameAsc": "Nazwa A-Z", + "memberPortalSortNameDesc": "Nazwa Z-A", + "memberPortalSortDomainAsc": "Domena A-Z", + "memberPortalSortDomainDesc": "Domena Z-A", + "memberPortalSortEnabledFirst": "Włączone najpierw", + "memberPortalSortDisabledFirst": "Wyłączone najpierw", + "memberPortalRefresh": "Odśwież", + "memberPortalRefreshResources": "Odśwież zasoby", + "memberPortalFailedToLoad": "Nie udało się załadować zasobów", + "memberPortalFailedToLoadDescription": "Nie udało się załadować zasobów. Sprawdź połączenie i spróbuj ponownie.", + "memberPortalUnableToLoad": "Nie można załadować zasobów", + "memberPortalTryAgain": "Spróbuj ponownie", + "memberPortalNoResourcesFound": "Nie znaleziono zasobów", + "memberPortalNoResourcesAvailable": "Brak dostępnych zasobów", + "memberPortalNoResourcesMatchSearch": "Żadne zasoby nie pasują do „{query}”. Spróbuj dostosować swoje warunki wyszukiwania lub wyczyść wyszukiwanie, aby zobaczyć wszystkie zasoby.", + "memberPortalNoResourcesAccess": "Nie masz jeszcze dostępu do żadnych zasobów. Skontaktuj się z administratorem, aby uzyskać dostęp do potrzebnych zasobów.", + "memberPortalClearSearch": "Wyczyść wyszukiwanie", + "memberPortalPublicResources": "Publiczne zasoby", + "memberPortalPublicResourcesDescription": "Aplikacje i usługi internetowe dostępne za pośrednictwem przeglądarki", + "memberPortalCopiedToClipboard": "Skopiowano do schowka", + "memberPortalCopiedUrlDescription": "URL zasobu został skopiowany do schowka.", + "memberPortalOpenResource": "Otwórz zasób", + "memberPortalPrivateResources": "Prywatne zasoby", + "memberPortalPrivateResourcesDescription": "Zasoby sieci wewnętrznej dostępne za pośrednictwem klienta", + "memberPortalResourceDetails": "Szczegóły zasobu", + "memberPortalMode": "Tryb", + "memberPortalDestination": "Miejsce docelowe", + "memberPortalAlias": "Pseudonim", + "memberPortalCopiedAliasDescription": "Alias zasobu został skopiowany do schowka.", + "memberPortalCopiedDestinationDescription": "Miejsce docelowe zasobu zostało skopiowane do schowka.", + "memberPortalRequiresClientConnection": "Wymaga połączenia z klientem", + "memberPortalAuthMethods": "Metody uwierzytelniania", + "memberPortalSso": "Jednorazowe logowanie (SSO)", + "memberPortalPasswordProtected": "Chronione hasłem", + "memberPortalPinCode": "Kod PIN", + "memberPortalEmailWhitelist": "Biała lista e-mail", + "memberPortalResourceDisabled": "Zasób wyłączony", + "memberPortalShowingResources": "Wyświetlanie zasobów od {start} do {end} z {total}", + "memberPortalPrevious": "Poprzedni", + "memberPortalNext": "Następny" } From 4df27b316c67c89eb900f6493dc3d8158b5371bd Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:43 -0700 Subject: [PATCH 032/139] New translations en-us.json (Portuguese) [ci skip] --- messages/pt-PT.json | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index a7c60c38c..ce674a81a 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Saiba mais", "health": "Saúde", "domainPendingErrorTitle": "Problema de Verificação", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", - "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalTitle": "Recursos", + "memberPortalDescription": "Recursos aos quais você tem acesso nesta organização", + "memberPortalSortBy": "Ordenar por...", + "memberPortalSortNameAsc": "Nome A-Z", + "memberPortalSortNameDesc": "Nome Z-A", + "memberPortalSortDomainAsc": "Domínio A-Z", + "memberPortalSortDomainDesc": "Domínio Z-A", + "memberPortalSortEnabledFirst": "Habilitados Primeiro", + "memberPortalSortDisabledFirst": "Desabilitados Primeiro", + "memberPortalRefresh": "Atualizar", + "memberPortalRefreshResources": "Atualizar Recursos", + "memberPortalFailedToLoad": "Falha ao carregar recursos", + "memberPortalFailedToLoadDescription": "Falha ao carregar recursos. Por favor, verifique sua conexão e tente novamente.", + "memberPortalUnableToLoad": "Incapaz de Carregar Recursos", + "memberPortalTryAgain": "Tentar Novamente", + "memberPortalNoResourcesFound": "Nenhum Recurso Encontrado", + "memberPortalNoResourcesAvailable": "Nenhum Recurso Disponível", + "memberPortalNoResourcesMatchSearch": "Nenhum recurso corresponde a \"{query}\". Tente ajustar seus termos de pesquisa ou limpe a pesquisa para ver todos os recursos.", + "memberPortalNoResourcesAccess": "Você ainda não tem acesso a nenhum recurso. Entre em contato com seu administrador para obter acesso aos recursos que precisa.", + "memberPortalClearSearch": "Limpar Pesquisa", + "memberPortalPublicResources": "Recursos Públicos", + "memberPortalPublicResourcesDescription": "Aplicações e serviços web acessíveis via navegador", + "memberPortalCopiedToClipboard": "Copiado para a área de transferência", + "memberPortalCopiedUrlDescription": "A URL do recurso foi copiada para sua área de transferência.", + "memberPortalOpenResource": "Abrir Recurso", + "memberPortalPrivateResources": "Recursos Privados", + "memberPortalPrivateResourcesDescription": "Recursos da rede interna acessíveis via cliente", + "memberPortalResourceDetails": "Detalhes do Recurso", + "memberPortalMode": "Modo", + "memberPortalDestination": "Destino", + "memberPortalAlias": "Apelido", + "memberPortalCopiedAliasDescription": "O apelido do recurso foi copiado para sua área de transferência.", + "memberPortalCopiedDestinationDescription": "O destino do recurso foi copiado para sua área de transferência.", + "memberPortalRequiresClientConnection": "Requer Conexão de Cliente", + "memberPortalAuthMethods": "Métodos de Autenticação", + "memberPortalSso": "Logon Único (SSO)", + "memberPortalPasswordProtected": "Protegido por Senha", + "memberPortalPinCode": "Código PIN", + "memberPortalEmailWhitelist": "Lista de E-mails Permitidos", + "memberPortalResourceDisabled": "Recurso Desativado", + "memberPortalShowingResources": "Mostrando {start}-{end} de {total} recursos", + "memberPortalPrevious": "Anterior", + "memberPortalNext": "Próximo" } From caaae77f74b457a874265ad491c927be227075cc Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:46 -0700 Subject: [PATCH 033/139] New translations en-us.json (Russian) [ci skip] --- messages/ru-RU.json | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 73fb6de94..8f8579fa9 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Узнать больше", "health": "Состояние", "domainPendingErrorTitle": "Проблема с подтверждением", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", - "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalTitle": "Ресурсы", + "memberPortalDescription": "Ресурсы, к которым у вас есть доступ в этой организации", + "memberPortalSortBy": "Сортировать по...", + "memberPortalSortNameAsc": "Имя A-Я", + "memberPortalSortNameDesc": "Имя Я-A", + "memberPortalSortDomainAsc": "Домен A-Я", + "memberPortalSortDomainDesc": "Домен Я-A", + "memberPortalSortEnabledFirst": "Включённые сначала", + "memberPortalSortDisabledFirst": "Отключённые сначала", + "memberPortalRefresh": "Обновить", + "memberPortalRefreshResources": "Обновить ресурсы", + "memberPortalFailedToLoad": "Не удалось загрузить ресурсы", + "memberPortalFailedToLoadDescription": "Не удалось загрузить ресурсы. Пожалуйста, проверьте подключение и попробуйте снова.", + "memberPortalUnableToLoad": "Не удалось загрузить ресурсы", + "memberPortalTryAgain": "Попробуйте снова", + "memberPortalNoResourcesFound": "Ресурсы не найдены", + "memberPortalNoResourcesAvailable": "Нет доступных ресурсов", + "memberPortalNoResourcesMatchSearch": "Нет ресурсов, соответствующих \"{query}\". Попробуйте изменить условия поиска или очистить поиск, чтобы увидеть все ресурсы.", + "memberPortalNoResourcesAccess": "У вас пока нет доступа к ресурсам. Свяжитесь с администратором, чтобы получить доступ к нужным вам ресурсам.", + "memberPortalClearSearch": "Очистить поиск", + "memberPortalPublicResources": "Публичные ресурсы", + "memberPortalPublicResourcesDescription": "Веб-приложения и сервисы, доступные через браузер", + "memberPortalCopiedToClipboard": "Скопировано в буфер обмена", + "memberPortalCopiedUrlDescription": "URL ресурса был скопирован в ваш буфер обмена.", + "memberPortalOpenResource": "Открыть ресурс", + "memberPortalPrivateResources": "Приватные ресурсы", + "memberPortalPrivateResourcesDescription": "Ресурсы внутренней сети, доступные через клиент", + "memberPortalResourceDetails": "Детали ресурса", + "memberPortalMode": "Режим", + "memberPortalDestination": "Назначение", + "memberPortalAlias": "Псевдоним", + "memberPortalCopiedAliasDescription": "Псевдоним ресурса был скопирован в ваш буфер обмена.", + "memberPortalCopiedDestinationDescription": "Назначение ресурса было скопировано в ваш буфер обмена.", + "memberPortalRequiresClientConnection": "Требуется подключение клиента", + "memberPortalAuthMethods": "Методы аутентификации", + "memberPortalSso": "Единый вход (SSO)", + "memberPortalPasswordProtected": "Защищено паролем", + "memberPortalPinCode": "PIN-код", + "memberPortalEmailWhitelist": "Белый список email", + "memberPortalResourceDisabled": "Ресурс отключён", + "memberPortalShowingResources": "Показаны {start}-{end} из {total} ресурсов", + "memberPortalPrevious": "Предыдущий", + "memberPortalNext": "Следующий" } From 428f91b5fa34f33d296f6b738e68e5bfa1759990 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:48 -0700 Subject: [PATCH 034/139] New translations en-us.json (Turkish) [ci skip] --- messages/tr-TR.json | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 9684557e7..6aa50e636 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Daha fazla bilgi", "health": "Sağlık", "domainPendingErrorTitle": "Doğrulama Sorunu", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", - "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalTitle": "Kaynaklar", + "memberPortalDescription": "Bu organizasyondaki erişiminiz olan kaynaklar", + "memberPortalSortBy": "Şuna göre sırala...", + "memberPortalSortNameAsc": "İsim A-Z", + "memberPortalSortNameDesc": "İsim Z-A", + "memberPortalSortDomainAsc": "Alan A-Z", + "memberPortalSortDomainDesc": "Alan Z-A", + "memberPortalSortEnabledFirst": "İlk Etkinleştirilenler", + "memberPortalSortDisabledFirst": "İlk Devre Dışı Bırakılanlar", + "memberPortalRefresh": "Yenile", + "memberPortalRefreshResources": "Kaynakları Yenile", + "memberPortalFailedToLoad": "Kaynaklar yüklenemedi", + "memberPortalFailedToLoadDescription": "Kaynaklar yüklenemedi. Lütfen bağlantınızı kontrol edin ve tekrar deneyin.", + "memberPortalUnableToLoad": "Kaynaklar Yüklenemiyor", + "memberPortalTryAgain": "Tekrar Dene", + "memberPortalNoResourcesFound": "Hiçbir Kaynak Bulunamadı", + "memberPortalNoResourcesAvailable": "Uygun Kaynak Yok", + "memberPortalNoResourcesMatchSearch": "Hiçbir kaynak \"{query}\" ile eşleşmiyor. Arama terimlerinizi değiştirerek veya tüm kaynakları görmek için aramayı temizleyerek deneyin.", + "memberPortalNoResourcesAccess": "Henüz herhangi bir kaynağa erişiminiz yok. İhtiyacınız olan kaynaklara erişim sağlamak için yöneticinizle iletişime geçin.", + "memberPortalClearSearch": "Aramayı Temizle", + "memberPortalPublicResources": "Genel Kaynaklar", + "memberPortalPublicResourcesDescription": "Tarayıcı üzerinden erişilebilen web uygulamaları ve hizmetler", + "memberPortalCopiedToClipboard": "Panoya kopyalandı", + "memberPortalCopiedUrlDescription": "Kaynak URL'si panonuza kopyalandı.", + "memberPortalOpenResource": "Kaynağı Aç", + "memberPortalPrivateResources": "Özel Kaynaklar", + "memberPortalPrivateResourcesDescription": "İstemci üzerinden erişilebilen dahili ağ kaynakları", + "memberPortalResourceDetails": "Kaynak Detayları", + "memberPortalMode": "Mod", + "memberPortalDestination": "Hedef", + "memberPortalAlias": "Takma İsim", + "memberPortalCopiedAliasDescription": "Kaynak takma adı panonuza kopyalandı.", + "memberPortalCopiedDestinationDescription": "Kaynak hedefi panonuza kopyalandı.", + "memberPortalRequiresClientConnection": "İstemci Bağlantısı Gerektirir", + "memberPortalAuthMethods": "Kimlik Doğrulama Yöntemleri", + "memberPortalSso": "Tek Oturum Açma (SSO)", + "memberPortalPasswordProtected": "Parola ile Korunan", + "memberPortalPinCode": "PIN Kodu", + "memberPortalEmailWhitelist": "E-posta Beyaz Listesi", + "memberPortalResourceDisabled": "Kaynak Devre Dışı", + "memberPortalShowingResources": "{total} kaynaktan {start}-{end} gösteriliyor", + "memberPortalPrevious": "Önceki", + "memberPortalNext": "Sonraki" } From 4b777b1488a95934c696460a5589d7c0260360cc Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:50 -0700 Subject: [PATCH 035/139] New translations en-us.json (Chinese Simplified) [ci skip] --- messages/zh-CN.json | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 24f2e1f26..2f80cbe30 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "了解更多", "health": "健康", "domainPendingErrorTitle": "验证问题", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", - "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalTitle": "资源", + "memberPortalDescription": "您在此组织中可以访问的资源", + "memberPortalSortBy": "排序依据……", + "memberPortalSortNameAsc": "名称 A-Z", + "memberPortalSortNameDesc": "名称 Z-A", + "memberPortalSortDomainAsc": "域名 A-Z", + "memberPortalSortDomainDesc": "域名 Z-A", + "memberPortalSortEnabledFirst": "启用优先", + "memberPortalSortDisabledFirst": "禁用优先", + "memberPortalRefresh": "刷新", + "memberPortalRefreshResources": "刷新资源", + "memberPortalFailedToLoad": "加载资源失败", + "memberPortalFailedToLoadDescription": "加载资源失败。请检查您的连接并再试一次。", + "memberPortalUnableToLoad": "无法加载资源", + "memberPortalTryAgain": "再试一次", + "memberPortalNoResourcesFound": "找不到资源", + "memberPortalNoResourcesAvailable": "无可用资源", + "memberPortalNoResourcesMatchSearch": "没有与\"{query}\"匹配的资源。尝试调整您的搜索词或清除搜索以查看所有资源。", + "memberPortalNoResourcesAccess": "您尚无访问任何资源的权限。请联系您的管理员获取所需资源的访问权限。", + "memberPortalClearSearch": "清除搜索", + "memberPortalPublicResources": "公共资源", + "memberPortalPublicResourcesDescription": "通过浏览器可访问的网络应用和服务", + "memberPortalCopiedToClipboard": "已复制到剪贴板", + "memberPortalCopiedUrlDescription": "资源 URL 已复制到您的剪贴板。", + "memberPortalOpenResource": "打开资源", + "memberPortalPrivateResources": "私有资源", + "memberPortalPrivateResourcesDescription": "通过客户端可访问的内部网络资源", + "memberPortalResourceDetails": "资源详情", + "memberPortalMode": "模式", + "memberPortalDestination": "目标", + "memberPortalAlias": "别名", + "memberPortalCopiedAliasDescription": "资源别名已复制到您的剪贴板。", + "memberPortalCopiedDestinationDescription": "资源目的地已复制到您的剪贴板。", + "memberPortalRequiresClientConnection": "需要客户端连接", + "memberPortalAuthMethods": "身份验证方法", + "memberPortalSso": "单一登录 (SSO)", + "memberPortalPasswordProtected": "密码保护", + "memberPortalPinCode": "PIN 码", + "memberPortalEmailWhitelist": "电子邮件白名单", + "memberPortalResourceDisabled": "资源已禁用", + "memberPortalShowingResources": "显示 {start}-{end} 共 {total} 个资源", + "memberPortalPrevious": "上一页", + "memberPortalNext": "下一页" } From 7bb2a5a0a58f2225211e5900f11becddb70300e9 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:52 -0700 Subject: [PATCH 036/139] New translations en-us.json (Norwegian Bokmal) [ci skip] --- messages/nb-NO.json | 86 ++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 7f428a59f..18ab00bbf 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Lær mer", "health": "Helse", "domainPendingErrorTitle": "Verifiseringsproblem", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", - "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalTitle": "Ressurser", + "memberPortalDescription": "Ressurser du har tilgang til i denne organisasjonen", + "memberPortalSortBy": "Sorter etter...", + "memberPortalSortNameAsc": "Navn A-Å", + "memberPortalSortNameDesc": "Navn Å-A", + "memberPortalSortDomainAsc": "Domene A-Å", + "memberPortalSortDomainDesc": "Domene Å-A", + "memberPortalSortEnabledFirst": "Aktivert først", + "memberPortalSortDisabledFirst": "Deaktivert først", + "memberPortalRefresh": "Oppdater", + "memberPortalRefreshResources": "Oppdater ressurser", + "memberPortalFailedToLoad": "Kunne ikke laste inn ressurser", + "memberPortalFailedToLoadDescription": "Kunne ikke laste inn ressurser. Vennligst sjekk tilkoblingen din og prøv igjen.", + "memberPortalUnableToLoad": "Kan ikke laste inn ressurser", + "memberPortalTryAgain": "Prøv igjen", + "memberPortalNoResourcesFound": "Ingen ressurser funnet", + "memberPortalNoResourcesAvailable": "Ingen ressurser tilgjengelig", + "memberPortalNoResourcesMatchSearch": "Ingen ressurser samsvarer med \"{query}\". Prøv å justere søkeordene dine eller fjern søket for å se alle ressurser.", + "memberPortalNoResourcesAccess": "Du har ennå ikke tilgang til noen ressurser. Kontakt administratoren din for å få tilgang til de ressursene du trenger.", + "memberPortalClearSearch": "Fjern søk", + "memberPortalPublicResources": "Offentlige ressurser", + "memberPortalPublicResourcesDescription": "Webapplikasjoner og -tjenester tilgjengelige via nettleser", + "memberPortalCopiedToClipboard": "Kopiert til utklippstavlen", + "memberPortalCopiedUrlDescription": "Ressurs-URL er kopiert til utklippstavlen din.", + "memberPortalOpenResource": "Åpne ressurs", + "memberPortalPrivateResources": "Private ressurser", + "memberPortalPrivateResourcesDescription": "Interne nettverksressurser tilgjengelige via klient", + "memberPortalResourceDetails": "Ressursdetaljer", + "memberPortalMode": "Modus", + "memberPortalDestination": "Destinasjon", + "memberPortalAlias": "Navn", + "memberPortalCopiedAliasDescription": "Ressursalias er kopiert til utklippstavlen din.", + "memberPortalCopiedDestinationDescription": "Ressursdestinasjon er kopiert til utklippstavlen din.", + "memberPortalRequiresClientConnection": "Krever klienttilkobling", + "memberPortalAuthMethods": "Autentiseringsmetoder", + "memberPortalSso": "Enkeltpålogging (SSO)", + "memberPortalPasswordProtected": "Passordbeskyttet", + "memberPortalPinCode": "PIN-kode", + "memberPortalEmailWhitelist": "E-post-hviteliste", + "memberPortalResourceDisabled": "Ressurs deaktivert", + "memberPortalShowingResources": "Viser {start}-{end} av {total} ressurser", + "memberPortalPrevious": "Forrige", + "memberPortalNext": "Neste" } From 46b72b9e8c8e0a82fce0445c0ac6c1f01ce03ea8 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 11:14:54 -0700 Subject: [PATCH 037/139] New translations en-us.json (Spanish) [ci skip] --- messages/es-ES.json | 84 ++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index f5b81e55c..75c465fdd 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -3209,47 +3209,47 @@ "domainPickerWildcardCertWarningLink": "Más información", "health": "Salud", "domainPendingErrorTitle": "Problema de verificación", - "memberPortalTitle": "Resources", - "memberPortalDescription": "Resources you have access to in this organization", - "memberPortalSortBy": "Sort by...", - "memberPortalSortNameAsc": "Name A-Z", - "memberPortalSortNameDesc": "Name Z-A", - "memberPortalSortDomainAsc": "Domain A-Z", - "memberPortalSortDomainDesc": "Domain Z-A", - "memberPortalSortEnabledFirst": "Enabled First", - "memberPortalSortDisabledFirst": "Disabled First", - "memberPortalRefresh": "Refresh", - "memberPortalRefreshResources": "Refresh Resources", - "memberPortalFailedToLoad": "Failed to load resources", - "memberPortalFailedToLoadDescription": "Failed to load resources. Please check your connection and try again.", - "memberPortalUnableToLoad": "Unable to Load Resources", - "memberPortalTryAgain": "Try Again", - "memberPortalNoResourcesFound": "No Resources Found", - "memberPortalNoResourcesAvailable": "No Resources Available", - "memberPortalNoResourcesMatchSearch": "No resources match \"{query}\". Try adjusting your search terms or clearing the search to see all resources.", - "memberPortalNoResourcesAccess": "You don't have access to any resources yet. Contact your administrator to get access to resources you need.", - "memberPortalClearSearch": "Clear Search", - "memberPortalPublicResources": "Public Resources", - "memberPortalPublicResourcesDescription": "Web applications and services accessible via browser", - "memberPortalCopiedToClipboard": "Copied to clipboard", - "memberPortalCopiedUrlDescription": "Resource URL has been copied to your clipboard.", - "memberPortalOpenResource": "Open Resource", - "memberPortalPrivateResources": "Private Resources", - "memberPortalPrivateResourcesDescription": "Internal network resources accessible via client", - "memberPortalResourceDetails": "Resource Details", - "memberPortalMode": "Mode", - "memberPortalDestination": "Destination", + "memberPortalTitle": "Recursos", + "memberPortalDescription": "Recursos a los que tiene acceso en esta organización", + "memberPortalSortBy": "Ordenar por...", + "memberPortalSortNameAsc": "Nombre A-Z", + "memberPortalSortNameDesc": "Nombre Z-A", + "memberPortalSortDomainAsc": "Dominio A-Z", + "memberPortalSortDomainDesc": "Dominio Z-A", + "memberPortalSortEnabledFirst": "Habilitado Primero", + "memberPortalSortDisabledFirst": "Deshabilitado Primero", + "memberPortalRefresh": "Actualizar", + "memberPortalRefreshResources": "Actualizar Recursos", + "memberPortalFailedToLoad": "No se pudieron cargar los recursos", + "memberPortalFailedToLoadDescription": "No se pudieron cargar los recursos. Por favor, revise su conexión e intente de nuevo.", + "memberPortalUnableToLoad": "No se pudieron cargar los recursos", + "memberPortalTryAgain": "Intentar de Nuevo", + "memberPortalNoResourcesFound": "No se encontraron Recursos", + "memberPortalNoResourcesAvailable": "No Hay Recursos Disponibles", + "memberPortalNoResourcesMatchSearch": "No hay recursos que coincidan con \"{query}\". Intenta ajustar tus términos de búsqueda o limpiar la búsqueda para ver todos los recursos.", + "memberPortalNoResourcesAccess": "Aún no tiene acceso a ningún recurso. Comuníquese con su administrador para obtener acceso a los recursos que necesita.", + "memberPortalClearSearch": "Limpiar Búsqueda", + "memberPortalPublicResources": "Recursos Públicos", + "memberPortalPublicResourcesDescription": "Aplicaciones web y servicios accesibles vía navegador", + "memberPortalCopiedToClipboard": "Copiado al portapapeles", + "memberPortalCopiedUrlDescription": "La URL del recurso ha sido copiada a su portapapeles.", + "memberPortalOpenResource": "Abrir Recurso", + "memberPortalPrivateResources": "Recursos Privados", + "memberPortalPrivateResourcesDescription": "Recursos de red interna accesibles vía cliente", + "memberPortalResourceDetails": "Detalles del Recurso", + "memberPortalMode": "Modo", + "memberPortalDestination": "Destino", "memberPortalAlias": "Alias", - "memberPortalCopiedAliasDescription": "Resource alias has been copied to your clipboard.", - "memberPortalCopiedDestinationDescription": "Resource destination has been copied to your clipboard.", - "memberPortalRequiresClientConnection": "Requires Client Connection", - "memberPortalAuthMethods": "Authentication Methods", - "memberPortalSso": "Single Sign-On (SSO)", - "memberPortalPasswordProtected": "Password Protected", - "memberPortalPinCode": "PIN Code", - "memberPortalEmailWhitelist": "Email Whitelist", - "memberPortalResourceDisabled": "Resource Disabled", - "memberPortalShowingResources": "Showing {start}-{end} of {total} resources", - "memberPortalPrevious": "Previous", - "memberPortalNext": "Next" + "memberPortalCopiedAliasDescription": "El alias del recurso ha sido copiado a su portapapeles.", + "memberPortalCopiedDestinationDescription": "El destino del recurso ha sido copiado a su portapapeles.", + "memberPortalRequiresClientConnection": "Requiere Conexión de Cliente", + "memberPortalAuthMethods": "Métodos de Autenticación", + "memberPortalSso": "Inicio de Sesión Único (SSO)", + "memberPortalPasswordProtected": "Protegido por Contraseña", + "memberPortalPinCode": "Código PIN", + "memberPortalEmailWhitelist": "Lista Blanca de Correo", + "memberPortalResourceDisabled": "Recurso Deshabilitado", + "memberPortalShowingResources": "Mostrando {start}-{end} de {total} recursos", + "memberPortalPrevious": "Anterior", + "memberPortalNext": "Siguiente" } From dd18375f233872ed97c0d1cc18b7678a42cd5621 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 13:57:17 -0700 Subject: [PATCH 038/139] Fix org selectors --- .../(private)/idp/[idpId]/general/page.tsx | 1 + .../settings/(private)/idp/create/page.tsx | 990 +++++++++--------- src/app/admin/idp/[idpId]/policies/page.tsx | 3 + src/components/AutoProvisionConfigWidget.tsx | 5 +- src/components/RoleMappingConfigFields.tsx | 14 +- 5 files changed, 529 insertions(+), 484 deletions(-) diff --git a/src/app/[orgId]/settings/(private)/idp/[idpId]/general/page.tsx b/src/app/[orgId]/settings/(private)/idp/[idpId]/general/page.tsx index 69d57345c..b2ad61d67 100644 --- a/src/app/[orgId]/settings/(private)/idp/[idpId]/general/page.tsx +++ b/src/app/[orgId]/settings/(private)/idp/[idpId]/general/page.tsx @@ -500,6 +500,7 @@ export default function GeneralPage() { onAutoProvisionChange={(checked) => { form.setValue("autoProvision", checked); }} + orgId={orgId as string} roleMappingMode={roleMappingMode} onRoleMappingModeChange={(data) => { setRoleMappingMode(data); diff --git a/src/app/[orgId]/settings/(private)/idp/create/page.tsx b/src/app/[orgId]/settings/(private)/idp/create/page.tsx index a7796e2a9..33ef71eee 100644 --- a/src/app/[orgId]/settings/(private)/idp/create/page.tsx +++ b/src/app/[orgId]/settings/(private)/idp/create/page.tsx @@ -246,523 +246,559 @@ export default function Page() { -
- - - - - {t("idpTitle")} - - - {t("idpCreateSettingsDescription")} - - - - { - applyOidcIdpProviderType(form.setValue, next); - }} - /> +
+ + + + + {t("idpTitle")} + + + {t("idpCreateSettingsDescription")} + + + + { + applyOidcIdpProviderType( + form.setValue, + next + ); + }} + /> - + +
+ + ( + + + {t("name")} + + + + + + {t("idpDisplayName")} + + + + )} + /> + + +
+
+
+ + {/* Auto Provision Settings */} + + + + {t("idpAutoProvisionUsers")} + + + + + + +
- ( - - - {t("name")} - - - - - - {t("idpDisplayName")} - - - - )} + { + form.setValue( + "autoProvision", + checked + ); + }} + orgId={params.orgId as string} + roleMappingMode={roleMappingMode} + onRoleMappingModeChange={(data) => { + setRoleMappingMode(data); + }} + roles={roles} + fixedRoleNames={fixedRoleNames} + onFixedRoleNamesChange={ + setFixedRoleNames + } + mappingBuilderClaimPath={ + mappingBuilderClaimPath + } + onMappingBuilderClaimPathChange={ + setMappingBuilderClaimPath + } + mappingBuilderRules={ + mappingBuilderRules + } + onMappingBuilderRulesChange={ + setMappingBuilderRules + } + rawExpression={rawRoleExpression} + onRawExpressionChange={ + setRawRoleExpression + } + orgMappingField={{ + control: form.control, + name: "orgMapping" + }} /> - -
-
- - {/* Auto Provision Settings */} - - - - {t("idpAutoProvisionUsers")} - - - - - - - -
- - { - form.setValue("autoProvision", checked); - }} - roleMappingMode={roleMappingMode} - onRoleMappingModeChange={(data) => { - setRoleMappingMode(data); - }} - roles={roles} - fixedRoleNames={fixedRoleNames} - onFixedRoleNamesChange={setFixedRoleNames} - mappingBuilderClaimPath={ - mappingBuilderClaimPath - } - onMappingBuilderClaimPathChange={ - setMappingBuilderClaimPath - } - mappingBuilderRules={mappingBuilderRules} - onMappingBuilderRulesChange={ - setMappingBuilderRules - } - rawExpression={rawRoleExpression} - onRawExpressionChange={setRawRoleExpression} - orgMappingField={{ - control: form.control, - name: "orgMapping" - }} - /> - - -
-
- - {form.watch("type") === "google" && ( - - - - {t("idpGoogleConfigurationTitle")} - - - {t("idpGoogleConfigurationDescription")} - - - - -
- - ( - - - {t("idpClientId")} - - - - - - {t( - "idpGoogleClientIdDescription" - )} - - - - )} - /> - - ( - - - {t("idpClientSecret")} - - - - - - {t( - "idpGoogleClientSecretDescription" - )} - - - - )} - /> - - -
- )} - {form.watch("type") === "azure" && ( - - - - {t("idpAzureConfigurationTitle")} - - - {t("idpAzureConfigurationDescription")} - - - - -
- - ( - - - {t("idpTenantIdLabel")} - - - - - - {t( - "idpAzureTenantIdDescription" - )} - - - - )} - /> - - ( - - - {t("idpClientId")} - - - - - - {t( - "idpAzureClientIdDescription2" - )} - - - - )} - /> - - ( - - - {t("idpClientSecret")} - - - - - - {t( - "idpAzureClientSecretDescription2" - )} - - - - )} - /> - - -
-
-
- )} - - {form.watch("type") === "oidc" && ( - + {form.watch("type") === "google" && ( - {t("idpOidcConfigure")} + {t("idpGoogleConfigurationTitle")} - {t("idpOidcConfigureDescription")} + {t("idpGoogleConfigurationDescription")} -
- - ( - - - {t("idpClientId")} - - - - - - {t( - "idpClientIdDescription" - )} - - - + + + + > + ( + + + {t("idpClientId")} + + + + + + {t( + "idpGoogleClientIdDescription" + )} + + + + )} + /> - ( - - - {t("idpClientSecret")} - - - - - - {t( - "idpClientSecretDescription" - )} - - - - )} - /> - - ( - - - {t("idpAuthUrl")} - - - - - - {t( - "idpAuthUrlDescription" - )} - - - - )} - /> - - ( - - - {t("idpTokenUrl")} - - - - - - {t( - "idpTokenUrlDescription" - )} - - - - )} - /> - - + ( + + + {t( + "idpClientSecret" + )} + + + + + + {t( + "idpGoogleClientSecretDescription" + )} + + + + )} + /> + + +
+ )} + {form.watch("type") === "azure" && ( - {t("idpToken")} + {t("idpAzureConfigurationTitle")} - {t("idpTokenDescription")} + {t("idpAzureConfigurationDescription")} -
- - ( - - - {t("idpJmespathLabel")} - - - - - - {t( - "idpJmespathLabelDescription" - )} - - - + + + + > + ( + + + {t( + "idpTenantIdLabel" + )} + + + + + + {t( + "idpAzureTenantIdDescription" + )} + + + + )} + /> - ( - - - {t( - "idpJmespathEmailPathOptional" - )} - - - - - - {t( - "idpJmespathEmailPathOptionalDescription" - )} - - - - )} - /> + ( + + + {t("idpClientId")} + + + + + + {t( + "idpAzureClientIdDescription2" + )} + + + + )} + /> - ( - - - {t( - "idpJmespathNamePathOptional" - )} - - - - - - {t( - "idpJmespathNamePathOptionalDescription" - )} - - - - )} - /> - - ( - - - {t( - "idpOidcConfigureScopes" - )} - - - - - - {t( - "idpOidcConfigureScopesDescription" - )} - - - - )} - /> - - + ( + + + {t( + "idpClientSecret" + )} + + + + + + {t( + "idpAzureClientSecretDescription2" + )} + + + + )} + /> + + +
-
- )} -
+ )} -
- - -
+ {form.watch("type") === "oidc" && ( + + + + + {t("idpOidcConfigure")} + + + {t("idpOidcConfigureDescription")} + + + +
+ + ( + + + {t("idpClientId")} + + + + + + {t( + "idpClientIdDescription" + )} + + + + )} + /> + + ( + + + {t( + "idpClientSecret" + )} + + + + + + {t( + "idpClientSecretDescription" + )} + + + + )} + /> + + ( + + + {t("idpAuthUrl")} + + + + + + {t( + "idpAuthUrlDescription" + )} + + + + )} + /> + + ( + + + {t("idpTokenUrl")} + + + + + + {t( + "idpTokenUrlDescription" + )} + + + + )} + /> + + +
+
+ + + + + {t("idpToken")} + + + {t("idpTokenDescription")} + + + +
+ + ( + + + {t( + "idpJmespathLabel" + )} + + + + + + {t( + "idpJmespathLabelDescription" + )} + + + + )} + /> + + ( + + + {t( + "idpJmespathEmailPathOptional" + )} + + + + + + {t( + "idpJmespathEmailPathOptionalDescription" + )} + + + + )} + /> + + ( + + + {t( + "idpJmespathNamePathOptional" + )} + + + + + + {t( + "idpJmespathNamePathOptionalDescription" + )} + + + + )} + /> + + ( + + + {t( + "idpOidcConfigureScopes" + )} + + + + + + {t( + "idpOidcConfigureScopesDescription" + )} + + + + )} + /> + + +
+
+
+ )} + + +
+ + +
); diff --git a/src/app/admin/idp/[idpId]/policies/page.tsx b/src/app/admin/idp/[idpId]/policies/page.tsx index e9438da33..e1a098e83 100644 --- a/src/app/admin/idp/[idpId]/policies/page.tsx +++ b/src/app/admin/idp/[idpId]/policies/page.tsx @@ -681,6 +681,9 @@ export default function PoliciesPage() { control: form.control, name: "orgMapping" }} + orgId={ + editingPolicy?.orgId || policyFormOrgId + } roleMappingFieldIdPrefix="admin-idp-policy-role" roleMappingMode={policyRoleMappingMode} onRoleMappingModeChange={ diff --git a/src/components/AutoProvisionConfigWidget.tsx b/src/components/AutoProvisionConfigWidget.tsx index 4767544d0..4cf939444 100644 --- a/src/components/AutoProvisionConfigWidget.tsx +++ b/src/components/AutoProvisionConfigWidget.tsx @@ -47,6 +47,7 @@ type AutoProvisionConfigWidgetProps = { roleMappingFieldIdPrefix?: string; showFreeformRoleNamesHint?: boolean; autoProvisionSwitchId?: string; + orgId?: string; }; export default function AutoProvisionConfigWidget({ @@ -67,7 +68,8 @@ export default function AutoProvisionConfigWidget({ showAutoProvisionSwitch = true, roleMappingFieldIdPrefix = "org-idp-auto-provision", showFreeformRoleNamesHint = false, - autoProvisionSwitchId = "auto-provision-toggle" + autoProvisionSwitchId = "auto-provision-toggle", + orgId }: AutoProvisionConfigWidgetProps) { const t = useTranslations(); const { isPaidUser } = usePaidStatus(); @@ -106,6 +108,7 @@ export default function AutoProvisionConfigWidget({ showFreeformRoleNamesHint={ showFreeformRoleNamesHint } + orgId={orgId} roleMappingMode={roleMappingMode} onRoleMappingModeChange={onRoleMappingModeChange} roles={roles} diff --git a/src/components/RoleMappingConfigFields.tsx b/src/components/RoleMappingConfigFields.tsx index 906f85f62..08b4e36bc 100644 --- a/src/components/RoleMappingConfigFields.tsx +++ b/src/components/RoleMappingConfigFields.tsx @@ -17,7 +17,6 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { build } from "@server/build"; import { RolesSelector } from "./roles-selector"; -import { useParams } from "next/navigation"; export type RoleMappingRoleOption = { roleId: number; @@ -40,6 +39,8 @@ export type RoleMappingConfigFieldsProps = { fieldIdPrefix?: string; /** When true, show extra hint for global default policies (no org role list). */ showFreeformRoleNamesHint?: boolean; + /** Org ID to use for role lookup. Falls back to URL params when not provided. */ + orgId?: string; }; export default function RoleMappingConfigFields({ @@ -55,14 +56,13 @@ export default function RoleMappingConfigFields({ rawExpression, onRawExpressionChange, fieldIdPrefix = "role-mapping", - showFreeformRoleNamesHint = false + showFreeformRoleNamesHint = false, + orgId }: RoleMappingConfigFieldsProps) { const t = useTranslations(); const { env } = useEnvContext(); const { isPaidUser } = usePaidStatus(); - const { orgId } = useParams(); - const supportsMultipleRolesPerUser = isPaidUser(tierMatrix.fullRbac); const showSingleRoleDisclaimer = !env.flags.disableEnterpriseFeatures && @@ -242,6 +242,7 @@ export default function RoleMappingConfigFields({ showFreeformRoleNamesHint={ showFreeformRoleNamesHint } + orgId={orgId} supportsMultipleRolesPerUser={ supportsMultipleRolesPerUser } @@ -318,7 +319,8 @@ function BuilderRuleRow({ supportsMultipleRolesPerUser, showRemoveButton, onChange, - onRemove + onRemove, + orgId }: { rule: MappingBuilderRule; roleOptions: Tag[]; @@ -330,10 +332,10 @@ function BuilderRuleRow({ showRemoveButton: boolean; onChange: (rule: MappingBuilderRule) => void; onRemove: () => void; + orgId?: string; }) { const t = useTranslations(); const [activeTagIndex, setActiveTagIndex] = useState(null); - const { orgId } = useParams(); return (
Date: Wed, 6 May 2026 14:05:18 -0700 Subject: [PATCH 039/139] Fix the input to be tags --- src/components/RoleMappingConfigFields.tsx | 118 ++++++++++++++++----- 1 file changed, 89 insertions(+), 29 deletions(-) diff --git a/src/components/RoleMappingConfigFields.tsx b/src/components/RoleMappingConfigFields.tsx index 08b4e36bc..3fa96fc86 100644 --- a/src/components/RoleMappingConfigFields.tsx +++ b/src/components/RoleMappingConfigFields.tsx @@ -95,6 +95,10 @@ export default function RoleMappingConfigFields({ } }, [supportsMultipleRolesPerUser, fixedRoleNames, onFixedRoleNamesChange]); + const [fixedRolesActiveTagIndex, setFixedRolesActiveTagIndex] = useState< + number | null + >(null); + const fixedRadioId = `${fieldIdPrefix}-fixed-roles-mode`; const builderRadioId = `${fieldIdPrefix}-mapping-builder-mode`; const rawRadioId = `${fieldIdPrefix}-raw-expression-mode`; @@ -161,38 +165,94 @@ export default function RoleMappingConfigFields({ {roleMappingMode === "fixedRoles" && (
- ({ - id: name, - text: name - }))} - mapRolesByName - orgId={orgId as string} - onSelectRoles={(nextTags) => { - let names = [ - ...new Set(nextTags.map((tag) => tag.text)) - ]; + {restrictToOrgRoles ? ( + ({ + id: name, + text: name + }))} + mapRolesByName + orgId={orgId as string} + onSelectRoles={(nextTags) => { + let names = [ + ...new Set(nextTags.map((tag) => tag.text)) + ]; - if (!supportsMultipleRolesPerUser) { - if ( - names.length === 0 && - fixedRoleNames.length > 0 - ) { - onFixedRoleNamesChange([ - fixedRoleNames[ - fixedRoleNames.length - 1 - ]! - ]); - return; + if (!supportsMultipleRolesPerUser) { + if ( + names.length === 0 && + fixedRoleNames.length > 0 + ) { + onFixedRoleNamesChange([ + fixedRoleNames[ + fixedRoleNames.length - 1 + ]! + ]); + return; + } + if (names.length > 1) { + names = [names[names.length - 1]!]; + } } - if (names.length > 1) { - names = [names[names.length - 1]!]; - } - } - onFixedRoleNamesChange(names); - }} - /> + onFixedRoleNamesChange(names); + }} + /> + ) : ( + ({ + id: name, + text: name + }))} + setTags={(nextTags) => { + const prev = fixedRoleNames.map((name) => ({ + id: name, + text: name + })); + const next = + typeof nextTags === "function" + ? nextTags(prev) + : nextTags; + + let names = [ + ...new Set(next.map((tag) => tag.text)) + ]; + + if (!supportsMultipleRolesPerUser) { + if ( + names.length === 0 && + fixedRoleNames.length > 0 + ) { + onFixedRoleNamesChange([ + fixedRoleNames[ + fixedRoleNames.length - 1 + ]! + ]); + return; + } + if (names.length > 1) { + names = [names[names.length - 1]!]; + } + } + + onFixedRoleNamesChange(names); + }} + activeTagIndex={fixedRolesActiveTagIndex} + setActiveTagIndex={setFixedRolesActiveTagIndex} + placeholder={t( + "roleMappingAssignRolesPlaceholderFreeform" + )} + enableAutocomplete={false} + autocompleteOptions={roleOptions} + restrictTagsToAutocompleteOptions={false} + allowDuplicates={false} + sortTags={true} + size="sm" + styleClasses={{ + inlineTagsContainer: "min-w-0 max-w-full" + }} + /> + )} {showFreeformRoleNamesHint ? t("roleMappingFixedRolesDescriptionDefaultPolicy") From a8c50b86185f10e98322ad4bd2e9e7e7a34bc754 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 14:08:28 -0700 Subject: [PATCH 040/139] Add clear certificates pangctl command --- cli/commands/clearCertificates.ts | 28 ++++++++++++++++++++++++++++ cli/index.ts | 2 ++ 2 files changed, 30 insertions(+) create mode 100644 cli/commands/clearCertificates.ts diff --git a/cli/commands/clearCertificates.ts b/cli/commands/clearCertificates.ts new file mode 100644 index 000000000..ff6ef8239 --- /dev/null +++ b/cli/commands/clearCertificates.ts @@ -0,0 +1,28 @@ +import { CommandModule } from "yargs"; +import { db, certificates } from "@server/db"; + +type ClearCertificatesArgs = {}; + +export const clearCertificates: CommandModule<{}, ClearCertificatesArgs> = { + command: "clear-certificates", + describe: "Delete all entries from the certificates table", + builder: (yargs) => { + return yargs; + }, + handler: async (argv: {}) => { + try { + console.log("Clearing all certificates from the database..."); + + const deleted = await db.delete(certificates).returning(); + + console.log( + `Deleted ${deleted.length} certificate(s) from the database` + ); + + process.exit(0); + } catch (error) { + console.error("Error:", error); + process.exit(1); + } + } +}; diff --git a/cli/index.ts b/cli/index.ts index 7605904ee..3664bb8f8 100644 --- a/cli/index.ts +++ b/cli/index.ts @@ -9,6 +9,7 @@ import { rotateServerSecret } from "./commands/rotateServerSecret"; import { clearLicenseKeys } from "./commands/clearLicenseKeys"; import { deleteClient } from "./commands/deleteClient"; import { generateOrgCaKeys } from "./commands/generateOrgCaKeys"; +import { clearCertificates } from "./commands/clearCertificates"; yargs(hideBin(process.argv)) .scriptName("pangctl") @@ -19,5 +20,6 @@ yargs(hideBin(process.argv)) .command(clearLicenseKeys) .command(deleteClient) .command(generateOrgCaKeys) + .command(clearCertificates) .demandCommand() .help().argv; From 19f89562189ef7f2c9367473d2b1205159e82bfd Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 14:30:57 -0700 Subject: [PATCH 041/139] Support flattened data fields --- server/private/lib/alerts/sendAlertWebhook.ts | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/server/private/lib/alerts/sendAlertWebhook.ts b/server/private/lib/alerts/sendAlertWebhook.ts index dd5088a6c..27e142cc4 100644 --- a/server/private/lib/alerts/sendAlertWebhook.ts +++ b/server/private/lib/alerts/sendAlertWebhook.ts @@ -236,15 +236,43 @@ interface TemplateContext { } /** - * Render a body template with {{event}}, {{timestamp}}, {{status}}, and - * {{data}} placeholders, mirroring the logic in HttpLogDestination. + * Render a body template with {{event}}, {{timestamp}}, {{status}}, {{data}}, + * and individual data-field placeholders (e.g. {{orgId}}, {{siteId}}, …). * - * {{data}} is replaced first (as raw JSON) so that any literal "{{…}}" - * strings inside data values are not re-expanded. + * Replacement order: + * 1. {{data}} → raw JSON of the full data object (prevents re-expansion of + * nested values that might look like placeholders). + * 2. Top-level scalar fields from data (string values are JSON-escaped; + * numbers and booleans are rendered as-is). Unknown placeholders are + * left untouched. + * 3. The fixed top-level keys: event, timestamp, status. */ function renderTemplate(template: string, ctx: TemplateContext): string { - const rendered = template - .replace(/\{\{data\}\}/g, JSON.stringify(ctx.data)) + // Step 1 – expand {{data}} first so its contents are already serialised + // and won't be touched by later passes. + let rendered = template.replace(/\{\{data\}\}/g, JSON.stringify(ctx.data)); + + // Step 2 – expand individual data fields. Only replace placeholders whose + // key actually exists in ctx.data; leave everything else as-is. + for (const [key, value] of Object.entries(ctx.data)) { + if (value === null || value === undefined) continue; + const placeholder = new RegExp( + `\\{\\{${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\}\\}`, + "g" + ); + let serialised: string; + if (typeof value === "string") { + serialised = escapeJsonString(value); + } else if (typeof value === "number" || typeof value === "boolean") { + serialised = String(value); + } else { + serialised = escapeJsonString(JSON.stringify(value)); + } + rendered = rendered.replace(placeholder, serialised); + } + + // Step 3 – expand the fixed top-level keys. + rendered = rendered .replace(/\{\{event\}\}/g, escapeJsonString(ctx.event)) .replace(/\{\{timestamp\}\}/g, escapeJsonString(ctx.timestamp)) .replace(/\{\{status\}\}/g, escapeJsonString(ctx.status)); From f01c9ee41cd8332d01de496a0811e66e70aa1890 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 14:45:18 -0700 Subject: [PATCH 042/139] Try to fix time issue Fixes #3007 --- server/lib/statusHistory.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/server/lib/statusHistory.ts b/server/lib/statusHistory.ts index 3a9b1f6ef..8db76bbb0 100644 --- a/server/lib/statusHistory.ts +++ b/server/lib/statusHistory.ts @@ -24,8 +24,11 @@ export async function getCachedStatusHistory( return cached; } - const nowSec = Math.floor(Date.now() / 1000); - const startSec = nowSec - days * 86400; + // Anchor to UTC midnight so the query window aligns with stable calendar days + const utcToday = new Date(); + utcToday.setUTCHours(0, 0, 0, 0); + const todayMidnightSec = Math.floor(utcToday.getTime() / 1000); + const startSec = todayMidnightSec - days * 86400; const events = await logsDb .select() @@ -110,11 +113,18 @@ export function computeBuckets( days: number ): { buckets: StatusHistoryDayBucket[]; totalDowntime: number } { const nowSec = Math.floor(Date.now() / 1000); + + // Anchor bucket boundaries to UTC midnight so dates are stable calendar days + // and don't drift as the cache expires and is recomputed + const utcToday = new Date(); + utcToday.setUTCHours(0, 0, 0, 0); + const todayMidnightSec = Math.floor(utcToday.getTime() / 1000); + const buckets: StatusHistoryDayBucket[] = []; let totalDowntime = 0; for (let d = 0; d < days; d++) { - const dayStartSec = nowSec - (days - d) * 86400; + const dayStartSec = todayMidnightSec - (days - d) * 86400; const dayEndSec = dayStartSec + 86400; const dayEvents = events.filter( From af1739fbcb84bfd7ece7ca199de8bc2f187fd7a7 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 15:15:03 -0700 Subject: [PATCH 043/139] 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 3d290d93b..b71b3299f 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.18.2"; +export const APP_VERSION = "1.18.3"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); From ce7df5ddaa8c99136c352e380debaaa7556b1420 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 15:19:13 -0700 Subject: [PATCH 044/139] Update log message --- server/setup/scriptsPg/1.18.3.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/setup/scriptsPg/1.18.3.ts b/server/setup/scriptsPg/1.18.3.ts index 301ed820c..e71de3f89 100644 --- a/server/setup/scriptsPg/1.18.3.ts +++ b/server/setup/scriptsPg/1.18.3.ts @@ -3,6 +3,8 @@ import { sql } from "drizzle-orm"; const version = "1.18.3"; +await migration(); + export default async function migration() { console.log(`Running setup script ${version}...`); @@ -77,7 +79,7 @@ export default async function migration() { } console.log( - `Migrated ${existingHealthChecks.length} targetHealthCheck row(s) with corrected IDs` + `Updated names for ${existingHealthChecks.length} existing targetHealthCheck row(s)` ); } catch (e) { console.error("Error while migrating targetHealthCheck rows:", e); From 49c73193422a6ed6b210a12e2d6b0bc2f6241e3a Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 15:51:05 -0700 Subject: [PATCH 045/139] Format and make the error a warning --- server/private/lib/alerts/processAlerts.ts | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/server/private/lib/alerts/processAlerts.ts b/server/private/lib/alerts/processAlerts.ts index a08a55494..c6e7ded9a 100644 --- a/server/private/lib/alerts/processAlerts.ts +++ b/server/private/lib/alerts/processAlerts.ts @@ -29,7 +29,10 @@ import { decrypt } from "@server/lib/crypto"; import logger from "@server/logger"; import { sendAlertWebhook } from "./sendAlertWebhook"; import { sendAlertEmail } from "./sendAlertEmail"; -import { AlertContext, WebhookAlertConfig } from "@server/routers/alertRule/types"; +import { + AlertContext, + WebhookAlertConfig +} from "@server/routers/alertRule/types"; /** * Core alert processing pipeline. @@ -99,7 +102,10 @@ export async function processAlerts(context: AlertContext): Promise { baseConditions, or( eq(alertRules.allHealthChecks, true), - eq(alertHealthChecks.healthCheckId, context.healthCheckId) + eq( + alertHealthChecks.healthCheckId, + context.healthCheckId + ) ) ) ); @@ -208,14 +214,19 @@ async function processRule( for (const action of emailActions) { try { - const recipients = await resolveEmailRecipients(action.emailActionId); + const recipients = await resolveEmailRecipients( + action.emailActionId + ); if (recipients.length > 0) { await sendAlertEmail(recipients, context); await db .update(alertEmailActions) .set({ lastSentAt: now }) .where( - eq(alertEmailActions.emailActionId, action.emailActionId) + eq( + alertEmailActions.emailActionId, + action.emailActionId + ) ); } } catch (err) { @@ -269,7 +280,7 @@ async function processRule( ) ); } catch (err) { - logger.error( + logger.warn( `processAlerts: failed to send alert webhook for action ${action.webhookActionId}`, err ); @@ -289,7 +300,9 @@ async function processRule( * - All users in a role (by `roleId`, resolved via `userOrgRoles`) * - Direct external email addresses */ -async function resolveEmailRecipients(emailActionId: number): Promise { +async function resolveEmailRecipients( + emailActionId: number +): Promise { const rows = await db .select() .from(alertEmailRecipients) From 65ee9b9544ad4587379b95f0565424046f153e5e Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 15:53:46 -0700 Subject: [PATCH 046/139] Add transaction to alias address picking --- server/lib/blueprints/clientResources.ts | 2 +- server/lib/ip.ts | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/server/lib/blueprints/clientResources.ts b/server/lib/blueprints/clientResources.ts index 21476b580..22b951870 100644 --- a/server/lib/blueprints/clientResources.ts +++ b/server/lib/blueprints/clientResources.ts @@ -361,7 +361,7 @@ export async function updateClientResources( } else { let aliasAddress: string | null = null; if (resourceData.mode === "host" || resourceData.mode === "http") { - aliasAddress = await getNextAvailableAliasAddress(orgId); + aliasAddress = await getNextAvailableAliasAddress(orgId, trx); } let domainInfo: diff --git a/server/lib/ip.ts b/server/lib/ip.ts index 929399f7b..1d72a5f43 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -372,9 +372,10 @@ export async function getNextAvailableClientSubnet( } export async function getNextAvailableAliasAddress( - orgId: string + orgId: string, + trx: Transaction | typeof db = db ): Promise { - const [org] = await db.select().from(orgs).where(eq(orgs.orgId, orgId)); + const [org] = await trx.select().from(orgs).where(eq(orgs.orgId, orgId)); if (!org) { throw new Error(`Organization with ID ${orgId} not found`); @@ -390,7 +391,7 @@ export async function getNextAvailableAliasAddress( ); } - const existingAddresses = await db + const existingAddresses = await trx .select({ aliasAddress: siteResources.aliasAddress }) @@ -478,7 +479,12 @@ export type Alias = { alias: string | null; aliasAddress: string | null }; export function generateAliasConfig(allSiteResources: SiteResource[]): Alias[] { return allSiteResources - .filter((sr) => sr.aliasAddress && ((sr.alias && sr.mode == "host") || (sr.fullDomain && sr.mode == "http"))) + .filter( + (sr) => + sr.aliasAddress && + ((sr.alias && sr.mode == "host") || + (sr.fullDomain && sr.mode == "http")) + ) .map((sr) => ({ alias: sr.alias || sr.fullDomain, aliasAddress: sr.aliasAddress From b046ab75134deffb06fe71c08b20fbb7cc23e514 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 15:58:51 -0700 Subject: [PATCH 047/139] Add locks to allocations --- server/lib/ip.ts | 202 ++++++++++++++++++++++++++--------------------- 1 file changed, 114 insertions(+), 88 deletions(-) diff --git a/server/lib/ip.ts b/server/lib/ip.ts index 1d72a5f43..9989b978f 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -6,6 +6,7 @@ import z from "zod"; import logger from "@server/logger"; import semver from "semver"; import { getValidCertificatesForDomains } from "#dynamic/lib/certificates"; +import { lockManager } from "#dynamic/lib/lock"; interface IPRange { start: bigint; @@ -327,121 +328,146 @@ export async function getNextAvailableClientSubnet( orgId: string, transaction: Transaction | typeof db = db ): Promise { - const [org] = await transaction - .select() - .from(orgs) - .where(eq(orgs.orgId, orgId)); + return await lockManager.withLock( + `client-subnet-allocation:${orgId}`, + async () => { + const [org] = await transaction + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)); - if (!org) { - throw new Error(`Organization with ID ${orgId} not found`); - } + if (!org) { + throw new Error(`Organization with ID ${orgId} not found`); + } - if (!org.subnet) { - throw new Error(`Organization with ID ${orgId} has no subnet defined`); - } + if (!org.subnet) { + throw new Error( + `Organization with ID ${orgId} has no subnet defined` + ); + } - const existingAddressesSites = await transaction - .select({ - address: sites.address - }) - .from(sites) - .where(and(isNotNull(sites.address), eq(sites.orgId, orgId))); + const existingAddressesSites = await transaction + .select({ + address: sites.address + }) + .from(sites) + .where(and(isNotNull(sites.address), eq(sites.orgId, orgId))); - const existingAddressesClients = await transaction - .select({ - address: clients.subnet - }) - .from(clients) - .where(and(isNotNull(clients.subnet), eq(clients.orgId, orgId))); + const existingAddressesClients = await transaction + .select({ + address: clients.subnet + }) + .from(clients) + .where( + and(isNotNull(clients.subnet), eq(clients.orgId, orgId)) + ); - const addresses = [ - ...existingAddressesSites.map( - (site) => `${site.address?.split("/")[0]}/32` - ), // we are overriding the 32 so that we pick individual addresses in the subnet of the org for the site and the client even though they are stored with the /block_size of the org - ...existingAddressesClients.map( - (client) => `${client.address.split("/")}/32` - ) - ].filter((address) => address !== null) as string[]; + const addresses = [ + ...existingAddressesSites.map( + (site) => `${site.address?.split("/")[0]}/32` + ), // we are overriding the 32 so that we pick individual addresses in the subnet of the org for the site and the client even though they are stored with the /block_size of the org + ...existingAddressesClients.map( + (client) => `${client.address.split("/")}/32` + ) + ].filter((address) => address !== null) as string[]; - const subnet = findNextAvailableCidr(addresses, 32, org.subnet); // pick the sites address in the org - if (!subnet) { - throw new Error("No available subnets remaining in space"); - } + const subnet = findNextAvailableCidr(addresses, 32, org.subnet); // pick the sites address in the org + if (!subnet) { + throw new Error("No available subnets remaining in space"); + } - return subnet; + return subnet; + } + ); } export async function getNextAvailableAliasAddress( orgId: string, trx: Transaction | typeof db = db ): Promise { - const [org] = await trx.select().from(orgs).where(eq(orgs.orgId, orgId)); + return await lockManager.withLock( + `alias-address-allocation:${orgId}`, + async () => { + const [org] = await trx + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)); - if (!org) { - throw new Error(`Organization with ID ${orgId} not found`); - } + if (!org) { + throw new Error(`Organization with ID ${orgId} not found`); + } - if (!org.subnet) { - throw new Error(`Organization with ID ${orgId} has no subnet defined`); - } + if (!org.subnet) { + throw new Error( + `Organization with ID ${orgId} has no subnet defined` + ); + } - if (!org.utilitySubnet) { - throw new Error( - `Organization with ID ${orgId} has no utility subnet defined` - ); - } + if (!org.utilitySubnet) { + throw new Error( + `Organization with ID ${orgId} has no utility subnet defined` + ); + } - const existingAddresses = await trx - .select({ - aliasAddress: siteResources.aliasAddress - }) - .from(siteResources) - .where( - and( - isNotNull(siteResources.aliasAddress), - eq(siteResources.orgId, orgId) - ) - ); + const existingAddresses = await trx + .select({ + aliasAddress: siteResources.aliasAddress + }) + .from(siteResources) + .where( + and( + isNotNull(siteResources.aliasAddress), + eq(siteResources.orgId, orgId) + ) + ); - const addresses = [ - ...existingAddresses.map( - (site) => `${site.aliasAddress?.split("/")[0]}/32` - ), - // reserve a /29 for the dns server and other stuff - `${org.utilitySubnet.split("/")[0]}/29` - ].filter((address) => address !== null) as string[]; + const addresses = [ + ...existingAddresses.map( + (site) => `${site.aliasAddress?.split("/")[0]}/32` + ), + // reserve a /29 for the dns server and other stuff + `${org.utilitySubnet.split("/")[0]}/29` + ].filter((address) => address !== null) as string[]; - let subnet = findNextAvailableCidr(addresses, 32, org.utilitySubnet); - if (!subnet) { - throw new Error("No available subnets remaining in space"); - } + let subnet = findNextAvailableCidr( + addresses, + 32, + org.utilitySubnet + ); + if (!subnet) { + throw new Error("No available subnets remaining in space"); + } - // remove the cidr - subnet = subnet.split("/")[0]; + // remove the cidr + subnet = subnet.split("/")[0]; - return subnet; + return subnet; + } + ); } export async function getNextAvailableOrgSubnet(): Promise { - const existingAddresses = await db - .select({ - subnet: orgs.subnet - }) - .from(orgs) - .where(isNotNull(orgs.subnet)); + return await lockManager.withLock("org-subnet-allocation", async () => { + const existingAddresses = await db + .select({ + subnet: orgs.subnet + }) + .from(orgs) + .where(isNotNull(orgs.subnet)); - const addresses = existingAddresses.map((org) => org.subnet!); + const addresses = existingAddresses.map((org) => org.subnet!); - const subnet = findNextAvailableCidr( - addresses, - config.getRawConfig().orgs.block_size, - config.getRawConfig().orgs.subnet_group - ); - if (!subnet) { - throw new Error("No available subnets remaining in space"); - } + const subnet = findNextAvailableCidr( + addresses, + config.getRawConfig().orgs.block_size, + config.getRawConfig().orgs.subnet_group + ); + if (!subnet) { + throw new Error("No available subnets remaining in space"); + } - return subnet; + return subnet; + }); } export function generateRemoteSubnets( From 998364b09d90cd18897aff4dcf767e2566310b95 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 16:13:07 -0700 Subject: [PATCH 048/139] Properly respect flags.disableEnterpriseFeatures --- messages/en-US.json | 8 ++++---- src/app/navigation.tsx | 14 ++++++++------ src/components/InternalResourceForm.tsx | 16 ++++++++++------ src/components/UptimeAlertSection.tsx | 8 ++++++-- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 3e1b29d88..a598dcc39 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Reason", - "requestLogs": "HTTPS Request Logs", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Request Analytics", "host": "Host", "location": "Location", "actionLogs": "Admin Action Logs", - "sidebarLogsRequest": "HTTPS Request Logs", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Authentication Logs", "sidebarLogsAction": "Admin Action Logs", "logRetention": "Log Retention", "logRetentionDescription": "Manage how long different types of logs are retained for this organization or disable them", "requestLogsDescription": "View detailed request logs for HTTPS resources in this organization", "requestAnalyticsDescription": "View detailed request analytics for resources in this organization", - "logRetentionRequestLabel": "HTTPS Request Log Retention", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "How long to retain request logs", "logRetentionAccessLabel": "Authentication Log Retention", "logRetentionAccessDescription": "How long to retain access logs", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Administrative actions performed by users within the organization.", "httpDestConnectionLogsTitle": "Network Logs", "httpDestConnectionLogsDescription": "Site and tunnel connection events, including connects and disconnects.", - "httpDestRequestLogsTitle": "HTTPS Request Logs", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "HTTP request logs for proxied resources, including method, path, and response code.", "httpDestSaveChanges": "Save Changes", "httpDestCreateDestination": "Create Destination", diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 1989161a9..3cfe867e3 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -217,15 +217,17 @@ export const orgNavSections = ( { title: "sidebarAlerting", href: "/{orgId}/settings/alerting", - icon: + icon: ( + + ) + }, + { + title: "sidebarProvisioning", + href: "/{orgId}/settings/provisioning", + icon: } ] : []), - { - title: "sidebarProvisioning", - href: "/{orgId}/settings/provisioning", - icon: - }, { title: "sidebarBluePrints", href: "/{orgId}/settings/blueprints", diff --git a/src/components/InternalResourceForm.tsx b/src/components/InternalResourceForm.tsx index 3d4febe44..357353d8e 100644 --- a/src/components/InternalResourceForm.tsx +++ b/src/components/InternalResourceForm.tsx @@ -840,12 +840,16 @@ export function InternalResourceForm({ modeCidrKey ) }, - { - value: "http", - label: t( - modeHttpKey - ) - } + ...(!disableEnterpriseFeatures + ? [ + { + value: "http" as const, + label: t( + modeHttpKey + ) + } + ] + : []) ]; return ( diff --git a/src/components/UptimeAlertSection.tsx b/src/components/UptimeAlertSection.tsx index 6c9edc923..a671a2dd5 100644 --- a/src/components/UptimeAlertSection.tsx +++ b/src/components/UptimeAlertSection.tsx @@ -53,10 +53,12 @@ export default function UptimeAlertSection({ days = 90 }: UptimeAlertSectionProps) { const t = useTranslations(); - const api = createApiClient(useEnvContext()); + const envContext = useEnvContext(); + const api = createApiClient(envContext); const queryClient = useQueryClient(); const { isPaidUser } = usePaidStatus(); const isPaid = isPaidUser(tierMatrix.alertingRules); + const { env } = envContext; const [open, setOpen] = useState(false); const [name, setName] = useState( @@ -176,7 +178,9 @@ export default function UptimeAlertSection({ {t("uptimeSectionDescription", { days })}
- {alertButton} + {!env.flags.disableEnterpriseFeatures + ? alertButton + : null}
From 508966038185218067b51cdecfb90202d67d8d36 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:33 -0700 Subject: [PATCH 049/139] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index dfbee6967..16bfd5161 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Raison", - "requestLogs": "Journal des requêtes", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Demander des analyses", "host": "Hôte", "location": "Localisation", "actionLogs": "Journaux des actions", - "sidebarLogsRequest": "Journal des requêtes", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Journaux d'accès", "sidebarLogsAction": "Journaux des actions", "logRetention": "Journaliser la rétention", "logRetentionDescription": "Gérer la durée de conservation des différents types de logs pour cette organisation ou les désactiver", "requestLogsDescription": "Voir les journaux détaillés des requêtes pour les ressources de cette organisation", "requestAnalyticsDescription": "Voir les analyses détaillées des demandes pour les ressources de cette organisation", - "logRetentionRequestLabel": "Demander la rétention des journaux", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Durée de conservation des journaux de requêtes", "logRetentionAccessLabel": "Rétention du journal d'accès", "logRetentionAccessDescription": "Durée de conservation des journaux d'accès", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Actions administratives effectuées par les utilisateurs au sein de l'organisation.", "httpDestConnectionLogsTitle": "Journaux de connexion", "httpDestConnectionLogsDescription": "Événements de connexion du site et du tunnel, y compris les connexions et les déconnexions.", - "httpDestRequestLogsTitle": "Journal des requêtes", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "Journaux des requêtes HTTP pour les ressources proxiées, y compris la méthode, le chemin et le code de réponse.", "httpDestSaveChanges": "Enregistrer les modifications", "httpDestCreateDestination": "Créer une destination", From 428e9b546e10fef4ecc39f0cd29511fc263a67f4 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:35 -0700 Subject: [PATCH 050/139] New translations en-us.json (Bulgarian) [ci skip] --- messages/bg-BG.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 11fc53fe5..4bec9dcae 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "Няма валидни методи за удостоверение", "ip": "IP", "reason": "Причина", - "requestLogs": "Заявка за логове", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Анализи На Заявки", "host": "Хост", "location": "Местоположение", "actionLogs": "Дневници на действията", - "sidebarLogsRequest": "Заявка за логове", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Достъп до логове", "sidebarLogsAction": "Дневници на действията", "logRetention": "Задържане на логове", "logRetentionDescription": "Управлявайте времето за задържане на различни видове логове за тази организация или ги деактивирайте", "requestLogsDescription": "Прегледайте подробни логове на заявки за ресурси в тази организация", "requestAnalyticsDescription": "Вижте подробни анализи на заявки за ресурсите в тази организация", - "logRetentionRequestLabel": "Задържане на логове на заявки", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Колко дълго да се задържат логовете на заявките", "logRetentionAccessLabel": "Задържане на логове за достъп", "logRetentionAccessDescription": "Колко дълго да се задържат логовете за достъп", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Административни действия, извършени от потребители в организацията.", "httpDestConnectionLogsTitle": "Логове на връзката", "httpDestConnectionLogsDescription": "Събития на свързване и прекъсване на сайта и тунела, включително свръзки и прекъсвания.", - "httpDestRequestLogsTitle": "Заявки за логове", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "Регистри за HTTP заявките към проксирани ресурси, включително метод, път и код на отговор.", "httpDestSaveChanges": "Запази промените", "httpDestCreateDestination": "Създаване на дестинация", From 2b0e6649faedf15e28da36758a78bfbb362e5534 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:37 -0700 Subject: [PATCH 051/139] New translations en-us.json (Czech) [ci skip] --- messages/cs-CZ.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 86571a022..4bb04def6 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP adresa", "reason": "Důvod", - "requestLogs": "Záznamy požadavků", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Vyžádat analýzu", "host": "Hostitel", "location": "Poloha", "actionLogs": "Záznamy akcí", - "sidebarLogsRequest": "Záznamy požadavků", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Protokoly přístupu", "sidebarLogsAction": "Záznamy akcí", "logRetention": "Zaznamenávání záznamu", "logRetentionDescription": "Spravovat, jak dlouho jsou různé typy logů uloženy pro tuto organizaci nebo je zakázat", "requestLogsDescription": "Zobrazit podrobné protokoly požadavků pro zdroje v této organizaci", "requestAnalyticsDescription": "Zobrazit podrobnou analýzu požadavků pro zdroje v této organizaci", - "logRetentionRequestLabel": "Zachování logu žádosti", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Jak dlouho uchovávat záznamy požadavků", "logRetentionAccessLabel": "Zachování záznamu přístupu", "logRetentionAccessDescription": "Jak dlouho uchovávat přístupové záznamy", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Správní opatření prováděná uživateli v rámci organizace.", "httpDestConnectionLogsTitle": "Protokoly připojení", "httpDestConnectionLogsDescription": "Události týkající se připojení lokality a tunelu, včetně připojení a odpojení.", - "httpDestRequestLogsTitle": "Záznamy požadavků", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "HTTP záznamy požadavků pro proxy zdroje, včetně metod, cesty a kódu odpovědi.", "httpDestSaveChanges": "Uložit změny", "httpDestCreateDestination": "Vytvořit cíl", From c59505be8d1b68adc9dd65f4f13f6aa608ff9a8d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:38 -0700 Subject: [PATCH 052/139] New translations en-us.json (German) [ci skip] --- messages/de-DE.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index d0b8e9115..4fa9eea9a 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "Keine gültige Authentifizierungsmethode verfügbar", "ip": "IP", "reason": "Grund", - "requestLogs": "Logs anfordern", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Anfrage-Analyse anzeigen", "host": "Host", "location": "Standort", "actionLogs": "Aktionsprotokolle", - "sidebarLogsRequest": "Logs anfordern", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Zugriffsprotokolle", "sidebarLogsAction": "Aktionsprotokolle", "logRetention": "Log-Speicherung", "logRetentionDescription": "Verwalten, wie lange verschiedene Logs für diese Organisation gespeichert werden oder deaktivieren", "requestLogsDescription": "Detaillierte Request-Logs für Ressourcen in dieser Organisation anzeigen", "requestAnalyticsDescription": "Detaillierte Anfrage-Analyse für Ressourcen in dieser Organisation anzeigen", - "logRetentionRequestLabel": "Log-Speicherung anfordern", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Wie lange sollen Request-Logs gespeichert werden", "logRetentionAccessLabel": "Zugriffsprotokoll-Speicherung", "logRetentionAccessDescription": "Wie lange Zugriffsprotokolle beibehalten werden sollen", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Administrative Maßnahmen, die von Benutzern innerhalb der Organisation durchgeführt werden.", "httpDestConnectionLogsTitle": "Verbindungsprotokolle", "httpDestConnectionLogsDescription": "Site- und Tunnelverbindungen, einschließlich Verbindungen und Trennungen.", - "httpDestRequestLogsTitle": "Logs anfordern", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "HTTP-Request-Protokolle für proxiierte Ressourcen, einschließlich Methode, Pfad und Antwort-Code.", "httpDestSaveChanges": "Änderungen speichern", "httpDestCreateDestination": "Ziel erstellen", From 5ff2569ece0787cc4fa1162db92c7170fa49153b Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:40 -0700 Subject: [PATCH 053/139] New translations en-us.json (Italian) [ci skip] --- messages/it-IT.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index 17040fa30..93b5cbc9c 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Motivo", - "requestLogs": "Log Richiesta", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Richiedi Analisi", "host": "Host", "location": "Posizione", "actionLogs": "Log Azioni", - "sidebarLogsRequest": "Log Richiesta", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Log Accesso", "sidebarLogsAction": "Log Azioni", "logRetention": "Ritenzione Registro", "logRetentionDescription": "Gestisci per quanto tempo i diversi tipi di log sono mantenuti per questa organizzazione o disabilitali", "requestLogsDescription": "Visualizza i registri di richiesta dettagliati per le risorse in questa organizzazione", "requestAnalyticsDescription": "Visualizza le analisi dettagliate della richiesta per le risorse in questa organizzazione", - "logRetentionRequestLabel": "Richiedi Ritenzione Log", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Per quanto tempo conservare i log delle richieste", "logRetentionAccessLabel": "Ritenzione Registro Accesso", "logRetentionAccessDescription": "Per quanto tempo conservare i log di accesso", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Azioni amministrative eseguite dagli utenti all'interno dell'organizzazione.", "httpDestConnectionLogsTitle": "Log Di Connessione", "httpDestConnectionLogsDescription": "Eventi di connessione al sito e al tunnel, inclusi collegamenti e disconnessioni.", - "httpDestRequestLogsTitle": "Log Richiesta", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "Registri di richiesta HTTP per le risorse proxy, inclusi metodo, percorso e codice di risposta.", "httpDestSaveChanges": "Salva Modifiche", "httpDestCreateDestination": "Crea Destinazione", From 7eab2cc0bb9d806b0c881eabd703adbd2a71b45a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:42 -0700 Subject: [PATCH 054/139] New translations en-us.json (Korean) [ci skip] --- messages/ko-KR.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 2fd449a63..f4b11ba83 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "유효한 인증 없음", "ip": "IP", "reason": "이유", - "requestLogs": "요청 로그", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "요청 분석", "host": "호스트", "location": "위치", "actionLogs": "작업 로그", - "sidebarLogsRequest": "요청 로그", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "접근 로그", "sidebarLogsAction": "작업 로그", "logRetention": "로그 보관", "logRetentionDescription": "다양한 유형의 로그를 이 조직에 대해 얼마나 오래 보관할지 관리하거나 비활성화합니다", "requestLogsDescription": "이 조직의 자원에 대한 상세한 요청 로그를 봅니다", "requestAnalyticsDescription": "이 조직의 리소스에 대한 자세한 요청 분석 보기", - "logRetentionRequestLabel": "요청 로그 보관", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "요청 로그를 얼마나 오래 보관할지", "logRetentionAccessLabel": "접근 로그 보관", "logRetentionAccessDescription": "접근 로그를 얼마나 오래 보관할지", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "조직 내에서 사용자가 수행한 관리 작업.", "httpDestConnectionLogsTitle": "연결 로그", "httpDestConnectionLogsDescription": "사이트 및 터널 연결 이벤트, 연결 및 연결 끊기를 포함합니다.", - "httpDestRequestLogsTitle": "요청 로그", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "프록시된 리소스에 대한 HTTP 요청 로그, 메서드, 경로 및 응답 코드를 포함합니다.", "httpDestSaveChanges": "변경 사항 저장", "httpDestCreateDestination": "대상지 생성", From b46c9485220bb5e599bc947877d89c770a18b977 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:44 -0700 Subject: [PATCH 055/139] New translations en-us.json (Dutch) [ci skip] --- messages/nl-NL.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index e91c84276..05d763079 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP-adres", "reason": "Reden", - "requestLogs": "Logboeken aanvragen", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Analytics opvragen", "host": "Hostnaam", "location": "Locatie", "actionLogs": "Actie logs", - "sidebarLogsRequest": "Logboeken aanvragen", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Toegang tot logboek", "sidebarLogsAction": "Actie logs", "logRetention": "Log bewaring", "logRetentionDescription": "Beheren hoe lang verschillende soorten logs bewaard worden voor deze organisatie of schakel ze uit", "requestLogsDescription": "Bekijk gedetailleerde verzoeklogboeken voor resources in deze organisatie", "requestAnalyticsDescription": "Bekijk gedetailleerde request analytics voor resources in deze organisatie", - "logRetentionRequestLabel": "Logboekbewaring aanvragen", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Hoe lang de aanvraaglogboeken te behouden", "logRetentionAccessLabel": "Toegang logboek bewaring", "logRetentionAccessDescription": "Hoe lang de toegangslogboeken behouden blijven", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Administratieve acties uitgevoerd door gebruikers binnen de organisatie.", "httpDestConnectionLogsTitle": "Connectie Logs", "httpDestConnectionLogsDescription": "Verbinding met de Site en tunnel maken verbroken, inclusief verbindingen en verbindingen.", - "httpDestRequestLogsTitle": "Logboeken aanvragen", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "HTTP request logs voor proxied hulpmiddelen, waaronder methode, pad en response code.", "httpDestSaveChanges": "Wijzigingen opslaan", "httpDestCreateDestination": "Maak bestemming aan", From a784cd307e59439225be2d35f29d82d5e3ff750b Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:46 -0700 Subject: [PATCH 056/139] New translations en-us.json (Polish) [ci skip] --- messages/pl-PL.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 4b69190c5..e47b01110 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Powód", - "requestLogs": "Dzienniki żądań", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Żądanie Analityki", "host": "Host", "location": "Lokalizacja", "actionLogs": "Dzienniki działań", - "sidebarLogsRequest": "Dzienniki żądań", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Logi dostępu", "sidebarLogsAction": "Dzienniki działań", "logRetention": "Zachowanie dziennika", "logRetentionDescription": "Zarządzaj jak długo różne typy logów są zachowane dla tej organizacji lub wyłącz je", "requestLogsDescription": "Zobacz szczegółowe dzienniki żądań zasobów w tej organizacji", "requestAnalyticsDescription": "Zobacz szczegółowe analizy żądań dla zasobów w tej organizacji", - "logRetentionRequestLabel": "Zachowanie dziennika żądań", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Jak długo zachować dzienniki żądań", "logRetentionAccessLabel": "Zachowanie dziennika dostępu", "logRetentionAccessDescription": "Jak długo zachować dzienniki dostępu", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Działania administracyjne wykonywane przez użytkowników w organizacji.", "httpDestConnectionLogsTitle": "Dzienniki połączeń", "httpDestConnectionLogsDescription": "Zdarzenia związane z miejscem i tunelem, w tym połączenia i rozłączenia.", - "httpDestRequestLogsTitle": "Dzienniki żądań", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "Logi żądań HTTP dla zasobów proxy, w tym metody, ścieżki i kodu odpowiedzi.", "httpDestSaveChanges": "Zapisz zmiany", "httpDestCreateDestination": "Utwórz cel", From 961cbfcacce40ca04bbdca58d3b5bb37d8f908b4 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:47 -0700 Subject: [PATCH 057/139] New translations en-us.json (Portuguese) [ci skip] --- messages/pt-PT.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index ce674a81a..1f658d18d 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "PI", "reason": "Motivo", - "requestLogs": "Registro de pedidos", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Solicitar análise", "host": "Servidor", "location": "Local:", "actionLogs": "Logs de Ações", - "sidebarLogsRequest": "Registro de pedidos", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Logs de Acesso", "sidebarLogsAction": "Logs de Ações", "logRetention": "Retenção de Log", "logRetentionDescription": "Gerenciar quanto tempo os diferentes tipos de logs são mantidos para esta organização ou desativá-los", "requestLogsDescription": "Ver registros de pedidos detalhados de recursos nesta organização", "requestAnalyticsDescription": "Exibir análise detalhada de pedidos para recursos nesta organização", - "logRetentionRequestLabel": "Solicitar retenção de registro", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Por quanto tempo manter os registros de pedidos", "logRetentionAccessLabel": "Retenção de Log de Acesso", "logRetentionAccessDescription": "Por quanto tempo manter os registros de acesso", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Ações administrativas realizadas por usuários dentro da organização.", "httpDestConnectionLogsTitle": "Logs da conexão", "httpDestConnectionLogsDescription": "Eventos de conexão de site e túnel, incluindo conexões e desconexões.", - "httpDestRequestLogsTitle": "Registro de pedidos", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "Logs de solicitação HTTP para recursos proxy incluindo o método, o caminho e o código de resposta.", "httpDestSaveChanges": "Salvar as alterações", "httpDestCreateDestination": "Criar destino", From 54820d1db0e08d357dd4725fbede1d6d625c6c8b Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:49 -0700 Subject: [PATCH 058/139] New translations en-us.json (Russian) [ci skip] --- messages/ru-RU.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 8f8579fa9..185e00006 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Причина", - "requestLogs": "Запросить журналы", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Аналитика запроса", "host": "Хост", "location": "Местоположение", "actionLogs": "Журнал действий", - "sidebarLogsRequest": "Запросить журналы", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Журналы доступа", "sidebarLogsAction": "Журнал действий", "logRetention": "Сохранение журнала", "logRetentionDescription": "Управление сохранением различных типов журналов для этой организации или отключение их", "requestLogsDescription": "Просмотреть подробные журналы запроса ресурсов в этой организации", "requestAnalyticsDescription": "Просмотреть подробную аналитику запроса для ресурсов в этой организации", - "logRetentionRequestLabel": "Запросить сохранение журнала", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Как долго сохранять журналы запросов", "logRetentionAccessLabel": "Хранение журнала доступа", "logRetentionAccessDescription": "Как долго сохранять журналы доступа", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Административные меры, осуществляемые пользователями в рамках организации.", "httpDestConnectionLogsTitle": "Журнал подключений", "httpDestConnectionLogsDescription": "События связи с сайтами и туннелями, включая соединения и отключения.", - "httpDestRequestLogsTitle": "Запросить журналы", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "Журналы запросов HTTP для проксируемых ресурсов, включая метод, путь и код ответа.", "httpDestSaveChanges": "Сохранить изменения", "httpDestCreateDestination": "Создать адрес назначения", From 0444d3490be727e38be23ffeb1d8cf5dff2f3c9b Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:51 -0700 Subject: [PATCH 059/139] New translations en-us.json (Turkish) [ci skip] --- messages/tr-TR.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 6aa50e636..b9d992276 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "Daha Fazla Kimlik Doğrulama Yöntemi Yok", "ip": "IP", "reason": "Sebep", - "requestLogs": "İstek Günlükleri", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "İstek Analizi", "host": "Sunucu", "location": "Konum", "actionLogs": "Eylem Günlükleri", - "sidebarLogsRequest": "İstek Günlükleri", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Erişim Günlükleri", "sidebarLogsAction": "Eylem Günlükleri", "logRetention": "Kayıt Saklama", "logRetentionDescription": "Bu organizasyon için farklı türdeki günlüklerin ne kadar süre saklanacağını yönetin veya devre dışı bırakın", "requestLogsDescription": "Bu organizasyondaki kaynaklar için ayrıntılı istek günlüklerini görüntüleyin", "requestAnalyticsDescription": "Bu organizasyondaki kaynaklar için ayrıntılı istek analizlerini görüntüleyin.", - "logRetentionRequestLabel": "İstek Günlüğü Saklama", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "İstek günlüklerini ne kadar süre tutacağını belirle", "logRetentionAccessLabel": "Erişim Günlüğü Saklama", "logRetentionAccessDescription": "Erişim günlüklerini ne kadar süre tutacağını belirle", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Kullanıcılar tarafından organizasyon içerisinde yapılan yönetici eylemleri.", "httpDestConnectionLogsTitle": "Bağlantı Kayıtları", "httpDestConnectionLogsDescription": "Site ve tünel bağlantı olayları, bağlantılar ve bağlantı kesilmeleri dahil.", - "httpDestRequestLogsTitle": "İstek Kayıtları", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "Yönlendirilmiş kaynaklar için HTTP istek kayıtları, yöntem, yol ve yanıt kodu dahil.", "httpDestSaveChanges": "Değişiklikleri Kaydet", "httpDestCreateDestination": "Hedef Oluştur", From 98bc199c8ef7619b4750054441b6a995fcb09cc7 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:53 -0700 Subject: [PATCH 060/139] New translations en-us.json (Chinese Simplified) [ci skip] --- messages/zh-CN.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 2f80cbe30..430faaf36 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "原因", - "requestLogs": "请求日志", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "请求分析", "host": "主机", "location": "地点", "actionLogs": "操作日志", - "sidebarLogsRequest": "请求日志", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "访问日志", "sidebarLogsAction": "操作日志", "logRetention": "日志保留", "logRetentionDescription": "管理不同类型的日志为这个机构保留多长时间或禁用这些日志", "requestLogsDescription": "查看此机构资源的详细请求日志", "requestAnalyticsDescription": "查看此机构资源的详细请求分析", - "logRetentionRequestLabel": "请求日志保留", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "保留请求日志的时间", "logRetentionAccessLabel": "访问日志保留", "logRetentionAccessDescription": "保留访问日志的时间", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "组织内部用户采取的行政行动。", "httpDestConnectionLogsTitle": "连接日志", "httpDestConnectionLogsDescription": "站点和隧道连接事件,包括连接和断开连接。", - "httpDestRequestLogsTitle": "请求日志", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "HTTP 请求代理资源日志,包括方法、路径和响应代码。", "httpDestSaveChanges": "保存更改", "httpDestCreateDestination": "创建目标", From 56ef3a934a20a94097445f4e5b8d96d8ad5a2622 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:55 -0700 Subject: [PATCH 061/139] New translations en-us.json (Norwegian Bokmal) [ci skip] --- messages/nb-NO.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 18ab00bbf..05686eaee 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Grunn", - "requestLogs": "Forespørselslogger (Automatic Translation)", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Be om analyser", "host": "Vert", "location": "Sted", "actionLogs": "Handlingslogger", - "sidebarLogsRequest": "Forespørselslogger (Automatic Translation)", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Tilgangslogger (Automatic Translation)", "sidebarLogsAction": "Handlingslogger", "logRetention": "Logg tilbaketrekning", "logRetentionDescription": "Håndter hvor lenge ulike typer logger beholdes for denne organisasjonen, eller deaktiver dem", "requestLogsDescription": "Se detaljerte forespørselslogger for ressurser i denne organisasjonen", "requestAnalyticsDescription": "Se detaljert rekvisisjonsanalyse for ressurser i denne organisasjonen", - "logRetentionRequestLabel": "Be om loggoverføring", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Hvor lenge du vil beholde forespørselslogger", "logRetentionAccessLabel": "Få tilgang til loggoverføring", "logRetentionAccessDescription": "Hvor lenge du vil beholde adgangslogger", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Administrative tiltak som utføres av brukere innenfor organisasjonen.", "httpDestConnectionLogsTitle": "Loggfiler for tilkobling", "httpDestConnectionLogsDescription": "Utstyrs- og tunneltilkoblingshendelser, inkludert forbindelser og frakobling.", - "httpDestRequestLogsTitle": "Forespørselslogger (Automatic Translation)", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "HTTP-forespørsel logger for bekreftede ressurser, inkludert metode, bane og responskode.", "httpDestSaveChanges": "Lagre endringer", "httpDestCreateDestination": "Opprett mål", From 885b9e638d4031a514e7ede129d6149e1b0dbbac Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:15:56 -0700 Subject: [PATCH 062/139] New translations en-us.json (Spanish) [ci skip] --- messages/es-ES.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index 75c465fdd..5ead411d0 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Razón", - "requestLogs": "Registros de Solicitud", + "requestLogs": "HTTP Request Logs", "requestAnalytics": "Analítica de Solicitud", "host": "Anfitrión", "location": "Ubicación", "actionLogs": "Registros de acción", - "sidebarLogsRequest": "Registros de Solicitud", + "sidebarLogsRequest": "HTTP Request Logs", "sidebarLogsAccess": "Registros de acceso", "sidebarLogsAction": "Registros de acción", "logRetention": "Retención de Log", "logRetentionDescription": "Administrar cuánto tiempo se conservan los diferentes tipos de registros para esta organización o desactivarlos", "requestLogsDescription": "Ver registros de solicitudes detallados para los recursos de esta organización", "requestAnalyticsDescription": "Ver análisis de solicitudes detalladas de recursos en esta organización", - "logRetentionRequestLabel": "Retención de Registro de Solicitud", + "logRetentionRequestLabel": "HTTP Request Log Retention", "logRetentionRequestDescription": "Cuánto tiempo conservar los registros de solicitudes", "logRetentionAccessLabel": "Retención de Log de Acceso", "logRetentionAccessDescription": "Cuánto tiempo retener los registros de acceso", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Acciones administrativas realizadas por los usuarios dentro de la organización.", "httpDestConnectionLogsTitle": "Registros de conexión", "httpDestConnectionLogsDescription": "Eventos de conexión de sitios y túneles, incluyendo conexiones y desconexiones.", - "httpDestRequestLogsTitle": "Registros de Solicitud", + "httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsDescription": "Registros de peticiones HTTP para recursos proxyficados, incluyendo método, ruta y código de respuesta.", "httpDestSaveChanges": "Guardar Cambios", "httpDestCreateDestination": "Crear destino", From 5e51b8ad746dd83bddf70ccb5a24a11488fb0003 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:23 -0700 Subject: [PATCH 063/139] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 16bfd5161..5250a684d 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Raison", - "requestLogs": "HTTP Request Logs", + "requestLogs": "Journal des Requêtes HTTP", "requestAnalytics": "Demander des analyses", "host": "Hôte", "location": "Localisation", "actionLogs": "Journaux des actions", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "Journal des Requêtes HTTP", "sidebarLogsAccess": "Journaux d'accès", "sidebarLogsAction": "Journaux des actions", "logRetention": "Journaliser la rétention", "logRetentionDescription": "Gérer la durée de conservation des différents types de logs pour cette organisation ou les désactiver", "requestLogsDescription": "Voir les journaux détaillés des requêtes pour les ressources de cette organisation", "requestAnalyticsDescription": "Voir les analyses détaillées des demandes pour les ressources de cette organisation", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Rétention des Journaux de Requêtes HTTP", "logRetentionRequestDescription": "Durée de conservation des journaux de requêtes", "logRetentionAccessLabel": "Rétention du journal d'accès", "logRetentionAccessDescription": "Durée de conservation des journaux d'accès", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Actions administratives effectuées par les utilisateurs au sein de l'organisation.", "httpDestConnectionLogsTitle": "Journaux de connexion", "httpDestConnectionLogsDescription": "Événements de connexion du site et du tunnel, y compris les connexions et les déconnexions.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "Journal des Requêtes HTTP", "httpDestRequestLogsDescription": "Journaux des requêtes HTTP pour les ressources proxiées, y compris la méthode, le chemin et le code de réponse.", "httpDestSaveChanges": "Enregistrer les modifications", "httpDestCreateDestination": "Créer une destination", From d96e930679b69e211425eaa615046be81bff7b3e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:25 -0700 Subject: [PATCH 064/139] New translations en-us.json (Bulgarian) [ci skip] --- messages/bg-BG.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 4bec9dcae..46327afd0 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "Няма валидни методи за удостоверение", "ip": "IP", "reason": "Причина", - "requestLogs": "HTTP Request Logs", + "requestLogs": "Логове за HTTP заявки", "requestAnalytics": "Анализи На Заявки", "host": "Хост", "location": "Местоположение", "actionLogs": "Дневници на действията", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "Логове за HTTP заявки", "sidebarLogsAccess": "Достъп до логове", "sidebarLogsAction": "Дневници на действията", "logRetention": "Задържане на логове", "logRetentionDescription": "Управлявайте времето за задържане на различни видове логове за тази организация или ги деактивирайте", "requestLogsDescription": "Прегледайте подробни логове на заявки за ресурси в тази организация", "requestAnalyticsDescription": "Вижте подробни анализи на заявки за ресурсите в тази организация", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Задържане на логове за HTTP заявки", "logRetentionRequestDescription": "Колко дълго да се задържат логовете на заявките", "logRetentionAccessLabel": "Задържане на логове за достъп", "logRetentionAccessDescription": "Колко дълго да се задържат логовете за достъп", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Административни действия, извършени от потребители в организацията.", "httpDestConnectionLogsTitle": "Логове на връзката", "httpDestConnectionLogsDescription": "Събития на свързване и прекъсване на сайта и тунела, включително свръзки и прекъсвания.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "Логове за HTTP заявки", "httpDestRequestLogsDescription": "Регистри за HTTP заявките към проксирани ресурси, включително метод, път и код на отговор.", "httpDestSaveChanges": "Запази промените", "httpDestCreateDestination": "Създаване на дестинация", From 19de12b12e89dc1abd3a2d4e0237648954556ad2 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:26 -0700 Subject: [PATCH 065/139] New translations en-us.json (Czech) [ci skip] --- messages/cs-CZ.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 4bb04def6..38da0604a 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP adresa", "reason": "Důvod", - "requestLogs": "HTTP Request Logs", + "requestLogs": "Záznamy HTTP požadavků", "requestAnalytics": "Vyžádat analýzu", "host": "Hostitel", "location": "Poloha", "actionLogs": "Záznamy akcí", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "Záznamy HTTP požadavků", "sidebarLogsAccess": "Protokoly přístupu", "sidebarLogsAction": "Záznamy akcí", "logRetention": "Zaznamenávání záznamu", "logRetentionDescription": "Spravovat, jak dlouho jsou různé typy logů uloženy pro tuto organizaci nebo je zakázat", "requestLogsDescription": "Zobrazit podrobné protokoly požadavků pro zdroje v této organizaci", "requestAnalyticsDescription": "Zobrazit podrobnou analýzu požadavků pro zdroje v této organizaci", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Zachování logu HTTP požadavků", "logRetentionRequestDescription": "Jak dlouho uchovávat záznamy požadavků", "logRetentionAccessLabel": "Zachování záznamu přístupu", "logRetentionAccessDescription": "Jak dlouho uchovávat přístupové záznamy", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Správní opatření prováděná uživateli v rámci organizace.", "httpDestConnectionLogsTitle": "Protokoly připojení", "httpDestConnectionLogsDescription": "Události týkající se připojení lokality a tunelu, včetně připojení a odpojení.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "Záznamy HTTP požadavků", "httpDestRequestLogsDescription": "HTTP záznamy požadavků pro proxy zdroje, včetně metod, cesty a kódu odpovědi.", "httpDestSaveChanges": "Uložit změny", "httpDestCreateDestination": "Vytvořit cíl", From 36e7054386536e7df21a95a739ebbba786641344 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:28 -0700 Subject: [PATCH 066/139] New translations en-us.json (German) [ci skip] --- messages/de-DE.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 4fa9eea9a..367c95cd3 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "Keine gültige Authentifizierungsmethode verfügbar", "ip": "IP", "reason": "Grund", - "requestLogs": "HTTP Request Logs", + "requestLogs": "HTTP Anforderungsprotokolle", "requestAnalytics": "Anfrage-Analyse anzeigen", "host": "Host", "location": "Standort", "actionLogs": "Aktionsprotokolle", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "HTTP Anforderungsprotokolle", "sidebarLogsAccess": "Zugriffsprotokolle", "sidebarLogsAction": "Aktionsprotokolle", "logRetention": "Log-Speicherung", "logRetentionDescription": "Verwalten, wie lange verschiedene Logs für diese Organisation gespeichert werden oder deaktivieren", "requestLogsDescription": "Detaillierte Request-Logs für Ressourcen in dieser Organisation anzeigen", "requestAnalyticsDescription": "Detaillierte Anfrage-Analyse für Ressourcen in dieser Organisation anzeigen", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "HTTP Anforderungsprotokoll Aufbewahrung", "logRetentionRequestDescription": "Wie lange sollen Request-Logs gespeichert werden", "logRetentionAccessLabel": "Zugriffsprotokoll-Speicherung", "logRetentionAccessDescription": "Wie lange Zugriffsprotokolle beibehalten werden sollen", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Administrative Maßnahmen, die von Benutzern innerhalb der Organisation durchgeführt werden.", "httpDestConnectionLogsTitle": "Verbindungsprotokolle", "httpDestConnectionLogsDescription": "Site- und Tunnelverbindungen, einschließlich Verbindungen und Trennungen.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "HTTP Anforderungsprotokolle", "httpDestRequestLogsDescription": "HTTP-Request-Protokolle für proxiierte Ressourcen, einschließlich Methode, Pfad und Antwort-Code.", "httpDestSaveChanges": "Änderungen speichern", "httpDestCreateDestination": "Ziel erstellen", From 5a5feccc768609e55675d155ad377f307853f56d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:30 -0700 Subject: [PATCH 067/139] New translations en-us.json (Italian) [ci skip] --- messages/it-IT.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index 93b5cbc9c..c739b269d 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Motivo", - "requestLogs": "HTTP Request Logs", + "requestLogs": "Log Richieste HTTP", "requestAnalytics": "Richiedi Analisi", "host": "Host", "location": "Posizione", "actionLogs": "Log Azioni", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "Log Richieste HTTP", "sidebarLogsAccess": "Log Accesso", "sidebarLogsAction": "Log Azioni", "logRetention": "Ritenzione Registro", "logRetentionDescription": "Gestisci per quanto tempo i diversi tipi di log sono mantenuti per questa organizzazione o disabilitali", "requestLogsDescription": "Visualizza i registri di richiesta dettagliati per le risorse in questa organizzazione", "requestAnalyticsDescription": "Visualizza le analisi dettagliate della richiesta per le risorse in questa organizzazione", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Conservazione Log Richieste HTTP", "logRetentionRequestDescription": "Per quanto tempo conservare i log delle richieste", "logRetentionAccessLabel": "Ritenzione Registro Accesso", "logRetentionAccessDescription": "Per quanto tempo conservare i log di accesso", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Azioni amministrative eseguite dagli utenti all'interno dell'organizzazione.", "httpDestConnectionLogsTitle": "Log Di Connessione", "httpDestConnectionLogsDescription": "Eventi di connessione al sito e al tunnel, inclusi collegamenti e disconnessioni.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "Log Richieste HTTP", "httpDestRequestLogsDescription": "Registri di richiesta HTTP per le risorse proxy, inclusi metodo, percorso e codice di risposta.", "httpDestSaveChanges": "Salva Modifiche", "httpDestCreateDestination": "Crea Destinazione", From 9c8ffdb661eba0d0e90f1d45a8f723b036d18b75 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:32 -0700 Subject: [PATCH 068/139] New translations en-us.json (Korean) [ci skip] --- messages/ko-KR.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index f4b11ba83..1d3d77fe2 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "유효한 인증 없음", "ip": "IP", "reason": "이유", - "requestLogs": "HTTP Request Logs", + "requestLogs": "HTTP 요청 로그", "requestAnalytics": "요청 분석", "host": "호스트", "location": "위치", "actionLogs": "작업 로그", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "HTTP 요청 로그", "sidebarLogsAccess": "접근 로그", "sidebarLogsAction": "작업 로그", "logRetention": "로그 보관", "logRetentionDescription": "다양한 유형의 로그를 이 조직에 대해 얼마나 오래 보관할지 관리하거나 비활성화합니다", "requestLogsDescription": "이 조직의 자원에 대한 상세한 요청 로그를 봅니다", "requestAnalyticsDescription": "이 조직의 리소스에 대한 자세한 요청 분석 보기", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "HTTP 요청 로그 보관", "logRetentionRequestDescription": "요청 로그를 얼마나 오래 보관할지", "logRetentionAccessLabel": "접근 로그 보관", "logRetentionAccessDescription": "접근 로그를 얼마나 오래 보관할지", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "조직 내에서 사용자가 수행한 관리 작업.", "httpDestConnectionLogsTitle": "연결 로그", "httpDestConnectionLogsDescription": "사이트 및 터널 연결 이벤트, 연결 및 연결 끊기를 포함합니다.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "HTTP 요청 로그", "httpDestRequestLogsDescription": "프록시된 리소스에 대한 HTTP 요청 로그, 메서드, 경로 및 응답 코드를 포함합니다.", "httpDestSaveChanges": "변경 사항 저장", "httpDestCreateDestination": "대상지 생성", From 385f57ec937c7dee76b53316cd0a2588dd845905 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:33 -0700 Subject: [PATCH 069/139] New translations en-us.json (Dutch) [ci skip] --- messages/nl-NL.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 05d763079..5218c3388 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP-adres", "reason": "Reden", - "requestLogs": "HTTP Request Logs", + "requestLogs": "HTTP-aanvraaglogboeken", "requestAnalytics": "Analytics opvragen", "host": "Hostnaam", "location": "Locatie", "actionLogs": "Actie logs", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "HTTP-aanvraaglogboeken", "sidebarLogsAccess": "Toegang tot logboek", "sidebarLogsAction": "Actie logs", "logRetention": "Log bewaring", "logRetentionDescription": "Beheren hoe lang verschillende soorten logs bewaard worden voor deze organisatie of schakel ze uit", "requestLogsDescription": "Bekijk gedetailleerde verzoeklogboeken voor resources in deze organisatie", "requestAnalyticsDescription": "Bekijk gedetailleerde request analytics voor resources in deze organisatie", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Bewaring van HTTP-aanvraaglogboeken", "logRetentionRequestDescription": "Hoe lang de aanvraaglogboeken te behouden", "logRetentionAccessLabel": "Toegang logboek bewaring", "logRetentionAccessDescription": "Hoe lang de toegangslogboeken behouden blijven", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Administratieve acties uitgevoerd door gebruikers binnen de organisatie.", "httpDestConnectionLogsTitle": "Connectie Logs", "httpDestConnectionLogsDescription": "Verbinding met de Site en tunnel maken verbroken, inclusief verbindingen en verbindingen.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "HTTP-aanvraaglogboeken", "httpDestRequestLogsDescription": "HTTP request logs voor proxied hulpmiddelen, waaronder methode, pad en response code.", "httpDestSaveChanges": "Wijzigingen opslaan", "httpDestCreateDestination": "Maak bestemming aan", From 10d2bc1e9e835f5e147ed86062f405af6eec8986 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:35 -0700 Subject: [PATCH 070/139] New translations en-us.json (Polish) [ci skip] --- messages/pl-PL.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index e47b01110..df4a391fc 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Powód", - "requestLogs": "HTTP Request Logs", + "requestLogs": "Dzienniki żądań HTTP", "requestAnalytics": "Żądanie Analityki", "host": "Host", "location": "Lokalizacja", "actionLogs": "Dzienniki działań", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "Dzienniki żądań HTTP", "sidebarLogsAccess": "Logi dostępu", "sidebarLogsAction": "Dzienniki działań", "logRetention": "Zachowanie dziennika", "logRetentionDescription": "Zarządzaj jak długo różne typy logów są zachowane dla tej organizacji lub wyłącz je", "requestLogsDescription": "Zobacz szczegółowe dzienniki żądań zasobów w tej organizacji", "requestAnalyticsDescription": "Zobacz szczegółowe analizy żądań dla zasobów w tej organizacji", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Przechowywanie dzienników żądań HTTP", "logRetentionRequestDescription": "Jak długo zachować dzienniki żądań", "logRetentionAccessLabel": "Zachowanie dziennika dostępu", "logRetentionAccessDescription": "Jak długo zachować dzienniki dostępu", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Działania administracyjne wykonywane przez użytkowników w organizacji.", "httpDestConnectionLogsTitle": "Dzienniki połączeń", "httpDestConnectionLogsDescription": "Zdarzenia związane z miejscem i tunelem, w tym połączenia i rozłączenia.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "Dzienniki żądań HTTP", "httpDestRequestLogsDescription": "Logi żądań HTTP dla zasobów proxy, w tym metody, ścieżki i kodu odpowiedzi.", "httpDestSaveChanges": "Zapisz zmiany", "httpDestCreateDestination": "Utwórz cel", From 70bb42f1fcf66dad7f2d82d9c67c40f9864f47eb Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:37 -0700 Subject: [PATCH 071/139] New translations en-us.json (Portuguese) [ci skip] --- messages/pt-PT.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 1f658d18d..bc683dc77 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "PI", "reason": "Motivo", - "requestLogs": "HTTP Request Logs", + "requestLogs": "Registros de Pedidos HTTP", "requestAnalytics": "Solicitar análise", "host": "Servidor", "location": "Local:", "actionLogs": "Logs de Ações", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "Registros de Pedidos HTTP", "sidebarLogsAccess": "Logs de Acesso", "sidebarLogsAction": "Logs de Ações", "logRetention": "Retenção de Log", "logRetentionDescription": "Gerenciar quanto tempo os diferentes tipos de logs são mantidos para esta organização ou desativá-los", "requestLogsDescription": "Ver registros de pedidos detalhados de recursos nesta organização", "requestAnalyticsDescription": "Exibir análise detalhada de pedidos para recursos nesta organização", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Retenção de Registro de Pedido HTTP", "logRetentionRequestDescription": "Por quanto tempo manter os registros de pedidos", "logRetentionAccessLabel": "Retenção de Log de Acesso", "logRetentionAccessDescription": "Por quanto tempo manter os registros de acesso", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Ações administrativas realizadas por usuários dentro da organização.", "httpDestConnectionLogsTitle": "Logs da conexão", "httpDestConnectionLogsDescription": "Eventos de conexão de site e túnel, incluindo conexões e desconexões.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "Registros de Pedidos HTTP", "httpDestRequestLogsDescription": "Logs de solicitação HTTP para recursos proxy incluindo o método, o caminho e o código de resposta.", "httpDestSaveChanges": "Salvar as alterações", "httpDestCreateDestination": "Criar destino", From cc2a416a929c75ccac7d59a329cf18bfa84381c5 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:39 -0700 Subject: [PATCH 072/139] New translations en-us.json (Russian) [ci skip] --- messages/ru-RU.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 185e00006..46bb5911a 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Причина", - "requestLogs": "HTTP Request Logs", + "requestLogs": "HTTP Запросы Логи", "requestAnalytics": "Аналитика запроса", "host": "Хост", "location": "Местоположение", "actionLogs": "Журнал действий", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "HTTP Запросы Логи", "sidebarLogsAccess": "Журналы доступа", "sidebarLogsAction": "Журнал действий", "logRetention": "Сохранение журнала", "logRetentionDescription": "Управление сохранением различных типов журналов для этой организации или отключение их", "requestLogsDescription": "Просмотреть подробные журналы запроса ресурсов в этой организации", "requestAnalyticsDescription": "Просмотреть подробную аналитику запроса для ресурсов в этой организации", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Сохранение HTTP Запросов Лога", "logRetentionRequestDescription": "Как долго сохранять журналы запросов", "logRetentionAccessLabel": "Хранение журнала доступа", "logRetentionAccessDescription": "Как долго сохранять журналы доступа", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Административные меры, осуществляемые пользователями в рамках организации.", "httpDestConnectionLogsTitle": "Журнал подключений", "httpDestConnectionLogsDescription": "События связи с сайтами и туннелями, включая соединения и отключения.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "HTTP Запросы Логи", "httpDestRequestLogsDescription": "Журналы запросов HTTP для проксируемых ресурсов, включая метод, путь и код ответа.", "httpDestSaveChanges": "Сохранить изменения", "httpDestCreateDestination": "Создать адрес назначения", From c95e66d53180e0056cc2a068c37f700dae119136 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:40 -0700 Subject: [PATCH 073/139] New translations en-us.json (Turkish) [ci skip] --- messages/tr-TR.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index b9d992276..0bcd5d313 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "Daha Fazla Kimlik Doğrulama Yöntemi Yok", "ip": "IP", "reason": "Sebep", - "requestLogs": "HTTP Request Logs", + "requestLogs": "HTTP İstek Günlükleri", "requestAnalytics": "İstek Analizi", "host": "Sunucu", "location": "Konum", "actionLogs": "Eylem Günlükleri", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "HTTP İstek Günlükleri", "sidebarLogsAccess": "Erişim Günlükleri", "sidebarLogsAction": "Eylem Günlükleri", "logRetention": "Kayıt Saklama", "logRetentionDescription": "Bu organizasyon için farklı türdeki günlüklerin ne kadar süre saklanacağını yönetin veya devre dışı bırakın", "requestLogsDescription": "Bu organizasyondaki kaynaklar için ayrıntılı istek günlüklerini görüntüleyin", "requestAnalyticsDescription": "Bu organizasyondaki kaynaklar için ayrıntılı istek analizlerini görüntüleyin.", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "HTTP İstek Günlüğü Saklama", "logRetentionRequestDescription": "İstek günlüklerini ne kadar süre tutacağını belirle", "logRetentionAccessLabel": "Erişim Günlüğü Saklama", "logRetentionAccessDescription": "Erişim günlüklerini ne kadar süre tutacağını belirle", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Kullanıcılar tarafından organizasyon içerisinde yapılan yönetici eylemleri.", "httpDestConnectionLogsTitle": "Bağlantı Kayıtları", "httpDestConnectionLogsDescription": "Site ve tünel bağlantı olayları, bağlantılar ve bağlantı kesilmeleri dahil.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "HTTP İstek Günlükleri", "httpDestRequestLogsDescription": "Yönlendirilmiş kaynaklar için HTTP istek kayıtları, yöntem, yol ve yanıt kodu dahil.", "httpDestSaveChanges": "Değişiklikleri Kaydet", "httpDestCreateDestination": "Hedef Oluştur", From 05e738e0f4e7912daf4e0b4974faf561f12d2111 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:42 -0700 Subject: [PATCH 074/139] New translations en-us.json (Chinese Simplified) [ci skip] --- messages/zh-CN.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 430faaf36..e61e0c61a 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "原因", - "requestLogs": "HTTP Request Logs", + "requestLogs": "请求日志", "requestAnalytics": "请求分析", "host": "主机", "location": "地点", "actionLogs": "操作日志", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "请求日志", "sidebarLogsAccess": "访问日志", "sidebarLogsAction": "操作日志", "logRetention": "日志保留", "logRetentionDescription": "管理不同类型的日志为这个机构保留多长时间或禁用这些日志", "requestLogsDescription": "查看此机构资源的详细请求日志", "requestAnalyticsDescription": "查看此机构资源的详细请求分析", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "HTTP 请求日志保留", "logRetentionRequestDescription": "保留请求日志的时间", "logRetentionAccessLabel": "访问日志保留", "logRetentionAccessDescription": "保留访问日志的时间", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "组织内部用户采取的行政行动。", "httpDestConnectionLogsTitle": "连接日志", "httpDestConnectionLogsDescription": "站点和隧道连接事件,包括连接和断开连接。", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "请求日志", "httpDestRequestLogsDescription": "HTTP 请求代理资源日志,包括方法、路径和响应代码。", "httpDestSaveChanges": "保存更改", "httpDestCreateDestination": "创建目标", From ed35d255985872e78273376e226d8b96096f94c6 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:44 -0700 Subject: [PATCH 075/139] New translations en-us.json (Norwegian Bokmal) [ci skip] --- messages/nb-NO.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 05686eaee..72a7c21df 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Grunn", - "requestLogs": "HTTP Request Logs", + "requestLogs": "HTTP-forespørselslogger", "requestAnalytics": "Be om analyser", "host": "Vert", "location": "Sted", "actionLogs": "Handlingslogger", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "HTTP-forespørselslogger", "sidebarLogsAccess": "Tilgangslogger (Automatic Translation)", "sidebarLogsAction": "Handlingslogger", "logRetention": "Logg tilbaketrekning", "logRetentionDescription": "Håndter hvor lenge ulike typer logger beholdes for denne organisasjonen, eller deaktiver dem", "requestLogsDescription": "Se detaljerte forespørselslogger for ressurser i denne organisasjonen", "requestAnalyticsDescription": "Se detaljert rekvisisjonsanalyse for ressurser i denne organisasjonen", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Be om loggbevaring", "logRetentionRequestDescription": "Hvor lenge du vil beholde forespørselslogger", "logRetentionAccessLabel": "Få tilgang til loggoverføring", "logRetentionAccessDescription": "Hvor lenge du vil beholde adgangslogger", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Administrative tiltak som utføres av brukere innenfor organisasjonen.", "httpDestConnectionLogsTitle": "Loggfiler for tilkobling", "httpDestConnectionLogsDescription": "Utstyrs- og tunneltilkoblingshendelser, inkludert forbindelser og frakobling.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "HTTP-forespørselslogger", "httpDestRequestLogsDescription": "HTTP-forespørsel logger for bekreftede ressurser, inkludert metode, bane og responskode.", "httpDestSaveChanges": "Lagre endringer", "httpDestCreateDestination": "Opprett mål", From 181bcffe7dec723160c50334ec824669803b2a93 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 16:17:46 -0700 Subject: [PATCH 076/139] New translations en-us.json (Spanish) [ci skip] --- messages/es-ES.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index 5ead411d0..e610233f7 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2660,19 +2660,19 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Razón", - "requestLogs": "HTTP Request Logs", + "requestLogs": "Registros de Solicitud HTTP", "requestAnalytics": "Analítica de Solicitud", "host": "Anfitrión", "location": "Ubicación", "actionLogs": "Registros de acción", - "sidebarLogsRequest": "HTTP Request Logs", + "sidebarLogsRequest": "Registros de Solicitud HTTP", "sidebarLogsAccess": "Registros de acceso", "sidebarLogsAction": "Registros de acción", "logRetention": "Retención de Log", "logRetentionDescription": "Administrar cuánto tiempo se conservan los diferentes tipos de registros para esta organización o desactivarlos", "requestLogsDescription": "Ver registros de solicitudes detallados para los recursos de esta organización", "requestAnalyticsDescription": "Ver análisis de solicitudes detalladas de recursos en esta organización", - "logRetentionRequestLabel": "HTTP Request Log Retention", + "logRetentionRequestLabel": "Retención de Registro de Solicitud HTTP", "logRetentionRequestDescription": "Cuánto tiempo conservar los registros de solicitudes", "logRetentionAccessLabel": "Retención de Log de Acceso", "logRetentionAccessDescription": "Cuánto tiempo retener los registros de acceso", @@ -3134,7 +3134,7 @@ "httpDestActionLogsDescription": "Acciones administrativas realizadas por los usuarios dentro de la organización.", "httpDestConnectionLogsTitle": "Registros de conexión", "httpDestConnectionLogsDescription": "Eventos de conexión de sitios y túneles, incluyendo conexiones y desconexiones.", - "httpDestRequestLogsTitle": "HTTP Request Logs", + "httpDestRequestLogsTitle": "Registros de Solicitud HTTP", "httpDestRequestLogsDescription": "Registros de peticiones HTTP para recursos proxyficados, incluyendo método, ruta y código de respuesta.", "httpDestSaveChanges": "Guardar Cambios", "httpDestCreateDestination": "Crear destino", From 72e48a56df5a11a7d726eface07362da5372d271 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 16:58:28 -0700 Subject: [PATCH 077/139] Remove explicit call --- server/setup/scriptsPg/1.18.3.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/setup/scriptsPg/1.18.3.ts b/server/setup/scriptsPg/1.18.3.ts index e71de3f89..bbed1ee0e 100644 --- a/server/setup/scriptsPg/1.18.3.ts +++ b/server/setup/scriptsPg/1.18.3.ts @@ -3,8 +3,6 @@ import { sql } from "drizzle-orm"; const version = "1.18.3"; -await migration(); - export default async function migration() { console.log(`Running setup script ${version}...`); From 451f3d24a8ffb312e1b3fcf54e563c508bbc2d09 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 6 May 2026 17:13:33 -0700 Subject: [PATCH 078/139] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 4bcbeaa0f..5250a684d 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -1356,7 +1356,7 @@ "sidebarSites": "Nœuds", "sidebarApprovals": "Demandes d'approbation", "sidebarResources": "Ressource", - "sidebarProxyResources": "Publiques", + "sidebarProxyResources": "Publique", "sidebarClientResources": "Privé", "sidebarAccessControl": "Contrôle d'accès", "sidebarLogsAndAnalytics": "Journaux & Analytiques", @@ -2458,8 +2458,8 @@ "manageUserDevicesDescription": "Voir et gérer les appareils que les utilisateurs utilisent pour se connecter en privé aux ressources", "downloadClientBannerTitle": "Télécharger le client Pangolin", "downloadClientBannerDescription": "Téléchargez le client Pangolin pour votre système afin de vous connecter au réseau Pangolin et accéder aux ressources de manière privée.", - "manageMachineClients": "Gérer les machines", - "manageMachineClientsDescription": "Créer et gérer les clients que les serveurs et systèmes utilisent pour se connecter en privé aux ressources", + "manageMachineClients": "Gérer les clients de la machine", + "manageMachineClientsDescription": "Créer et gérer des clients que les serveurs et les systèmes utilisent pour se connecter en privé aux ressources", "machineClientsBannerTitle": "Serveurs & Systèmes automatisés", "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", @@ -3154,7 +3154,6 @@ "healthCheckTabAdvanced": "Avancé", "healthCheckStrategyNotAvailable": "Cette stratégie n'est pas disponible. Veuillez contacter le service commercial pour activer cette fonctionnalité.", "uptime30d": "Disponibilité (30j)", - "uptimeNoData": "Aucune donnée", "idpAddActionCreateNew": "Créer un nouveau fournisseur d'identité", "idpAddActionImportFromOrg": "Importer d'une autre organisation", "idpImportDialogTitle": "Importer le fournisseur d'identité", From 7bc08c0425d100451fbedc0a1d71269d75093ae8 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 6 May 2026 20:00:23 -0700 Subject: [PATCH 079/139] If not exists on trial table --- server/setup/scriptsPg/1.18.3.ts | 6 +----- server/setup/scriptsSqlite/1.18.3.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/server/setup/scriptsPg/1.18.3.ts b/server/setup/scriptsPg/1.18.3.ts index bbed1ee0e..1e786fa07 100644 --- a/server/setup/scriptsPg/1.18.3.ts +++ b/server/setup/scriptsPg/1.18.3.ts @@ -44,7 +44,7 @@ export default async function migration() { await db.execute(sql`BEGIN`); await db.execute(sql` - CREATE TABLE "trialNotifications" ( + CREATE TABLE IF NOT EXISTS "trialNotifications" ( "notificationId" serial PRIMARY KEY NOT NULL, "subscriptionId" varchar(255) NOT NULL, "notificationType" varchar(50) NOT NULL, @@ -52,10 +52,6 @@ export default async function migration() { ); `); - await db.execute(sql` - ALTER TABLE "trialNotifications" ADD CONSTRAINT "trialNotifications_subscriptionId_subscriptions_subscriptionId_fk" FOREIGN KEY ("subscriptionId") REFERENCES "public"."subscriptions"("subscriptionId") ON DELETE cascade ON UPDATE no action; - `); - await db.execute(sql`COMMIT`); console.log("Migrated database"); } catch (e) { diff --git a/server/setup/scriptsSqlite/1.18.3.ts b/server/setup/scriptsSqlite/1.18.3.ts index ad82327fe..74f9b5344 100644 --- a/server/setup/scriptsSqlite/1.18.3.ts +++ b/server/setup/scriptsSqlite/1.18.3.ts @@ -16,7 +16,7 @@ export default async function migration() { db.transaction(() => { db.prepare( ` - CREATE TABLE 'trialNotifications' ( + CREATE TABLE IF NOT EXISTS 'trialNotifications' ( 'notificationId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, 'subscriptionId' text NOT NULL, 'notificationType' text NOT NULL, From 8ed01372b84283af9f126ff27beff0fc19a60df1 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 7 May 2026 15:14:44 -0700 Subject: [PATCH 080/139] Add org to logs --- .../routers/olm/handleOlmRegisterMessage.ts | 92 +++++++++++++------ 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/server/routers/olm/handleOlmRegisterMessage.ts b/server/routers/olm/handleOlmRegisterMessage.ts index a4a62973d..aaaf81c41 100644 --- a/server/routers/olm/handleOlmRegisterMessage.ts +++ b/server/routers/olm/handleOlmRegisterMessage.ts @@ -22,14 +22,14 @@ import { canCompress } from "@server/lib/clientVersionChecks"; import config from "@server/lib/config"; export const handleOlmRegisterMessage: MessageHandler = async (context) => { - logger.info("Handling register olm message!"); + logger.info("[handleOlmRegisterMessage] Handling register olm message"); const { message, client: c, sendToClient } = context; const olm = c as Olm; const now = Math.floor(Date.now() / 1000); if (!olm) { - logger.warn("Olm not found"); + logger.warn("[handleOlmRegisterMessage] Olm not found"); return; } @@ -46,16 +46,19 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { } = message.data; if (!olm.clientId) { - logger.warn("Olm client ID not found"); + logger.warn("[handleOlmRegisterMessage] Olm client ID not found"); sendOlmError(OlmErrorCodes.CLIENT_ID_NOT_FOUND, olm.olmId); return; } - logger.debug("Handling fingerprint insertion for olm register...", { - olmId: olm.olmId, - fingerprint, - postures - }); + logger.debug( + "[handleOlmRegisterMessage] Handling fingerprint insertion for olm register...", + { + olmId: olm.olmId, + fingerprint, + postures + } + ); const isUserDevice = olm.userId !== null && olm.userId !== undefined; @@ -85,14 +88,17 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { .limit(1); if (!client) { - logger.warn("Client ID not found"); + logger.warn("[handleOlmRegisterMessage] Client not found", { + clientId: olm.clientId + }); sendOlmError(OlmErrorCodes.CLIENT_NOT_FOUND, olm.olmId); return; } if (client.blocked) { logger.debug( - `Client ${client.clientId} is blocked. Ignoring register.` + `[handleOlmRegisterMessage] Client ${client.clientId} is blocked. Ignoring register.`, + { orgId: client.orgId } ); sendOlmError(OlmErrorCodes.CLIENT_BLOCKED, olm.olmId); return; @@ -100,7 +106,8 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { if (client.approvalState == "pending") { logger.debug( - `Client ${client.clientId} approval is pending. Ignoring register.` + `[handleOlmRegisterMessage] Client ${client.clientId} approval is pending. Ignoring register.`, + { orgId: client.orgId } ); sendOlmError(OlmErrorCodes.CLIENT_PENDING, olm.olmId); return; @@ -128,14 +135,18 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { .limit(1); if (!org) { - logger.warn("Org not found"); + logger.warn("[handleOlmRegisterMessage] Org not found", { + orgId: client.orgId + }); sendOlmError(OlmErrorCodes.ORG_NOT_FOUND, olm.olmId); return; } if (orgId) { if (!olm.userId) { - logger.warn("Olm has no user ID"); + logger.warn("[handleOlmRegisterMessage] Olm has no user ID", { + orgId: client.orgId + }); sendOlmError(OlmErrorCodes.USER_ID_NOT_FOUND, olm.olmId); return; } @@ -143,12 +154,18 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { const { session: userSession, user } = await validateSessionToken(userToken); if (!userSession || !user) { - logger.warn("Invalid user session for olm register"); + logger.warn( + "[handleOlmRegisterMessage] Invalid user session for olm register", + { orgId: client.orgId } + ); sendOlmError(OlmErrorCodes.INVALID_USER_SESSION, olm.olmId); return; } if (user.userId !== olm.userId) { - logger.warn("User ID mismatch for olm register"); + logger.warn( + "[handleOlmRegisterMessage] User ID mismatch for olm register", + { orgId: client.orgId } + ); sendOlmError(OlmErrorCodes.USER_ID_MISMATCH, olm.olmId); return; } @@ -163,11 +180,15 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { sessionId // this is the user token passed in the message }); - logger.debug("Policy check result:", policyCheck); + logger.debug("[handleOlmRegisterMessage] Policy check result", { + orgId: client.orgId, + policyCheck + }); if (policyCheck?.error) { logger.error( - `Error checking access policies for olm user ${olm.userId} in org ${orgId}: ${policyCheck?.error}` + `[handleOlmRegisterMessage] Error checking access policies for olm user ${olm.userId} in org ${orgId}: ${policyCheck?.error}`, + { orgId: client.orgId } ); sendOlmError(OlmErrorCodes.ORG_ACCESS_POLICY_DENIED, olm.olmId); return; @@ -175,7 +196,8 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { if (policyCheck.policies?.passwordAge?.compliant === false) { logger.warn( - `Olm user ${olm.userId} has non-compliant password age for org ${orgId}` + `[handleOlmRegisterMessage] Olm user ${olm.userId} has non-compliant password age for org ${orgId}`, + { orgId: client.orgId } ); sendOlmError( OlmErrorCodes.ORG_ACCESS_POLICY_PASSWORD_EXPIRED, @@ -186,7 +208,8 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { policyCheck.policies?.maxSessionLength?.compliant === false ) { logger.warn( - `Olm user ${olm.userId} has non-compliant session length for org ${orgId}` + `[handleOlmRegisterMessage] Olm user ${olm.userId} has non-compliant session length for org ${orgId}`, + { orgId: client.orgId } ); sendOlmError( OlmErrorCodes.ORG_ACCESS_POLICY_SESSION_EXPIRED, @@ -195,7 +218,8 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { return; } else if (policyCheck.policies?.requiredTwoFactor === false) { logger.warn( - `Olm user ${olm.userId} does not have 2FA enabled for org ${orgId}` + `[handleOlmRegisterMessage] Olm user ${olm.userId} does not have 2FA enabled for org ${orgId}`, + { orgId: client.orgId } ); sendOlmError( OlmErrorCodes.ORG_ACCESS_POLICY_2FA_REQUIRED, @@ -204,7 +228,8 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { return; } else if (!policyCheck.allowed) { logger.warn( - `Olm user ${olm.userId} does not pass access policies for org ${orgId}: ${policyCheck.error}` + `[handleOlmRegisterMessage] Olm user ${olm.userId} does not pass access policies for org ${orgId}: ${policyCheck.error}`, + { orgId: client.orgId } ); sendOlmError(OlmErrorCodes.ORG_ACCESS_POLICY_DENIED, olm.olmId); return; @@ -226,29 +251,39 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { sitesCountResult.length > 0 ? sitesCountResult[0].count : 0; // Prepare an array to store site configurations - logger.debug(`Found ${sitesCount} sites for client ${client.clientId}`); + logger.debug( + `[handleOlmRegisterMessage] Found ${sitesCount} sites for client ${client.clientId}`, + { orgId: client.orgId } + ); let jitMode = false; if (sitesCount > 250 && build == "saas") { // THIS IS THE MAX ON THE BUSINESS TIER // we have too many sites // If we have too many sites we need to drop into fully JIT mode by not sending any of the sites - logger.info("Too many sites (%d), dropping into JIT mode", sitesCount); + logger.info( + `[handleOlmRegisterMessage] Too many sites (${sitesCount}), dropping into JIT mode`, + { orgId: client.orgId } + ); jitMode = true; } logger.debug( - `Olm client ID: ${client.clientId}, Public Key: ${publicKey}, Relay: ${relay}` + `[handleOlmRegisterMessage] Olm client ID: ${client.clientId}, Public Key: ${publicKey}, Relay: ${relay}`, + { orgId: client.orgId } ); if (!publicKey) { - logger.warn("Public key not provided"); + logger.warn("[handleOlmRegisterMessage] Public key not provided", { + orgId: client.orgId + }); return; } if (client.pubKey !== publicKey || client.archived) { logger.info( - "Public key mismatch. Updating public key and clearing session info..." + "[handleOlmRegisterMessage] Public key mismatch. Updating public key and clearing session info...", + { orgId: client.orgId } ); // Update the client's public key await db @@ -274,12 +309,13 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { // TODO: I still think there is a better way to do this rather than locking it out here but ??? if (now - (client.lastHolePunch || 0) > 5 && sitesCount > 0) { logger.warn( - `Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port ${config.getRawConfig().gerbil.clients_start_port}?` + `[handleOlmRegisterMessage] Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port ${config.getRawConfig().gerbil.clients_start_port}?`, + { orgId: client.orgId } ); return; } - // NOTE: its important that the client here is the old client and the public key is the new key + // NOTE: its important that the client here is the old client and the public key is the new key const siteConfigurations = await buildSiteConfigurationForOlmClient( client, publicKey, From 2a9481023a57639e16e64153916d061a6d754ceb Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 7 May 2026 15:15:03 -0700 Subject: [PATCH 081/139] Dont show link when wildcard --- src/app/[orgId]/settings/resources/proxy/page.tsx | 5 ++++- src/components/ProxyResourcesTable.tsx | 13 +++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/app/[orgId]/settings/resources/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/page.tsx index 0bbc8aa66..b94c4daf5 100644 --- a/src/app/[orgId]/settings/resources/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/page.tsx @@ -55,7 +55,9 @@ export default async function ProxyResourcesPage( pagination = responseData.pagination; } catch (e) {} - const siteIdParam = parsePositiveInt(searchParams.get("siteId") ?? undefined); + const siteIdParam = parsePositiveInt( + searchParams.get("siteId") ?? undefined + ); let initialFilterSite: { siteId: number; @@ -122,6 +124,7 @@ export default async function ProxyResourcesPage( domainId: resource.domainId || undefined, fullDomain: resource.fullDomain ?? null, ssl: resource.ssl, + wildcard: resource.wildcard, targets: resource.targets?.map((target) => ({ targetId: target.targetId, ip: target.ip, diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index 813d232cc..98ddd8eb7 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -96,6 +96,7 @@ export type ResourceRow = { targets?: TargetHealth[]; health?: "healthy" | "degraded" | "unhealthy" | "unknown"; sites: ResourceSiteRow[]; + wildcard?: boolean; }; function StatusIcon({ @@ -570,10 +571,14 @@ export default function ProxyResourcesTable({ /> ) : null}
- + {!resourceRow.wildcard ? ( + + ) : ( + {resourceRow.domain} + )}
); From 4eb49e3e603027495c28b7d4af1fbc71b7f44372 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 7 May 2026 15:40:34 -0700 Subject: [PATCH 082/139] Make the rebuild long running function background --- server/lib/calculateUserClientsForOrgs.ts | 6 +-- server/private/routers/user/addUserRole.ts | 24 ++++++--- server/private/routers/user/removeUserRole.ts | 17 +++++-- .../private/routers/user/setUserOrgRoles.ts | 17 +++++-- server/routers/auth/deleteMyAccount.ts | 9 +++- server/routers/client/createClient.ts | 24 +++++++-- server/routers/client/createUserClient.ts | 14 ++++-- server/routers/client/deleteClient.ts | 36 +++++++++---- server/routers/olm/createUserOlm.ts | 23 +++++---- server/routers/olm/deleteUserOlm.ts | 39 ++++++++------- .../batchAddClientToSiteResources.ts | 9 +++- server/routers/user/acceptInvite.ts | 24 ++++++--- server/routers/user/addUserRoleLegacy.ts | 18 +++++-- server/routers/user/adminRemoveUser.ts | 8 ++- server/routers/user/createOrgUser.ts | 50 ++++++++++++------- server/routers/user/removeUserOrg.ts | 26 +++------- 16 files changed, 230 insertions(+), 114 deletions(-) diff --git a/server/lib/calculateUserClientsForOrgs.ts b/server/lib/calculateUserClientsForOrgs.ts index 7d8f41a1e..6354dd81f 100644 --- a/server/lib/calculateUserClientsForOrgs.ts +++ b/server/lib/calculateUserClientsForOrgs.ts @@ -25,9 +25,9 @@ import { tierMatrix } from "./billing/tierMatrix"; export async function calculateUserClientsForOrgs( userId: string, - trx?: Transaction + trx: Transaction | typeof db = db ): Promise { - const execute = async (transaction: Transaction) => { + const execute = async (transaction: Transaction | typeof db) => { const orgCache = new Map(); const adminRoleCache = new Map< string, @@ -437,7 +437,7 @@ export async function calculateUserClientsForOrgs( async function cleanupOrphanedClients( userId: string, - trx: Transaction, + trx: Transaction | typeof db, userOrgIds: string[] = [] ): Promise { // Find all OLM clients for this user that should be deleted diff --git a/server/private/routers/user/addUserRole.ts b/server/private/routers/user/addUserRole.ts index 0789373a0..90fa79ee3 100644 --- a/server/private/routers/user/addUserRole.ts +++ b/server/private/routers/user/addUserRole.ts @@ -14,7 +14,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import stoi from "@server/lib/stoi"; -import { clients, db } from "@server/db"; +import { clients, db, primaryDb, Client } from "@server/db"; import { userOrgRoles, userOrgs, roles } from "@server/db"; import { eq, and } from "drizzle-orm"; import response from "@server/lib/response"; @@ -122,8 +122,12 @@ export async function addUserRole( ); } - let newUserRole: { userId: string; orgId: string; roleId: number } | null = - null; + let newUserRole: { + userId: string; + orgId: string; + roleId: number; + } | null = null; + let orgClientsToRebuild: Client[] = []; await db.transaction(async (trx) => { const inserted = await trx .insert(userOrgRoles) @@ -149,11 +153,19 @@ export async function addUserRole( ) ); - for (const orgClient of orgClients) { - await rebuildClientAssociationsFromClient(orgClient, trx); - } + orgClientsToRebuild = orgClients; }); + for (const orgClient of orgClientsToRebuild) { + rebuildClientAssociationsFromClient(orgClient, primaryDb).catch( + (e) => { + logger.error( + `Failed to rebuild client associations for client ${orgClient.clientId} after adding role: ${e}` + ); + } + ); + } + return response(res, { data: newUserRole ?? { userId, orgId: role.orgId, roleId }, success: true, diff --git a/server/private/routers/user/removeUserRole.ts b/server/private/routers/user/removeUserRole.ts index bd5c530d2..1a7b763d4 100644 --- a/server/private/routers/user/removeUserRole.ts +++ b/server/private/routers/user/removeUserRole.ts @@ -14,7 +14,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import stoi from "@server/lib/stoi"; -import { db } from "@server/db"; +import { db, primaryDb, Client } from "@server/db"; import { userOrgRoles, userOrgs, roles, clients } from "@server/db"; import { eq, and } from "drizzle-orm"; import response from "@server/lib/response"; @@ -129,6 +129,7 @@ export async function removeUserRole( } } + let orgClientsToRebuild: Client[] = []; await db.transaction(async (trx) => { await trx .delete(userOrgRoles) @@ -150,11 +151,19 @@ export async function removeUserRole( ) ); - for (const orgClient of orgClients) { - await rebuildClientAssociationsFromClient(orgClient, trx); - } + orgClientsToRebuild = orgClients; }); + for (const orgClient of orgClientsToRebuild) { + rebuildClientAssociationsFromClient(orgClient, primaryDb).catch( + (e) => { + logger.error( + `Failed to rebuild client associations for client ${orgClient.clientId} after removing role: ${e}` + ); + } + ); + } + return response(res, { data: { userId, orgId: role.orgId, roleId }, success: true, diff --git a/server/private/routers/user/setUserOrgRoles.ts b/server/private/routers/user/setUserOrgRoles.ts index d1df4965a..7567ffc54 100644 --- a/server/private/routers/user/setUserOrgRoles.ts +++ b/server/private/routers/user/setUserOrgRoles.ts @@ -13,7 +13,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { clients, db } from "@server/db"; +import { clients, db, primaryDb, Client } from "@server/db"; import { userOrgRoles, userOrgs, roles } from "@server/db"; import { eq, and, inArray } from "drizzle-orm"; import response from "@server/lib/response"; @@ -115,6 +115,7 @@ export async function setUserOrgRoles( ); } + let orgClientsToRebuild: Client[] = []; await db.transaction(async (trx) => { await trx .delete(userOrgRoles) @@ -142,11 +143,19 @@ export async function setUserOrgRoles( and(eq(clients.userId, userId), eq(clients.orgId, orgId)) ); - for (const orgClient of orgClients) { - await rebuildClientAssociationsFromClient(orgClient, trx); - } + orgClientsToRebuild = orgClients; }); + for (const orgClient of orgClientsToRebuild) { + rebuildClientAssociationsFromClient(orgClient, primaryDb).catch( + (e) => { + logger.error( + `Failed to rebuild client associations for client ${orgClient.clientId} after setting roles: ${e}` + ); + } + ); + } + return response(res, { data: { userId, orgId, roleIds: uniqueRoleIds }, success: true, diff --git a/server/routers/auth/deleteMyAccount.ts b/server/routers/auth/deleteMyAccount.ts index 07bdf883d..d03af5631 100644 --- a/server/routers/auth/deleteMyAccount.ts +++ b/server/routers/auth/deleteMyAccount.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, orgs, userOrgs, users } from "@server/db"; +import { db, orgs, userOrgs, users, primaryDb } from "@server/db"; import { eq, and, inArray, not } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -218,13 +218,18 @@ export async function deleteMyAccount( await db.transaction(async (trx) => { await trx.delete(users).where(eq(users.userId, userId)); - await calculateUserClientsForOrgs(userId, trx); // loop through the other orgs and decrement the count for (const userOrg of otherOrgsTheUserWasIn) { await usageService.add(userOrg.orgId, FeatureId.USERS, -1, trx); } }); + calculateUserClientsForOrgs(userId, primaryDb).catch((e) => { + logger.error( + `Failed to calculate user clients after deleting account for user ${userId}: ${e}` + ); + }); + try { await invalidateSession(session.sessionId); } catch (error) { diff --git a/server/routers/client/createClient.ts b/server/routers/client/createClient.ts index 337d7e714..029f3c159 100644 --- a/server/routers/client/createClient.ts +++ b/server/routers/client/createClient.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db } from "@server/db"; +import { db, primaryDb } from "@server/db"; import { roles, Client, @@ -92,7 +92,10 @@ export async function createClient( const { orgId } = parsedParams.data; - if (req.user && (!req.userOrgRoleIds || req.userOrgRoleIds.length === 0)) { + if ( + req.user && + (!req.userOrgRoleIds || req.userOrgRoleIds.length === 0) + ) { return next( createHttpError(HttpCode.FORBIDDEN, "User does not have a role") ); @@ -198,7 +201,10 @@ export async function createClient( 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."}`) + createHttpError( + HttpCode.NOT_FOUND, + `No exit nodes available. ${build == "saas" ? "Please contact support." : "You need to install gerbil to use the clients."}` + ) ); } @@ -256,10 +262,18 @@ export async function createClient( clientId: newClient.clientId, dateCreated: moment().toISOString() }); - - await rebuildClientAssociationsFromClient(newClient, trx); }); + if (newClient) { + rebuildClientAssociationsFromClient(newClient, primaryDb).catch( + (e) => { + logger.error( + `Failed to rebuild client associations after creating client: ${e}` + ); + } + ); + } + return response(res, { data: newClient, success: true, diff --git a/server/routers/client/createUserClient.ts b/server/routers/client/createUserClient.ts index d61eab15f..e702796fd 100644 --- a/server/routers/client/createUserClient.ts +++ b/server/routers/client/createUserClient.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db } from "@server/db"; +import { db, primaryDb } from "@server/db"; import { roles, Client, @@ -237,10 +237,18 @@ export async function createUserClient( userId, clientId: newClient.clientId }); - - await rebuildClientAssociationsFromClient(newClient, trx); }); + if (newClient) { + rebuildClientAssociationsFromClient(newClient, primaryDb).catch( + (e) => { + logger.error( + `Failed to rebuild client associations after creating user client: ${e}` + ); + } + ); + } + return response(res, { data: newClient, success: true, diff --git a/server/routers/client/deleteClient.ts b/server/routers/client/deleteClient.ts index 276bfde96..378639af6 100644 --- a/server/routers/client/deleteClient.ts +++ b/server/routers/client/deleteClient.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, olms } from "@server/db"; +import { db, olms, primaryDb, Client, Olm } from "@server/db"; import { clients, clientSitesAssociationsCache } from "@server/db"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; @@ -71,14 +71,17 @@ export async function deleteClient( ); } + let deletedClient: Client | undefined; + let olm: Olm | undefined; + await db.transaction(async (trx) => { // Then delete the client itself - const [deletedClient] = await trx + [deletedClient] = await trx .delete(clients) .where(eq(clients.clientId, clientId)) .returning(); - const [olm] = await trx + [olm] = await trx .select() .from(olms) .where(eq(olms.clientId, clientId)) @@ -88,14 +91,29 @@ export async function deleteClient( if (!client.userId && client.olmId) { await trx.delete(olms).where(eq(olms.olmId, client.olmId)); } - - await rebuildClientAssociationsFromClient(deletedClient, trx); - - if (olm) { - await sendTerminateClient(deletedClient.clientId, OlmErrorCodes.TERMINATED_DELETED, olm.olmId); // the olmId needs to be provided because it cant look it up after deletion - } }); + if (deletedClient) { + rebuildClientAssociationsFromClient(deletedClient, primaryDb).catch( + (e) => { + logger.error( + `Failed to rebuild client associations after deleting client ${clientId}: ${e}` + ); + } + ); + if (olm) { + sendTerminateClient( + deletedClient.clientId, + OlmErrorCodes.TERMINATED_DELETED, + olm.olmId + ).catch((e) => { + logger.error( + `Failed to send terminate message for client ${deletedClient?.clientId} after deleting client ${clientId}: ${e}` + ); + }); + } + } + return response(res, { data: null, success: true, diff --git a/server/routers/olm/createUserOlm.ts b/server/routers/olm/createUserOlm.ts index 0fc1e452d..105a94ff2 100644 --- a/server/routers/olm/createUserOlm.ts +++ b/server/routers/olm/createUserOlm.ts @@ -1,5 +1,5 @@ import { NextFunction, Request, Response } from "express"; -import { db, olms } from "@server/db"; +import { db, olms, primaryDb } from "@server/db"; import HttpCode from "@server/types/HttpCode"; import { z } from "zod"; import createHttpError from "http-errors"; @@ -81,16 +81,19 @@ export async function createUserOlm( const secretHash = await hashPassword(secret); - await db.transaction(async (trx) => { - await trx.insert(olms).values({ - olmId: olmId, - userId, - name, - secretHash, - dateCreated: moment().toISOString() - }); + await db.insert(olms).values({ + olmId: olmId, + userId, + name, + secretHash, + dateCreated: moment().toISOString() + }); - await calculateUserClientsForOrgs(userId, trx); + calculateUserClientsForOrgs(userId, primaryDb).catch((e) => { + console.error( + "Error calculating user clients after creating olm:", + e + ); }); return response(res, { diff --git a/server/routers/olm/deleteUserOlm.ts b/server/routers/olm/deleteUserOlm.ts index 2c2814899..fb1f5604c 100644 --- a/server/routers/olm/deleteUserOlm.ts +++ b/server/routers/olm/deleteUserOlm.ts @@ -1,5 +1,5 @@ import { NextFunction, Request, Response } from "express"; -import { Client, db } from "@server/db"; +import { Client, db, Olm, primaryDb } from "@server/db"; import { olms, clients, clientSitesAssociationsCache } from "@server/db"; import { eq } from "drizzle-orm"; import HttpCode from "@server/types/HttpCode"; @@ -49,6 +49,7 @@ export async function deleteUserOlm( const { olmId } = parsedParams.data; + let deletedClient: Client | undefined; // Delete associated clients and the OLM in a transaction await db.transaction(async (trx) => { // Find all clients associated with this OLM @@ -57,7 +58,6 @@ export async function deleteUserOlm( .from(clients) .where(eq(clients.olmId, olmId)); - let deletedClient: Client | null = null; // Delete all associated clients if (associatedClients.length > 0) { [deletedClient] = await trx @@ -67,23 +67,28 @@ export async function deleteUserOlm( } // Finally, delete the OLM itself - const [olm] = await trx - .delete(olms) - .where(eq(olms.olmId, olmId)) - .returning(); - - if (deletedClient) { - await rebuildClientAssociationsFromClient(deletedClient, trx); - if (olm) { - await sendTerminateClient( - deletedClient.clientId, - OlmErrorCodes.TERMINATED_DELETED, - olm.olmId - ); // the olmId needs to be provided because it cant look it up after deletion - } - } + await trx.delete(olms).where(eq(olms.olmId, olmId)).returning(); }); + if (deletedClient) { + rebuildClientAssociationsFromClient(deletedClient, primaryDb).catch( + (e) => { + logger.error( + `Failed to rebuild client-site associations after deleting OLM ${olmId}: ${e}` + ); + } + ); + sendTerminateClient( + deletedClient.clientId, + OlmErrorCodes.TERMINATED_DELETED, + olmId + ).catch((e) => { + logger.error( + `Failed to send terminate message for client ${deletedClient?.clientId} after deleting OLM ${olmId}: ${e}` + ); + }); + } + return response(res, { data: null, success: true, diff --git a/server/routers/siteResource/batchAddClientToSiteResources.ts b/server/routers/siteResource/batchAddClientToSiteResources.ts index c3ad3859a..34c7b58fe 100644 --- a/server/routers/siteResource/batchAddClientToSiteResources.ts +++ b/server/routers/siteResource/batchAddClientToSiteResources.ts @@ -5,7 +5,8 @@ import { clients, clientSiteResources, siteResources, - apiKeyOrg + apiKeyOrg, + primaryDb } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -220,8 +221,12 @@ export async function batchAddClientToSiteResources( siteResourceId: siteResource.siteResourceId }); } + }); - await rebuildClientAssociationsFromClient(client, trx); + rebuildClientAssociationsFromClient(client, primaryDb).catch((e) => { + logger.error( + `Failed to rebuild client associations after batch adding site resources for client ${clientId}: ${e}` + ); }); return response(res, { diff --git a/server/routers/user/acceptInvite.ts b/server/routers/user/acceptInvite.ts index 88010e580..e3366a0c5 100644 --- a/server/routers/user/acceptInvite.ts +++ b/server/routers/user/acceptInvite.ts @@ -1,7 +1,13 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, orgs } from "@server/db"; -import { roles, userInviteRoles, userInvites, userOrgs, users } from "@server/db"; +import { db, orgs, primaryDb } from "@server/db"; +import { + roles, + userInviteRoles, + userInvites, + userOrgs, + users +} from "@server/db"; import { eq, and, inArray } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -146,9 +152,7 @@ export async function acceptInvite( .from(userInviteRoles) .where(eq(userInviteRoles.inviteId, inviteId)); - const inviteRoleIds = [ - ...new Set(inviteRoleRows.map((r) => r.roleId)) - ]; + const inviteRoleIds = [...new Set(inviteRoleRows.map((r) => r.roleId))]; if (inviteRoleIds.length === 0) { return next( createHttpError( @@ -193,13 +197,19 @@ export async function acceptInvite( .delete(userInvites) .where(eq(userInvites.inviteId, inviteId)); - await calculateUserClientsForOrgs(existingUser[0].userId, trx); - logger.debug( `User ${existingUser[0].userId} accepted invite to org ${existingInvite.orgId}` ); }); + calculateUserClientsForOrgs(existingUser[0].userId, primaryDb).catch( + (e) => { + logger.error( + `Failed to calculate user clients after accepting invite for user ${existingUser[0].userId}: ${e}` + ); + } + ); + return response(res, { data: { accepted: true, orgId: existingInvite.orgId }, success: true, diff --git a/server/routers/user/addUserRoleLegacy.ts b/server/routers/user/addUserRoleLegacy.ts index db0c6182f..9696e4aac 100644 --- a/server/routers/user/addUserRoleLegacy.ts +++ b/server/routers/user/addUserRoleLegacy.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import stoi from "@server/lib/stoi"; -import { clients, db } from "@server/db"; +import { clients, db, primaryDb, Client } from "@server/db"; import { userOrgRoles, userOrgs, roles } from "@server/db"; import { eq, and } from "drizzle-orm"; import response from "@server/lib/response"; @@ -112,6 +112,8 @@ export async function addUserRoleLegacy( ); } + let orgClientsToRebuild: Client[] = []; + await db.transaction(async (trx) => { await trx .delete(userOrgRoles) @@ -138,11 +140,19 @@ export async function addUserRoleLegacy( ) ); - for (const orgClient of orgClients) { - await rebuildClientAssociationsFromClient(orgClient, trx); - } + orgClientsToRebuild = orgClients; }); + for (const orgClient of orgClientsToRebuild) { + rebuildClientAssociationsFromClient(orgClient, primaryDb).catch( + (e) => { + logger.error( + `Failed to rebuild client associations for client ${orgClient.clientId} after adding role: ${e}` + ); + } + ); + } + return response(res, { data: { ...existingUser, roleId }, success: true, diff --git a/server/routers/user/adminRemoveUser.ts b/server/routers/user/adminRemoveUser.ts index ae7f9f470..38713ce26 100644 --- a/server/routers/user/adminRemoveUser.ts +++ b/server/routers/user/adminRemoveUser.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db } from "@server/db"; +import { db, primaryDb } from "@server/db"; import { users } from "@server/db"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; @@ -53,8 +53,12 @@ export async function adminRemoveUser( await db.transaction(async (trx) => { await trx.delete(users).where(eq(users.userId, userId)); + }); - await calculateUserClientsForOrgs(userId, trx); + calculateUserClientsForOrgs(userId, primaryDb).catch((e) => { + logger.error( + `Failed to calculate user clients after removing user ${userId}: ${e}` + ); }); return response(res, { diff --git a/server/routers/user/createOrgUser.ts b/server/routers/user/createOrgUser.ts index ddc37d3a2..ed3824b24 100644 --- a/server/routers/user/createOrgUser.ts +++ b/server/routers/user/createOrgUser.ts @@ -6,7 +6,7 @@ import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; -import { db, orgs } from "@server/db"; +import { db, orgs, primaryDb } from "@server/db"; import { and, eq, inArray } from "drizzle-orm"; import { idp, idpOidcConfig, roles, userOrgs, users } from "@server/db"; import { generateId } from "@server/auth/sessions/app"; @@ -34,8 +34,7 @@ const bodySchema = z roleId: z.number().int().positive().optional() }) .refine( - (d) => - (d.roleIds != null && d.roleIds.length > 0) || d.roleId != null, + (d) => (d.roleIds != null && d.roleIds.length > 0) || d.roleId != null, { message: "roleIds or roleId is required", path: ["roleIds"] } ) .transform((data) => ({ @@ -100,8 +99,14 @@ export async function createOrgUser( } const { orgId } = parsedParams.data; - const { username, email, name, type, idpId, roleIds: uniqueRoleIds } = - parsedBody.data; + const { + username, + email, + name, + type, + idpId, + roleIds: uniqueRoleIds + } = parsedBody.data; if (build == "saas") { const usage = await usageService.getUsage(orgId, FeatureId.USERS); @@ -232,6 +237,7 @@ export async function createOrgUser( ); } + let userIdForClients: string | undefined; await db.transaction(async (trx) => { const [existingUser] = await trx .select() @@ -270,7 +276,7 @@ export async function createOrgUser( { orgId, userId: existingUser.userId, - autoProvisioned: false, + autoProvisioned: false }, uniqueRoleIds, trx @@ -292,20 +298,30 @@ export async function createOrgUser( }) .returning(); - await assignUserToOrg( - org, - { - orgId, - userId: newUser.userId, - autoProvisioned: false, - }, - uniqueRoleIds, - trx - ); + await assignUserToOrg( + org, + { + orgId, + userId: newUser.userId, + autoProvisioned: false + }, + uniqueRoleIds, + trx + ); } - await calculateUserClientsForOrgs(userId, trx); + userIdForClients = userId; }); + + if (userIdForClients) { + calculateUserClientsForOrgs(userIdForClients, primaryDb).catch( + (e) => { + logger.error( + `Failed to calculate user clients after creating org user: ${e}` + ); + } + ); + } } else { return next( createHttpError(HttpCode.BAD_REQUEST, "User type is required") diff --git a/server/routers/user/removeUserOrg.ts b/server/routers/user/removeUserOrg.ts index 3c86a03c5..fcb3313a8 100644 --- a/server/routers/user/removeUserOrg.ts +++ b/server/routers/user/removeUserOrg.ts @@ -7,7 +7,8 @@ import { siteResources, sites, UserOrg, - userSiteResources + userSiteResources, + primaryDb } from "@server/db"; import { userOrgs, userResources, users, userSites } from "@server/db"; import { and, count, eq, exists, inArray } from "drizzle-orm"; @@ -91,25 +92,12 @@ export async function removeUserOrg( await db.transaction(async (trx) => { await removeUserFromOrg(org, userId, trx); + }); - // if (build === "saas") { - // const [rootUser] = await trx - // .select() - // .from(users) - // .where(eq(users.userId, userId)); - // - // const [leftInOrgs] = await trx - // .select({ count: count() }) - // .from(userOrgs) - // .where(eq(userOrgs.userId, userId)); - // - // // if the user is not an internal user and does not belong to any org, delete the entire user - // if (rootUser?.type !== UserType.Internal && !leftInOrgs.count) { - // await trx.delete(users).where(eq(users.userId, userId)); - // } - // } - - await calculateUserClientsForOrgs(userId, trx); + calculateUserClientsForOrgs(userId, primaryDb).catch((e) => { + logger.error( + `Failed to calculate user clients after removing user ${userId} from org ${orgId}: ${e}` + ); }); return response(res, { From a3ce382725fb1edc5407fa2e37b05dbbec50f2b6 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 7 May 2026 15:49:12 -0700 Subject: [PATCH 083/139] Pick up other domains in the sans field --- server/private/lib/acmeCertSync.ts | 290 ++++++++++++++++------------- 1 file changed, 158 insertions(+), 132 deletions(-) diff --git a/server/private/lib/acmeCertSync.ts b/server/private/lib/acmeCertSync.ts index 03051b11d..b69c2ae89 100644 --- a/server/private/lib/acmeCertSync.ts +++ b/server/private/lib/acmeCertSync.ts @@ -485,6 +485,133 @@ async function syncAcmeCertsFromHttp(endpoint: string): Promise { } } +async function storeCertForDomain( + domain: string, + certPem: string, + keyPem: string, + validatedX509: crypto.X509Certificate +): Promise { + const wildcard = domain.startsWith("*."); + + const existing = await db + .select() + .from(certificates) + .where(eq(certificates.domain, domain)) + .limit(1); + + let oldCertPem: string | null = null; + let oldKeyPem: string | null = null; + + if (existing.length > 0 && existing[0].certFile) { + try { + const storedCertPem = decrypt( + existing[0].certFile, + config.getRawConfig().server.secret! + ); + const wildcardUnchanged = existing[0].wildcard === wildcard; + if (storedCertPem === certPem && wildcardUnchanged) { + return; + } + oldCertPem = storedCertPem; + if (existing[0].keyFile) { + try { + oldKeyPem = decrypt( + existing[0].keyFile, + config.getRawConfig().server.secret! + ); + } catch (keyErr) { + logger.debug( + `acmeCertSync: could not decrypt stored key for ${domain}: ${keyErr}` + ); + } + } + } catch (err) { + logger.debug( + `acmeCertSync: could not decrypt stored cert for ${domain}, will update: ${err}` + ); + } + } + + let expiresAt: number | null = null; + try { + expiresAt = Math.floor( + new Date(validatedX509.validTo).getTime() / 1000 + ); + } catch (err) { + logger.debug( + `acmeCertSync: could not parse cert expiry for ${domain}: ${err}` + ); + } + + const encryptedCert = encrypt( + certPem, + config.getRawConfig().server.secret! + ); + const encryptedKey = encrypt(keyPem, config.getRawConfig().server.secret!); + const now = Math.floor(Date.now() / 1000); + + const domainId = await findDomainId(domain); + if (domainId) { + logger.debug( + `acmeCertSync: resolved domainId "${domainId}" for cert domain "${domain}"` + ); + } else { + logger.debug( + `acmeCertSync: no matching domain record found for cert domain "${domain}"` + ); + } + + if (existing.length > 0) { + logger.debug( + `acmeCertSync: updating existing certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` + ); + await db + .update(certificates) + .set({ + certFile: encryptedCert, + keyFile: encryptedKey, + status: "valid", + expiresAt, + updatedAt: now, + wildcard, + ...(domainId !== null && { domainId }) + }) + .where(eq(certificates.domain, domain)); + + logger.debug( + `acmeCertSync: updated certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` + ); + + await pushCertUpdateToAffectedNewts( + domain, + domainId, + oldCertPem, + oldKeyPem + ); + } else { + logger.debug( + `acmeCertSync: inserting new certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` + ); + await db.insert(certificates).values({ + domain, + domainId, + certFile: encryptedCert, + keyFile: encryptedKey, + status: "valid", + expiresAt, + createdAt: now, + updatedAt: now, + wildcard + }); + + logger.debug( + `acmeCertSync: inserted new certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` + ); + + await pushCertUpdateToAffectedNewts(domain, domainId, null, null); + } +} + function findAcmeJsonFiles(dirPath: string): string[] { const results: string[] = []; let entries: fs.Dirent[]; @@ -575,18 +702,16 @@ async function syncAcmeCerts(acmeJsonPath: string): Promise { } for (const cert of allCerts) { - const domain = cert?.domain?.main; + const mainDomain = cert?.domain?.main; - if (!domain || typeof domain !== "string") { + if (!mainDomain || typeof mainDomain !== "string") { logger.debug(`acmeCertSync: skipping cert with missing domain`); continue; } - const { wildcard } = detectWildcard(domain, cert.domain?.sans); - if (!cert.certificate || !cert.key) { logger.debug( - `acmeCertSync: skipping cert for ${domain} - empty certificate or key field` + `acmeCertSync: skipping cert for ${mainDomain} - empty certificate or key field` ); continue; } @@ -598,14 +723,14 @@ async function syncAcmeCerts(acmeJsonPath: string): Promise { keyPem = Buffer.from(cert.key, "base64").toString("utf8"); } catch (err) { logger.debug( - `acmeCertSync: skipping cert for ${domain} - failed to base64-decode cert/key: ${err}` + `acmeCertSync: skipping cert for ${mainDomain} - failed to base64-decode cert/key: ${err}` ); continue; } if (!certPem.trim() || !keyPem.trim()) { logger.debug( - `acmeCertSync: skipping cert for ${domain} - blank PEM after base64 decode` + `acmeCertSync: skipping cert for ${mainDomain} - blank PEM after base64 decode` ); continue; } @@ -616,7 +741,7 @@ async function syncAcmeCerts(acmeJsonPath: string): Promise { const firstCertPemForValidation = extractFirstCert(certPem); if (!firstCertPemForValidation) { logger.debug( - `acmeCertSync: skipping cert for ${domain} - no PEM certificate block found` + `acmeCertSync: skipping cert for ${mainDomain} - no PEM certificate block found` ); continue; } @@ -628,7 +753,7 @@ async function syncAcmeCerts(acmeJsonPath: string): Promise { ); } catch (err) { logger.debug( - `acmeCertSync: skipping cert for ${domain} - invalid X.509 certificate: ${err}` + `acmeCertSync: skipping cert for ${mainDomain} - invalid X.509 certificate: ${err}` ); continue; } @@ -638,139 +763,40 @@ async function syncAcmeCerts(acmeJsonPath: string): Promise { crypto.createPrivateKey(keyPem); } catch (err) { logger.debug( - `acmeCertSync: skipping cert for ${domain} - invalid private key: ${err}` + `acmeCertSync: skipping cert for ${mainDomain} - invalid private key: ${err}` ); continue; } - // Check if cert already exists in DB - const existing = await db - .select() - .from(certificates) - .where(and(eq(certificates.domain, domain))) - .limit(1); - - let oldCertPem: string | null = null; - let oldKeyPem: string | null = null; - - if (existing.length > 0 && existing[0].certFile) { - try { - const storedCertPem = decrypt( - existing[0].certFile, - config.getRawConfig().server.secret! - ); - const wildcardUnchanged = existing[0].wildcard === wildcard; - if (storedCertPem === certPem && wildcardUnchanged) { - // logger.debug( - // `acmeCertSync: cert for ${domain} is unchanged, skipping` - // ); - continue; + // Collect all domains covered by this cert: main + every SAN. + // Each domain gets its own row in the certificates table so that + // lookups by any hostname on the cert succeed independently. + const allDomains = new Set([mainDomain]); + if (Array.isArray(cert.domain?.sans)) { + for (const san of cert.domain.sans) { + if (typeof san === "string" && san.trim()) { + allDomains.add(san.trim()); } - // Cert has changed; capture old values so we can send a correct - // update message to the newt after the DB write. - oldCertPem = storedCertPem; - if (existing[0].keyFile) { - try { - oldKeyPem = decrypt( - existing[0].keyFile, - config.getRawConfig().server.secret! - ); - } catch (keyErr) { - logger.debug( - `acmeCertSync: could not decrypt stored key for ${domain}: ${keyErr}` - ); - } - } - } catch (err) { - // Decryption failure means we should proceed with the update - logger.debug( - `acmeCertSync: could not decrypt stored cert for ${domain}, will update: ${err}` - ); } } - // Parse cert expiry from the validated X.509 certificate - let expiresAt: number | null = null; - try { - expiresAt = Math.floor( - new Date(validatedX509.validTo).getTime() / 1000 - ); - } catch (err) { - logger.debug( - `acmeCertSync: could not parse cert expiry for ${domain}: ${err}` - ); - } - - const encryptedCert = encrypt( - certPem, - config.getRawConfig().server.secret! + logger.debug( + `acmeCertSync: cert for ${mainDomain} covers ${allDomains.size} domain(s): ${[...allDomains].join(", ")}` ); - const encryptedKey = encrypt( - keyPem, - config.getRawConfig().server.secret! - ); - const now = Math.floor(Date.now() / 1000); - const domainId = await findDomainId(domain); - if (domainId) { - logger.debug( - `acmeCertSync: resolved domainId "${domainId}" for cert domain "${domain}"` - ); - } else { - logger.debug( - `acmeCertSync: no matching domain record found for cert domain "${domain}"` - ); - } - - if (existing.length > 0) { - logger.debug( - `acmeCertSync: updating existing certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` - ); - await db - .update(certificates) - .set({ - certFile: encryptedCert, - keyFile: encryptedKey, - status: "valid", - expiresAt, - updatedAt: now, - wildcard, - ...(domainId !== null && { domainId }) - }) - .where(eq(certificates.domain, domain)); - - logger.debug( - `acmeCertSync: updated certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` - ); - - await pushCertUpdateToAffectedNewts( - domain, - domainId, - oldCertPem, - oldKeyPem - ); - } else { - logger.debug( - `acmeCertSync: inserting new certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` - ); - await db.insert(certificates).values({ - domain, - domainId, - certFile: encryptedCert, - keyFile: encryptedKey, - status: "valid", - expiresAt, - createdAt: now, - updatedAt: now, - wildcard - }); - - logger.debug( - `acmeCertSync: inserted new certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` - ); - - // For a brand-new cert, push to any SSL resources that were waiting for it - await pushCertUpdateToAffectedNewts(domain, domainId, null, null); + for (const domain of allDomains) { + try { + await storeCertForDomain( + domain, + certPem, + keyPem, + validatedX509 + ); + } catch (err) { + logger.error( + `acmeCertSync: error storing cert for domain "${domain}": ${err}` + ); + } } } } From ce04ea9720e7119a3bd548076da2ab4bbec0dbe8 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 7 May 2026 15:56:33 -0700 Subject: [PATCH 084/139] Fix not including today Fixes #3028 --- server/lib/statusHistory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/statusHistory.ts b/server/lib/statusHistory.ts index 8db76bbb0..7239601c0 100644 --- a/server/lib/statusHistory.ts +++ b/server/lib/statusHistory.ts @@ -124,7 +124,7 @@ export function computeBuckets( let totalDowntime = 0; for (let d = 0; d < days; d++) { - const dayStartSec = todayMidnightSec - (days - d) * 86400; + const dayStartSec = todayMidnightSec - (days - 1 - d) * 86400; const dayEndSec = dayStartSec + 86400; const dayEvents = events.filter( From c46ef2fe9c12bcf976bb9ded86b6b6a342163c5a Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 7 May 2026 20:03:48 -0700 Subject: [PATCH 085/139] Fix ts type issue --- server/db/pg/driver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/db/pg/driver.ts b/server/db/pg/driver.ts index 86a0c0352..9f6901dda 100644 --- a/server/db/pg/driver.ts +++ b/server/db/pg/driver.ts @@ -87,7 +87,7 @@ function createDb() { export const db = createDb(); export default db; -export const primaryDb = db.$primary; +export const primaryDb = db.$primary as typeof db; // is this typeof a problem - techincally they are different types export type Transaction = Parameters< Parameters<(typeof db)["transaction"]>[0] >[0]; From 5b8994d14357f85d3b223b0d43e8675b8614421d Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 7 May 2026 20:07:06 -0700 Subject: [PATCH 086/139] Cange to use primaryDb --- server/routers/siteResource/createSiteResource.ts | 13 ++++++------- server/routers/siteResource/deleteSiteResource.ts | 12 +++++------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index 1cf9c38d5..bc80e8b41 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -10,7 +10,8 @@ import { SiteResource, siteResources, sites, - userSiteResources + userSiteResources, + primaryDb } from "@server/db"; import { getUniqueSiteResourceName } from "@server/db/names"; import { @@ -519,12 +520,10 @@ export async function createSiteResource( // own transaction so it always executes on the primary — avoiding any // replica-lag issues while still allowing the HTTP response to return // early. - db.transaction(async (trx) => { - await rebuildClientAssociationsFromSiteResource( - newSiteResource!, - trx - ); - }).catch((err) => { + rebuildClientAssociationsFromSiteResource( + newSiteResource!, + primaryDb + ).catch((err) => { logger.error( `Error rebuilding client associations for site resource ${newSiteResource!.siteResourceId}:`, err diff --git a/server/routers/siteResource/deleteSiteResource.ts b/server/routers/siteResource/deleteSiteResource.ts index 7dbb111ad..5a4eb1cc9 100644 --- a/server/routers/siteResource/deleteSiteResource.ts +++ b/server/routers/siteResource/deleteSiteResource.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, newts, sites } from "@server/db"; +import { db, newts, primaryDb, sites } from "@server/db"; import { siteResources } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -73,12 +73,10 @@ export async function deleteSiteResource( // own transaction so it always executes on the primary — avoiding any // replica-lag issues while still allowing the HTTP response to return // early. - db.transaction(async (trx) => { - await rebuildClientAssociationsFromSiteResource( - removedSiteResource, - trx - ); - }).catch((err) => { + rebuildClientAssociationsFromSiteResource( + removedSiteResource, + primaryDb + ).catch((err) => { logger.error( `Error rebuilding client associations for site resource ${removedSiteResource!.siteResourceId}:`, err From cbdc74768f8a29199969a89f613d4bd3e5cfac6d Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 7 May 2026 21:09:21 -0700 Subject: [PATCH 087/139] Implement s3 streaming destination --- messages/en-US.json | 29 +- .../lib/logStreaming/LogStreamingManager.ts | 42 +- .../providers/S3LogDestination.ts | 279 ++++++++++ server/private/lib/logStreaming/types.ts | 34 ++ .../[orgId]/settings/logs/streaming/page.tsx | 61 ++- src/components/S3DestinationCredenza.tsx | 487 +++++++++++++++++- 6 files changed, 895 insertions(+), 37 deletions(-) create mode 100644 server/private/lib/logStreaming/providers/S3LogDestination.ts diff --git a/messages/en-US.json b/messages/en-US.json index a598dcc39..0e9cb5786 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Edit Destination", "S3DestAddTitle": "Add S3 Destination", "S3DestEditDescription": "Update the configuration for this S3 event streaming destination.", - "S3DestAddDescription": "Configure a new S3 endpoint to receive your organization's events.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Edit Destination", "datadogDestAddTitle": "Add Datadog Destination", "datadogDestEditDescription": "Update the configuration for this Datadog event streaming destination.", diff --git a/server/private/lib/logStreaming/LogStreamingManager.ts b/server/private/lib/logStreaming/LogStreamingManager.ts index 1df67c886..a9575fec6 100644 --- a/server/private/lib/logStreaming/LogStreamingManager.ts +++ b/server/private/lib/logStreaming/LogStreamingManager.ts @@ -30,10 +30,12 @@ import { LOG_TYPES, LogEvent, DestinationFailureState, - HttpConfig + HttpConfig, + S3Config } from "./types"; import { LogDestinationProvider } from "./providers/LogDestinationProvider"; import { HttpLogDestination } from "./providers/HttpLogDestination"; +import { S3LogDestination } from "./providers/S3LogDestination"; import type { EventStreamingDestination } from "@server/db"; // --------------------------------------------------------------------------- @@ -72,11 +74,11 @@ const MAX_CATCHUP_BATCHES = 20; * After the last entry the max value is re-used. */ const BACKOFF_SCHEDULE_MS = [ - 60_000, // 1 min (failure 1) - 2 * 60_000, // 2 min (failure 2) - 5 * 60_000, // 5 min (failure 3) - 10 * 60_000, // 10 min (failure 4) - 30 * 60_000 // 30 min (failure 5+) + 60_000, // 1 min (failure 1) + 2 * 60_000, // 2 min (failure 2) + 5 * 60_000, // 5 min (failure 3) + 10 * 60_000, // 10 min (failure 4) + 30 * 60_000 // 30 min (failure 5+) ]; /** @@ -204,7 +206,10 @@ export class LogStreamingManager { this.pollTimer = null; this.runPoll() .catch((err) => - logger.error("LogStreamingManager: unexpected poll error", err) + logger.error( + "LogStreamingManager: unexpected poll error", + err + ) ) .finally(() => { if (this.isRunning) { @@ -275,10 +280,13 @@ export class LogStreamingManager { } // Decrypt and parse config – skip destination if either step fails - let configFromDb: HttpConfig; + let configFromDb: unknown; try { - const decryptedConfig = decrypt(dest.config, config.getRawConfig().server.secret!); - configFromDb = JSON.parse(decryptedConfig) as HttpConfig; + const decryptedConfig = decrypt( + dest.config, + config.getRawConfig().server.secret! + ); + configFromDb = JSON.parse(decryptedConfig); } catch (err) { logger.error( `LogStreamingManager: destination ${dest.destinationId} has invalid or undecryptable config`, @@ -362,7 +370,10 @@ export class LogStreamingManager { .from(eventStreamingCursors) .where( and( - eq(eventStreamingCursors.destinationId, dest.destinationId), + eq( + eventStreamingCursors.destinationId, + dest.destinationId + ), eq(eventStreamingCursors.logType, logType) ) ) @@ -431,9 +442,7 @@ export class LogStreamingManager { if (rows.length === 0) break; - const events = rows.map((row) => - this.rowToLogEvent(logType, row) - ); + const events = rows.map((row) => this.rowToLogEvent(logType, row)); // Throws on failure – caught by the caller which applies back-off await provider.send(events); @@ -677,8 +686,7 @@ export class LogStreamingManager { break; } - const orgId = - typeof row.orgId === "string" ? row.orgId : ""; + const orgId = typeof row.orgId === "string" ? row.orgId : ""; return { id: row.id, @@ -708,6 +716,8 @@ export class LogStreamingManager { switch (type) { case "http": return new HttpLogDestination(config as HttpConfig); + case "s3": + return new S3LogDestination(config as S3Config); // Future providers: // case "datadog": return new DatadogLogDestination(config as DatadogConfig); default: diff --git a/server/private/lib/logStreaming/providers/S3LogDestination.ts b/server/private/lib/logStreaming/providers/S3LogDestination.ts new file mode 100644 index 000000000..2e84e667d --- /dev/null +++ b/server/private/lib/logStreaming/providers/S3LogDestination.ts @@ -0,0 +1,279 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025-2026 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + +import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; +import { gzip as gzipCallback } from "zlib"; +import { promisify } from "util"; +import { randomUUID } from "crypto"; +import logger from "@server/logger"; +import { LogEvent, S3Config, S3PayloadFormat } from "../types"; +import { LogDestinationProvider } from "./LogDestinationProvider"; + +const gzipAsync = promisify(gzipCallback); + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/** Maximum time (ms) to wait for a single S3 PutObject response. */ +const REQUEST_TIMEOUT_MS = 60_000; + +/** Default payload format when none is specified in the config. */ +const DEFAULT_FORMAT: S3PayloadFormat = "json_array"; + +// --------------------------------------------------------------------------- +// S3LogDestination +// --------------------------------------------------------------------------- + +/** + * Forwards a batch of log events to an S3-compatible object store by + * uploading a single object per `send()` call. + * + * **Object key layout** + * ``` + * {prefix}/{logType}/{YYYY}/{MM}/{DD}/{HH}-{mm}-{ss}-{uuid}.{ext}[.gz] + * ``` + * - `prefix` – from `config.prefix` (default: empty – key starts at logType) + * - `logType` – one of "request", "action", "access", "connection" + * - Date components are derived from the upload time (UTC) + * - `ext` – `json` | `ndjson` | `csv` + * - `.gz` – appended when `config.gzip` is true + * + * **Payload formats** (controlled by `config.format`): + * - `json_array` (default) – body is a JSON array of event objects. + * - `ndjson` – one JSON object per line (newline-delimited). + * - `csv` – RFC-4180 CSV with a header row; columns are the + * union of all field names in the batch's event data. + * + * **Compression**: when `config.gzip` is `true` the body is gzip-compressed + * before upload and `Content-Encoding: gzip` is set on the object. + * + * **Custom endpoint**: set `config.endpoint` to target any S3-compatible + * storage service (e.g. MinIO, Cloudflare R2). + */ +export class S3LogDestination implements LogDestinationProvider { + readonly type = "s3"; + + private readonly config: S3Config; + + constructor(config: S3Config) { + this.config = config; + } + + // ----------------------------------------------------------------------- + // LogDestinationProvider implementation + // ----------------------------------------------------------------------- + + async send(events: LogEvent[]): Promise { + if (events.length === 0) return; + + const format = this.config.format ?? DEFAULT_FORMAT; + const useGzip = this.config.gzip ?? false; + const logType = events[0].logType; + + const rawBody = this.serialize(events, format); + const bodyBuffer = Buffer.from(rawBody, "utf-8"); + + let uploadBody: Buffer; + let contentEncoding: string | undefined; + + if (useGzip) { + uploadBody = (await gzipAsync(bodyBuffer)) as Buffer; + contentEncoding = "gzip"; + } else { + uploadBody = bodyBuffer; + } + + const key = this.buildObjectKey(logType, format, useGzip); + const contentType = this.contentType(format); + + const clientConfig: ConstructorParameters[0] = { + region: this.config.region, + credentials: { + accessKeyId: this.config.accessKeyId, + secretAccessKey: this.config.secretAccessKey + }, + requestHandler: { + requestTimeout: REQUEST_TIMEOUT_MS + } + }; + + if (this.config.endpoint?.trim()) { + clientConfig.endpoint = this.config.endpoint.trim(); + } + + const client = new S3Client(clientConfig); + + try { + await client.send( + new PutObjectCommand({ + Bucket: this.config.bucket, + Key: key, + Body: uploadBody, + ContentType: contentType, + ...(contentEncoding + ? { ContentEncoding: contentEncoding } + : {}) + }) + ); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error( + `S3LogDestination: failed to upload object "${key}" ` + + `to bucket "${this.config.bucket}" – ${msg}` + ); + } + } + + // ----------------------------------------------------------------------- + // Internal helpers + // ----------------------------------------------------------------------- + + /** + * Construct a unique S3 object key for the given log type and format. + * Keys are partitioned by logType and date so they can be queried or + * lifecycle-managed independently. + */ + private buildObjectKey( + logType: string, + format: S3PayloadFormat, + gzip: boolean + ): string { + const now = new Date(); + const year = now.getUTCFullYear(); + const month = String(now.getUTCMonth() + 1).padStart(2, "0"); + const day = String(now.getUTCDate()).padStart(2, "0"); + const hh = String(now.getUTCHours()).padStart(2, "0"); + const mm = String(now.getUTCMinutes()).padStart(2, "0"); + const ss = String(now.getUTCSeconds()).padStart(2, "0"); + const uid = randomUUID(); + + const ext = + format === "csv" ? "csv" : format === "ndjson" ? "ndjson" : "json"; + const fileName = `${hh}-${mm}-${ss}-${uid}.${ext}${gzip ? ".gz" : ""}`; + + const rawPrefix = (this.config.prefix ?? "").trim().replace(/\/+$/, ""); + const parts = [ + rawPrefix, + logType, + `${year}/${month}/${day}`, + fileName + ].filter((p) => p !== ""); + + return parts.join("/"); + } + + private contentType(format: S3PayloadFormat): string { + switch (format) { + case "csv": + return "text/csv; charset=utf-8"; + case "ndjson": + return "application/x-ndjson"; + default: + return "application/json"; + } + } + + private serialize(events: LogEvent[], format: S3PayloadFormat): string { + switch (format) { + case "json_array": + return JSON.stringify(events.map(toPayload)); + case "ndjson": + return events + .map((e) => JSON.stringify(toPayload(e))) + .join("\n"); + case "csv": + return toCsv(events); + } + } +} + +// --------------------------------------------------------------------------- +// Payload helpers +// --------------------------------------------------------------------------- + +function toPayload(event: LogEvent): unknown { + return { + event: event.logType, + timestamp: new Date(event.timestamp * 1000).toISOString(), + data: event.data + }; +} + +/** + * Convert a batch of events to RFC-4180 CSV. + * + * The column set is the union of `event`, `timestamp`, and all keys present in + * `event.data` across the batch, preserving insertion order. Values that + * contain commas, double-quotes, or newlines are quoted and escaped. + */ +function toCsv(events: LogEvent[]): string { + if (events.length === 0) return ""; + + // Collect all unique data keys in stable order + const keySet = new LinkedSet(); + keySet.add("event"); + keySet.add("timestamp"); + for (const e of events) { + for (const k of Object.keys(e.data)) { + keySet.add(k); + } + } + const headers = keySet.toArray(); + + const rows: string[] = [headers.map(csvEscape).join(",")]; + + for (const e of events) { + const flat: Record = { + event: e.logType, + timestamp: new Date(e.timestamp * 1000).toISOString(), + ...e.data + }; + rows.push( + headers.map((h) => csvEscape(flattenValue(flat[h]))).join(",") + ); + } + + return rows.join("\n"); +} + +/** Flatten a value to a plain string suitable for a CSV cell. */ +function flattenValue(value: unknown): string { + if (value === null || value === undefined) return ""; + if (typeof value === "object") return JSON.stringify(value); + return String(value); +} + +/** RFC-4180 CSV escaping. */ +function csvEscape(value: string): string { + if (/[",\n\r]/.test(value)) { + return `"${value.replace(/"/g, '""')}"`; + } + return value; +} + +// --------------------------------------------------------------------------- +// Minimal ordered set (preserves insertion order, deduplicates) +// --------------------------------------------------------------------------- + +class LinkedSet { + private readonly map = new Map(); + + add(value: T): void { + this.map.set(value, true); + } + + toArray(): T[] { + return Array.from(this.map.keys()); + } +} diff --git a/server/private/lib/logStreaming/types.ts b/server/private/lib/logStreaming/types.ts index 1bcd25a66..193a5ff6b 100644 --- a/server/private/lib/logStreaming/types.ts +++ b/server/private/lib/logStreaming/types.ts @@ -107,6 +107,40 @@ export interface HttpConfig { bodyTemplate?: string; } +// --------------------------------------------------------------------------- +// S3 destination configuration +// --------------------------------------------------------------------------- + +/** + * Controls how the batch of events is serialised into each S3 object. + * + * - `json_array` – `[{…}, {…}]` – default; each object is a JSON array. + * - `ndjson` – `{…}\n{…}` – newline-delimited JSON, one object per line. + * - `csv` – RFC-4180 CSV with a header row derived from the event fields. + */ +export type S3PayloadFormat = "json_array" | "ndjson" | "csv"; + +export interface S3Config { + /** Human-readable label for the destination */ + name: string; + /** AWS Access Key ID */ + accessKeyId: string; + /** AWS Secret Access Key */ + secretAccessKey: string; + /** AWS region (e.g. "us-east-1") */ + region: string; + /** Target S3 bucket name */ + bucket: string; + /** Optional key prefix – appended before the auto-generated path */ + prefix?: string; + /** Override the S3 endpoint for S3-compatible storage (e.g. MinIO, R2) */ + endpoint?: string; + /** How events are serialised into each object. Defaults to "json_array". */ + format: S3PayloadFormat; + /** Whether to gzip-compress the object before upload. */ + gzip: boolean; +} + // --------------------------------------------------------------------------- // Per-destination per-log-type cursor (reflects the DB table) // --------------------------------------------------------------------------- diff --git a/src/app/[orgId]/settings/logs/streaming/page.tsx b/src/app/[orgId]/settings/logs/streaming/page.tsx index 022a8eb2e..661fbd786 100644 --- a/src/app/[orgId]/settings/logs/streaming/page.tsx +++ b/src/app/[orgId]/settings/logs/streaming/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { useParams } from "next/navigation"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; @@ -38,7 +38,10 @@ import { HttpDestinationCredenza, parseHttpConfig } from "@app/components/HttpDestinationCredenza"; -import { S3DestinationCredenza } from "@app/components/S3DestinationCredenza"; +import { + S3DestinationCredenza, + parseS3Config +} from "@app/components/S3DestinationCredenza"; import { DatadogDestinationCredenza } from "@app/components/DatadogDestinationCredenza"; import { useTranslations } from "next-intl"; @@ -64,6 +67,42 @@ interface DestinationCardProps { disabled?: boolean; } +function getDestinationDisplay(destination: Destination): { + name: string; + typeLabel: string; + detail: string; + icon: React.ReactNode; +} { + if (destination.type === "s3") { + const cfg = parseS3Config(destination.config); + const detail = cfg.bucket + ? `s3://${cfg.bucket}${cfg.prefix ? `/${cfg.prefix.replace(/^\/+/, "")}` : ""}` + : ""; + return { + name: cfg.name, + typeLabel: "Amazon S3", + detail, + icon: ( + Amazon S3 + ) + }; + } + // Default: HTTP + const cfg = parseHttpConfig(destination.config); + return { + name: cfg.name, + typeLabel: "HTTP", + detail: cfg.url, + icon: + }; +} + function DestinationCard({ destination, onToggle, @@ -73,25 +112,25 @@ function DestinationCard({ disabled = false }: DestinationCardProps) { const t = useTranslations(); - const cfg = parseHttpConfig(destination.config); + const { name, typeLabel, detail, icon } = + getDestinationDisplay(destination); return (
{/* Top row: icon + name/type + toggle */}
- {/* Squirkle icon: gray outer → white inner → black globe */}
- + {icon}

- {cfg.name || t("streamingUnnamedDestination")} + {name || t("streamingUnnamedDestination")}

- HTTP + {typeLabel}

@@ -105,9 +144,9 @@ function DestinationCard({ />
- {/* URL preview */} + {/* Detail preview (URL for HTTP, s3:// path for S3) */}

- {cfg.url || ( + {detail || ( {t("streamingNoUrlConfigured")} @@ -485,7 +524,7 @@ export default function StreamingDestinationsPage() { if (!v) setDeleteTarget(null); }} string={ - parseHttpConfig(deleteTarget.config).name || + getDestinationDisplay(deleteTarget).name || t("streamingDeleteDialogThisDestination") } title={t("streamingDeleteTitle")} @@ -493,7 +532,7 @@ export default function StreamingDestinationsPage() {

{t("streamingDeleteDialogAreYouSure")}{" "} - {parseHttpConfig(deleteTarget.config).name || + {getDestinationDisplay(deleteTarget).name || t("streamingDeleteDialogThisDestination")} {t("streamingDeleteDialogPermanentlyRemoved")} diff --git a/src/components/S3DestinationCredenza.tsx b/src/components/S3DestinationCredenza.tsx index 7702e7932..03b055f58 100644 --- a/src/components/S3DestinationCredenza.tsx +++ b/src/components/S3DestinationCredenza.tsx @@ -1,6 +1,6 @@ "use client"; - +import { useState, useEffect } from "react"; import { Credenza, CredenzaBody, @@ -12,13 +12,62 @@ import { CredenzaTitle } from "@app/components/Credenza"; import { Button } from "@app/components/ui/button"; -import { ContactSalesBanner } from "@app/components/ContactSalesBanner"; +import { Input } from "@app/components/ui/input"; +import { Label } from "@app/components/ui/label"; +import { Switch } from "@app/components/ui/switch"; +import { HorizontalTabs } from "@app/components/HorizontalTabs"; +import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group"; +import { Checkbox } from "@app/components/ui/checkbox"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { toast } from "@app/hooks/useToast"; import { useTranslations } from "next-intl"; +import { Destination } from "@app/components/HttpDestinationCredenza"; + +// ── Types ────────────────────────────────────────────────────────────────────── + +export type S3PayloadFormat = "json_array" | "ndjson" | "csv"; + +export interface S3Config { + name: string; + accessKeyId: string; + secretAccessKey: string; + region: string; + bucket: string; + prefix: string; + endpoint: string; + format: S3PayloadFormat; + gzip: boolean; +} + +// ── Helpers ──────────────────────────────────────────────────────────────────── + +export const defaultS3Config = (): S3Config => ({ + name: "", + accessKeyId: "", + secretAccessKey: "", + region: "us-east-1", + bucket: "", + prefix: "", + endpoint: "", + format: "json_array", + gzip: false +}); + +export function parseS3Config(raw: string): S3Config { + try { + return { ...defaultS3Config(), ...JSON.parse(raw) }; + } catch { + return defaultS3Config(); + } +} + +// ── Component ────────────────────────────────────────────────────────────────── export interface S3DestinationCredenzaProps { open: boolean; onOpenChange: (open: boolean) => void; - editing: any; + editing: Destination | null; orgId: string; onSaved: () => void; } @@ -28,18 +77,84 @@ export function S3DestinationCredenza({ onOpenChange, editing, orgId, - onSaved, + onSaved }: S3DestinationCredenzaProps) { + const api = createApiClient(useEnvContext()); const t = useTranslations(); + const [saving, setSaving] = useState(false); + const [cfg, setCfg] = useState(defaultS3Config()); + const [sendAccessLogs, setSendAccessLogs] = useState(false); + const [sendActionLogs, setSendActionLogs] = useState(false); + const [sendConnectionLogs, setSendConnectionLogs] = useState(false); + const [sendRequestLogs, setSendRequestLogs] = useState(false); + + useEffect(() => { + if (open) { + setCfg(editing ? parseS3Config(editing.config) : defaultS3Config()); + setSendAccessLogs(editing?.sendAccessLogs ?? false); + setSendActionLogs(editing?.sendActionLogs ?? false); + setSendConnectionLogs(editing?.sendConnectionLogs ?? false); + setSendRequestLogs(editing?.sendRequestLogs ?? false); + } + }, [open, editing]); + + const update = (patch: Partial) => + setCfg((prev) => ({ ...prev, ...patch })); + + const isValid = + cfg.name.trim() !== "" && + cfg.accessKeyId.trim() !== "" && + cfg.secretAccessKey.trim() !== "" && + cfg.region.trim() !== "" && + cfg.bucket.trim() !== ""; + + async function handleSave() { + if (!isValid) return; + setSaving(true); + try { + const payload = { + type: "s3", + config: JSON.stringify(cfg), + sendAccessLogs, + sendActionLogs, + sendConnectionLogs, + sendRequestLogs + }; + if (editing) { + await api.post( + `/org/${orgId}/event-streaming-destination/${editing.destinationId}`, + payload + ); + toast({ title: t("s3DestUpdatedSuccess") }); + } else { + await api.put( + `/org/${orgId}/event-streaming-destination`, + payload + ); + toast({ title: t("s3DestCreatedSuccess") }); + } + onSaved(); + onOpenChange(false); + } catch (e) { + toast({ + variant: "destructive", + title: editing + ? t("s3DestUpdateFailed") + : t("s3DestCreateFailed"), + description: formatAxiosError(e, t("streamingUnexpectedError")) + }); + } finally { + setSaving(false); + } + } + return ( - {editing - ? t("S3DestEditTitle") - : t("S3DestAddTitle")} + {editing ? t("S3DestEditTitle") : t("S3DestAddTitle")} {editing @@ -49,13 +164,367 @@ export function S3DestinationCredenza({ - + + {/* ── Settings tab ────────────────────────────── */} +

+ {/* Name */} +
+ + + update({ name: e.target.value }) + } + /> +
+ + {/* AWS Access Key ID */} +
+ + + update({ + accessKeyId: e.target.value + }) + } + autoComplete="off" + /> +
+ + {/* AWS Secret Access Key */} +
+ + + update({ + secretAccessKey: e.target.value + }) + } + autoComplete="new-password" + /> +
+ + {/* Region */} +
+ + + update({ region: e.target.value }) + } + /> +
+ + {/* Bucket */} +
+ + + update({ bucket: e.target.value }) + } + /> +
+ + {/* Prefix */} +
+ + + update({ prefix: e.target.value }) + } + /> +

+ {t("s3DestPrefixDescription")} +

+
+ + {/* Custom endpoint (optional – for S3-compatible storage) */} +
+ + + update({ endpoint: e.target.value }) + } + /> +

+ {t("s3DestEndpointDescription")} +

+
+
+ + {/* ── Format tab ───────────────────────────────── */} +
+ {/* Gzip compression toggle */} +
+ update({ gzip: v })} + className="mt-0.5" + /> +
+ +

+ {t("s3DestGzipDescription")} +

+
+
+ + {/* Payload format selector */} +
+
+ +

+ {t("s3DestFormatDescription")} +

+
+ + + update({ + format: v as S3PayloadFormat + }) + } + className="gap-2" + > + {/* JSON Array */} + + + {/* NDJSON */} + + + {/* CSV */} + + +
+
+ + {/* ── Logs tab ──────────────────────────────────── */} +
+
+ +

+ {t("httpDestLogTypesDescription")} +

+
+ +
+
+ + setSendAccessLogs(v === true) + } + className="mt-0.5" + /> +
+ +

+ {t("httpDestAccessLogsDescription")} +

+
+
+ +
+ + setSendActionLogs(v === true) + } + className="mt-0.5" + /> +
+ +

+ {t("httpDestActionLogsDescription")} +

+
+
+ +
+ + setSendConnectionLogs(v === true) + } + className="mt-0.5" + /> +
+ +

+ {t( + "httpDestConnectionLogsDescription" + )} +

+
+
+ +
+ + setSendRequestLogs(v === true) + } + className="mt-0.5" + /> +
+ +

+ {t( + "httpDestRequestLogsDescription" + )} +

+
+
+
+
+ - + + From 10fa9274d01940a902898574bf1b8f3162604fab Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 8 May 2026 16:26:10 -0700 Subject: [PATCH 088/139] Add streaming errors for debug --- messages/en-US.json | 2 +- server/db/pg/schema/privateSchema.ts | 2 + server/db/sqlite/schema/privateSchema.ts | 2 + .../lib/logStreaming/LogStreamingManager.ts | 49 ++++++++ .../listEventStreamingDestinations.ts | 5 +- .../[orgId]/settings/logs/streaming/page.tsx | 38 ++++++- src/components/HttpDestinationCredenza.tsx | 105 ++++++++++++------ src/components/S3DestinationCredenza.tsx | 10 ++ 8 files changed, 176 insertions(+), 37 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 0e9cb5786..9a23043d5 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Forward events directly to your Datadog account.", "streamingTypePickerDescription": "Choose a destination type to get started.", - "streamingFailedToLoad": "Failed to load destinations", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "An unexpected error occurred.", "streamingFailedToUpdate": "Failed to update destination", "streamingDeletedSuccess": "Destination deleted successfully", diff --git a/server/db/pg/schema/privateSchema.ts b/server/db/pg/schema/privateSchema.ts index 0f1914fad..6137743bf 100644 --- a/server/db/pg/schema/privateSchema.ts +++ b/server/db/pg/schema/privateSchema.ts @@ -439,6 +439,8 @@ export const eventStreamingDestinations = pgTable( type: varchar("type", { length: 50 }).notNull(), // e.g. "http", "kafka", etc. config: text("config").notNull(), // JSON string with the configuration for the destination enabled: boolean("enabled").notNull().default(true), + lastError: text("lastError"), // last send error message, null if healthy + lastErrorAt: bigint("lastErrorAt", { mode: "number" }), // epoch ms of last error, null if healthy createdAt: bigint("createdAt", { mode: "number" }).notNull(), updatedAt: bigint("updatedAt", { mode: "number" }).notNull() } diff --git a/server/db/sqlite/schema/privateSchema.ts b/server/db/sqlite/schema/privateSchema.ts index 05c917887..a25183055 100644 --- a/server/db/sqlite/schema/privateSchema.ts +++ b/server/db/sqlite/schema/privateSchema.ts @@ -445,6 +445,8 @@ export const eventStreamingDestinations = sqliteTable( enabled: integer("enabled", { mode: "boolean" }) .notNull() .default(true), + lastError: text("lastError"), // last send error message, null if healthy + lastErrorAt: integer("lastErrorAt"), // epoch ms of last error, null if healthy createdAt: integer("createdAt").notNull(), updatedAt: integer("updatedAt").notNull() } diff --git a/server/private/lib/logStreaming/LogStreamingManager.ts b/server/private/lib/logStreaming/LogStreamingManager.ts index a9575fec6..03efc2809 100644 --- a/server/private/lib/logStreaming/LogStreamingManager.ts +++ b/server/private/lib/logStreaming/LogStreamingManager.ts @@ -313,6 +313,7 @@ export class LogStreamingManager { if (enabledTypes.length === 0) return; let anyFailure = false; + let firstError: string | null = null; for (const logType of enabledTypes) { if (!this.isRunning) break; @@ -320,6 +321,10 @@ export class LogStreamingManager { await this.processLogType(dest, provider, logType); } catch (err) { anyFailure = true; + if (firstError === null) { + firstError = + err instanceof Error ? err.message : String(err); + } logger.error( `LogStreamingManager: failed to process "${logType}" logs ` + `for destination ${dest.destinationId}`, @@ -330,6 +335,10 @@ export class LogStreamingManager { if (anyFailure) { this.recordFailure(dest.destinationId); + await this.setDestinationError( + dest.destinationId, + firstError ?? "Unknown error" + ); } else { // Any success resets the failure/back-off state if (this.failures.has(dest.destinationId)) { @@ -338,6 +347,7 @@ export class LogStreamingManager { `LogStreamingManager: destination ${dest.destinationId} recovered` ); } + await this.clearDestinationError(dest.destinationId); } } @@ -759,6 +769,45 @@ export class LogStreamingManager { // DB helpers // ------------------------------------------------------------------------- + private async setDestinationError( + destinationId: number, + errorMessage: string + ): Promise { + // Truncate to 1000 chars so it fits comfortably in the text column. + const truncated = errorMessage.slice(0, 1000); + try { + await db + .update(eventStreamingDestinations) + .set({ lastError: truncated, lastErrorAt: Date.now() }) + .where( + eq(eventStreamingDestinations.destinationId, destinationId) + ); + } catch (err) { + logger.warn( + `LogStreamingManager: could not persist error status for destination ${destinationId}`, + err + ); + } + } + + private async clearDestinationError(destinationId: number): Promise { + try { + // Only update if there is actually an error stored, to avoid + // unnecessary writes on every successful poll cycle. + await db + .update(eventStreamingDestinations) + .set({ lastError: null, lastErrorAt: null }) + .where( + eq(eventStreamingDestinations.destinationId, destinationId) + ); + } catch (err) { + logger.warn( + `LogStreamingManager: could not clear error status for destination ${destinationId}`, + err + ); + } + } + private async loadEnabledDestinations(): Promise< EventStreamingDestination[] > { diff --git a/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts b/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts index 10a6c3600..27b5d9a5b 100644 --- a/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts +++ b/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts @@ -51,6 +51,8 @@ export type ListEventStreamingDestinationsResponse = { type: string; config: string; enabled: boolean; + lastError: string | null; + lastErrorAt: number | null; createdAt: number; updatedAt: number; sendConnectionLogs: boolean; @@ -79,7 +81,8 @@ async function query(orgId: string, limit: number, offset: number) { registry.registerPath({ method: "get", path: "/org/{orgId}/event-streaming-destination", - description: "List all event streaming destinations for a specific organization.", + description: + "List all event streaming destinations for a specific organization.", tags: [OpenAPITags.Org], request: { query: querySchema, diff --git a/src/app/[orgId]/settings/logs/streaming/page.tsx b/src/app/[orgId]/settings/logs/streaming/page.tsx index 661fbd786..8579527d0 100644 --- a/src/app/[orgId]/settings/logs/streaming/page.tsx +++ b/src/app/[orgId]/settings/logs/streaming/page.tsx @@ -22,7 +22,18 @@ import { } from "@app/components/Credenza"; import { Button } from "@app/components/ui/button"; import { Switch } from "@app/components/ui/switch"; -import { Globe, MoreHorizontal, Plus } from "lucide-react"; +import { + Globe, + MoreHorizontal, + Plus, + AlertCircle, + ChevronDown +} from "lucide-react"; +import { + Popover, + PopoverContent, + PopoverTrigger +} from "@app/components/ui/popover"; import { AxiosResponse } from "axios"; import { build } from "@server/build"; import Image from "next/image"; @@ -153,6 +164,31 @@ function DestinationCard({ )}

+ {/* Error indicator */} + {destination.lastError && ( + + + + + + {destination.lastError} + + + )} + {/* Footer: edit button + three-dots menu */}
@@ -375,15 +378,21 @@ export function HttpDestinationCredenza({ htmlFor="auth-bearer" className="cursor-pointer font-medium" > - {t("httpDestAuthBearerTitle")} + {t( + "httpDestAuthBearerTitle" + )}

- {t("httpDestAuthBearerDescription")} + {t( + "httpDestAuthBearerDescription" + )}

{cfg.authType === "bearer" && ( - {t("httpDestAuthBasicTitle")} + {t( + "httpDestAuthBasicTitle" + )}

- {t("httpDestAuthBasicDescription")} + {t( + "httpDestAuthBasicDescription" + )}

{cfg.authType === "basic" && ( - {t("httpDestAuthCustomTitle")} + {t( + "httpDestAuthCustomTitle" + )}

- {t("httpDestAuthCustomDescription")} + {t( + "httpDestAuthCustomDescription" + )}

{cfg.authType === "custom" && (
- {t("httpDestFormatJsonArrayTitle")} + {t( + "httpDestFormatJsonArrayTitle" + )}

- {t("httpDestFormatJsonArrayDescription")} + {t( + "httpDestFormatJsonArrayDescription" + )}

@@ -616,7 +643,9 @@ export function HttpDestinationCredenza({ {t("httpDestFormatNdjsonTitle")}

- {t("httpDestFormatNdjsonDescription")} + {t( + "httpDestFormatNdjsonDescription" + )}

@@ -636,7 +665,9 @@ export function HttpDestinationCredenza({ {t("httpDestFormatSingleTitle")}

- {t("httpDestFormatSingleDescription")} + {t( + "httpDestFormatSingleDescription" + )}

@@ -717,7 +748,9 @@ export function HttpDestinationCredenza({ {t("httpDestConnectionLogsTitle")}

- {t("httpDestConnectionLogsDescription")} + {t( + "httpDestConnectionLogsDescription" + )}

@@ -739,7 +772,9 @@ export function HttpDestinationCredenza({ {t("httpDestRequestLogsTitle")}

- {t("httpDestRequestLogsDescription")} + {t( + "httpDestRequestLogsDescription" + )}

@@ -764,10 +799,12 @@ export function HttpDestinationCredenza({ loading={saving} disabled={!isValid || saving} > - {editing ? t("httpDestSaveChanges") : t("httpDestCreateDestination")} + {editing + ? t("httpDestSaveChanges") + : t("httpDestCreateDestination")} ); -} \ No newline at end of file +} diff --git a/src/components/S3DestinationCredenza.tsx b/src/components/S3DestinationCredenza.tsx index 03b055f58..e6c128805 100644 --- a/src/components/S3DestinationCredenza.tsx +++ b/src/components/S3DestinationCredenza.tsx @@ -18,6 +18,8 @@ import { Switch } from "@app/components/ui/switch"; import { HorizontalTabs } from "@app/components/HorizontalTabs"; import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group"; import { Checkbox } from "@app/components/ui/checkbox"; +import { AlertCircle } from "lucide-react"; +import { Alert, AlertDescription } from "@app/components/ui/alert"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; @@ -164,6 +166,14 @@ export function S3DestinationCredenza({ + {editing?.lastError && ( + + + + {editing.lastError} + + + )} Date: Fri, 8 May 2026 17:04:58 -0700 Subject: [PATCH 089/139] Add client endpoint to network log --- server/db/pg/schema/privateSchema.ts | 1 + server/db/sqlite/schema/privateSchema.ts | 1 + server/private/lib/logConnectionAudit.ts | 1 + .../auditLogs/queryConnectionAuditLog.ts | 41 +++++++------------ .../newt/handleConnectionLogMessage.ts | 26 ++++++++++-- server/routers/auditLogs/types.ts | 1 + .../[orgId]/settings/logs/connection/page.tsx | 6 +++ 7 files changed, 46 insertions(+), 31 deletions(-) diff --git a/server/db/pg/schema/privateSchema.ts b/server/db/pg/schema/privateSchema.ts index 0f1914fad..2cf5f154c 100644 --- a/server/db/pg/schema/privateSchema.ts +++ b/server/db/pg/schema/privateSchema.ts @@ -332,6 +332,7 @@ export const connectionAuditLog = pgTable( clientId: integer("clientId").references(() => clients.clientId, { onDelete: "cascade" }), + clientEndpoint: text("clientEndpoint"), userId: text("userId").references(() => users.userId, { onDelete: "cascade" }), diff --git a/server/db/sqlite/schema/privateSchema.ts b/server/db/sqlite/schema/privateSchema.ts index 05c917887..c096c5a94 100644 --- a/server/db/sqlite/schema/privateSchema.ts +++ b/server/db/sqlite/schema/privateSchema.ts @@ -332,6 +332,7 @@ export const connectionAuditLog = sqliteTable( clientId: integer("clientId").references(() => clients.clientId, { onDelete: "cascade" }), + clientEndpoint: text("clientEndpoint"), userId: text("userId").references(() => users.userId, { onDelete: "cascade" }), diff --git a/server/private/lib/logConnectionAudit.ts b/server/private/lib/logConnectionAudit.ts index 039b75ec9..ce4856da0 100644 --- a/server/private/lib/logConnectionAudit.ts +++ b/server/private/lib/logConnectionAudit.ts @@ -46,6 +46,7 @@ export interface ConnectionLogRecord { orgId: string; siteId: number; clientId: number | null; + clientEndpoint: string | null; userId: string | null; sourceAddr: string; destAddr: string; diff --git a/server/private/routers/auditLogs/queryConnectionAuditLog.ts b/server/private/routers/auditLogs/queryConnectionAuditLog.ts index 715652838..930ee6111 100644 --- a/server/private/routers/auditLogs/queryConnectionAuditLog.ts +++ b/server/private/routers/auditLogs/queryConnectionAuditLog.ts @@ -124,15 +124,11 @@ function getWhere(data: Q) { data.clientId ? eq(connectionAuditLog.clientId, data.clientId) : undefined, - data.siteId - ? eq(connectionAuditLog.siteId, data.siteId) - : undefined, + data.siteId ? eq(connectionAuditLog.siteId, data.siteId) : undefined, data.siteResourceId ? eq(connectionAuditLog.siteResourceId, data.siteResourceId) : undefined, - data.userId - ? eq(connectionAuditLog.userId, data.userId) - : undefined + data.userId ? eq(connectionAuditLog.userId, data.userId) : undefined ); } @@ -144,6 +140,7 @@ export function queryConnection(data: Q) { orgId: connectionAuditLog.orgId, siteId: connectionAuditLog.siteId, clientId: connectionAuditLog.clientId, + clientEndpoint: connectionAuditLog.clientEndpoint, userId: connectionAuditLog.userId, sourceAddr: connectionAuditLog.sourceAddr, destAddr: connectionAuditLog.destAddr, @@ -203,10 +200,7 @@ async function enrichWithDetails( ]; // Fetch resource details from main database - const resourceMap = new Map< - number, - { name: string; niceId: string } - >(); + const resourceMap = new Map(); if (siteResourceIds.length > 0) { const resourceDetails = await primaryDb .select({ @@ -268,10 +262,7 @@ async function enrichWithDetails( } // Fetch user details from main database - const userMap = new Map< - string, - { email: string | null } - >(); + const userMap = new Map(); if (userIds.length > 0) { const userDetails = await primaryDb .select({ @@ -290,29 +281,25 @@ async function enrichWithDetails( return logs.map((log) => ({ ...log, resourceName: log.siteResourceId - ? resourceMap.get(log.siteResourceId)?.name ?? null + ? (resourceMap.get(log.siteResourceId)?.name ?? null) : null, resourceNiceId: log.siteResourceId - ? resourceMap.get(log.siteResourceId)?.niceId ?? null - : null, - siteName: log.siteId - ? siteMap.get(log.siteId)?.name ?? null + ? (resourceMap.get(log.siteResourceId)?.niceId ?? null) : null, + siteName: log.siteId ? (siteMap.get(log.siteId)?.name ?? null) : null, siteNiceId: log.siteId - ? siteMap.get(log.siteId)?.niceId ?? null + ? (siteMap.get(log.siteId)?.niceId ?? null) : null, clientName: log.clientId - ? clientMap.get(log.clientId)?.name ?? null + ? (clientMap.get(log.clientId)?.name ?? null) : null, clientNiceId: log.clientId - ? clientMap.get(log.clientId)?.niceId ?? null + ? (clientMap.get(log.clientId)?.niceId ?? null) : null, clientType: log.clientId - ? clientMap.get(log.clientId)?.type ?? null + ? (clientMap.get(log.clientId)?.type ?? null) : null, - userEmail: log.userId - ? userMap.get(log.userId)?.email ?? null - : null + userEmail: log.userId ? (userMap.get(log.userId)?.email ?? null) : null })); } @@ -521,4 +508,4 @@ export async function queryConnectionAuditLogs( createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") ); } -} \ No newline at end of file +} diff --git a/server/private/routers/newt/handleConnectionLogMessage.ts b/server/private/routers/newt/handleConnectionLogMessage.ts index 6355eb783..cf2ba2daa 100644 --- a/server/private/routers/newt/handleConnectionLogMessage.ts +++ b/server/private/routers/newt/handleConnectionLogMessage.ts @@ -11,7 +11,7 @@ * This file is not licensed under the AGPLv3. */ -import { db } from "@server/db"; +import { clientSitesAssociationsCache, db } from "@server/db"; import { MessageHandler } from "@server/routers/ws"; import { sites, Newt, clients, orgs } from "@server/db"; import { and, eq, inArray } from "drizzle-orm"; @@ -146,7 +146,11 @@ export const handleConnectionLogMessage: MessageHandler = async (context) => { // each unique sourceAddr + the org's CIDR suffix and do a targeted IN query. const ipToClient = new Map< string, - { clientId: number; userId: string | null } + { + clientId: number; + userId: string | null; + clientEndpoint: string | null; + } >(); if (cidrSuffix) { @@ -172,9 +176,21 @@ export const handleConnectionLogMessage: MessageHandler = async (context) => { .select({ clientId: clients.clientId, userId: clients.userId, - subnet: clients.subnet + subnet: clients.subnet, + clientEndpoint: clientSitesAssociationsCache.endpoint }) .from(clients) + .leftJoin( + // this should be one to one + clientSitesAssociationsCache, + and( + eq( + clients.clientId, + clientSitesAssociationsCache.clientId + ), + eq(clientSitesAssociationsCache.siteId, newt.siteId) + ) + ) .where( and( eq(clients.orgId, orgId), @@ -189,7 +205,8 @@ export const handleConnectionLogMessage: MessageHandler = async (context) => { ); ipToClient.set(ip, { clientId: c.clientId, - userId: c.userId + userId: c.userId, + clientEndpoint: c.clientEndpoint }); } } @@ -234,6 +251,7 @@ export const handleConnectionLogMessage: MessageHandler = async (context) => { orgId, siteId: newt.siteId, clientId: clientInfo?.clientId ?? null, + clientEndpoint: clientInfo?.clientEndpoint ?? null, userId: clientInfo?.userId ?? null, sourceAddr: session.sourceAddr, destAddr: session.destAddr, diff --git a/server/routers/auditLogs/types.ts b/server/routers/auditLogs/types.ts index 972eebfe3..b8168ef1e 100644 --- a/server/routers/auditLogs/types.ts +++ b/server/routers/auditLogs/types.ts @@ -100,6 +100,7 @@ export type QueryConnectionAuditLogResponse = { orgId: string | null; siteId: number | null; clientId: number | null; + clientEndpoint: string | null; userId: string | null; sourceAddr: string; destAddr: string; diff --git a/src/app/[orgId]/settings/logs/connection/page.tsx b/src/app/[orgId]/settings/logs/connection/page.tsx index 0fc8f95b7..c2a630332 100644 --- a/src/app/[orgId]/settings/logs/connection/page.tsx +++ b/src/app/[orgId]/settings/logs/connection/page.tsx @@ -645,6 +645,12 @@ export default function ConnectionLogsPage() { )} */} +
+ Client Endpoint:{" "} + + {row.clientEndpoint ?? "-"} + +
Site: {row.siteName ?? "-"} {row.siteNiceId && ( From 4de2dfff85470569825d7c72741090e5acbaf5b7 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:13:46 -0700 Subject: [PATCH 090/139] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 5250a684d..ea384773d 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Transférer des événements directement sur votre compte Datadog. Prochainement.", "streamingTypePickerDescription": "Choisissez un type de destination pour commencer.", - "streamingFailedToLoad": "Impossible de charger les destinations", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Une erreur inattendue s'est produite.", "streamingFailedToUpdate": "Impossible de mettre à jour la destination", "streamingDeletedSuccess": "Destination supprimée avec succès", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Modifier la destination", "S3DestAddTitle": "Ajouter une destination S3", "S3DestEditDescription": "Mettre à jour la configuration de cette destination de diffusion d'événements S3.", - "S3DestAddDescription": "Configurer un nouveau point de terminaison S3 pour recevoir les événements de votre organisation.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Modifier la destination", "datadogDestAddTitle": "Ajouter une destination Datadog", "datadogDestEditDescription": "Mettre à jour la configuration de cette destination de diffusion d'événements Datadog.", From 975f3a01f56e693d8ee4db02503ed031632aa397 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:13:48 -0700 Subject: [PATCH 091/139] New translations en-us.json (Bulgarian) [ci skip] --- messages/bg-BG.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 46327afd0..2766df9a9 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Пресочвайте събития директно към вашият акаунт в Datadog. Очаквайте скоро.", "streamingTypePickerDescription": "Изберете вид на дестинацията, за да започнете.", - "streamingFailedToLoad": "Неуспешно зареждане на дестинации", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Възникна неочаквана грешка.", "streamingFailedToUpdate": "Неуспешно актуализиране на дестинация", "streamingDeletedSuccess": "Дестинацията беше изтрита успешно", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Редактиране на дестинацията", "S3DestAddTitle": "Добавете S3 дестинация", "S3DestEditDescription": "Актуализирайте конфигурацията за тази S3 дестинация за предаване на събития.", - "S3DestAddDescription": "Конфигурирайте нов крайна точка на S3, за да получавате събития на вашата организация.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Редактиране на дестинация", "datadogDestAddTitle": "Добавяне на Datadog дестинация", "datadogDestEditDescription": "Актуализирайте конфигурацията за тази Datadog дестинация за предаване на събития.", From b6b2dbd8abc0d4be874f8dd0602f0793f6df6423 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:13:50 -0700 Subject: [PATCH 092/139] New translations en-us.json (Czech) [ci skip] --- messages/cs-CZ.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 38da0604a..09cc67c6b 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Přeposlat události přímo do vašeho účtu Datadog účtu. Brzy přijde.", "streamingTypePickerDescription": "Vyberte cílový typ pro začátek.", - "streamingFailedToLoad": "Nepodařilo se načíst destinace", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Došlo k neočekávané chybě.", "streamingFailedToUpdate": "Nepodařilo se aktualizovat cíl", "streamingDeletedSuccess": "Cíl byl úspěšně odstraněn", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Upravit cíl", "S3DestAddTitle": "Přidat S3 cíl", "S3DestEditDescription": "Aktualizujte konfiguraci tohoto S3 cíle pro streamování událostí.", - "S3DestAddDescription": "Konfigurujte nový S3 koncový bod pro přijímání událostí vaší organizace.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Upravit cíl", "datadogDestAddTitle": "Přidat Datadog cíl", "datadogDestEditDescription": "Aktualizujte konfiguraci tohoto Datadog cíle pro streamování událostí.", From b42672530fdeb19be161fc6111f32a4554158356 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:13:51 -0700 Subject: [PATCH 093/139] New translations en-us.json (German) [ci skip] --- messages/de-DE.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 367c95cd3..6c906f49d 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Events direkt an Ihr Datadog Konto weiterleiten. Kommen Sie bald.", "streamingTypePickerDescription": "Wählen Sie einen Zieltyp aus, um loszulegen.", - "streamingFailedToLoad": "Fehler beim Laden der Ziele", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Ein unerwarteter Fehler ist aufgetreten.", "streamingFailedToUpdate": "Fehler beim Aktualisieren des Ziels", "streamingDeletedSuccess": "Ziel erfolgreich gelöscht", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Ziel bearbeiten", "S3DestAddTitle": "S3-Ziel hinzufügen", "S3DestEditDescription": "Konfiguration für dieses S3-Ereignis-Streamingziel aktualisieren.", - "S3DestAddDescription": "Neuen S3-Endpunkt konfigurieren, um die Ereignisse Ihrer Organisation zu erhalten.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Ziel bearbeiten", "datadogDestAddTitle": "Datadog-Ziel hinzufügen", "datadogDestEditDescription": "Konfiguration für dieses Datadog-Ereignis-Streamingziel aktualisieren.", From d7b96ba3f596a782418e9170b9544d117f7df5c9 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:13:53 -0700 Subject: [PATCH 094/139] New translations en-us.json (Italian) [ci skip] --- messages/it-IT.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index c739b269d..fc8f2bd41 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Inoltra gli eventi direttamente al tuo account Datadog. In arrivo.", "streamingTypePickerDescription": "Scegli un tipo di destinazione per iniziare.", - "streamingFailedToLoad": "Impossibile caricare le destinazioni", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Si è verificato un errore imprevisto.", "streamingFailedToUpdate": "Impossibile aggiornare la destinazione", "streamingDeletedSuccess": "Destinazione eliminata con successo", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Modifica Destinazione", "S3DestAddTitle": "Aggiungi Destinazione S3", "S3DestEditDescription": "Aggiorna la configurazione per questa destinazione di streaming eventi S3.", - "S3DestAddDescription": "Configura un nuovo endpoint S3 per ricevere gli eventi della tua organizzazione.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Modifica Destinazione", "datadogDestAddTitle": "Aggiungi Destinazione Datadog", "datadogDestEditDescription": "Aggiorna la configurazione per questa destinazione di streaming eventi Datadog.", From 97be5eb7d5629d2f517c18683faab63e29f31bcf Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:13:55 -0700 Subject: [PATCH 095/139] New translations en-us.json (Korean) [ci skip] --- messages/ko-KR.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 1d3d77fe2..114fec818 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "데이터독", "streamingDatadogDescription": "이벤트를 직접 Datadog 계정으로 전달합니다. 곧 제공됩니다.", "streamingTypePickerDescription": "목표 유형을 선택하여 시작합니다.", - "streamingFailedToLoad": "대상 로드에 실패했습니다", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "예기치 않은 오류가 발생했습니다.", "streamingFailedToUpdate": "대상지를 업데이트하는 데 실패했습니다", "streamingDeletedSuccess": "대상지가 성공적으로 삭제되었습니다", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "대상지 수정", "S3DestAddTitle": "S3 대상지 추가", "S3DestEditDescription": "이 S3 이벤트 스트리밍 대상지의 구성을 업데이트하세요.", - "S3DestAddDescription": "조직의 이벤트를 받기 위한 새로운 S3 엔드포인트를 구성하세요.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "대상지 수정", "datadogDestAddTitle": "Datadog 대상지 추가", "datadogDestEditDescription": "이 Datadog 이벤트 스트리밍 대상지의 구성을 업데이트하세요.", From 026260ddfb7ac824296249f8a5eb9641a30d417c Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:13:57 -0700 Subject: [PATCH 096/139] New translations en-us.json (Dutch) [ci skip] --- messages/nl-NL.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 5218c3388..abc289652 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Stuur gebeurtenissen rechtstreeks door naar je Datadog account. Binnenkort beschikbaar.", "streamingTypePickerDescription": "Kies een bestemmingstype om te beginnen.", - "streamingFailedToLoad": "Laden van bestemmingen mislukt", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Er is een onverwachte fout opgetreden.", "streamingFailedToUpdate": "Bijwerken bestemming mislukt", "streamingDeletedSuccess": "Bestemming succesvol verwijderd", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Bestemming bewerken", "S3DestAddTitle": "S3-bestemming toevoegen", "S3DestEditDescription": "Werk de configuratie bij voor deze S3-gebeurtenisstreamingbestemming.", - "S3DestAddDescription": "Configureer een nieuw S3-eindpunt om de gebeurtenissen van uw organisatie te ontvangen.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Bestemming bewerken", "datadogDestAddTitle": "Datadog-bestemming toevoegen", "datadogDestEditDescription": "Werk de configuratie bij voor deze Datadog-gebeurtenisstreamingbestemming.", From e38bbde3486fddba71fe57874de4bd9981a466ad Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:13:58 -0700 Subject: [PATCH 097/139] New translations en-us.json (Polish) [ci skip] --- messages/pl-PL.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index df4a391fc..c6830f069 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Przekaż wydarzenia bezpośrednio do Twojego konta Datadog. Już wkrótce.", "streamingTypePickerDescription": "Wybierz typ docelowy, aby rozpocząć.", - "streamingFailedToLoad": "Nie udało się załadować miejsc docelowych", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Wystąpił nieoczekiwany błąd.", "streamingFailedToUpdate": "Nie udało się zaktualizować miejsca docelowego", "streamingDeletedSuccess": "Cel usunięty pomyślnie", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Edytuj Miejsce Docelowe", "S3DestAddTitle": "Dodaj Miejsce Docelowe S3", "S3DestEditDescription": "Zaktualizuj konfigurację dla tego miejsca docelowego strumieniowego zdarzeń S3.", - "S3DestAddDescription": "Skonfiguruj nowy punkt końcowy S3, aby odbierać zdarzenia Twojej organizacji.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Edytuj Miejsce Docelowe", "datadogDestAddTitle": "Dodaj Miejsce Docelowe Datadog", "datadogDestEditDescription": "Zaktualizuj konfigurację dla tego miejsca docelowego strumieniowego zdarzeń Datadog.", From a97a4b6ec14f280a9938fe72a6cf48ea78c55cd2 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:14:00 -0700 Subject: [PATCH 098/139] New translations en-us.json (Portuguese) [ci skip] --- messages/pt-PT.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index bc683dc77..21e7292c0 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Encaminha eventos diretamente para a sua conta no Datadog. Em breve.", "streamingTypePickerDescription": "Escolha um tipo de destino para começar.", - "streamingFailedToLoad": "Falha ao carregar destinos", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Ocorreu um erro inesperado.", "streamingFailedToUpdate": "Falha ao atualizar destino", "streamingDeletedSuccess": "Destino apagado com sucesso", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Editar Destino", "S3DestAddTitle": "Adicionar Destino S3", "S3DestEditDescription": "Atualize a configuração para este destino de streaming de eventos S3.", - "S3DestAddDescription": "Configure um novo endpoint S3 para receber os eventos da sua organização.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Editar Destino", "datadogDestAddTitle": "Adicionar Destino Datadog", "datadogDestEditDescription": "Atualize a configuração para este destino de streaming de eventos Datadog.", From e76e7581a56bc616ab480f14d6b49c0e7a6f0216 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:14:02 -0700 Subject: [PATCH 099/139] New translations en-us.json (Russian) [ci skip] --- messages/ru-RU.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 46bb5911a..d1741897e 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Перенаправлять события непосредственно на ваш аккаунт в Datadog. Скоро будет доступно.", "streamingTypePickerDescription": "Выберите тип назначения, чтобы начать.", - "streamingFailedToLoad": "Не удалось загрузить места назначения", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Произошла непредвиденная ошибка.", "streamingFailedToUpdate": "Не удалось обновить место назначения", "streamingDeletedSuccess": "Адрес назначения успешно удален", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Редактировать пункт назначения", "S3DestAddTitle": "Добавить S3 пункт назначения", "S3DestEditDescription": "Обновите конфигурацию для этого S3 пункта назначения потоковых событий.", - "S3DestAddDescription": "Настройте новую S3 конечную точку для получения событий вашей организации.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Редактировать пункт назначения", "datadogDestAddTitle": "Добавить пункт назначения Datadog", "datadogDestEditDescription": "Обновите конфигурацию для этого пункта назначения потоковых событий Datadog.", From 65ec8da1000575259fa3b93da0e1697751158176 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:14:04 -0700 Subject: [PATCH 100/139] New translations en-us.json (Turkish) [ci skip] --- messages/tr-TR.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 0bcd5d313..ec5eab1f9 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Olayları doğrudan Datadog hesabınıza iletin. Yakında gelicek.", "streamingTypePickerDescription": "Başlamak için bir hedef türü seçin.", - "streamingFailedToLoad": "Hedefler yüklenemedi", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Beklenmeyen bir hata oluştu.", "streamingFailedToUpdate": "Hedef güncellenemedi", "streamingDeletedSuccess": "Hedef başarıyla silindi", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Hedefi Düzenle", "S3DestAddTitle": "S3 Hedefi Ekle", "S3DestEditDescription": "Bu S3 olay akışı hedefi için yapılandırmayı güncelleyin.", - "S3DestAddDescription": "Kuruluşunuzun olaylarını almak için yeni bir S3 uç noktası yapılandırın.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Hedefi Düzenle", "datadogDestAddTitle": "Datadog Hedefi Ekle", "datadogDestEditDescription": "Bu Datadog olay akışı hedefi için yapılandırmayı güncelleyin.", From c5084137ab0f2c6a6223cc48abaea56c8bf80aec Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:14:06 -0700 Subject: [PATCH 101/139] New translations en-us.json (Chinese Simplified) [ci skip] --- messages/zh-CN.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index e61e0c61a..c84947d80 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "直接转发事件到您的Datadog 帐户。即将推出。", "streamingTypePickerDescription": "选择要开始的目标类型。", - "streamingFailedToLoad": "加载目的地失败", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "发生意外错误.", "streamingFailedToUpdate": "更新目标失败", "streamingDeletedSuccess": "目标删除成功", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "编辑目的地", "S3DestAddTitle": "添加 S3 目的地", "S3DestEditDescription": "更新此 S3 事件流目的地的配置。", - "S3DestAddDescription": "配置新的 S3 终端以接收您的组织事件。", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "编辑目的地", "datadogDestAddTitle": "添加 Datadog 目的地", "datadogDestEditDescription": "更新此 Datadog 事件流目的地的配置。", From 82a36fd632c76720d50484ca4fe01f286efb3eea Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:14:07 -0700 Subject: [PATCH 102/139] New translations en-us.json (Norwegian Bokmal) [ci skip] --- messages/nb-NO.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 72a7c21df..8dc2cb9e3 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Videresend arrangementer direkte til din Datadog-konto. Kommer snart.", "streamingTypePickerDescription": "Velg en måltype for å komme i gang.", - "streamingFailedToLoad": "Kan ikke laste inn destinasjoner", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "En uventet feil oppstod.", "streamingFailedToUpdate": "Kunne ikke oppdatere destinasjon", "streamingDeletedSuccess": "Målet ble slettet", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Rediger destinasjon", "S3DestAddTitle": "Legg til S3 destinasjon", "S3DestEditDescription": "Oppdatere konfigurasjonen for denne S3-hendelsesstrømmingsdestinasjonen.", - "S3DestAddDescription": "Konfigurer et nytt S3-endepunkt for å motta organisasjonens hendelser.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Rediger destinasjon", "datadogDestAddTitle": "Legg til Datadog destinasjon", "datadogDestEditDescription": "Oppdatere konfigurasjonen for denne Datadog-hendelsesstrømmingsdestinasjonen.", From a0f3ee74f96a4bbff2a52ac5586ec5f2814eb364 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:14:09 -0700 Subject: [PATCH 103/139] New translations en-us.json (Spanish) [ci skip] --- messages/es-ES.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index e610233f7..fdd8bd63f 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Reenviar eventos directamente a tu cuenta de Datadog. Próximamente.", "streamingTypePickerDescription": "Elija un tipo de destino para empezar.", - "streamingFailedToLoad": "Error al cargar destinos", + "streamingLastSyncError": "An error occurred on the last sync", "streamingUnexpectedError": "Se ha producido un error inesperado.", "streamingFailedToUpdate": "Error al actualizar destino", "streamingDeletedSuccess": "Destino eliminado correctamente", @@ -3079,7 +3079,34 @@ "S3DestEditTitle": "Editar destino", "S3DestAddTitle": "Añadir destino S3", "S3DestEditDescription": "Actualice la configuración para este destino de transmisión de eventos S3.", - "S3DestAddDescription": "Configure un nuevo punto final S3 para recibir los eventos de su organización.", + "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", + "s3DestTabSettings": "Settings", + "s3DestTabFormat": "Format", + "s3DestNameLabel": "Name", + "s3DestNamePlaceholder": "My S3 destination", + "s3DestAccessKeyIdLabel": "AWS Access Key ID", + "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", + "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", + "s3DestRegionLabel": "AWS Region", + "s3DestBucketLabel": "Bucket Name", + "s3DestPrefixLabel": "Key Prefix (optional)", + "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Custom Endpoint (optional)", + "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", + "s3DestGzipLabel": "Gzip compression", + "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", + "s3DestFormatTitle": "File Format", + "s3DestFormatDescription": "How events are serialised inside each uploaded object.", + "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", + "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestFormatCsvTitle": "CSV", + "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", + "s3DestSaveChanges": "Save Changes", + "s3DestCreateDestination": "Create Destination", + "s3DestUpdatedSuccess": "Destination updated successfully", + "s3DestCreatedSuccess": "Destination created successfully", + "s3DestUpdateFailed": "Failed to update destination", + "s3DestCreateFailed": "Failed to create destination", "datadogDestEditTitle": "Editar destino", "datadogDestAddTitle": "Añadir destino Datadog", "datadogDestEditDescription": "Actualice la configuración para este destino de transmisión de eventos Datadog.", From ee2a1e2bc386aa19a2ddcfa84d7a61fddf7545f4 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:09 -0700 Subject: [PATCH 104/139] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 54 ++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index ea384773d..4391e9946 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Transférer des événements directement sur votre compte Datadog. Prochainement.", "streamingTypePickerDescription": "Choisissez un type de destination pour commencer.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Une erreur s'est produite lors de la dernière synchronisation", "streamingUnexpectedError": "Une erreur inattendue s'est produite.", "streamingFailedToUpdate": "Impossible de mettre à jour la destination", "streamingDeletedSuccess": "Destination supprimée avec succès", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Modifier la destination", "S3DestAddTitle": "Ajouter une destination S3", "S3DestEditDescription": "Mettre à jour la configuration de cette destination de diffusion d'événements S3.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", + "S3DestAddDescription": "Configurez un nouveau bucket Amazon S3 (ou compatible S3) pour recevoir les événements de votre organisation.", + "s3DestTabSettings": "Réglages", "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestNameLabel": "Nom", + "s3DestNamePlaceholder": "Ma destination S3", + "s3DestAccessKeyIdLabel": "ID de clé d'accès AWS", + "s3DestSecretAccessKeyLabel": "Clé d'accès secrète AWS", + "s3DestSecretAccessKeyPlaceholder": "Votre clé d'accès secrète AWS", + "s3DestRegionLabel": "Région AWS", + "s3DestBucketLabel": "Nom du bucket", + "s3DestPrefixLabel": "Préfixe clé (facultatif)", + "s3DestPrefixDescription": "Préfixe de chemin facultatif préfixé à chaque clé d'objet. Les objets sont stockés à {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Point de terminaison personnalisé (facultatif)", + "s3DestEndpointDescription": "Modifiez le point de terminaison S3 pour un stockage compatible S3 tel que MinIO ou Cloudflare R2. Laissez vide pour l'AWS S3 standard.", + "s3DestGzipLabel": "Compression Gzip", + "s3DestGzipDescription": "Compressez chaque objet téléchargé avec gzip. Réduit les coûts de stockage et la taille de téléchargement.", + "s3DestFormatTitle": "Format de fichier", + "s3DestFormatDescription": "Comment les événements sont sérialisés dans chaque objet téléchargé.", + "s3DestFormatJsonArrayDescription": "Chaque objet est un tableau JSON des enregistrements d'événements. Compatible avec la plupart des outils d'analyse.", + "s3DestFormatNdjsonDescription": "Chaque objet contient un enregistrement JSON par ligne (JSON délimité par saut de ligne). Compatible avec Athena, BigQuery et Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Chaque objet est un fichier CSV RFC-4180 avec une ligne d'en-tête. Les noms de colonne sont dérivés des champs de données de l'événement.", + "s3DestSaveChanges": "Enregistrer les modifications", + "s3DestCreateDestination": "Créer une destination", + "s3DestUpdatedSuccess": "Destination mise à jour avec succès", + "s3DestCreatedSuccess": "Destination créée avec succès", + "s3DestUpdateFailed": "Échec de la mise à jour de la destination", + "s3DestCreateFailed": "Échec de la création de la destination", "datadogDestEditTitle": "Modifier la destination", "datadogDestAddTitle": "Ajouter une destination Datadog", "datadogDestEditDescription": "Mettre à jour la configuration de cette destination de diffusion d'événements Datadog.", From 3bce8d35963ad0cef14c123ab25b2ef9ee462e16 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:10 -0700 Subject: [PATCH 105/139] New translations en-us.json (Bulgarian) [ci skip] --- messages/bg-BG.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 2766df9a9..bd35c7bf4 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Пресочвайте събития директно към вашият акаунт в Datadog. Очаквайте скоро.", "streamingTypePickerDescription": "Изберете вид на дестинацията, за да започнете.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Възникна грешка при последната синхронизация", "streamingUnexpectedError": "Възникна неочаквана грешка.", "streamingFailedToUpdate": "Неуспешно актуализиране на дестинация", "streamingDeletedSuccess": "Дестинацията беше изтрита успешно", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Редактиране на дестинацията", "S3DestAddTitle": "Добавете S3 дестинация", "S3DestEditDescription": "Актуализирайте конфигурацията за тази S3 дестинация за предаване на събития.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "S3DestAddDescription": "Конфигурирайте ново хранилище Amazon S3 (или съвместимо с S3), за да получавате събития на вашата организация.", + "s3DestTabSettings": "Настройки", + "s3DestTabFormat": "Формат", + "s3DestNameLabel": "Име", + "s3DestNamePlaceholder": "Моята S3 дестинация", + "s3DestAccessKeyIdLabel": "Идентификатор на достъп за AWS Key ID", + "s3DestSecretAccessKeyLabel": "Тайният ключ за достъп на AWS", + "s3DestSecretAccessKeyPlaceholder": "Вашият таен ключ за достъп за AWS", + "s3DestRegionLabel": "AWS Регион", + "s3DestBucketLabel": "Име на хранилище", + "s3DestPrefixLabel": "Префикс на ключ (по избор)", + "s3DestPrefixDescription": "По избор пътеводен префикс, добавен към всеки обектен ключ. Обектите се съхраняват в {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Потребителски крайна точка (по избор)", + "s3DestEndpointDescription": "Заместете крайната точка на S3 за съвместимо с S3 хранилище като MinIO или Cloudflare R2. Оставете празно за стандартното AWS S3.", + "s3DestGzipLabel": "Gzip компресия", + "s3DestGzipDescription": "Компресирайте всеки качен обект с gzip. Намалява разходите за съхранение и размера на качването.", + "s3DestFormatTitle": "Формат на файл", + "s3DestFormatDescription": "Как събитията са сериализирани вътре във всеки качен обект.", + "s3DestFormatJsonArrayDescription": "Всеки обект е JSON масив от записи на събития. Съвместим с повечето аналитични инструменти.", + "s3DestFormatNdjsonDescription": "Всеки обект съдържа един JSON запис на ред (форматиран JSON с нов ред). Съвместим с Athena, BigQuery и Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Всеки обект е RFC-4180 CSV файл с ред заглавие. Имената на колоните са извлечени от полетата на данните за събитията.", + "s3DestSaveChanges": "Запази промените", + "s3DestCreateDestination": "Създаване на дестинация", + "s3DestUpdatedSuccess": "Дестинацията е актуализирана успешно", + "s3DestCreatedSuccess": "Дестинацията е създадена успешно", + "s3DestUpdateFailed": "Неуспешно актуализиране на дестинацията", + "s3DestCreateFailed": "Неуспешно създаване на дестинация", "datadogDestEditTitle": "Редактиране на дестинация", "datadogDestAddTitle": "Добавяне на Datadog дестинация", "datadogDestEditDescription": "Актуализирайте конфигурацията за тази Datadog дестинация за предаване на събития.", From c81d855741aaa11d4316d4ed8b822c2dc2e39c64 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:12 -0700 Subject: [PATCH 106/139] New translations en-us.json (Czech) [ci skip] --- messages/cs-CZ.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 09cc67c6b..80c242e39 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Přeposlat události přímo do vašeho účtu Datadog účtu. Brzy přijde.", "streamingTypePickerDescription": "Vyberte cílový typ pro začátek.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Došlo k chybě při poslední synchronizaci", "streamingUnexpectedError": "Došlo k neočekávané chybě.", "streamingFailedToUpdate": "Nepodařilo se aktualizovat cíl", "streamingDeletedSuccess": "Cíl byl úspěšně odstraněn", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Upravit cíl", "S3DestAddTitle": "Přidat S3 cíl", "S3DestEditDescription": "Aktualizujte konfiguraci tohoto S3 cíle pro streamování událostí.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "S3DestAddDescription": "Nakonfigurujte nový Amazon S3 (nebo S3-kompatibilní) bucket, aby přijímal události vaší organizace.", + "s3DestTabSettings": "Nastavení", + "s3DestTabFormat": "Formát", + "s3DestNameLabel": "Jméno", + "s3DestNamePlaceholder": "Moje cílové S3", + "s3DestAccessKeyIdLabel": "ID přístupového klíče AWS", + "s3DestSecretAccessKeyLabel": "Tajný přístupový klíč AWS", + "s3DestSecretAccessKeyPlaceholder": "Váš tajný přístupový klíč AWS", + "s3DestRegionLabel": "Oblast AWS", + "s3DestBucketLabel": "Název bucketu", + "s3DestPrefixLabel": "Předpona klíče (volitelné)", + "s3DestPrefixDescription": "Volitelná cesta předpony přidaná ke každému objektovému klíči. Objekty jsou uloženy na {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Vlastní koncový bod (volitelné)", + "s3DestEndpointDescription": "Přepište koncový bod S3 pro S3-kompatibilní úložiště, jako je MinIO nebo Cloudflare R2. Nechte prázdné pro standardní AWS S3.", + "s3DestGzipLabel": "Komprese Gzip", + "s3DestGzipDescription": "Komprimujte každý nahraný objekt pomocí gzip. Snižuje náklady na uložení a velikost nahrávání.", + "s3DestFormatTitle": "Formát souboru", + "s3DestFormatDescription": "Jak jsou události serializovány v každém nahraném objektu.", + "s3DestFormatJsonArrayDescription": "Každý objekt je pole JSON záznamů událostí. Kompatibilní s většinou analytických nástrojů.", + "s3DestFormatNdjsonDescription": "Každý objekt obsahuje jeden JSON záznam na řádku (newline-delimited JSON). Kompatibilní s Athena, BigQuery a Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Každý objekt je soubor CSV podle RFC-4180 s řádkem záhlaví. Názvy sloupců jsou odvozeny z polí dat událostí.", + "s3DestSaveChanges": "Uložit změny", + "s3DestCreateDestination": "Vytvořit destinaci", + "s3DestUpdatedSuccess": "Destinace úspěšně aktualizována", + "s3DestCreatedSuccess": "Destinace úspěšně vytvořena", + "s3DestUpdateFailed": "Aktualizace destinace se nezdařila", + "s3DestCreateFailed": "Vytvoření destinace se nezdařilo", "datadogDestEditTitle": "Upravit cíl", "datadogDestAddTitle": "Přidat Datadog cíl", "datadogDestEditDescription": "Aktualizujte konfiguraci tohoto Datadog cíle pro streamování událostí.", From 3b2622d590a01af1b56c9aa9e79fde9a84761259 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:14 -0700 Subject: [PATCH 107/139] New translations en-us.json (German) [ci skip] --- messages/de-DE.json | 52 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 6c906f49d..a7e1a5fa7 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Events direkt an Ihr Datadog Konto weiterleiten. Kommen Sie bald.", "streamingTypePickerDescription": "Wählen Sie einen Zieltyp aus, um loszulegen.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Beim letzten Synchronisieren ist ein Fehler aufgetreten.", "streamingUnexpectedError": "Ein unerwarteter Fehler ist aufgetreten.", "streamingFailedToUpdate": "Fehler beim Aktualisieren des Ziels", "streamingDeletedSuccess": "Ziel erfolgreich gelöscht", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Ziel bearbeiten", "S3DestAddTitle": "S3-Ziel hinzufügen", "S3DestEditDescription": "Konfiguration für dieses S3-Ereignis-Streamingziel aktualisieren.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", + "S3DestAddDescription": "Konfigurieren Sie einen neuen Amazon S3 (oder S3-kompatiblen) Bucket, um die Ereignisse Ihrer Organisation zu empfangen.", + "s3DestTabSettings": "Einstellungen", "s3DestTabFormat": "Format", "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestNamePlaceholder": "Mein S3-Ziel", + "s3DestAccessKeyIdLabel": "AWS-Zugriffsschlüssel-ID", + "s3DestSecretAccessKeyLabel": "AWS-Geheimzugriffsschlüssel", + "s3DestSecretAccessKeyPlaceholder": "Ihr AWS-Geheimzugriffsschlüssel", + "s3DestRegionLabel": "AWS-Region", + "s3DestBucketLabel": "Bucket-Name", + "s3DestPrefixLabel": "Schlüssel-Präfix (optional)", + "s3DestPrefixDescription": "Optionales Pfadpräfix, das jedem Objektschlüssel vorangestellt wird. Objekte werden unter {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename} gespeichert.", + "s3DestEndpointLabel": "Benutzerdefinierter Endpunkt (optional)", + "s3DestEndpointDescription": "Überschreiben Sie den S3-Endpunkt für S3-kompatiblen Speicher wie MinIO oder Cloudflare R2. Lassen Sie das Feld leer für standardmäßiges AWS S3.", + "s3DestGzipLabel": "Gzip-Komprimierung", + "s3DestGzipDescription": "Jedes hochgeladene Objekt mit Gzip komprimieren. Reduziert die Speicherkosten und die Upload-Größe.", + "s3DestFormatTitle": "Dateiformat", + "s3DestFormatDescription": "Wie Ereignisse in jedem hochgeladenen Objekt serialisiert werden.", + "s3DestFormatJsonArrayDescription": "Jedes Objekt ist ein JSON-Array von Ereignisdaten. Kompatibel mit den meisten Analysetools.", + "s3DestFormatNdjsonDescription": "Jedes Objekt enthält einen JSON-Datensatz pro Zeile (newline-delimited JSON). Kompatibel mit Athena, BigQuery und Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Jedes Objekt ist eine RFC-4180 CSV-Datei mit einer Kopfzeile. Spaltennamen werden aus den Ereignisdatenfeldern abgeleitet.", + "s3DestSaveChanges": "Änderungen speichern", + "s3DestCreateDestination": "Ziel erstellen", + "s3DestUpdatedSuccess": "Ziel erfolgreich aktualisiert", + "s3DestCreatedSuccess": "Ziel erfolgreich erstellt", + "s3DestUpdateFailed": "Fehler beim Aktualisieren des Ziels", + "s3DestCreateFailed": "Fehler beim Erstellen des Ziels", "datadogDestEditTitle": "Ziel bearbeiten", "datadogDestAddTitle": "Datadog-Ziel hinzufügen", "datadogDestEditDescription": "Konfiguration für dieses Datadog-Ereignis-Streamingziel aktualisieren.", From 874dc2b33e3fa94fd7555e3f10a4da0d162a58e5 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:16 -0700 Subject: [PATCH 108/139] New translations en-us.json (Italian) [ci skip] --- messages/it-IT.json | 58 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index fc8f2bd41..5c432eadd 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Inoltra gli eventi direttamente al tuo account Datadog. In arrivo.", "streamingTypePickerDescription": "Scegli un tipo di destinazione per iniziare.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Si è verificato un errore durante l'ultima sincronizzazione", "streamingUnexpectedError": "Si è verificato un errore imprevisto.", "streamingFailedToUpdate": "Impossibile aggiornare la destinazione", "streamingDeletedSuccess": "Destinazione eliminata con successo", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Modifica Destinazione", "S3DestAddTitle": "Aggiungi Destinazione S3", "S3DestEditDescription": "Aggiorna la configurazione per questa destinazione di streaming eventi S3.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", - "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "S3DestAddDescription": "Configura un nuovo bucket Amazon S3 (o compatibile con S3) per ricevere gli eventi della tua organizzazione.", + "s3DestTabSettings": "Impostazioni", + "s3DestTabFormat": "Formato", + "s3DestNameLabel": "Nome", + "s3DestNamePlaceholder": "La mia destinazione S3", + "s3DestAccessKeyIdLabel": "ID Chiave Accesso AWS", + "s3DestSecretAccessKeyLabel": "Chiave Segreta Accesso AWS", + "s3DestSecretAccessKeyPlaceholder": "La tua chiave segreta di accesso AWS", + "s3DestRegionLabel": "Regione AWS", + "s3DestBucketLabel": "Nome Bucket", + "s3DestPrefixLabel": "Prefisso Chiave (facoltativo)", + "s3DestPrefixDescription": "Prefisso percorso facoltativo anteposto a ogni chiave oggetto. Gli oggetti vengono archiviati in {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Endpoint personalizzato (facoltativo)", + "s3DestEndpointDescription": "Sostituisci l'endpoint S3 per lo storage compatibile con S3 come MinIO o Cloudflare R2. Lasciare vuoto per l'AWS S3 standard.", + "s3DestGzipLabel": "Compressione Gzip", + "s3DestGzipDescription": "Comprimi ogni oggetto caricato con gzip. Riduce i costi di archiviazione e la dimensione di caricamento.", + "s3DestFormatTitle": "Formato del File", + "s3DestFormatDescription": "Come gli eventi sono serializzati all'interno di ciascun oggetto caricato.", + "s3DestFormatJsonArrayDescription": "Ogni oggetto è un array JSON di record di eventi. Compatibile con la maggior parte degli strumenti analitici.", + "s3DestFormatNdjsonDescription": "Ogni oggetto contiene un record JSON per linea (JSON delimitato da newline). Compatibile con Athena, BigQuery e Spark.", + "s3DestFormatCsvTitle": "\"CSV\"", + "s3DestFormatCsvDescription": "Ogni oggetto è un file CSV RFC-4180 con una riga di intestazione. I nomi delle colonne sono derivati dai campi dei dati degli eventi.", + "s3DestSaveChanges": "Salva modifiche", + "s3DestCreateDestination": "Crea destinazione", + "s3DestUpdatedSuccess": "Destinazione aggiornata con successo", + "s3DestCreatedSuccess": "Destinazione creata con successo", + "s3DestUpdateFailed": "Aggiornamento della destinazione fallito", + "s3DestCreateFailed": "Creazione della destinazione fallita", "datadogDestEditTitle": "Modifica Destinazione", "datadogDestAddTitle": "Aggiungi Destinazione Datadog", "datadogDestEditDescription": "Aggiorna la configurazione per questa destinazione di streaming eventi Datadog.", From ce5e62d21678ab39e767310c335ff4cb1e0e3306 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:17 -0700 Subject: [PATCH 109/139] New translations en-us.json (Korean) [ci skip] --- messages/ko-KR.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 114fec818..ef66f1470 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "데이터독", "streamingDatadogDescription": "이벤트를 직접 Datadog 계정으로 전달합니다. 곧 제공됩니다.", "streamingTypePickerDescription": "목표 유형을 선택하여 시작합니다.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "마지막 동기화에서 오류가 발생했습니다.", "streamingUnexpectedError": "예기치 않은 오류가 발생했습니다.", "streamingFailedToUpdate": "대상지를 업데이트하는 데 실패했습니다", "streamingDeletedSuccess": "대상지가 성공적으로 삭제되었습니다", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "대상지 수정", "S3DestAddTitle": "S3 대상지 추가", "S3DestEditDescription": "이 S3 이벤트 스트리밍 대상지의 구성을 업데이트하세요.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "S3DestAddDescription": "조직의 이벤트를 수신할 새로운 Amazon S3(또는 S3 호환) 버킷을 구성하세요.", + "s3DestTabSettings": "설정", + "s3DestTabFormat": "형식", + "s3DestNameLabel": "이름", + "s3DestNamePlaceholder": "내 S3 대상", + "s3DestAccessKeyIdLabel": "AWS 액세스 키 ID", + "s3DestSecretAccessKeyLabel": "AWS 비밀 액세스 키", + "s3DestSecretAccessKeyPlaceholder": "귀하의 AWS 비밀 액세스 키", + "s3DestRegionLabel": "AWS 지역", + "s3DestBucketLabel": "버킷 이름", + "s3DestPrefixLabel": "키 접두사(선택 사항)", + "s3DestPrefixDescription": "하나의 객체 키 앞에 붙이는 선택적 경로 접두사입니다. 객체는 {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}에 저장됩니다.", + "s3DestEndpointLabel": "사용자 정의 엔드포인트(선택 사항)", + "s3DestEndpointDescription": "MinIO 또는 Cloudflare R2와 같은 S3 호환 저장소에 대한 S3 엔드포인트를 재정의합니다. 표준 AWS S3의 경우 비워 두십시오.", + "s3DestGzipLabel": "Gzip 압축", + "s3DestGzipDescription": "각 업로드된 객체를 gzip으로 압축합니다. 저장 비용과 업로드 크기를 줄입니다.", + "s3DestFormatTitle": "파일 형식", + "s3DestFormatDescription": "업로드된 각 객체 내에서 이벤트가 직렬화되는 방식입니다.", + "s3DestFormatJsonArrayDescription": "각 객체는 이벤트 기록의 JSON 배열입니다. 대부분의 분석 도구와 호환됩니다.", + "s3DestFormatNdjsonDescription": "각 객체는 한 줄당 하나의 JSON 레코드를 포함합니다(새 줄로 구분된 JSON). Athena, BigQuery, Spark와 호환됩니다.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "각 객체는 헤더 행이 있는 RFC-4180 CSV 파일입니다. 열 이름은 이벤트 데이터 필드에서 파생됩니다.", + "s3DestSaveChanges": "변경 사항 저장", + "s3DestCreateDestination": "대상 생성", + "s3DestUpdatedSuccess": "대상이 성공적으로 업데이트되었습니다", + "s3DestCreatedSuccess": "대상이 성공적으로 생성되었습니다", + "s3DestUpdateFailed": "대상 업데이트에 실패했습니다", + "s3DestCreateFailed": "대상 생성에 실패했습니다", "datadogDestEditTitle": "대상지 수정", "datadogDestAddTitle": "Datadog 대상지 추가", "datadogDestEditDescription": "이 Datadog 이벤트 스트리밍 대상지의 구성을 업데이트하세요.", From 724e41a54f6fe08c7e95ab158c5c452a07d2bb9e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:19 -0700 Subject: [PATCH 110/139] New translations en-us.json (Dutch) [ci skip] --- messages/nl-NL.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index abc289652..de4ccdd33 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Stuur gebeurtenissen rechtstreeks door naar je Datadog account. Binnenkort beschikbaar.", "streamingTypePickerDescription": "Kies een bestemmingstype om te beginnen.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Er is een fout opgetreden bij de laatste synchronisatie", "streamingUnexpectedError": "Er is een onverwachte fout opgetreden.", "streamingFailedToUpdate": "Bijwerken bestemming mislukt", "streamingDeletedSuccess": "Bestemming succesvol verwijderd", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Bestemming bewerken", "S3DestAddTitle": "S3-bestemming toevoegen", "S3DestEditDescription": "Werk de configuratie bij voor deze S3-gebeurtenisstreamingbestemming.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "S3DestAddDescription": "Configureer een nieuwe Amazon S3 (of S3-compatibele) bucket om de gebeurtenissen van uw organisatie te ontvangen.", + "s3DestTabSettings": "Instellingen", + "s3DestTabFormat": "Formaat", + "s3DestNameLabel": "Naam", + "s3DestNamePlaceholder": "Mijn S3-bestemming", + "s3DestAccessKeyIdLabel": "AWS-toegangssleutel-ID", + "s3DestSecretAccessKeyLabel": "AWS Geheime Toegangssleutel", + "s3DestSecretAccessKeyPlaceholder": "Uw AWS geheime toegangssleutel", + "s3DestRegionLabel": "AWS-regio", + "s3DestBucketLabel": "Bucketnaam", + "s3DestPrefixLabel": "Sleutelvoorvoegsel (optioneel)", + "s3DestPrefixDescription": "Optioneel padvoorvoegsel dat aan elke object sleutel wordt toegevoegd. Objecten worden opgeslagen op {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Aangepast Eindpunt (optioneel)", + "s3DestEndpointDescription": "Overschrijf het S3-eindpunt voor S3-compatibele opslag zoals MinIO of Cloudflare R2. Laat leeg voor standaard AWS S3.", + "s3DestGzipLabel": "Gzip-compressie", + "s3DestGzipDescription": "Comprimeer elk geüpload object met gzip. Verlaagt opslagkosten en uploadgrootte.", + "s3DestFormatTitle": "Bestandsformaat", + "s3DestFormatDescription": "Hoe gebeurtenissen binnen elk geüpload object worden geserialiseerd.", + "s3DestFormatJsonArrayDescription": "Elk object is een JSON-array van gebeurtenisrecords. Compatibel met de meeste analysetools.", + "s3DestFormatNdjsonDescription": "Elk object bevat één JSON-record per regel (nieuwregel-gescheiden JSON). Compatibel met Athena, BigQuery en Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Elk object is een RFC-4180 CSV-bestand met een kopregel. Kolomnamen zijn afgeleid van de gebeurtenis gegevensvelden.", + "s3DestSaveChanges": "Wijzigingen opslaan", + "s3DestCreateDestination": "Bestemming maken", + "s3DestUpdatedSuccess": "Bestemming succesvol bijgewerkt", + "s3DestCreatedSuccess": "Bestemming succesvol gecreëerd", + "s3DestUpdateFailed": "Bijwerken bestemming mislukt", + "s3DestCreateFailed": "Aanmaken bestemming mislukt", "datadogDestEditTitle": "Bestemming bewerken", "datadogDestAddTitle": "Datadog-bestemming toevoegen", "datadogDestEditDescription": "Werk de configuratie bij voor deze Datadog-gebeurtenisstreamingbestemming.", From 2ca3ef019c86e2025a8d7958f2717245807650ef Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:21 -0700 Subject: [PATCH 111/139] New translations en-us.json (Polish) [ci skip] --- messages/pl-PL.json | 50 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index c6830f069..8189a927d 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Przekaż wydarzenia bezpośrednio do Twojego konta Datadog. Już wkrótce.", "streamingTypePickerDescription": "Wybierz typ docelowy, aby rozpocząć.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Wystąpił błąd podczas ostatniej synchronizacji", "streamingUnexpectedError": "Wystąpił nieoczekiwany błąd.", "streamingFailedToUpdate": "Nie udało się zaktualizować miejsca docelowego", "streamingDeletedSuccess": "Cel usunięty pomyślnie", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Edytuj Miejsce Docelowe", "S3DestAddTitle": "Dodaj Miejsce Docelowe S3", "S3DestEditDescription": "Zaktualizuj konfigurację dla tego miejsca docelowego strumieniowego zdarzeń S3.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", + "S3DestAddDescription": "Skonfiguruj nowy zasobnik Amazon S3 (lub zgodny z S3), aby otrzymywać zdarzenia twojej organizacji.", + "s3DestTabSettings": "Ustawienia", "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", + "s3DestNameLabel": "Nazwa", + "s3DestNamePlaceholder": "Moje miejsce docelowe S3", "s3DestAccessKeyIdLabel": "AWS Access Key ID", "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestSecretAccessKeyPlaceholder": "Twój AWS Secret Access Key", + "s3DestRegionLabel": "Region AWS", + "s3DestBucketLabel": "Nazwa kubła", + "s3DestPrefixLabel": "Prefiks klucza (opcjonalnie)", + "s3DestPrefixDescription": "Opcjonalny prefiks ścieżki dołączony do każdego klucza obiektu. Obiekty są przechowywane w {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Niestandardowy punkt końcowy (opcjonalnie)", + "s3DestEndpointDescription": "Nadpisz punkt końcowy S3 dla zgodnego przechowywania danych, takiego jak MinIO lub Cloudflare R2. Pozostaw puste dla standardowego AWS S3.", + "s3DestGzipLabel": "Kompresja Gzip", + "s3DestGzipDescription": "Skompresuj każdy przesłany obiekt za pomocą gzip. Zmniejsza koszty przechowywania i rozmiar przesyłu.", + "s3DestFormatTitle": "Format pliku", + "s3DestFormatDescription": "Jak zdarzenia są serializowane w każdym przesłanym obiekcie.", + "s3DestFormatJsonArrayDescription": "Każdy obiekt to tablica JSON z rekordami zdarzeń. Zgodne z większością narzędzi analitycznych.", + "s3DestFormatNdjsonDescription": "Każdy obiekt zawiera jeden rekord JSON na linię (nowa linia-dzielone JSON). Zgodne z Athena, BigQuery i Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Każdy obiekt to plik CSV zgodny z RFC-4180 z wierszem nagłówka. Nazwy kolumn pochodzą z pól danych zdarzeń.", + "s3DestSaveChanges": "Zapisz zmiany", + "s3DestCreateDestination": "Utwórz miejsce docelowe", + "s3DestUpdatedSuccess": "Miejsce docelowe zaktualizowane pomyślnie", + "s3DestCreatedSuccess": "Miejsce docelowe utworzone pomyślnie", + "s3DestUpdateFailed": "Nie udało się zaktualizować miejsca docelowego", + "s3DestCreateFailed": "Nie udało się utworzyć miejsca docelowego", "datadogDestEditTitle": "Edytuj Miejsce Docelowe", "datadogDestAddTitle": "Dodaj Miejsce Docelowe Datadog", "datadogDestEditDescription": "Zaktualizuj konfigurację dla tego miejsca docelowego strumieniowego zdarzeń Datadog.", From 13ec6b6620b8e0faef716ca60c6388f26b103469 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:23 -0700 Subject: [PATCH 112/139] New translations en-us.json (Portuguese) [ci skip] --- messages/pt-PT.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 21e7292c0..a3baabef8 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Encaminha eventos diretamente para a sua conta no Datadog. Em breve.", "streamingTypePickerDescription": "Escolha um tipo de destino para começar.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Ocorreu um erro na última sincronização", "streamingUnexpectedError": "Ocorreu um erro inesperado.", "streamingFailedToUpdate": "Falha ao atualizar destino", "streamingDeletedSuccess": "Destino apagado com sucesso", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Editar Destino", "S3DestAddTitle": "Adicionar Destino S3", "S3DestEditDescription": "Atualize a configuração para este destino de streaming de eventos S3.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "S3DestAddDescription": "Configure um novo bucket Amazon S3 (ou compatível com S3) para receber os eventos da sua organização.", + "s3DestTabSettings": "Configurações", + "s3DestTabFormat": "Formato", + "s3DestNameLabel": "Nome", + "s3DestNamePlaceholder": "Meu destino S3", + "s3DestAccessKeyIdLabel": "ID da Chave de Acesso AWS", + "s3DestSecretAccessKeyLabel": "Chave de Acesso Secreta AWS", + "s3DestSecretAccessKeyPlaceholder": "Sua chave de acesso secreta AWS", + "s3DestRegionLabel": "Região AWS", + "s3DestBucketLabel": "Nome do Bucket", + "s3DestPrefixLabel": "Prefixo da Chave (opcional)", + "s3DestPrefixDescription": "Prefixo de caminho opcional adicionado a cada chave de objeto. Os objetos são armazenados em {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Endpoint Personalizado (opcional)", + "s3DestEndpointDescription": "Substitua o endpoint S3 por armazenamento compatível com S3, como MinIO ou Cloudflare R2. Deixe em branco para o padrão AWS S3.", + "s3DestGzipLabel": "Compressão Gzip", + "s3DestGzipDescription": "Comprime cada objeto carregado com gzip. Reduz custos de armazenamento e tamanho de upload.", + "s3DestFormatTitle": "Formato de Arquivo", + "s3DestFormatDescription": "Como os eventos são serializados dentro de cada objeto carregado.", + "s3DestFormatJsonArrayDescription": "Cada objeto é um array JSON de registros de eventos. Compatível com a maioria das ferramentas de análise.", + "s3DestFormatNdjsonDescription": "Cada objeto contém um registro JSON por linha (JSON delimitado por nova linha). Compatível com Athena, BigQuery e Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Cada objeto é um arquivo CSV RFC-4180 com uma linha de cabeçalho. Nomes de colunas são derivados dos campos de dados do evento.", + "s3DestSaveChanges": "Salvar Alterações", + "s3DestCreateDestination": "Criar Destino", + "s3DestUpdatedSuccess": "Destino atualizado com sucesso", + "s3DestCreatedSuccess": "Destino criado com sucesso", + "s3DestUpdateFailed": "Falha ao atualizar destino", + "s3DestCreateFailed": "Falha ao criar destino", "datadogDestEditTitle": "Editar Destino", "datadogDestAddTitle": "Adicionar Destino Datadog", "datadogDestEditDescription": "Atualize a configuração para este destino de streaming de eventos Datadog.", From 9320df8be667c235e6070c73a922f0e37225cd7f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:24 -0700 Subject: [PATCH 113/139] New translations en-us.json (Russian) [ci skip] --- messages/ru-RU.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index d1741897e..766268cb9 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Перенаправлять события непосредственно на ваш аккаунт в Datadog. Скоро будет доступно.", "streamingTypePickerDescription": "Выберите тип назначения, чтобы начать.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Во время последней синхронизации произошла ошибка", "streamingUnexpectedError": "Произошла непредвиденная ошибка.", "streamingFailedToUpdate": "Не удалось обновить место назначения", "streamingDeletedSuccess": "Адрес назначения успешно удален", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Редактировать пункт назначения", "S3DestAddTitle": "Добавить S3 пункт назначения", "S3DestEditDescription": "Обновите конфигурацию для этого S3 пункта назначения потоковых событий.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "S3DestAddDescription": "Настройте новый Amazon S3 (или совместимое S3) хранилище для получения событий вашей организации.", + "s3DestTabSettings": "Настройки", + "s3DestTabFormat": "Формат", + "s3DestNameLabel": "Имя", + "s3DestNamePlaceholder": "Моя S3 конечная точка", + "s3DestAccessKeyIdLabel": "Идентификатор ключа доступа AWS", + "s3DestSecretAccessKeyLabel": "Секретный ключ доступа AWS", + "s3DestSecretAccessKeyPlaceholder": "Ваш секретный ключ доступа AWS", + "s3DestRegionLabel": "Регион AWS", + "s3DestBucketLabel": "Имя хранилища", + "s3DestPrefixLabel": "Префикс ключа (по желанию)", + "s3DestPrefixDescription": "Необязательный префикс пути, добавляется к каждому ключу объекта. Объекты хранятся в {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Пользовательская конечная точка (по желанию)", + "s3DestEndpointDescription": "Переопределите конечную точку S3 для совместимого хранилища, такого как MinIO или Cloudflare R2. Оставьте пустым для стандартного AWS S3.", + "s3DestGzipLabel": "Сжатие Gzip", + "s3DestGzipDescription": "Сжимайте каждый загруженный объект с помощью gzip. Уменьшает стоимость хранения и размер загрузки.", + "s3DestFormatTitle": "Формат файла", + "s3DestFormatDescription": "Как события сериализуются внутри каждого загруженного объекта.", + "s3DestFormatJsonArrayDescription": "Каждый объект — это JSON массив записей событий. Совместим с большинством аналитических инструментов.", + "s3DestFormatNdjsonDescription": "Каждый объект содержит одну запись JSON на строку (JSON, разделённый новой строкой). Совместим с Athena, BigQuery и Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Каждый объект представляет собой CSV файл по стандарту RFC-4180 с заголовочной строкой. Имена столбцов выведены из полей данных событий.", + "s3DestSaveChanges": "Сохранить изменения", + "s3DestCreateDestination": "Создать конечную точку", + "s3DestUpdatedSuccess": "Конечная точка успешно обновлена", + "s3DestCreatedSuccess": "Конечная точка успешно создана", + "s3DestUpdateFailed": "Не удалось обновить конечную точку", + "s3DestCreateFailed": "Не удалось создать конечную точку", "datadogDestEditTitle": "Редактировать пункт назначения", "datadogDestAddTitle": "Добавить пункт назначения Datadog", "datadogDestEditDescription": "Обновите конфигурацию для этого пункта назначения потоковых событий Datadog.", From 4959d66ac1984df49ff62fcfc800f1f280d51072 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:26 -0700 Subject: [PATCH 114/139] New translations en-us.json (Turkish) [ci skip] --- messages/tr-TR.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index ec5eab1f9..145a59eee 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Olayları doğrudan Datadog hesabınıza iletin. Yakında gelicek.", "streamingTypePickerDescription": "Başlamak için bir hedef türü seçin.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Son senkronizasyonda bir hata oluştu", "streamingUnexpectedError": "Beklenmeyen bir hata oluştu.", "streamingFailedToUpdate": "Hedef güncellenemedi", "streamingDeletedSuccess": "Hedef başarıyla silindi", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Hedefi Düzenle", "S3DestAddTitle": "S3 Hedefi Ekle", "S3DestEditDescription": "Bu S3 olay akışı hedefi için yapılandırmayı güncelleyin.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "S3DestAddDescription": "Kuruluşunuzun etkinliklerini almak için yeni bir Amazon S3 (veya S3-uyumlu) kovası yapılandırın.", + "s3DestTabSettings": "Ayarlar", + "s3DestTabFormat": "Biçim", + "s3DestNameLabel": "Ad", + "s3DestNamePlaceholder": "Benim S3 hedefim", + "s3DestAccessKeyIdLabel": "AWS Erişim Anahtar Kimliği", + "s3DestSecretAccessKeyLabel": "AWS Gizli Erişim Anahtarı", + "s3DestSecretAccessKeyPlaceholder": "AWS gizli erişim anahtarınız", + "s3DestRegionLabel": "AWS Bölgesi", + "s3DestBucketLabel": "Kova Adı", + "s3DestPrefixLabel": "Anahtar Ön Eki (isteğe bağlı)", + "s3DestPrefixDescription": "Her nesne anahtarının önüne eklenen isteğe bağlı yol öneki. Nesneler {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename} konumunda saklanır.", + "s3DestEndpointLabel": "Özel Uç Nokta (isteğe bağlı)", + "s3DestEndpointDescription": "MinIO veya Cloudflare R2 gibi S3-uyumlu depolama için S3 uç noktasını geçersiz kılın. Standart AWS S3 için boş bırakın.", + "s3DestGzipLabel": "Gzip sıkıştırması", + "s3DestGzipDescription": "Her yüklü nesneyi gzip ile sıkıştırın. Depolama maliyetlerini ve yükleme boyutunu azaltır.", + "s3DestFormatTitle": "Dosya Biçimi", + "s3DestFormatDescription": "Etkinliklerin her yüklendiği nesne içinde nasıl serileştirildiği.", + "s3DestFormatJsonArrayDescription": "Her nesne bir olay kayıtlarının JSON dizisidir. Çoğu analiz aracıyla uyumludur.", + "s3DestFormatNdjsonDescription": "Her nesne satır başına bir JSON kaydı içerir (yeni satır ile ayrılmış JSON). Athena, BigQuery ve Spark ile uyumludur.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Her nesne, bir başlık satırı ile birlikte RFC-4180 CSV dosyasıdır. Sütun isimleri olay verileri alanlarından türetilmiştir.", + "s3DestSaveChanges": "Değişiklikleri Kaydet", + "s3DestCreateDestination": "Hedef Oluştur", + "s3DestUpdatedSuccess": "Hedef başarıyla güncellendi", + "s3DestCreatedSuccess": "Hedef başarıyla oluşturuldu", + "s3DestUpdateFailed": "Hedef güncellenemedi", + "s3DestCreateFailed": "Hedef oluşturulamadı", "datadogDestEditTitle": "Hedefi Düzenle", "datadogDestAddTitle": "Datadog Hedefi Ekle", "datadogDestEditDescription": "Bu Datadog olay akışı hedefi için yapılandırmayı güncelleyin.", From 0800aa2a6175873a365eb23af8f51def28c51109 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:28 -0700 Subject: [PATCH 115/139] New translations en-us.json (Chinese Simplified) [ci skip] --- messages/zh-CN.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index c84947d80..f0162f95d 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "直接转发事件到您的Datadog 帐户。即将推出。", "streamingTypePickerDescription": "选择要开始的目标类型。", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "最后一次同步时发生错误", "streamingUnexpectedError": "发生意外错误.", "streamingFailedToUpdate": "更新目标失败", "streamingDeletedSuccess": "目标删除成功", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "编辑目的地", "S3DestAddTitle": "添加 S3 目的地", "S3DestEditDescription": "更新此 S3 事件流目的地的配置。", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "S3DestAddDescription": "配置一个新的 Amazon S3(或兼容 S3 的)存储桶以接收您的组织事件。", + "s3DestTabSettings": "设置", + "s3DestTabFormat": "格式", + "s3DestNameLabel": "名称", + "s3DestNamePlaceholder": "我的 S3 目的地", + "s3DestAccessKeyIdLabel": "AWS 访问密钥 ID", + "s3DestSecretAccessKeyLabel": "AWS 秘密访问密钥", + "s3DestSecretAccessKeyPlaceholder": "您的 AWS 密钥", + "s3DestRegionLabel": "AWS 地区", + "s3DestBucketLabel": "存储桶名称", + "s3DestPrefixLabel": "密钥前缀(可选)", + "s3DestPrefixDescription": "每个对象密钥前加的可选路径前缀。对象存储在 {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}。", + "s3DestEndpointLabel": "自定义端点(可选)", + "s3DestEndpointDescription": "替代 S3 端点用于 MinIO 或 Cloudflare R2 等兼容 S3 的存储。标准 AWS S3 留空。", + "s3DestGzipLabel": "Gzip 压缩", + "s3DestGzipDescription": "使用 gzip 压缩每个上传的对象。减少存储成本和上传大小。", + "s3DestFormatTitle": "文件格式", + "s3DestFormatDescription": "事件在每个上传对象内的序列化方式。", + "s3DestFormatJsonArrayDescription": "每个对象是事件记录的 JSON 数组。兼容大多数分析工具。", + "s3DestFormatNdjsonDescription": "每个对象每行包含一个 JSON 记录(换行分隔的 JSON)。兼容 Athena、BigQuery 和 Spark。", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "每个对象是带有标题行的 RFC-4180 CSV 文件。列名来自事件数据字段。", + "s3DestSaveChanges": "保存更改", + "s3DestCreateDestination": "创建目的地", + "s3DestUpdatedSuccess": "目的地更新成功", + "s3DestCreatedSuccess": "目的地创建成功", + "s3DestUpdateFailed": "更新目的地失败", + "s3DestCreateFailed": "创建目的地失败", "datadogDestEditTitle": "编辑目的地", "datadogDestAddTitle": "添加 Datadog 目的地", "datadogDestEditDescription": "更新此 Datadog 事件流目的地的配置。", From de1338a8cdf5d26384b4dec1b0058788daf14899 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:29 -0700 Subject: [PATCH 116/139] New translations en-us.json (Norwegian Bokmal) [ci skip] --- messages/nb-NO.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 8dc2cb9e3..15e7e246a 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Videresend arrangementer direkte til din Datadog-konto. Kommer snart.", "streamingTypePickerDescription": "Velg en måltype for å komme i gang.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Det oppstod en feil under siste synkronisering", "streamingUnexpectedError": "En uventet feil oppstod.", "streamingFailedToUpdate": "Kunne ikke oppdatere destinasjon", "streamingDeletedSuccess": "Målet ble slettet", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Rediger destinasjon", "S3DestAddTitle": "Legg til S3 destinasjon", "S3DestEditDescription": "Oppdatere konfigurasjonen for denne S3-hendelsesstrømmingsdestinasjonen.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", + "S3DestAddDescription": "Konfigurer en ny Amazon S3 (eller S3-kompatibel) bucket for å motta din organisasjons hendelser.", + "s3DestTabSettings": "Innstillinger", "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "s3DestNameLabel": "Navn", + "s3DestNamePlaceholder": "Min S3-destinasjon", + "s3DestAccessKeyIdLabel": "AWS tilgangsnøkkel-ID", + "s3DestSecretAccessKeyLabel": "AWS hemmelige tilgangsnøkkel", + "s3DestSecretAccessKeyPlaceholder": "Din AWS secret access key", + "s3DestRegionLabel": "AWS-region", + "s3DestBucketLabel": "Bucket-navn", + "s3DestPrefixLabel": "Nøkkelprefiks (valgfritt)", + "s3DestPrefixDescription": "Valgfritt bane-prefiks lagt til hver objektnøkkel. Objekter er lagret på {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Egendefinert endepunkt (valgfritt)", + "s3DestEndpointDescription": "Overstyr S3-endepunktet for S3-kompatibel lagring som MinIO eller Cloudflare R2. La stå tomt for standard AWS S3.", + "s3DestGzipLabel": "Gzip-komprimering", + "s3DestGzipDescription": "Komprimer hvert opplastede objekt med gzip. Reduserer lagringskostnader og opplastingsstørrelse.", + "s3DestFormatTitle": "Filformat", + "s3DestFormatDescription": "Hvordan hendelser er serialisert inni hvert opplastede objekt.", + "s3DestFormatJsonArrayDescription": "Hvert objekt er et JSON-array av hendelsesposter. Kompatibel med de fleste analyseverktøy.", + "s3DestFormatNdjsonDescription": "Hvert objekt inneholder en JSON-post per linje (nylinje-delt JSON). Kompatibel med Athena, BigQuery, og Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Hvert objekt er en RFC-4180 CSV-fil med en overskriftsrad. Kolonnenavn er avledet fra hendelsesdatafeltene.", + "s3DestSaveChanges": "Lagre endringer", + "s3DestCreateDestination": "Opprett destinasjon", + "s3DestUpdatedSuccess": "Destinasjon oppdatert vellykket", + "s3DestCreatedSuccess": "Destinasjon opprettet vellykket", + "s3DestUpdateFailed": "Kunne ikke oppdatere destinasjon", + "s3DestCreateFailed": "Kunne ikke opprette destinasjon", "datadogDestEditTitle": "Rediger destinasjon", "datadogDestAddTitle": "Legg til Datadog destinasjon", "datadogDestEditDescription": "Oppdatere konfigurasjonen for denne Datadog-hendelsesstrømmingsdestinasjonen.", @@ -3201,7 +3201,7 @@ "publicIpEndpoint": "Endepunkt", "lastTriggeredAt": "Siste utløste", "reject": "Avvis", - "uptimeDaysAgo": "{count} days ago", + "uptimeDaysAgo": "{count} dager siden", "uptimeToday": "I dag", "uptimeNoDataAvailable": "Ingen data tilgjengelig", "uptimeSuffix": "oppetid", From 5f3fafb1b0171972dccd7ac57b1601a65e58a2be Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Fri, 8 May 2026 17:16:31 -0700 Subject: [PATCH 117/139] New translations en-us.json (Spanish) [ci skip] --- messages/es-ES.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index fdd8bd63f..3175b4844 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -3062,7 +3062,7 @@ "streamingDatadogTitle": "Datadog", "streamingDatadogDescription": "Reenviar eventos directamente a tu cuenta de Datadog. Próximamente.", "streamingTypePickerDescription": "Elija un tipo de destino para empezar.", - "streamingLastSyncError": "An error occurred on the last sync", + "streamingLastSyncError": "Ocurrió un error en la última sincronización.", "streamingUnexpectedError": "Se ha producido un error inesperado.", "streamingFailedToUpdate": "Error al actualizar destino", "streamingDeletedSuccess": "Destino eliminado correctamente", @@ -3079,34 +3079,34 @@ "S3DestEditTitle": "Editar destino", "S3DestAddTitle": "Añadir destino S3", "S3DestEditDescription": "Actualice la configuración para este destino de transmisión de eventos S3.", - "S3DestAddDescription": "Configure a new Amazon S3 (or S3-compatible) bucket to receive your organization's events.", - "s3DestTabSettings": "Settings", - "s3DestTabFormat": "Format", - "s3DestNameLabel": "Name", - "s3DestNamePlaceholder": "My S3 destination", - "s3DestAccessKeyIdLabel": "AWS Access Key ID", - "s3DestSecretAccessKeyLabel": "AWS Secret Access Key", - "s3DestSecretAccessKeyPlaceholder": "Your AWS secret access key", - "s3DestRegionLabel": "AWS Region", - "s3DestBucketLabel": "Bucket Name", - "s3DestPrefixLabel": "Key Prefix (optional)", - "s3DestPrefixDescription": "Optional path prefix prepended to every object key. Objects are stored at {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", - "s3DestEndpointLabel": "Custom Endpoint (optional)", - "s3DestEndpointDescription": "Override the S3 endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave blank for standard AWS S3.", - "s3DestGzipLabel": "Gzip compression", - "s3DestGzipDescription": "Compress each uploaded object with gzip. Reduces storage costs and upload size.", - "s3DestFormatTitle": "File Format", - "s3DestFormatDescription": "How events are serialised inside each uploaded object.", - "s3DestFormatJsonArrayDescription": "Each object is a JSON array of event records. Compatible with most analytics tools.", - "s3DestFormatNdjsonDescription": "Each object contains one JSON record per line (newline-delimited JSON). Compatible with Athena, BigQuery, and Spark.", + "S3DestAddDescription": "Configura un nuevo bucket de Amazon S3 (o compatible con S3) para recibir los eventos de tu organización.", + "s3DestTabSettings": "Ajustes", + "s3DestTabFormat": "Formato", + "s3DestNameLabel": "Nombre", + "s3DestNamePlaceholder": "Mi destino S3", + "s3DestAccessKeyIdLabel": "ID de clave de acceso de AWS", + "s3DestSecretAccessKeyLabel": "Clave de acceso secreta de AWS", + "s3DestSecretAccessKeyPlaceholder": "Tu clave de acceso secreta de AWS", + "s3DestRegionLabel": "Región de AWS", + "s3DestBucketLabel": "Nombre del bucket", + "s3DestPrefixLabel": "Prefijo clave (opcional)", + "s3DestPrefixDescription": "Prefijo de ruta opcional preanexado a cada clave de objeto. Los objetos se almacenan en {prefix}/{logType}/{YYYY}/{MM}/{DD}/{filename}.", + "s3DestEndpointLabel": "Punto final personalizado (opcional)", + "s3DestEndpointDescription": "Sobrescribe el punto final de S3 para almacenamiento compatible con S3 como MinIO o Cloudflare R2. Deja en blanco para el estándar AWS S3.", + "s3DestGzipLabel": "Compresión Gzip", + "s3DestGzipDescription": "Comprime cada objeto subido con gzip. Reduce costos de almacenamiento y tamaño de carga.", + "s3DestFormatTitle": "Formato de archivo", + "s3DestFormatDescription": "Cómo se serializan los eventos dentro de cada objeto cargado.", + "s3DestFormatJsonArrayDescription": "Cada objeto es un arreglo JSON de registros de eventos. Compatible con la mayoría de las herramientas de analítica.", + "s3DestFormatNdjsonDescription": "Cada objeto contiene un registro JSON por línea (JSON delimitado por nueva línea). Compatible con Athena, BigQuery y Spark.", "s3DestFormatCsvTitle": "CSV", - "s3DestFormatCsvDescription": "Each object is an RFC-4180 CSV file with a header row. Column names are derived from the event data fields.", - "s3DestSaveChanges": "Save Changes", - "s3DestCreateDestination": "Create Destination", - "s3DestUpdatedSuccess": "Destination updated successfully", - "s3DestCreatedSuccess": "Destination created successfully", - "s3DestUpdateFailed": "Failed to update destination", - "s3DestCreateFailed": "Failed to create destination", + "s3DestFormatCsvDescription": "Cada objeto es un archivo CSV conforme a RFC-4180 con una fila de encabezado. Los nombres de columna se derivan de los campos de datos del evento.", + "s3DestSaveChanges": "Guardar cambios", + "s3DestCreateDestination": "Crear destino", + "s3DestUpdatedSuccess": "Destino actualizado con éxito", + "s3DestCreatedSuccess": "Destino creado con éxito", + "s3DestUpdateFailed": "No se pudo actualizar el destino", + "s3DestCreateFailed": "No se pudo crear el destino", "datadogDestEditTitle": "Editar destino", "datadogDestAddTitle": "Añadir destino Datadog", "datadogDestEditDescription": "Actualice la configuración para este destino de transmisión de eventos Datadog.", From 9fb677e952b8593a4ca93d597445828059a7ced6 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 8 May 2026 17:48:26 -0700 Subject: [PATCH 118/139] allow editing self and owner user roles --- messages/en-US.json | 11 +++ server/private/routers/user/addUserRole.ts | 9 -- server/private/routers/user/removeUserRole.ts | 4 +- .../private/routers/user/setUserOrgRoles.ts | 23 ++--- server/routers/user/addUserRoleLegacy.ts | 4 +- server/routers/user/getOrgUser.ts | 8 +- .../users/[userId]/access-controls/page.tsx | 82 ++++++++++++++--- src/components/UsersTable.tsx | 88 +++++++++++-------- .../multi-select/multi-select-content.tsx | 2 +- src/components/roles-selector.tsx | 2 +- 10 files changed, 153 insertions(+), 80 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 9a23043d5..9995d3af5 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -523,6 +523,12 @@ "userMessageOrgRemove": "Once removed, this user will no longer have access to the organization. You can always re-invite them later, but they will need to accept the invitation again.", "userRemoveOrgConfirm": "Confirm Remove User", "userRemoveOrg": "Remove User from Organization", + "userQuestionOrgRemoveSelf": "Are you sure you want to remove yourself from this organization?", + "userMessageOrgRemoveSelf": "You will lose access immediately. An administrator can invite you again later, but you will need to accept a new invitation.", + "userRemoveOrgConfirmSelf": "Confirm Remove Myself", + "userRemoveOrgSelf": "Remove yourself from the organization", + "userRemoveOrgSelfWarning": "You will lose access to this organization immediately.", + "userRemoveOrgConfirmPhraseSelf": "REMOVE MYSELF FROM ORG", "users": "Users", "accessRoleMember": "Member", "accessRoleOwner": "Owner", @@ -531,6 +537,11 @@ "emailInvalid": "Invalid email address", "inviteValidityDuration": "Please select a duration", "accessRoleSelectPlease": "Please select a role", + "removeOwnAdminRoleConfirmTitle": "Remove your administrator access?", + "removeOwnAdminRoleConfirmDescription": "You will no longer have administrator permissions in this organization after saving. Another administrator can restore access if needed.", + "removeOwnAdminRoleConfirmButton": "Remove My Administrator Access", + "removeOwnAdminRoleConfirmPhrase": "REMOVE MY ADMIN ACCESS", + "ownerMustRetainAdminRole": "The organization owner must keep at least one administrator role.", "usernameRequired": "Username is required", "idpSelectPlease": "Please select an identity provider", "idpGenericOidc": "Generic OAuth2/OIDC provider.", diff --git a/server/private/routers/user/addUserRole.ts b/server/private/routers/user/addUserRole.ts index 90fa79ee3..1789ca9c4 100644 --- a/server/private/routers/user/addUserRole.ts +++ b/server/private/routers/user/addUserRole.ts @@ -98,15 +98,6 @@ export async function addUserRole( ); } - if (existingUser[0].isOwner) { - return next( - createHttpError( - HttpCode.FORBIDDEN, - "Cannot change the role of the owner of the organization" - ) - ); - } - const roleExists = await db .select() .from(roles) diff --git a/server/private/routers/user/removeUserRole.ts b/server/private/routers/user/removeUserRole.ts index 1a7b763d4..7cd805240 100644 --- a/server/private/routers/user/removeUserRole.ts +++ b/server/private/routers/user/removeUserRole.ts @@ -98,11 +98,11 @@ export async function removeUserRole( ); } - if (existingUser.isOwner) { + if (existingUser.isOwner && role.isAdmin === true) { return next( createHttpError( HttpCode.FORBIDDEN, - "Cannot change the roles of the owner of the organization" + "Cannot remove the administrator role from the organization owner" ) ); } diff --git a/server/private/routers/user/setUserOrgRoles.ts b/server/private/routers/user/setUserOrgRoles.ts index 7567ffc54..7790eacfb 100644 --- a/server/private/routers/user/setUserOrgRoles.ts +++ b/server/private/routers/user/setUserOrgRoles.ts @@ -87,17 +87,8 @@ export async function setUserOrgRoles( ); } - if (existingUser.isOwner) { - return next( - createHttpError( - HttpCode.FORBIDDEN, - "Cannot change the roles of the owner of the organization" - ) - ); - } - const orgRoles = await db - .select({ roleId: roles.roleId }) + .select({ roleId: roles.roleId, isAdmin: roles.isAdmin }) .from(roles) .where( and( @@ -115,6 +106,18 @@ export async function setUserOrgRoles( ); } + if (existingUser.isOwner) { + const hasAdminRole = orgRoles.some((r) => r.isAdmin === true); + if (!hasAdminRole) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "The organization owner must retain an administrator role" + ) + ); + } + } + let orgClientsToRebuild: Client[] = []; await db.transaction(async (trx) => { await trx diff --git a/server/routers/user/addUserRoleLegacy.ts b/server/routers/user/addUserRoleLegacy.ts index 9696e4aac..6e5b805ab 100644 --- a/server/routers/user/addUserRoleLegacy.ts +++ b/server/routers/user/addUserRoleLegacy.ts @@ -88,11 +88,11 @@ export async function addUserRoleLegacy( ); } - if (existingUser.isOwner) { + if (existingUser.isOwner && role.isAdmin !== true) { return next( createHttpError( HttpCode.FORBIDDEN, - "Cannot change the role of the owner of the organization" + "The organization owner must retain an administrator role" ) ); } diff --git a/server/routers/user/getOrgUser.ts b/server/routers/user/getOrgUser.ts index c415e186c..af900150b 100644 --- a/server/routers/user/getOrgUser.ts +++ b/server/routers/user/getOrgUser.ts @@ -47,10 +47,7 @@ export async function queryUser(orgId: string, userId: string) { .from(userOrgRoles) .leftJoin(roles, eq(userOrgRoles.roleId, roles.roleId)) .where( - and( - eq(userOrgRoles.userId, userId), - eq(userOrgRoles.orgId, orgId) - ) + and(eq(userOrgRoles.userId, userId), eq(userOrgRoles.orgId, orgId)) ); const isAdmin = roleRows.some((r) => r.isAdmin); @@ -61,7 +58,8 @@ export async function queryUser(orgId: string, userId: string) { roleIds: roleRows.map((r) => r.roleId), roles: roleRows.map((r) => ({ roleId: r.roleId, - name: r.roleName ?? "" + name: r.roleName ?? "", + isAdmin: r.isAdmin === true })) }; } diff --git a/src/app/[orgId]/settings/access/users/[userId]/access-controls/page.tsx b/src/app/[orgId]/settings/access/users/[userId]/access-controls/page.tsx index 717d7f211..2bb9723ed 100644 --- a/src/app/[orgId]/settings/access/users/[userId]/access-controls/page.tsx +++ b/src/app/[orgId]/settings/access/users/[userId]/access-controls/page.tsx @@ -1,5 +1,6 @@ "use client"; +import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import IdpTypeBadge from "@app/components/IdpTypeBadge"; import OrgRolesTagField from "@app/components/OrgRolesTagField"; import { @@ -25,6 +26,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { userOrgUserContext } from "@app/hooks/useOrgUserContext"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { toast } from "@app/hooks/useToast"; +import { useUserContext } from "@app/hooks/useUserContext"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { build } from "@server/build"; @@ -32,7 +34,7 @@ import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { UserType } from "@server/types/UserTypes"; import { useTranslations } from "next-intl"; import { useParams } from "next/navigation"; -import { useActionState, useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; @@ -42,13 +44,15 @@ const accessControlsFormSchema = z.object({ roles: z.array( z.object({ id: z.string(), - text: z.string() + text: z.string(), + isAdmin: z.boolean().optional() }) ) }); export default function AccessControlsPage() { const { orgUser: user, updateOrgUser } = userOrgUserContext(); + const { user: sessionUser } = useUserContext(); const { env } = useEnvContext(); const api = createApiClient({ env }); @@ -72,7 +76,8 @@ export default function AccessControlsPage() { autoProvisioned: user.autoProvisioned || false, roles: (user.roles ?? []).map((r) => ({ id: r.roleId.toString(), - text: r.name + text: r.name, + isAdmin: r.isAdmin === true })) } }); @@ -84,7 +89,8 @@ export default function AccessControlsPage() { "roles", (user.roles ?? []).map((r) => ({ id: r.roleId.toString(), - text: r.name + text: r.name, + isAdmin: r.isAdmin === true })) ); form.setValue("autoProvisioned", user.autoProvisioned || false); @@ -95,11 +101,11 @@ export default function AccessControlsPage() { ? t("singleRolePerUserPlanNotice") : t("singleRolePerUserEditionNotice"); - const [, action, isSubmitting] = useActionState(onSubmit, null); - async function onSubmit() { - const isValid = await form.trigger(); - if (!isValid) return; + const [isSaving, setIsSaving] = useState(false); + const [confirmRemoveOwnAdminOpen, setConfirmRemoveOwnAdminOpen] = + useState(false); + async function executeSave() { const values = form.getValues(); if (values.roles.length === 0) { @@ -111,6 +117,7 @@ export default function AccessControlsPage() { return; } + setIsSaving(true); try { const roleIds = values.roles.map((r) => parseInt(r.id, 10)); const updateRoleRequest = supportsMultipleRolesPerUser @@ -130,7 +137,8 @@ export default function AccessControlsPage() { roleIds, roles: values.roles.map((r) => ({ roleId: parseInt(r.id, 10), - name: r.text + name: r.text, + isAdmin: r.isAdmin === true })), autoProvisioned: values.autoProvisioned }); @@ -149,11 +157,61 @@ export default function AccessControlsPage() { t("accessRoleErrorAddDescription") ) }); + } finally { + setIsSaving(false); } } + async function handleAccessControlsSubmit(e: React.FormEvent) { + e.preventDefault(); + + const isValid = await form.trigger(); + if (!isValid) return; + + const values = form.getValues(); + + if (values.roles.length === 0) { + toast({ + variant: "destructive", + title: t("accessRoleErrorAdd"), + description: t("accessRoleSelectPlease") + }); + return; + } + + const willHaveAdminRole = values.roles.some( + (r) => r.isAdmin === true + ); + + const isRemovingOwnAdmin = + sessionUser.userId === user.userId && + user.isAdmin && + !willHaveAdminRole; + + if (isRemovingOwnAdmin) { + setConfirmRemoveOwnAdminOpen(true); + return; + } + + await executeSave(); + } + return ( + +

{t("removeOwnAdminRoleConfirmDescription")}

+
+ } + buttonText={t("removeOwnAdminRoleConfirmButton")} + string={t("removeOwnAdminRoleConfirmPhrase")} + onConfirm={executeSave} + /> + @@ -168,7 +226,7 @@ export default function AccessControlsPage() {
void handleAccessControlsSubmit(e)} className="space-y-4" id="access-controls-form" > @@ -237,8 +295,8 @@ export default function AccessControlsPage() { - ) : ( - - - - )} + ); } @@ -359,22 +348,45 @@ export default function UsersTable({ }} dialog={
-

{t("userQuestionOrgRemove")}

-

{t("userMessageOrgRemove")}

+

+ {t( + isRemovingSelf + ? "userQuestionOrgRemoveSelf" + : "userQuestionOrgRemove" + )} +

+

+ {t( + isRemovingSelf + ? "userMessageOrgRemoveSelf" + : "userMessageOrgRemove" + )} +

} - buttonText={t("userRemoveOrgConfirm")} + buttonText={t( + isRemovingSelf + ? "userRemoveOrgConfirmSelf" + : "userRemoveOrgConfirm" + )} + warningText={ + isRemovingSelf ? t("userRemoveOrgSelfWarning") : undefined + } onConfirm={async () => startTransition(removeUser)} string={ - selectedUser - ? getUserDisplayName({ - email: selectedUser.email, - name: selectedUser.name, - username: selectedUser.username - }) - : "" + isRemovingSelf + ? t("userRemoveOrgConfirmPhraseSelf") + : selectedUser + ? getUserDisplayName({ + email: selectedUser.email, + name: selectedUser.name, + username: selectedUser.username + }) + : "" } - title={t("userRemoveOrg")} + title={t( + isRemovingSelf ? "userRemoveOrgSelf" : "userRemoveOrg" + )} /> = { emptyPlaceholder?: string; diff --git a/src/components/roles-selector.tsx b/src/components/roles-selector.tsx index 7f1b62e60..811971f49 100644 --- a/src/components/roles-selector.tsx +++ b/src/components/roles-selector.tsx @@ -6,7 +6,7 @@ import { useDebounce } from "use-debounce"; import { useTranslations } from "next-intl"; import { MultiSelectTagInput } from "./multi-select/multi-select-tag-input"; -export type SelectedRole = { id: string; text: string }; +export type SelectedRole = { id: string; text: string; isAdmin?: boolean }; export type RolesSelectorProps = { orgId: string; From a066a68e1ac8be44967f00aa89567303060320a1 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 11 May 2026 11:28:32 -0700 Subject: [PATCH 119/139] Pick the most specific domain Fixes #3047 --- server/lib/blueprints/proxyResources.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/lib/blueprints/proxyResources.ts b/server/lib/blueprints/proxyResources.ts index 34b352a42..178991962 100644 --- a/server/lib/blueprints/proxyResources.ts +++ b/server/lib/blueprints/proxyResources.ts @@ -1227,7 +1227,11 @@ async function getDomainId( return null; } - const domainSelection = validDomains[0].domains; + // Pick the most specific (longest baseDomain) valid domain so that, e.g., + // *.test.dev.example.com is assigned to *.dev.example.com rather than *.example.com. + const domainSelection = validDomains.sort( + (a, b) => b.domains.baseDomain.length - a.domains.baseDomain.length + )[0].domains; const baseDomain = domainSelection.baseDomain; // Wildcard full-domains are not allowed on namespace (provided/free) domains From 4aa72eb1a37aafb274c4594cea0decbc5b55ccf8 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 11 May 2026 11:49:51 -0700 Subject: [PATCH 120/139] Confirm delete of share links --- messages/en-US.json | 4 ++++ src/components/ShareLinksTable.tsx | 34 +++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 9995d3af5..e095ee662 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "An error occurred deleting link", "shareDeleted": "Link deleted", "shareDeletedDescription": "The link has been deleted", + "shareDelete": "Delete Share Link", + "shareDeleteConfirm": "Confirm Delete Share Link", + "shareQuestionRemove": "Are you sure you want to delete this share link?", + "shareMessageRemove": "Once deleted, the link will no longer work and anyone using it will lose access to the resource.", "shareTokenDescription": "The access token can be passed in two ways: as a query parameter or in the request headers. These must be passed from the client on every request for authenticated access.", "accessToken": "Access Token", "usageExamples": "Usage Examples", diff --git a/src/components/ShareLinksTable.tsx b/src/components/ShareLinksTable.tsx index 333cee03f..239a12cc8 100644 --- a/src/components/ShareLinksTable.tsx +++ b/src/components/ShareLinksTable.tsx @@ -61,6 +61,8 @@ export default function ShareLinksTable({ const api = createApiClient(useEnvContext()); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [selectedLink, setSelectedLink] = useState(null); const [rows, setRows] = useState(shareLinks); const [isRefreshing, setIsRefreshing] = useState(false); @@ -92,6 +94,7 @@ export default function ShareLinksTable({ title: t("shareErrorDelete"), description: formatAxiosError(e, t("shareErrorDeleteMessage")) }); + throw e; }); const newRows = rows.filter((r) => r.accessTokenId !== id); @@ -293,9 +296,10 @@ export default function ShareLinksTable({ {/* */} @@ -307,6 +311,30 @@ export default function ShareLinksTable({ return ( <> + {selectedLink && ( + { + setIsDeleteModalOpen(val); + if (!val) setSelectedLink(null); + }} + dialog={ +
+

{t("shareQuestionRemove")}

+

{t("shareMessageRemove")}

+
+ } + buttonText={t("shareDeleteConfirm")} + onConfirm={async () => + deleteSharelink(selectedLink.accessTokenId) + } + string={ + selectedLink.title || selectedLink.resourceName + } + title={t("shareDelete")} + /> + )} + Date: Mon, 11 May 2026 12:06:36 -0700 Subject: [PATCH 121/139] Use the right param for user --- src/components/InviteStatusCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/InviteStatusCard.tsx b/src/components/InviteStatusCard.tsx index f35f47629..40b7123e7 100644 --- a/src/components/InviteStatusCard.tsx +++ b/src/components/InviteStatusCard.tsx @@ -99,7 +99,7 @@ export default function InviteStatusCard({ router.push(redirectUrl); } else if (!user && type === "not_logged_in") { const redirectUrl = email - ? `/auth/login?redirect=/invite?token=${tokenParam}&email=${email}` + ? `/auth/login?redirect=/invite?token=${tokenParam}&user=${email}` : `/auth/login?redirect=/invite?token=${tokenParam}`; router.push(redirectUrl); } else { @@ -113,7 +113,7 @@ export default function InviteStatusCard({ async function goToLogin() { await api.post("/auth/logout", {}); const redirectUrl = email - ? `/auth/login?redirect=/invite?token=${tokenParam}&email=${email}` + ? `/auth/login?redirect=/invite?token=${tokenParam}&user=${email}` : `/auth/login?redirect=/invite?token=${tokenParam}`; router.push(redirectUrl); } From 77d17af15b48a5eb8ab8f813e8d6aabe2bc26fe8 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 11 May 2026 16:18:57 -0700 Subject: [PATCH 122/139] Add global hide_powered_by and make it backward --- server/private/lib/config.ts | 7 +++++++ server/private/lib/readConfigFile.ts | 1 + src/components/OrgLoginPage.tsx | 29 +++++++++++++++------------ src/components/ResourceAuthPortal.tsx | 3 ++- src/lib/pullEnv.ts | 2 ++ src/lib/types/env.ts | 1 + 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/server/private/lib/config.ts b/server/private/lib/config.ts index 9884fe252..75600fba6 100644 --- a/server/private/lib/config.ts +++ b/server/private/lib/config.ts @@ -97,6 +97,13 @@ export class PrivateConfig { ); } + process.env.BRANDING_HIDE_POWERED_BY = + this.rawPrivateConfig.branding?.hide_powered_by === true || + this.rawPrivateConfig.branding?.resource_auth_page + ?.hide_powered_by === true + ? "true" + : "false"; + process.env.LOGIN_PAGE_SUBTITLE_TEXT = this.rawPrivateConfig.branding?.login_page?.subtitle_text || ""; diff --git a/server/private/lib/readConfigFile.ts b/server/private/lib/readConfigFile.ts index 63ca0b068..974e8e590 100644 --- a/server/private/lib/readConfigFile.ts +++ b/server/private/lib/readConfigFile.ts @@ -141,6 +141,7 @@ export const privateConfigSchema = z ) .optional(), hide_auth_layout_footer: z.boolean().optional().default(false), + hide_powered_by: z.boolean().optional(), login_page: z .object({ subtitle_text: z.string().optional() diff --git a/src/components/OrgLoginPage.tsx b/src/components/OrgLoginPage.tsx index 26cc23814..3270b7cb4 100644 --- a/src/components/OrgLoginPage.tsx +++ b/src/components/OrgLoginPage.tsx @@ -16,6 +16,7 @@ import Link from "next/link"; import { replacePlaceholder } from "@app/lib/replacePlaceholder"; import { getTranslations } from "next-intl/server"; import { pullEnv } from "@app/lib/pullEnv"; +import { build } from "@server/build"; type OrgLoginPageProps = { loginPage: LoadLoginPageResponse | undefined; @@ -52,19 +53,21 @@ export default async function OrgLoginPage({ const t = await getTranslations(); return (
-
- - {t("poweredBy")}{" "} - - {env.branding.appName || "Pangolin"} - - -
+ {build !== "enterprise" || !env.branding.hidePoweredBy ? ( +
+ + {t("poweredBy")}{" "} + + {env.branding.appName || "Pangolin"} + + +
+ ) : null} {branding?.logoUrl && ( diff --git a/src/components/ResourceAuthPortal.tsx b/src/components/ResourceAuthPortal.tsx index 0020330c6..64e1d2725 100644 --- a/src/components/ResourceAuthPortal.tsx +++ b/src/components/ResourceAuthPortal.tsx @@ -375,7 +375,8 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { {!accessDenied ? (
{isUnlocked() && build === "enterprise" ? ( - !env.branding.resourceAuthPage?.hidePoweredBy && ( + !env.branding.resourceAuthPage?.hidePoweredBy && + !env.branding.hidePoweredBy && (
{t("poweredBy")}{" "} diff --git a/src/lib/pullEnv.ts b/src/lib/pullEnv.ts index ddbd42c26..21390effc 100644 --- a/src/lib/pullEnv.ts +++ b/src/lib/pullEnv.ts @@ -81,6 +81,8 @@ export function pullEnv(): Env { process.env.BRANDING_HIDE_AUTH_LAYOUT_FOOTER === "true" ? true : false, + hidePoweredBy: + process.env.BRANDING_HIDE_POWERED_BY === "true" ? true : false, logo: { lightPath: process.env.BRANDING_LOGO_LIGHT_PATH as string, darkPath: process.env.BRANDING_LOGO_DARK_PATH as string, diff --git a/src/lib/types/env.ts b/src/lib/types/env.ts index 46513ae52..2a9fd2549 100644 --- a/src/lib/types/env.ts +++ b/src/lib/types/env.ts @@ -41,6 +41,7 @@ export type Env = { appName?: string; background_image_path?: string; hideAuthLayoutFooter?: boolean; + hidePoweredBy?: boolean; logo?: { lightPath?: string; darkPath?: string; From b6caeda0a58575b345ce2b3af06f690660c7daba Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 11 May 2026 22:06:43 -0700 Subject: [PATCH 123/139] improve targets round robin warning --- messages/en-US.json | 1 + .../resources/proxy/[niceId]/proxy/page.tsx | 20 +++++++++----- .../settings/resources/proxy/create/page.tsx | 26 ++++++++++++------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index e095ee662..7cbf6b166 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -673,6 +673,7 @@ "targetNoOneDescription": "Adding more than one target above will enable load balancing.", "targetsSubmit": "Save Targets", "addTarget": "Add Target", + "proxyMultiSiteRoundRobinNodeHelp": "Round robin routing will not work between sites that are not connected to the same node, but failover will work.", "targetErrorInvalidIp": "Invalid IP address", "targetErrorInvalidIpDescription": "Please enter a valid IP address or hostname", "targetErrorInvalidPort": "Invalid port", diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx index ba237b9b6..823c0f957 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx @@ -84,6 +84,7 @@ import { AlertTriangle, CircleCheck, CircleX, + ExternalLink, Info, Plus, Settings @@ -961,13 +962,18 @@ function ProxyResourceTargetsForm({ {build === "saas" && targets.length > 1 && new Set(targets.map((t) => t.siteId)).size > 1 && ( -

- - - Round robin routing will not work between - sites that are not connected to the same - node, but failover will work. - +

+ {t("proxyMultiSiteRoundRobinNodeHelp")}{" "} + + {t("learnMore")} + + + .

)} diff --git a/src/app/[orgId]/settings/resources/proxy/create/page.tsx b/src/app/[orgId]/settings/resources/proxy/create/page.tsx index 65d671681..d69bbdcf0 100644 --- a/src/app/[orgId]/settings/resources/proxy/create/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/create/page.tsx @@ -82,8 +82,8 @@ import { AxiosResponse } from "axios"; import { CircleCheck, CircleX, + ExternalLink, Info, - InfoIcon, Plus, Settings, SquareArrowOutUpRight @@ -1425,16 +1425,22 @@ export default function Page() {
)} - {build === "enterprise" && + {build === "saas" && targets.length > 1 && - new Set(targets.map((t) => t.siteId)).size > 1 && ( -

- - - Round robin routing will not work between - sites that are not connected to the same - node, but failover will work. - + new Set(targets.map((t) => t.siteId)).size > + 1 && ( +

+ {t("proxyMultiSiteRoundRobinNodeHelp")}{" "} + + {t("learnMore")} + + + .

)} From f91d914ec612bcd7156686692b0d886069618280 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 12 May 2026 20:13:45 -0700 Subject: [PATCH 124/139] Show when a domain is config managed --- cli/commands/disableUser2fa.ts | 60 ++++++++++++++++++++ cli/index.ts | 2 + messages/en-US.json | 2 + src/components/DomainPageClient.tsx | 21 ++++++- src/components/DomainsTable.tsx | 85 +++++++++++++++++++++-------- 5 files changed, 146 insertions(+), 24 deletions(-) create mode 100644 cli/commands/disableUser2fa.ts diff --git a/cli/commands/disableUser2fa.ts b/cli/commands/disableUser2fa.ts new file mode 100644 index 000000000..8b602c334 --- /dev/null +++ b/cli/commands/disableUser2fa.ts @@ -0,0 +1,60 @@ +import { CommandModule } from "yargs"; +import { db, users } from "@server/db"; +import { eq } from "drizzle-orm"; + +/** + * Disable 2FA for a user by email address. + */ +type DisableUser2faArgs = { + email: string; +}; + +export const disableUser2fa: CommandModule<{}, DisableUser2faArgs> = { + command: "disable-user-2fa", + describe: "Disable 2FA for a user (sets twoFactorEnabled=false, clears secret)", + builder: (yargs) => { + return yargs.option("email", { + type: "string", + demandOption: true, + describe: "User email address" + }); + }, + handler: async (argv: { email: string }) => { + try { + const { email } = argv; + console.log(`Looking for user with email: ${email}`); + + // Find the user by email + const [user] = await db + .select() + .from(users) + .where(eq(users.email, email)) + .limit(1); + + if (!user) { + console.error(`User with email '${email}' not found`); + process.exit(1); + } + + if (!user.twoFactorEnabled) { + console.log(`2FA is already disabled for user '${email}'.`); + process.exit(0); + } + + // Update user: disable 2FA and clear secret + await db.update(users) + .set({ + twoFactorEnabled: false, + twoFactorSecret: null, + twoFactorSetupRequested: false + }) + .where(eq(users.userId, user.userId)); + + console.log(`2FA disabled for user '${email}'.`); + process.exit(0); + } catch (error) { + console.error("Error disabling 2FA:", error); + process.exit(1); + } + } +}; diff --git a/cli/index.ts b/cli/index.ts index 3664bb8f8..19585bc6f 100644 --- a/cli/index.ts +++ b/cli/index.ts @@ -10,6 +10,7 @@ import { clearLicenseKeys } from "./commands/clearLicenseKeys"; import { deleteClient } from "./commands/deleteClient"; import { generateOrgCaKeys } from "./commands/generateOrgCaKeys"; import { clearCertificates } from "./commands/clearCertificates"; +import { disableUser2fa } from "./commands/disableUser2fa"; yargs(hideBin(process.argv)) .scriptName("pangctl") @@ -21,5 +22,6 @@ yargs(hideBin(process.argv)) .command(deleteClient) .command(generateOrgCaKeys) .command(clearCertificates) + .command(disableUser2fa) .demandCommand() .help().argv; diff --git a/messages/en-US.json b/messages/en-US.json index 7cbf6b166..52981764b 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2668,6 +2668,8 @@ "validPassword": "Valid Password", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "View", + "configManaged": "Config Managed", "connectedClient": "Connected Client", "resourceBlocked": "Resource Blocked", "droppedByRule": "Dropped by Rule", diff --git a/src/components/DomainPageClient.tsx b/src/components/DomainPageClient.tsx index 31527c5b8..6d64e42ce 100644 --- a/src/components/DomainPageClient.tsx +++ b/src/components/DomainPageClient.tsx @@ -13,6 +13,8 @@ import DomainCertForm from "@app/components/DomainCertForm"; import { build } from "@server/build"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useTranslations } from "next-intl"; +import { Lock } from "lucide-react"; +import { Badge } from "@app/components/ui/badge"; interface DomainPageClientProps { initialDomain: GetDomainResponse; @@ -49,7 +51,22 @@ export default function DomainPageClient({ <>
+ {domain.baseDomain} + {domain.configManaged && ( + + + {t("configManaged", { + fallback: "Config Managed" + })} + + )} + + } description={t("domainSettingDescription")} /> {env.flags.usePangolinDns && domain.failed ? ( @@ -90,4 +107,4 @@ export default function DomainPageClient({
); -} \ No newline at end of file +} diff --git a/src/components/DomainsTable.tsx b/src/components/DomainsTable.tsx index 2c3abeb1a..fda219f34 100644 --- a/src/components/DomainsTable.tsx +++ b/src/components/DomainsTable.tsx @@ -16,6 +16,7 @@ import { formatAxiosError } from "@app/lib/api"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { Badge } from "@app/components/ui/badge"; +import { Lock } from "lucide-react"; import { useTranslations } from "next-intl"; import CreateDomainForm from "@app/components/CreateDomainForm"; import { useToast } from "@app/hooks/useToast"; @@ -72,7 +73,11 @@ export default function DomainsTable({ domains, orgId }: Props) { const { org } = useOrgContext(); const queryClient = useQueryClient(); - const { data: rawDomains, isRefetching, refetch } = useQuery({ + const { + data: rawDomains, + isRefetching, + refetch + } = useQuery({ ...orgQueries.domains({ orgId }), initialData: domains as any, refetchInterval: durationToMs(10, "seconds") @@ -80,12 +85,15 @@ export default function DomainsTable({ domains, orgId }: Props) { const tableData = useMemo( () => - (rawDomains ?? []).map((d) => ({ - ...d, - baseDomain: toUnicode(d.baseDomain), - type: d.type ?? "", - errorMessage: d.errorMessage ?? null - } as DomainRow)), + (rawDomains ?? []).map( + (d) => + ({ + ...d, + baseDomain: toUnicode(d.baseDomain), + type: d.type ?? "", + errorMessage: d.errorMessage ?? null + }) as DomainRow + ), [rawDomains] ); @@ -198,12 +206,17 @@ export default function DomainsTable({ domains, orgId }: Props) { - + {t("failed", { fallback: "Failed" })} -

{errorMessage}

+

+ {errorMessage} +

@@ -220,12 +233,17 @@ export default function DomainsTable({ domains, orgId }: Props) { - + {t("pending")} -

{errorMessage}

+

+ {errorMessage} +

@@ -253,6 +271,25 @@ export default function DomainsTable({ domains, orgId }: Props) { ); + }, + cell: ({ row }) => { + const domain = row.original; + return ( + + {domain.baseDomain} + {domain.configManaged && ( + + + {t("configManaged", { + fallback: "Config Managed" + })} + + )} + + ); } }, ...(env.env.flags.usePangolinDns ? [typeColumn] : []), @@ -283,16 +320,18 @@ export default function DomainsTable({ domains, orgId }: Props) { {t("viewSettings")} - { - setSelectedDomain(domain); - setIsDeleteModalOpen(true); - }} - > - - {t("delete")} - - + {!domain.configManaged && ( + { + setSelectedDomain(domain); + setIsDeleteModalOpen(true); + }} + > + + {t("delete")} + + + )} {domain.failed && ( @@ -315,7 +354,9 @@ export default function DomainsTable({ domains, orgId }: Props) { href={`/${orgId}/settings/domains/${domain.domainId}`} > From 0005c11a0a113069685abe0c0273e9a6e5698dab Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:08 -0700 Subject: [PATCH 125/139] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 4391e9946..4c54bb85d 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Une erreur s'est produite lors de la suppression du lien", "shareDeleted": "Lien supprimé", "shareDeletedDescription": "Le lien a été supprimé", + "shareDelete": "Supprimer le lien de partage", + "shareDeleteConfirm": "Confirmer la suppression du lien de partage", + "shareQuestionRemove": "Êtes-vous sûr de vouloir supprimer ce lien de partage ?", + "shareMessageRemove": "Une fois supprimé, le lien ne fonctionnera plus et toute personne l'utilisant perdra l'accès à la ressource.", "shareTokenDescription": "Le jeton d'accès peut être passé de deux façons : en tant que paramètre de requête ou dans les en-têtes de la requête. Elles doivent être transmises par le client à chaque demande d'accès authentifié.", "accessToken": "Jeton d'accès", "usageExamples": "Exemples d'utilisation", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Une fois retiré, cet utilisateur n'aura plus accès à l'organisation. Vous pouvez toujours le réinviter plus tard, mais il devra accepter l'invitation à nouveau.", "userRemoveOrgConfirm": "Confirmer la suppression de l'utilisateur", "userRemoveOrg": "Retirer l'utilisateur de l'organisation", + "userQuestionOrgRemoveSelf": "Êtes-vous sûr de vouloir vous retirer de cette organisation ?", + "userMessageOrgRemoveSelf": "Vous perdrez immédiatement l'accès. Un administrateur pourra vous inviter à nouveau plus tard, mais vous devrez accepter une nouvelle invitation.", + "userRemoveOrgConfirmSelf": "Confirmer la suppression de moi-même", + "userRemoveOrgSelf": "Se retirer de l'organisation", + "userRemoveOrgSelfWarning": "Vous perdrez immédiatement l'accès à cette organisation.", + "userRemoveOrgConfirmPhraseSelf": "SUPPRIMER MOI-MÊME DE L'ORG", "users": "Utilisateurs", "accessRoleMember": "Membre", "accessRoleOwner": "Propriétaire", @@ -531,6 +541,11 @@ "emailInvalid": "Adresse e-mail invalide", "inviteValidityDuration": "Veuillez sélectionner une durée", "accessRoleSelectPlease": "Veuillez sélectionner un rôle", + "removeOwnAdminRoleConfirmTitle": "Retirer votre accès administrateur ?", + "removeOwnAdminRoleConfirmDescription": "Vous n'aurez plus de droits d'administrateur dans cette organisation après avoir enregistré. Un autre administrateur pourra restaurer cet accès si nécessaire.", + "removeOwnAdminRoleConfirmButton": "Retirer mon accès administrateur", + "removeOwnAdminRoleConfirmPhrase": "RETIRER MON ACCÈS ADMIN", + "ownerMustRetainAdminRole": "Le propriétaire de l'organisation doit conserver au moins un rôle d'administrateur.", "usernameRequired": "Le nom d'utilisateur est requis", "idpSelectPlease": "Veuillez sélectionner un fournisseur d'identité", "idpGenericOidc": "Fournisseur OAuth2/OIDC générique.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "L'ajout de plus d'une cible ci-dessus activera l'équilibrage de charge.", "targetsSubmit": "Enregistrer les cibles", "addTarget": "Ajouter une cible", + "proxyMultiSiteRoundRobinNodeHelp": "Le routage en tourniquet n'opérera pas entre des sites qui ne sont pas connectés au même nœud, mais le basculement fonctionnera.", "targetErrorInvalidIp": "Adresse IP invalide", "targetErrorInvalidIpDescription": "Veuillez entrer une adresse IP ou un nom d'hôte valide", "targetErrorInvalidPort": "Port invalide", @@ -2652,6 +2668,8 @@ "validPassword": "Mot de passe valide", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "Afficher", + "configManaged": "Configuration gérée", "connectedClient": "Client connecté", "resourceBlocked": "Ressource bloquée", "droppedByRule": "Abandonné par la règle", From 2d428d2fa06c3c4edcfaa70017a33b983eec055d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:10 -0700 Subject: [PATCH 126/139] New translations en-us.json (Bulgarian) [ci skip] --- messages/bg-BG.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index bd35c7bf4..d3a4d08fd 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Възникна грешка при изтриване на връзката", "shareDeleted": "Връзката беше изтрита", "shareDeletedDescription": "Връзката беше премахната", + "shareDelete": "Изтрийте споделената връзка", + "shareDeleteConfirm": "Потвърдете изтриването на споделената връзка", + "shareQuestionRemove": "Сигурни ли сте, че искате да изтриете тази споделена връзка?", + "shareMessageRemove": "След изтриване връзката вече няма да работи и всеки, който я използва, ще загуби достъп до ресурса.", "shareTokenDescription": "Достъпният токен може да бъде предаван по два начина: като параметър или в хедърите на заявките. Те трябва да бъдат предавани от клиента при всяка заявка за удостоверен достъп.", "accessToken": "Достъп Токен", "usageExamples": "Примери за използване", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "След като бъде премахнат, този потребител няма да има достъп до организацията. Винаги можете да го поканите отново по-късно, но той ще трябва да приеме отново поканата.", "userRemoveOrgConfirm": "Потвърдете премахването на потребителя", "userRemoveOrg": "Премахване на потребителя от организацията", + "userQuestionOrgRemoveSelf": "Сигурни ли сте, че искате да премахнете себе си от тази организация?", + "userMessageOrgRemoveSelf": "Ще загубите достъп незабавно. Администратор може да ви покани отново по-късно, но ще трябва да приемете нова покана.", + "userRemoveOrgConfirmSelf": "Потвърдете премахването на себе си", + "userRemoveOrgSelf": "Премахнете себе си от организацията", + "userRemoveOrgSelfWarning": "Ще загубите достъп до тази организация незабавно.", + "userRemoveOrgConfirmPhraseSelf": "ПРЕМАХНЕТЕ МЕ ОТ ОРГАНИЗАЦИЯТА", "users": "Потребители", "accessRoleMember": "Член", "accessRoleOwner": "Собственик", @@ -531,6 +541,11 @@ "emailInvalid": "Невалиден имейл адрес", "inviteValidityDuration": "Моля, изберете продължителност", "accessRoleSelectPlease": "Моля, изберете роля", + "removeOwnAdminRoleConfirmTitle": "Премахване на административния ви достъп?", + "removeOwnAdminRoleConfirmDescription": "След записване няма да имате повече администраторски права в тази организация. Друг администратор може да възстанови достъпа, ако е необходимо.", + "removeOwnAdminRoleConfirmButton": "Премахнете административния ми достъп", + "removeOwnAdminRoleConfirmPhrase": "ПРЕМАХНЕТЕ АДМИНИСТРАТИВНИЯ МИ ДОСТЪП", + "ownerMustRetainAdminRole": "Собственикът на организацията трябва да запази поне една администраторска роля.", "usernameRequired": "Необходимо е потребителско име", "idpSelectPlease": "Моля, изберете доставчик на идентичност", "idpGenericOidc": "Основен OAuth2/OIDC доставчик.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Добавянето на повече от една цел ще активира натоварването на баланса.", "targetsSubmit": "Запазване на целите", "addTarget": "Добавете цел", + "proxyMultiSiteRoundRobinNodeHelp": "Роунд Робин маршрутизирането няма да работи между сайтове, които не са свързани към един и същ възел, но автоматичното превключване ще работи.", "targetErrorInvalidIp": "Невалиден IP адрес", "targetErrorInvalidIpDescription": "Моля, въведете валиден IP адрес или име на хост", "targetErrorInvalidPort": "Невалиден порт", @@ -2652,6 +2668,8 @@ "validPassword": "Валидна парола", "validEmail": "Валиден имейл", "validSSO": "Валидно SSO", + "view": "Преглед", + "configManaged": "Управлявана конфигурация", "connectedClient": "Свързан клиент", "resourceBlocked": "Блокирани ресурси", "droppedByRule": "Прекратено от правило", @@ -3201,7 +3219,7 @@ "publicIpEndpoint": "Крайна точка", "lastTriggeredAt": "Последен тригер", "reject": "Отхвърляне", - "uptimeDaysAgo": "{count} days ago", + "uptimeDaysAgo": "преди {count} дни", "uptimeToday": "Днес", "uptimeNoDataAvailable": "Няма налични данни", "uptimeSuffix": "време без прекъсване", From 82e8e79b16122c1f33797bee808442f75948916d Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:12 -0700 Subject: [PATCH 127/139] New translations en-us.json (Czech) [ci skip] --- messages/cs-CZ.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 80c242e39..ecc0e675d 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Došlo k chybě při odstraňování odkazu", "shareDeleted": "Odkaz odstraněn", "shareDeletedDescription": "Odkaz byl odstraněn", + "shareDelete": "Smazat odkaz ke sdílení", + "shareDeleteConfirm": "Potvrdit smazání odkazu ke sdílení", + "shareQuestionRemove": "Jste si jisti, že chcete smazat tento odkaz ke sdílení?", + "shareMessageRemove": "Jakmile bude smazán, odkaz přestane fungovat a všichni, kdo jej používají, ztratí přístup k prostředku.", "shareTokenDescription": "Přístupový token může být předán dvěma způsoby: jako parametr dotazu nebo v záhlaví požadavku. Tyto údaje musí být předány klientovi na každé žádosti o ověřený přístup.", "accessToken": "Přístupový token", "usageExamples": "Příklady použití", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Po odstranění tohoto uživatele již nebude mít přístup k organizaci. Vždy je můžete znovu pozvat později, ale budou muset pozvání znovu přijmout.", "userRemoveOrgConfirm": "Potvrdit odebrání uživatele", "userRemoveOrg": "Odebrat uživatele z organizace", + "userQuestionOrgRemoveSelf": "Jste si jisti, že se chcete odstranit z této organizace?", + "userMessageOrgRemoveSelf": "Okamžitě ztratíte přístup. Administrátor vás může později znovu pozvat, ale budete muset přijmout nové pozvání.", + "userRemoveOrgConfirmSelf": "Potvrdit odstranění sebe", + "userRemoveOrgSelf": "Odstranit se z organizace", + "userRemoveOrgSelfWarning": "Okamžitě ztratíte přístup k této organizaci.", + "userRemoveOrgConfirmPhraseSelf": "ODSTRANIT SE Z ORGANIZACE", "users": "Uživatelé", "accessRoleMember": "Člen", "accessRoleOwner": "Vlastník", @@ -531,6 +541,11 @@ "emailInvalid": "Neplatná e-mailová adresa", "inviteValidityDuration": "Zvolte prosím dobu trvání", "accessRoleSelectPlease": "Vyberte prosím roli", + "removeOwnAdminRoleConfirmTitle": "Odebrat přístup správce?", + "removeOwnAdminRoleConfirmDescription": "Po uložení již nebudete mít oprávnění správce v této organizaci. Další administrátor vám může přístup obnovit, pokud bude potřeba.", + "removeOwnAdminRoleConfirmButton": "Odebrat mé administrátorské oprávnění", + "removeOwnAdminRoleConfirmPhrase": "ODEBRAT MÉ ADMINISTRÁTORSKÉ OPRÁVNĚNÍ", + "ownerMustRetainAdminRole": "Vlastník organizace musí zachovat alespoň jednu roli správce.", "usernameRequired": "Uživatelské jméno je povinné", "idpSelectPlease": "Vyberte poskytovatele identity", "idpGenericOidc": "Generic OAuth2/OIDC provider.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Přidáním více než jednoho cíle se umožní vyvážení zatížení.", "targetsSubmit": "Uložit cíle", "addTarget": "Add Target", + "proxyMultiSiteRoundRobinNodeHelp": "Round robin routing nebude fungovat mezi lokalitami, které nejsou připojeny ke stejnému uzlu, ale failover bude fungovat.", "targetErrorInvalidIp": "Neplatná IP adresa", "targetErrorInvalidIpDescription": "Zadejte prosím platnou IP adresu nebo název hostitele", "targetErrorInvalidPort": "Neplatný port", @@ -2652,6 +2668,8 @@ "validPassword": "Platné heslo", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "Zobrazit", + "configManaged": "Správa konfigurace", "connectedClient": "Připojený klient", "resourceBlocked": "Zablokované zdroje", "droppedByRule": "Zrušeno pravidlem", From ee8290d68c6a3f6a647e55fd789f2f139c2ca2bd Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:13 -0700 Subject: [PATCH 128/139] New translations en-us.json (German) [ci skip] --- messages/de-DE.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/de-DE.json b/messages/de-DE.json index a7e1a5fa7..504005334 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Fehler beim Löschen des Links", "shareDeleted": "Link gelöscht", "shareDeletedDescription": "Der Link wurde gelöscht", + "shareDelete": "Freigabelink löschen", + "shareDeleteConfirm": "Löschen des Freigabelinks bestätigen", + "shareQuestionRemove": "Sind Sie sicher, dass Sie diesen Freigabelink löschen möchten?", + "shareMessageRemove": "Nach dem Löschen funktioniert der Link nicht mehr, und jeder, der ihn nutzt, verliert den Zugriff auf die Ressource.", "shareTokenDescription": "Das Zugriffstoken kann auf zwei Arten übergeben werden: als Abfrageparameter oder in den Request-Headern. Diese müssen vom Client auf jeder Anfrage für authentifizierten Zugriff weitergegeben werden.", "accessToken": "Zugangs-Token", "usageExamples": "Nutzungsbeispiele", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Nach dem Entfernen hat dieser Benutzer keinen Zugriff mehr auf die Organisation. Sie können ihn später jederzeit wieder einladen, aber er muss die Einladung erneut annehmen.", "userRemoveOrgConfirm": "Entfernen des Benutzers bestätigen", "userRemoveOrg": "Benutzer aus der Organisation entfernen", + "userQuestionOrgRemoveSelf": "Sind Sie sicher, dass Sie sich aus dieser Organisation entfernen möchten?", + "userMessageOrgRemoveSelf": "Sie verlieren sofort den Zugriff. Ein Administrator kann Sie später erneut einladen, aber Sie müssen eine neue Einladung annehmen.", + "userRemoveOrgConfirmSelf": "Entfernung bestätigen", + "userRemoveOrgSelf": "Sich selbst aus der Organisation entfernen", + "userRemoveOrgSelfWarning": "Sie verlieren sofort den Zugriff auf diese Organisation.", + "userRemoveOrgConfirmPhraseSelf": "ENTFERNUNG MICH SELBST AUS DER ORGANISATION", "users": "Benutzer", "accessRoleMember": "Mitglied", "accessRoleOwner": "Eigentümer", @@ -531,6 +541,11 @@ "emailInvalid": "Ungültige E-Mail-Adresse", "inviteValidityDuration": "Bitte wählen Sie eine Dauer", "accessRoleSelectPlease": "Bitte wählen Sie eine Rolle", + "removeOwnAdminRoleConfirmTitle": "Möchten Sie Ihren Administratorzugriff entfernen?", + "removeOwnAdminRoleConfirmDescription": "Nach dem Speichern haben Sie keine Administratorrechte mehr in dieser Organisation. Ein anderer Administrator kann den Zugriff bei Bedarf wiederherstellen.", + "removeOwnAdminRoleConfirmButton": "Meinen Administratorzugriff entfernen", + "removeOwnAdminRoleConfirmPhrase": "NIMM MEINEN ADMIN-ZUGRIFF WEG", + "ownerMustRetainAdminRole": "Der Organisationsinhaber muss mindestens eine Administratorrolle behalten.", "usernameRequired": "Benutzername ist erforderlich", "idpSelectPlease": "Bitte wählen Sie einen Identitätsanbieter", "idpGenericOidc": "Generischer OAuth2/OIDC-Anbieter.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Das Hinzufügen von mehr als einem Ziel aktiviert den Lastausgleich.", "targetsSubmit": "Ziele speichern", "addTarget": "Ziel hinzufügen", + "proxyMultiSiteRoundRobinNodeHelp": "Round-Robin-Routing funktioniert nicht zwischen Standorten, die nicht mit demselben Knoten verbunden sind, aber Failover funktioniert.", "targetErrorInvalidIp": "Ungültige IP-Adresse", "targetErrorInvalidIpDescription": "Bitte geben Sie eine gültige IP-Adresse oder einen Hostnamen ein", "targetErrorInvalidPort": "Ungültiger Port", @@ -2652,6 +2668,8 @@ "validPassword": "Gültiges Passwort", "validEmail": "Gültige E-Mail-Adresse", "validSSO": "Gültige SSO-Anmeldung", + "view": "Ansehen", + "configManaged": "Konfiguration verwaltet", "connectedClient": "Verbundenes Gerät", "resourceBlocked": "Ressource blockiert", "droppedByRule": "Abgelegt durch Regel", From b27b62d4c844fe2229be68e6a2efe15470e5d094 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:15 -0700 Subject: [PATCH 129/139] New translations en-us.json (Italian) [ci skip] --- messages/it-IT.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/it-IT.json b/messages/it-IT.json index 5c432eadd..bdaee9833 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Si è verificato un errore durante l'eliminazione del link", "shareDeleted": "Link eliminato", "shareDeletedDescription": "Il link è stato eliminato", + "shareDelete": "Elimina Link di Condivisione", + "shareDeleteConfirm": "Conferma Eliminazione Link di Condivisione", + "shareQuestionRemove": "Sei sicuro di voler eliminare questo link di condivisione?", + "shareMessageRemove": "Una volta eliminato, il link non funzionerà più e chiunque lo utilizzi perderà l'accesso alla risorsa.", "shareTokenDescription": "Il token di accesso può essere passato in due modi: come parametro di interrogazione o nelle intestazioni della richiesta. Questi devono essere passati dal client su ogni richiesta di accesso autenticato.", "accessToken": "Token Di Accesso", "usageExamples": "Esempi Di Utilizzo", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Una volta rimosso questo utente non avrà più accesso all'organizzazione. Puoi sempre reinvitarlo in seguito, ma dovrà accettare nuovamente l'invito.", "userRemoveOrgConfirm": "Conferma Rimozione Utente", "userRemoveOrg": "Rimuovi Utente dall'Organizzazione", + "userQuestionOrgRemoveSelf": "Sei sicuro di voler rimuovere te stesso da questa organizzazione?", + "userMessageOrgRemoveSelf": "Perderai immediatamente l'accesso. Un amministratore può invitarti nuovamente in seguito, ma dovrai accettare un nuovo invito.", + "userRemoveOrgConfirmSelf": "Conferma Rimozione Me Stesso", + "userRemoveOrgSelf": "Rimuoviti dall'organizzazione", + "userRemoveOrgSelfWarning": "Perderai immediatamente l'accesso a questa organizzazione.", + "userRemoveOrgConfirmPhraseSelf": "RIMUOVITI DALL'ORGANIZZAZIONE", "users": "Utenti", "accessRoleMember": "Membro", "accessRoleOwner": "Proprietario", @@ -531,6 +541,11 @@ "emailInvalid": "Indirizzo email non valido", "inviteValidityDuration": "Seleziona una durata", "accessRoleSelectPlease": "Seleziona un ruolo", + "removeOwnAdminRoleConfirmTitle": "Rimuovere il tuo accesso amministrativo?", + "removeOwnAdminRoleConfirmDescription": "Non avrai più i permessi di amministratore in questa organizzazione dopo il salvataggio. Un altro amministratore può ripristinare l'accesso se necessario.", + "removeOwnAdminRoleConfirmButton": "Rimuovere il Mio Accesso Amministrativo", + "removeOwnAdminRoleConfirmPhrase": "RIMUOVERE IL MIO ACCESSO AMMINISTRATIVO", + "ownerMustRetainAdminRole": "Il proprietario dell'organizzazione deve mantenere almeno un ruolo di amministratore.", "usernameRequired": "Username richiesto", "idpSelectPlease": "Seleziona un provider di identità", "idpGenericOidc": "Provider OAuth2/OIDC generico.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "L'aggiunta di più di un target abiliterà il bilanciamento del carico.", "targetsSubmit": "Salva Target", "addTarget": "Aggiungi Target", + "proxyMultiSiteRoundRobinNodeHelp": "Il routing round robin non funzionerà tra siti che non sono connessi allo stesso nodo, ma il failover funzionerà.", "targetErrorInvalidIp": "Indirizzo IP non valido", "targetErrorInvalidIpDescription": "Inserisci un indirizzo IP o un hostname valido", "targetErrorInvalidPort": "Porta non valida", @@ -2652,6 +2668,8 @@ "validPassword": "Password Valida", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "Visualizza", + "configManaged": "Gestione Configurazione", "connectedClient": "Cliente Connesso", "resourceBlocked": "Risorsa Bloccata", "droppedByRule": "Eliminato dalla regola", From c69059b22763c5772d43b61cc8c679fab1e7997c Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:17 -0700 Subject: [PATCH 130/139] New translations en-us.json (Korean) [ci skip] --- messages/ko-KR.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index ef66f1470..1cec47c72 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "링크 삭제 중 오류가 발생했습니다.", "shareDeleted": "링크가 삭제되었습니다.", "shareDeletedDescription": "링크가 삭제되었습니다.", + "shareDelete": "공유 링크 삭제", + "shareDeleteConfirm": "공유 링크 삭제 확인", + "shareQuestionRemove": "이 공유 링크를 삭제하시겠습니까?", + "shareMessageRemove": "삭제되면 링크가 더 이상 작동하지 않으며, 이를 사용하는 모든 사용자는 자원에 대한 접근을 잃게 됩니다.", "shareTokenDescription": "액세스 토큰은 쿼리 매개변수 또는 요청 헤더의 두 가지 방법으로 전달될 수 있습니다. 이는 인증된 액세스를 위해 클라이언트에서 모든 요청마다 전달되어야 합니다.", "accessToken": "액세스 토큰", "usageExamples": "사용 예", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "이 사용자가 제거되면 더 이상 조직에 접근할 수 없습니다. 나중에 다시 초대할 수 있지만, 초대를 다시 수락해야 합니다.", "userRemoveOrgConfirm": "사용자 제거 확인", "userRemoveOrg": "조직에서 사용자 제거", + "userQuestionOrgRemoveSelf": "이 조직에서 자신을 제거하시겠습니까?", + "userMessageOrgRemoveSelf": "귀하는 즉시 접근 권한을 잃게 됩니다. 관리자가 나중에 다시 초대할 수 있지만, 새 초대를 수락해야 합니다.", + "userRemoveOrgConfirmSelf": "내 제거 확인", + "userRemoveOrgSelf": "조직에서 자신을 제거하십시오", + "userRemoveOrgSelfWarning": "귀하는 이 조직에 대한 접근 권한을 즉시 상실합니다.", + "userRemoveOrgConfirmPhraseSelf": "조직에서 나를 제거", "users": "사용자", "accessRoleMember": "회원", "accessRoleOwner": "소유자", @@ -531,6 +541,11 @@ "emailInvalid": "유효하지 않은 이메일 주소입니다.", "inviteValidityDuration": "지속 시간을 선택하십시오.", "accessRoleSelectPlease": "역할을 선택하세요", + "removeOwnAdminRoleConfirmTitle": "관리자 권한을 제거하시겠습니까?", + "removeOwnAdminRoleConfirmDescription": "저장 후 이 조직에 대한 관리자 권한이 없어집니다. 필요한 경우 다른 관리자가 접근 권한을 복구할 수 있습니다.", + "removeOwnAdminRoleConfirmButton": "내 관리자 권한 제거", + "removeOwnAdminRoleConfirmPhrase": "내 관리자 권한 제거", + "ownerMustRetainAdminRole": "조직 소유자는 최소한 하나의 관리자 역할을 유지해야 합니다.", "usernameRequired": "사용자 이름은 필수입니다.", "idpSelectPlease": "신원 제공자를 선택하십시오", "idpGenericOidc": "일반 OAuth2/OIDC 공급자.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "위에 하나 이상의 대상을 추가하면 로드 밸런싱이 활성화됩니다.", "targetsSubmit": "대상 저장", "addTarget": "대상 추가", + "proxyMultiSiteRoundRobinNodeHelp": "라운드 로빈 라우팅은 동일한 노드에 연결되지 않은 사이트 간에는 작동하지 않으나, 대체 라우팅은 작동합니다.", "targetErrorInvalidIp": "유효하지 않은 IP 주소", "targetErrorInvalidIpDescription": "유효한 IP 주소 또는 호스트 이름을 입력하세요.", "targetErrorInvalidPort": "유효하지 않은 포트", @@ -2652,6 +2668,8 @@ "validPassword": "유효한 비밀번호", "validEmail": "유효한 이메일", "validSSO": "유효한 SSO", + "view": "보기", + "configManaged": "구성 관리됨", "connectedClient": "연결된 클라이언트", "resourceBlocked": "리소스 차단됨", "droppedByRule": "룰에 의해 드롭됨", From b794d2aa40468c02f8eff82105079de2d3e806fd Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:18 -0700 Subject: [PATCH 131/139] New translations en-us.json (Dutch) [ci skip] --- messages/nl-NL.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index de4ccdd33..0f1daefbc 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Fout opgetreden tijdens het verwijderen link", "shareDeleted": "Link verwijderd", "shareDeletedDescription": "De link is verwijderd", + "shareDelete": "Verwijder Deel Link", + "shareDeleteConfirm": "Bevestig verwijdering van Deel Link", + "shareQuestionRemove": "Weet u zeker dat u deze deel link wilt verwijderen?", + "shareMessageRemove": "Zodra verwijderd, zal de link niet meer werken en zal iedereen die het gebruikt de toegang tot de bron verliezen.", "shareTokenDescription": "De toegangstoken kan op twee manieren worden doorgegeven: als queryparameter of in de aanvraagheaders. Deze moeten worden doorgegeven van de client op elk verzoek voor geverifieerde toegang.", "accessToken": "Toegangs-token", "usageExamples": "Voorbeelden van gebruik", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Eenmaal verwijderd, heeft deze gebruiker geen toegang meer tot de organisatie. Je kunt ze later altijd opnieuw uitnodigen, maar ze zullen de uitnodiging opnieuw moeten accepteren.", "userRemoveOrgConfirm": "Bevestig verwijderen gebruiker", "userRemoveOrg": "Gebruiker uit organisatie verwijderen", + "userQuestionOrgRemoveSelf": "Weet u zeker dat u zichzelf uit deze organisatie wilt verwijderen?", + "userMessageOrgRemoveSelf": "U verliest onmiddellijk toegang. Een beheerder kan u later opnieuw uitnodigen, maar u moet een nieuwe uitnodiging accepteren.", + "userRemoveOrgConfirmSelf": "Bevestig Verwijder Mijn Persoon", + "userRemoveOrgSelf": "Verwijder uzelf uit de organisatie", + "userRemoveOrgSelfWarning": "U verliest onmiddellijk toegang tot deze organisatie.", + "userRemoveOrgConfirmPhraseSelf": "VERWIJDER MIJ UIT ORGANISATIE", "users": "Gebruikers", "accessRoleMember": "Lid", "accessRoleOwner": "Eigenaar", @@ -531,6 +541,11 @@ "emailInvalid": "Ongeldig e-mailadres", "inviteValidityDuration": "Selecteer een tijdsduur", "accessRoleSelectPlease": "Selecteer een rol", + "removeOwnAdminRoleConfirmTitle": "Uw beheerderstoegang verwijderen?", + "removeOwnAdminRoleConfirmDescription": "U zult na het opslaan geen beheerdersrechten meer hebben in deze organisatie. Een andere beheerder kan de toegang indien nodig herstellen.", + "removeOwnAdminRoleConfirmButton": "Verwijder Mijn Beheerderstoegang", + "removeOwnAdminRoleConfirmPhrase": "VERWIJDER MIJN BEHEERDERSTOEGANG", + "ownerMustRetainAdminRole": "De organisatie-eigenaar moet minstens één beheerdersrol behouden.", "usernameRequired": "Gebruikersnaam is verplicht", "idpSelectPlease": "Selecteer een identiteitsprovider", "idpGenericOidc": "Algemene OAuth2/OIDC provider.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Het toevoegen van meer dan één doel hierboven zal de load balancering mogelijk maken.", "targetsSubmit": "Doelstellingen opslaan", "addTarget": "Doelwit toevoegen", + "proxyMultiSiteRoundRobinNodeHelp": "Round-robin routering werkt niet tussen locaties die niet met hetzelfde knooppunt zijn verbonden, maar failover werkt wel.", "targetErrorInvalidIp": "Ongeldig IP-adres", "targetErrorInvalidIpDescription": "Voer een geldig IP-adres of hostnaam in", "targetErrorInvalidPort": "Ongeldige poort", @@ -2652,6 +2668,8 @@ "validPassword": "Geldig wachtwoord", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "Bekijk", + "configManaged": "Configuratie Beheerd", "connectedClient": "Verbonden Client", "resourceBlocked": "Bron geblokkeerd", "droppedByRule": "Achtergelaten door regel", From 5189583d73f3457a9f29ca6c60288cd91a78db38 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:20 -0700 Subject: [PATCH 132/139] New translations en-us.json (Polish) [ci skip] --- messages/pl-PL.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 8189a927d..d92787719 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Wystąpił błąd podczas usuwania linku", "shareDeleted": "Link usunięty", "shareDeletedDescription": "Link został usunięty", + "shareDelete": "Usuń link udostępniania", + "shareDeleteConfirm": "Potwierdź usunięcie linku udostępniania", + "shareQuestionRemove": "Czy na pewno chcesz usunąć ten link udostępniania?", + "shareMessageRemove": "Po usunięciu, link przestanie działać i wszyscy korzystający z niego stracą dostęp do zasobu.", "shareTokenDescription": "Token dostępu może być przekazywany na dwa sposoby: jako parametr zapytania lub w nagłówkach żądania. Muszą być przekazywane z klienta na każde żądanie uwierzytelnionego dostępu.", "accessToken": "Token dostępu", "usageExamples": "Przykłady użycia", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Po usunięciu ten użytkownik nie będzie miał już dostępu do organizacji. Zawsze możesz ponownie go zaprosić później, ale będzie musiał ponownie zaakceptować zaproszenie.", "userRemoveOrgConfirm": "Potwierdź usunięcie użytkownika", "userRemoveOrg": "Usuń użytkownika z organizacji", + "userQuestionOrgRemoveSelf": "Czy na pewno chcesz usunąć się z tej organizacji?", + "userMessageOrgRemoveSelf": "Stracisz dostęp natychmiastowo. Administrator może cię ponownie zaprosić, ale będziesz musiał przyjąć nowe zaproszenie.", + "userRemoveOrgConfirmSelf": "Potwierdź usunięcie siebie", + "userRemoveOrgSelf": "Usuń siebie z organizacji", + "userRemoveOrgSelfWarning": "Natychmiast stracisz dostęp do tej organizacji.", + "userRemoveOrgConfirmPhraseSelf": "USUŃ SIEBIE Z ORGANIZACJI", "users": "Użytkownicy", "accessRoleMember": "Członek", "accessRoleOwner": "Właściciel", @@ -531,6 +541,11 @@ "emailInvalid": "Nieprawidłowy adres e-mail", "inviteValidityDuration": "Proszę wybrać okres ważności", "accessRoleSelectPlease": "Proszę wybrać rolę", + "removeOwnAdminRoleConfirmTitle": "Usunąć dostęp administratora?", + "removeOwnAdminRoleConfirmDescription": "Po zapisaniu nie będziesz już posiadał uprawnień administratora w tej organizacji. Inny administrator może przywrócić dostęp, jeśli to konieczne.", + "removeOwnAdminRoleConfirmButton": "Usuń mój dostęp administratora", + "removeOwnAdminRoleConfirmPhrase": "USUŃ MÓJ DOSTĘP ADMINISTRATORA", + "ownerMustRetainAdminRole": "Właściciel organizacji musi zachować co najmniej jedną rolę administratora.", "usernameRequired": "Nazwa użytkownika jest wymagana", "idpSelectPlease": "Proszę wybrać dostawcę tożsamości", "idpGenericOidc": "Ogólny dostawca OAuth2/OIDC.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Dodanie więcej niż jednego celu powyżej włączy równoważenie obciążenia.", "targetsSubmit": "Zapisz cele", "addTarget": "Dodaj cel", + "proxyMultiSiteRoundRobinNodeHelp": "Trasowanie round-robin nie będzie działać między witrynami, które nie są połączone z tym samym węzłem, ale przełączanie awaryjne będzie działać.", "targetErrorInvalidIp": "Nieprawidłowy adres IP", "targetErrorInvalidIpDescription": "Wprowadź prawidłowy adres IP lub nazwę hosta", "targetErrorInvalidPort": "Nieprawidłowy port", @@ -2652,6 +2668,8 @@ "validPassword": "Prawidłowe hasło", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "Zobacz", + "configManaged": "Konfiguracja zarządzana", "connectedClient": "Połączony Klient", "resourceBlocked": "Zasób zablokowany", "droppedByRule": "Upuszczone przez regułę", From 1df5d9fac86d68af024e15002d9718ec8e2d2519 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:21 -0700 Subject: [PATCH 133/139] New translations en-us.json (Portuguese) [ci skip] --- messages/pt-PT.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index a3baabef8..0b9f61765 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Ocorreu um erro ao apagar o link", "shareDeleted": "Link excluído", "shareDeletedDescription": "O link foi eliminado", + "shareDelete": "Excluir Link de Compartilhamento", + "shareDeleteConfirm": "Confirmar Exclusão de Link de Compartilhamento", + "shareQuestionRemove": "Tem certeza de que deseja excluir este link de compartilhamento?", + "shareMessageRemove": "Uma vez excluído, o link não funcionará mais e qualquer pessoa que o utilizar perderá o acesso ao recurso.", "shareTokenDescription": "O token de acesso pode ser passado de duas maneiras: como um parâmetro de consulta ou nos cabeçalhos da solicitação. Estes devem ser passados do cliente em todas as solicitações para acesso autenticado.", "accessToken": "Token de acesso", "usageExamples": "Exemplos de uso", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Uma vez removido, este utilizador não terá mais acesso à organização. Você sempre pode reconvidá-lo depois, mas eles precisarão aceitar o convite novamente.", "userRemoveOrgConfirm": "Confirmar Remoção do Usuário", "userRemoveOrg": "Remover Usuário da Organização", + "userQuestionOrgRemoveSelf": "Tem certeza de que deseja se remover desta organização?", + "userMessageOrgRemoveSelf": "Você perderá o acesso imediatamente. Um administrador poderá convidá-lo novamente mais tarde, mas você precisará aceitar um novo convite.", + "userRemoveOrgConfirmSelf": "Confirmar a Remoção de Mim Mesmo", + "userRemoveOrgSelf": "Remover-se da organização", + "userRemoveOrgSelfWarning": "Você perderá o acesso a esta organização imediatamente.", + "userRemoveOrgConfirmPhraseSelf": "REMOVER-ME DA ORG", "users": "Utilizadores", "accessRoleMember": "Membro", "accessRoleOwner": "Proprietário", @@ -531,6 +541,11 @@ "emailInvalid": "Endereço de email inválido", "inviteValidityDuration": "Por favor, selecione uma duração", "accessRoleSelectPlease": "Por favor, selecione uma função", + "removeOwnAdminRoleConfirmTitle": "Remover seu acesso de administrador?", + "removeOwnAdminRoleConfirmDescription": "Você não terá mais permissões de administrador nesta organização após salvar. Outro administrador pode restaurar seu acesso, se necessário.", + "removeOwnAdminRoleConfirmButton": "Remover Meu Acesso de Administrador", + "removeOwnAdminRoleConfirmPhrase": "REMOVER MEU ACESSO DE ADMIN", + "ownerMustRetainAdminRole": "O proprietário da organização deve manter pelo menos um papel de administrador.", "usernameRequired": "Nome de utilizador é obrigatório", "idpSelectPlease": "Por favor, selecione um provedor de identidade", "idpGenericOidc": "Provedor genérico OAuth2/OIDC.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Adicionar mais de um alvo acima habilitará o balanceamento de carga.", "targetsSubmit": "Guardar Alvos", "addTarget": "Adicionar Alvo", + "proxyMultiSiteRoundRobinNodeHelp": "O roteamento round robin não funcionará entre sites que não estão conectados ao mesmo nó, mas o failover funcionará.", "targetErrorInvalidIp": "Endereço IP inválido", "targetErrorInvalidIpDescription": "Por favor, insira um endereço IP ou nome de host válido", "targetErrorInvalidPort": "Porta inválida", @@ -2652,6 +2668,8 @@ "validPassword": "Senha válida", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "Visualizar", + "configManaged": "Configuração Gerenciada", "connectedClient": "Cliente Conectado", "resourceBlocked": "Recurso bloqueado", "droppedByRule": "Derrubado pela regra", From 0f82ba6627c50b64d4238ede2df4e88c2da182c8 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:23 -0700 Subject: [PATCH 134/139] New translations en-us.json (Russian) [ci skip] --- messages/ru-RU.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 766268cb9..361958b3f 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Произошла ошибка при удалении ссылки", "shareDeleted": "Ссылка удалена", "shareDeletedDescription": "Ссылка была успешно удалена", + "shareDelete": "Удалить общую ссылку", + "shareDeleteConfirm": "Подтвердите удаление общей ссылки", + "shareQuestionRemove": "Вы уверены, что хотите удалить эту общую ссылку?", + "shareMessageRemove": "После удаления ссылка перестанет работать, и все, кто ее использует, потеряют доступ к ресурсу.", "shareTokenDescription": "Токен доступа может быть передан двумя способами: как параметр запроса или в заголовках запроса. Они должны быть переданы от клиента по каждому запросу для аутентифицированного доступа.", "accessToken": "Токен доступа", "usageExamples": "Примеры использования", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "После удаления этот пользователь больше не будет иметь доступ к организации. Вы всегда можете пригласить его заново, но ему нужно будет снова принять приглашение.", "userRemoveOrgConfirm": "Подтвердить удаление пользователя", "userRemoveOrg": "Удалить пользователя из организации", + "userQuestionOrgRemoveSelf": "Вы уверены, что хотите удалить себя из этой организации?", + "userMessageOrgRemoveSelf": "Вы немедленно потеряете доступ. Администратор сможет снова пригласить вас позже, но вам нужно будет принять новое приглашение.", + "userRemoveOrgConfirmSelf": "Подтвердите удаление себя", + "userRemoveOrgSelf": "Удалите себя из организации", + "userRemoveOrgSelfWarning": "Вы немедленно потеряете доступ к этой организации.", + "userRemoveOrgConfirmPhraseSelf": "Удалить себя из организации", "users": "Пользователи", "accessRoleMember": "Участник", "accessRoleOwner": "Владелец", @@ -531,6 +541,11 @@ "emailInvalid": "Неверный адрес Email", "inviteValidityDuration": "Пожалуйста, выберите продолжительность", "accessRoleSelectPlease": "Пожалуйста, выберите роль", + "removeOwnAdminRoleConfirmTitle": "Удалить доступ администратора?", + "removeOwnAdminRoleConfirmDescription": "После сохранения у вас больше не будет прав администратора в этой организации. Другой администратор может восстановить доступ, если это необходимо.", + "removeOwnAdminRoleConfirmButton": "Удалить мой доступ администратора", + "removeOwnAdminRoleConfirmPhrase": "УДАЛИТЬ МОЙ ДОСТУП АДМИНИСТРАТОРА", + "ownerMustRetainAdminRole": "Владелец организации должен сохранить по крайней мере одну роль администратора.", "usernameRequired": "Имя пользователя обязательно", "idpSelectPlease": "Пожалуйста, выберите Identity Provider", "idpGenericOidc": "Обычный OAuth2/OIDC provider.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Добавление более одной цели выше включит балансировку нагрузки.", "targetsSubmit": "Сохранить цели", "addTarget": "Добавить цель", + "proxyMultiSiteRoundRobinNodeHelp": "Роутинг с балансировкой нагрузки не будет работать между сайтами, не подключенными к одному и тому же узлу, но подмена будет работать.", "targetErrorInvalidIp": "Неверный IP-адрес", "targetErrorInvalidIpDescription": "Пожалуйста, введите действительный IP адрес или имя хоста", "targetErrorInvalidPort": "Неверный порт", @@ -2652,6 +2668,8 @@ "validPassword": "Допустимый пароль", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "Просмотр", + "configManaged": "Конфигурация управляется", "connectedClient": "Подключенный клиент", "resourceBlocked": "Ресурс заблокирован", "droppedByRule": "Отброшено по правилам", From a4aa29e48ad8981515a6ba422272b2b5772f1a34 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:25 -0700 Subject: [PATCH 135/139] New translations en-us.json (Turkish) [ci skip] --- messages/tr-TR.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 145a59eee..58cba1c0a 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Bağlantı silinirken bir hata oluştu", "shareDeleted": "Bağlantı silindi", "shareDeletedDescription": "Bağlantı silindi", + "shareDelete": "Paylaşım Bağlantısını Sil", + "shareDeleteConfirm": "Paylaşım Bağlantısının Silinmesini Onayla", + "shareQuestionRemove": "Bu paylaşım bağlantısını silmek istediğinizden emin misiniz?", + "shareMessageRemove": "Silindikten sonra, bağlantı artık çalışmayacak ve kullanan herkes kaynağa erişimini kaybedecek.", "shareTokenDescription": "Erişim jetonunuz iki şekilde iletilebilir: sorgu parametresi olarak veya istek başlıklarında. Kimlik doğrulanmış erişim için her istekten müşteri tarafından iletilmelidir.", "accessToken": "Erişim Jetonu", "usageExamples": "Kullanım Örnekleri", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Kaldırıldığında, bu kullanıcı organizasyona artık erişim sağlayamayacak. Kullanıcı tekrar davet edilebilir, ancak daveti kabul etmesi gerekecek.", "userRemoveOrgConfirm": "Kullanıcıyı Kaldırmayı Onayla", "userRemoveOrg": "Kullanıcıyı Organizasyondan Kaldır", + "userQuestionOrgRemoveSelf": "Bu organizasyondan kendinizi kaldırmak istediğinizden emin misiniz?", + "userMessageOrgRemoveSelf": "Erişiminizi hemen kaybedeceksiniz. Bir yönetici daha sonra sizi tekrar davet edebilir, ancak yeni bir daveti kabul etmeniz gerekecek.", + "userRemoveOrgConfirmSelf": "Kendimi Kaldırmayı Onayla", + "userRemoveOrgSelf": "Kendinizi organizasyondan kaldırın", + "userRemoveOrgSelfWarning": "Bu organizasyona erişiminizi anında kaybedeceksiniz.", + "userRemoveOrgConfirmPhraseSelf": "KENDİMİ ORGANİZASYONDAN KALDIR", "users": "Kullanıcılar", "accessRoleMember": "Üye", "accessRoleOwner": "Sahip", @@ -531,6 +541,11 @@ "emailInvalid": "Geçersiz e-posta adresi", "inviteValidityDuration": "Lütfen bir süre seçin", "accessRoleSelectPlease": "Lütfen bir rol seçin", + "removeOwnAdminRoleConfirmTitle": "Yönetici erişiminizi kaldırmak istiyor musunuz?", + "removeOwnAdminRoleConfirmDescription": "Kaydettikten sonra, bu organizasyonda artık yönetici izinleriniz olmayacak. Gerekirse başka bir yönetici erişimi geri yükleyebilir.", + "removeOwnAdminRoleConfirmButton": "Yönetici Erişimi Kaldır", + "removeOwnAdminRoleConfirmPhrase": "YÖNETİCİ ERİŞİMİMİ KALDIR", + "ownerMustRetainAdminRole": "Organizasyon sahibi en az bir yönetici rolü bulundurmalıdır.", "usernameRequired": "Kullanıcı adı gereklidir", "idpSelectPlease": "Lütfen bir kimlik sağlayıcı seçin", "idpGenericOidc": "Genel OAuth2/OIDC sağlayıcısı.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Yukarıdaki birden fazla hedef ekleyerek yük dengeleme etkinleştirilecektir.", "targetsSubmit": "Hedefleri Kaydet", "addTarget": "Hedef Ekle", + "proxyMultiSiteRoundRobinNodeHelp": "Round robin yönlendirme, aynı düğüme bağlı olmayan siteler arasında çalışmayacaktır, ancak failover çalışacaktır.", "targetErrorInvalidIp": "Geçersiz IP adresi", "targetErrorInvalidIpDescription": "Lütfen geçerli bir IP adresi veya host adı girin", "targetErrorInvalidPort": "Geçersiz port", @@ -2652,6 +2668,8 @@ "validPassword": "Geçerli Şifre", "validEmail": "Geçerli E-posta", "validSSO": "Geçerli SSO", + "view": "Görüntüle", + "configManaged": "Yapılandırma Yönetildi", "connectedClient": "Bağlı İstemci", "resourceBlocked": "Kaynak Engellendi", "droppedByRule": "Kurallara Göre Çıkartıldı", From 3c82a228fbfb67969e9d14a009aaaeae75dd45c8 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:26 -0700 Subject: [PATCH 136/139] New translations en-us.json (Chinese Simplified) [ci skip] --- messages/zh-CN.json | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index f0162f95d..9ff214c23 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -32,7 +32,7 @@ "trialActive": "免费试用中", "trialExpired": "试用到期", "trialHasEnded": "您的试用已结束。", - "trialDaysRemaining": "{count, plural, one {# day remaining} other {# days remaining}}", + "trialDaysRemaining": "{count, plural, other {# 天剩余}}", "trialDaysLeftShort": "试用期剩余 {days} 天", "trialGoToBilling": "转到账单页面", "subscriptionViolationViewBilling": "查看计费", @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "删除链接时出错", "shareDeleted": "链接已删除", "shareDeletedDescription": "链接已删除", + "shareDelete": "删除共享链接", + "shareDeleteConfirm": "确认删除共享链接", + "shareQuestionRemove": "您确定要删除这个共享链接吗?", + "shareMessageRemove": "删除后,该链接将不再可用,使用它的任何人将失去对资源的访问权限。", "shareTokenDescription": "访问令牌可以通过两种方式传递:作为查询参数或请求标题。 每次验证访问请求都必须从客户端传递。", "accessToken": "访问令牌", "usageExamples": "用法示例", @@ -303,7 +307,7 @@ "accessUserManage": "管理用户", "accessUsersDescription": "邀请和管理访问此组织的用户", "accessUsersSearch": "搜索用户...", - "accessUsersRoleFilterCount": "{count, plural, one {# role} other {# roles}}", + "accessUsersRoleFilterCount": "{count, plural, other {# 角色}}", "accessUsersRoleFilterClear": "清除角色过滤器", "accessUserCreate": "创建用户", "accessUserRemove": "删除用户", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "一旦删除,这个用户将不再能够访问组织。 你总是可以稍后重新邀请他们,但他们需要再次接受邀请。", "userRemoveOrgConfirm": "确认删除用户", "userRemoveOrg": "从组织中删除用户", + "userQuestionOrgRemoveSelf": "你确定要将自己从这个组织中移除吗?", + "userMessageOrgRemoveSelf": "你将立即失去访问权限。管理员稍后可以再次邀请你,但你需要接受新的邀请。", + "userRemoveOrgConfirmSelf": "确认删除我自己", + "userRemoveOrgSelf": "将自己从组织中移除", + "userRemoveOrgSelfWarning": "你将立即失去对此组织的访问权限。", + "userRemoveOrgConfirmPhraseSelf": "从组织中移除我自己", "users": "用户", "accessRoleMember": "成员", "accessRoleOwner": "所有者", @@ -531,6 +541,11 @@ "emailInvalid": "无效的电子邮件地址", "inviteValidityDuration": "请选择持续时间", "accessRoleSelectPlease": "请选择一个角色", + "removeOwnAdminRoleConfirmTitle": "移除你的管理员权限?", + "removeOwnAdminRoleConfirmDescription": "保存后,你将不再拥有该组织的管理员权限。如果需要,其他管理员可以恢复访问。", + "removeOwnAdminRoleConfirmButton": "移除我的管理员访问权限", + "removeOwnAdminRoleConfirmPhrase": "移除我的管理员访问", + "ownerMustRetainAdminRole": "组织所有者必须保留至少一个管理员角色。", "usernameRequired": "必须输入用户名", "idpSelectPlease": "请选择身份提供商", "idpGenericOidc": "通用的 OAuth2/OIDC 提供商。", @@ -658,6 +673,7 @@ "targetNoOneDescription": "在上面添加多个目标将启用负载平衡。", "targetsSubmit": "保存目标", "addTarget": "添加目标", + "proxyMultiSiteRoundRobinNodeHelp": "轮询路由在未连接到相同节点的站点之间将不起作用,但故障转移会生效。", "targetErrorInvalidIp": "无效的 IP 地址", "targetErrorInvalidIpDescription": "请输入有效的IP地址或主机名", "targetErrorInvalidPort": "无效的端口", @@ -1499,7 +1515,7 @@ "alertingGraphCanvasTitle": "规则流程", "alertingGraphCanvasDescription": "源、触发器和操作的视觉概况。选择一个节点,在面板上进行编辑。", "alertingNodeNotConfigured": "尚未配置", - "alertingNodeActionsCount": "{count, plural, one {# action} other {# actions}}", + "alertingNodeActionsCount": "{count, plural, other {# 操作}}", "alertingNodeRoleSource": "来源", "alertingNodeRoleTrigger": "触发", "alertingNodeRoleAction": "行为", @@ -2051,7 +2067,7 @@ "createInternalResourceDialogName": "名称", "createInternalResourceDialogSite": "站点", "selectSite": "选择站点...", - "multiSitesSelectorSitesCount": "{count, plural, one {# site} other {# sites}}", + "multiSitesSelectorSitesCount": "{count, plural, other {# 个网站}}", "noSitesFound": "未找到站点。", "createInternalResourceDialogProtocol": "协议", "createInternalResourceDialogTcp": "TCP", @@ -2652,6 +2668,8 @@ "validPassword": "有效密码", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "查看", + "configManaged": "配置已管理", "connectedClient": "已连接客户端", "resourceBlocked": "资源被阻止", "droppedByRule": "被规则删除", From 4208a9f3729024c4c7eb2adf38cada795454cfcc Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:28 -0700 Subject: [PATCH 137/139] New translations en-us.json (Norwegian Bokmal) [ci skip] --- messages/nb-NO.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 15e7e246a..af3682d36 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "En feil oppstod ved sletting av lenke", "shareDeleted": "Lenke slettet", "shareDeletedDescription": "Lenken har blitt slettet", + "shareDelete": "Slett delingslenke", + "shareDeleteConfirm": "Bekreft sletting av delingslenke", + "shareQuestionRemove": "Er du sikker på at du vil slette denne delingslenken?", + "shareMessageRemove": "Når slettet, vil lenken ikke lenger fungere, og alle som bruker den vil miste tilgang til ressursen.", "shareTokenDescription": "Adgangstoken kan sendes på to måter: som en spørringsparameter eller i forespørselsoverskriftene. Disse må sendes fra klienten på hver forespørsel om autentisert tilgang.", "accessToken": "Tilgangsnøkkel", "usageExamples": "Brukseksempler", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Når denne brukeren er fjernet, vil de ikke lenger ha tilgang til organisasjonen. Du kan alltid invitere dem på nytt senere, men de vil måtte godta invitasjonen på nytt.", "userRemoveOrgConfirm": "Bekreft fjerning av bruker", "userRemoveOrg": "Fjern bruker fra organisasjon", + "userQuestionOrgRemoveSelf": "Er du sikker på at du vil fjerne deg selv fra denne organisasjonen?", + "userMessageOrgRemoveSelf": "Du vil miste tilgang umiddelbart. En administrator kan invitere deg igjen senere, men du må godta en ny invitasjon.", + "userRemoveOrgConfirmSelf": "Bekreft fjerning av meg selv", + "userRemoveOrgSelf": "Fjern deg selv fra organisasjonen", + "userRemoveOrgSelfWarning": "Du vil miste tilgangen til denne organisasjonen umiddelbart.", + "userRemoveOrgConfirmPhraseSelf": "FJERN MEG SELV FRA ORG", "users": "Brukere", "accessRoleMember": "Medlem", "accessRoleOwner": "Eier", @@ -531,6 +541,11 @@ "emailInvalid": "Ugyldig e-postadresse", "inviteValidityDuration": "Vennligst velg en varighet", "accessRoleSelectPlease": "Vennligst velg en rolle", + "removeOwnAdminRoleConfirmTitle": "Fjern din administratoradgang?", + "removeOwnAdminRoleConfirmDescription": "Du vil ikke lenger ha administratorrettigheter i denne organisasjonen etter lagring. En annen administrator kan gjenopprette tilgang hvis nødvendig.", + "removeOwnAdminRoleConfirmButton": "Fjern min administratoradgang", + "removeOwnAdminRoleConfirmPhrase": "FJERN MIN ADMINISTRATORADGANG", + "ownerMustRetainAdminRole": "Organisasjonseier må beholde minst én administratorrolle.", "usernameRequired": "Brukernavn er påkrevd", "idpSelectPlease": "Vennligst velg en identitetsleverandør", "idpGenericOidc": "Generisk OAuth2/OIDC-leverandør.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Å legge til mer enn ett mål ovenfor vil aktivere lastbalansering.", "targetsSubmit": "Lagre mål", "addTarget": "Legg til mål", + "proxyMultiSiteRoundRobinNodeHelp": "Rundkjøringrutefordeling vil ikke fungere mellom steder som ikke er koblet til samme node, men failover vil fungere.", "targetErrorInvalidIp": "Ugyldig IP-adresse", "targetErrorInvalidIpDescription": "Skriv inn en gyldig IP-adresse eller vertsnavn", "targetErrorInvalidPort": "Ugyldig port", @@ -2652,6 +2668,8 @@ "validPassword": "Gyldig passord", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "Vis", + "configManaged": "Konfigurasjon administrert", "connectedClient": "Tilkoblet klient", "resourceBlocked": "Ressurs blokkert", "droppedByRule": "Legg i regelen", From b6c8fbe43b4df889b749dbe68b45a88b0fc0266c Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Tue, 12 May 2026 20:41:30 -0700 Subject: [PATCH 138/139] New translations en-us.json (Spanish) [ci skip] --- messages/es-ES.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/messages/es-ES.json b/messages/es-ES.json index 3175b4844..e586bafdc 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -156,6 +156,10 @@ "shareErrorDeleteMessage": "Se ha producido un error al eliminar el enlace", "shareDeleted": "Enlace eliminado", "shareDeletedDescription": "El enlace ha sido eliminado", + "shareDelete": "Borrar Enlace Compartido", + "shareDeleteConfirm": "Confirmar Borrado del Enlace Compartido", + "shareQuestionRemove": "¿Está seguro de que desea borrar este enlace compartido?", + "shareMessageRemove": "Una vez borrado, el enlace dejará de funcionar y cualquier persona que lo use perderá acceso al recurso.", "shareTokenDescription": "El token de acceso puede ser pasado de dos maneras: como parámetro de consulta o en las cabeceras de solicitud. Estos deben ser pasados del cliente en cada solicitud de acceso autenticado.", "accessToken": "Token de acceso", "usageExamples": "Ejemplos de uso", @@ -523,6 +527,12 @@ "userMessageOrgRemove": "Una vez eliminado, este usuario ya no tendrá acceso a la organización. Siempre puede volver a invitarlos más tarde, pero tendrán que aceptar la invitación de nuevo.", "userRemoveOrgConfirm": "Confirmar eliminar usuario", "userRemoveOrg": "Eliminar usuario de la organización", + "userQuestionOrgRemoveSelf": "¿Está seguro de que desea eliminarse de esta organización?", + "userMessageOrgRemoveSelf": "Perderá acceso inmediatamente. Un administrador puede invitarlo de nuevo más tarde, pero necesitará aceptar una nueva invitación.", + "userRemoveOrgConfirmSelf": "Confirmar Eliminarme", + "userRemoveOrgSelf": "Eliminarse de la organización", + "userRemoveOrgSelfWarning": "Perderá acceso a esta organización inmediatamente.", + "userRemoveOrgConfirmPhraseSelf": "ELIMINARME DE LA ORGANIZACIÓN", "users": "Usuarios", "accessRoleMember": "Miembro", "accessRoleOwner": "Propietario", @@ -531,6 +541,11 @@ "emailInvalid": "Dirección de correo inválida", "inviteValidityDuration": "Por favor, seleccione una duración", "accessRoleSelectPlease": "Por favor, seleccione un rol", + "removeOwnAdminRoleConfirmTitle": "¿Eliminar su acceso de administrador?", + "removeOwnAdminRoleConfirmDescription": "Ya no tendrá permisos de administrador en esta organización después de guardar. Otro administrador puede restaurar el acceso si es necesario.", + "removeOwnAdminRoleConfirmButton": "Eliminar Mi Acceso de Administrador", + "removeOwnAdminRoleConfirmPhrase": "ELIMINAR MI ACCESO DE ADMINISTRADOR", + "ownerMustRetainAdminRole": "El propietario de la organización debe mantener al menos un rol de administrador.", "usernameRequired": "Nombre de usuario requerido", "idpSelectPlease": "Por favor, seleccione un proveedor de identidad", "idpGenericOidc": "Proveedor OAuth2/OIDC genérico.", @@ -658,6 +673,7 @@ "targetNoOneDescription": "Si se añade más de un objetivo anterior se activará el balance de carga.", "targetsSubmit": "Guardar objetivos", "addTarget": "Añadir destino", + "proxyMultiSiteRoundRobinNodeHelp": "El enrutamiento de turnos no funcionará entre sitios que no están conectados al mismo nodo, pero el failover funcionará.", "targetErrorInvalidIp": "Dirección IP inválida", "targetErrorInvalidIpDescription": "Por favor, introduzca una dirección IP válida o nombre de host", "targetErrorInvalidPort": "Puerto inválido", @@ -2652,6 +2668,8 @@ "validPassword": "Contraseña válida", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "Ver", + "configManaged": "Configuración Gestionada", "connectedClient": "Cliente conectado", "resourceBlocked": "Recurso bloqueado", "droppedByRule": "Soltado por regla", From efb1d69ac906e0c56cb2bfc09c140a92ad311df1 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 12 May 2026 20:59:24 -0700 Subject: [PATCH 139/139] Add migration --- server/lib/consts.ts | 2 +- server/setup/migrationsPg.ts | 4 ++- server/setup/migrationsSqlite.ts | 4 ++- server/setup/scriptsPg/1.18.4.ts | 34 ++++++++++++++++++++++ server/setup/scriptsSqlite/1.18.4.ts | 43 ++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 server/setup/scriptsPg/1.18.4.ts create mode 100644 server/setup/scriptsSqlite/1.18.4.ts diff --git a/server/lib/consts.ts b/server/lib/consts.ts index b71b3299f..e33ea52b9 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.18.3"; +export const APP_VERSION = "1.18.4"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index 7c7c67ad7..0b8af06bc 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -24,6 +24,7 @@ import m15 from "./scriptsPg/1.16.0"; import m16 from "./scriptsPg/1.17.0"; import m17 from "./scriptsPg/1.18.0"; import m18 from "./scriptsPg/1.18.3"; +import m19 from "./scriptsPg/1.18.4"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -47,7 +48,8 @@ const migrations = [ { version: "1.16.0", run: m15 }, { version: "1.17.0", run: m16 }, { version: "1.18.0", run: m17 }, - { version: "1.18.3", run: m18 } + { version: "1.18.3", run: m18 }, + { version: "1.18.4", run: m19 } // Add new migrations here as they are created ] as { version: string; diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 43ce07629..837b039f7 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -42,6 +42,7 @@ import m36 from "./scriptsSqlite/1.16.0"; import m37 from "./scriptsSqlite/1.17.0"; import m38 from "./scriptsSqlite/1.18.0"; import m39 from "./scriptsSqlite/1.18.3"; +import m40 from "./scriptsSqlite/1.18.4"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -81,7 +82,8 @@ const migrations = [ { version: "1.16.0", run: m36 }, { version: "1.17.0", run: m37 }, { version: "1.18.0", run: m38 }, - { version: "1.18.3", run: m39 } + { version: "1.18.3", run: m39 }, + { version: "1.18.4", run: m40 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scriptsPg/1.18.4.ts b/server/setup/scriptsPg/1.18.4.ts new file mode 100644 index 000000000..904d414c0 --- /dev/null +++ b/server/setup/scriptsPg/1.18.4.ts @@ -0,0 +1,34 @@ +import { db } from "@server/db/pg/driver"; +import { sql } from "drizzle-orm"; + +const version = "1.18.4"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + await db.execute(sql`BEGIN`); + + await db.execute(sql` + ALTER TABLE "connectionAuditLog" ADD COLUMN "clientEndpoint" text; + `); + + await db.execute(sql` + ALTER TABLE "eventStreamingDestinations" ADD COLUMN "lastError" text; + `); + + await db.execute(sql` + ALTER TABLE "eventStreamingDestinations" ADD COLUMN "lastErrorAt" bigint; + `); + + 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.18.4.ts b/server/setup/scriptsSqlite/1.18.4.ts new file mode 100644 index 000000000..73296ce02 --- /dev/null +++ b/server/setup/scriptsSqlite/1.18.4.ts @@ -0,0 +1,43 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.18.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.pragma("foreign_keys = OFF"); + + db.transaction(() => { + db.prepare( + ` + ALTER TABLE 'connectionAuditLog' ADD 'clientEndpoint' text; + ` + ).run(); + db.prepare( + ` + ALTER TABLE 'eventStreamingDestinations' ADD 'lastError' text; + ` + ).run(); + db.prepare( + ` + ALTER TABLE 'eventStreamingDestinations' ADD 'lastErrorAt' integer; + ` + ).run(); + })(); + + db.pragma("foreign_keys = ON"); + + console.log("Migrated database"); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } + + console.log(`${version} migration complete`); +}