diff --git a/server/lib/config.ts b/server/lib/config.ts index 4cd8bbfdb..6c6c876aa 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -112,6 +112,11 @@ export class Config { ? "true" : "false"; + process.env.FLAGS_DISABLE_VIRTUAL_API_KEYS_UI = parsedConfig.flags + ?.disable_virtual_api_keys_ui + ? "true" + : "false"; + this.rawConfig = parsedConfig; } diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts index bf144ac97..462d73c1c 100644 --- a/server/lib/readConfigFile.ts +++ b/server/lib/readConfigFile.ts @@ -442,6 +442,7 @@ export const configSchema = z disable_config_managed_domains: z.boolean().optional(), disable_product_help_banners: z.boolean().optional(), disable_enterprise_features: z.boolean().optional(), + disable_virtual_api_keys_ui: z.boolean().optional(), enable_acme_cert_sync: z.boolean().optional().default(true), disable_private_http_placeholder: z .boolean() diff --git a/server/private/routers/ws/ws.ts b/server/private/routers/ws/ws.ts index 34356a8c8..4a79ff006 100644 --- a/server/private/routers/ws/ws.ts +++ b/server/private/routers/ws/ws.ts @@ -104,7 +104,8 @@ const processMessage = async ( const handler = messageHandlers[message.type]; if (!handler) { - throw new Error(`Unsupported message type: ${message.type}`); + logger.debug(`No handler found for message type: ${message.type}`); + return; } const response = await handler({ diff --git a/server/routers/ws/ws.ts b/server/routers/ws/ws.ts index 62aee93bd..358ff8b81 100644 --- a/server/routers/ws/ws.ts +++ b/server/routers/ws/ws.ts @@ -353,7 +353,10 @@ const setupConnection = async ( const handler = messageHandlers[message.type]; if (!handler) { - throw new Error(`Unsupported message type: ${message.type}`); + logger.debug( + `No handler found for message type: ${message.type}` + ); + return; } const response = await handler({ diff --git a/src/app/[orgId]/keys/page.tsx b/src/app/[orgId]/keys/page.tsx index e8341dd29..9ec09df78 100644 --- a/src/app/[orgId]/keys/page.tsx +++ b/src/app/[orgId]/keys/page.tsx @@ -76,6 +76,11 @@ export default async function KeysPage(props: KeysPageProps) { redirect("/"); } + const env = pullEnv(); + if (env.flags.disableVirtualApiKeysUi) { + redirect(`/${orgId}`); + } + let keysData: ListMyVirtualApiKeysResponse | null = null; try { const res = await internal.get< @@ -90,7 +95,6 @@ export default async function KeysPage(props: KeysPageProps) { redirect(`/${orgId}`); } - const env = pullEnv(); const primaryOrg = orgs.find((o) => o.orgId === orgId)?.isPrimaryOrg; const isAdminOrOwner = Boolean(overview?.isAdmin || overview?.isOwner); @@ -106,6 +110,7 @@ export default async function KeysPage(props: KeysPageProps) { showSidebar={false} launcherMode showViewAsAdmin={isAdminOrOwner} + env={env} > diff --git a/src/app/[orgId]/page.tsx b/src/app/[orgId]/page.tsx index 614db095d..84d7f8edf 100644 --- a/src/app/[orgId]/page.tsx +++ b/src/app/[orgId]/page.tsx @@ -83,6 +83,7 @@ export default async function OrgPage(props: OrgPageProps) { showSidebar={false} launcherMode showViewAsAdmin={isAdminOrOwner} + env={env} > {overview && launcherData ? (
{ + e.preventDefault(); + formAction(); + }} className="grid gap-4" id="org-log-retention-settings-form" > @@ -827,6 +836,7 @@ function LogRetentionSectionForm({ org }: SectionFormProps) { function SecuritySettingsSectionForm({ org }: SectionFormProps) { const router = useRouter(); + const { updateOrg } = useOrgContext(); const form = useForm({ resolver: zodResolver( SecurityFormSchema.pick({ @@ -899,6 +909,11 @@ function SecuritySettingsSectionForm({ org }: SectionFormProps) { // Update organization await api.post(`/org/${org.orgId}`, reqData); + // Update the org context immediately so the dropdowns reflect + // the saved values without waiting on a re-fetch that could + // race a lagging read replica + updateOrg(reqData); + toast({ title: t("orgUpdated"), description: t("orgUpdatedDescription") @@ -942,7 +957,10 @@ function SecuritySettingsSectionForm({ org }: SectionFormProps) { { + e.preventDefault(); + formAction(); + }} ref={formRef} id="security-settings-section-form" className="space-y-4" diff --git a/src/app/auth/resource/[resourceGuid]/page.tsx b/src/app/auth/resource/[resourceGuid]/page.tsx index c3cb00615..318d63517 100644 --- a/src/app/auth/resource/[resourceGuid]/page.tsx +++ b/src/app/auth/resource/[resourceGuid]/page.tsx @@ -30,9 +30,15 @@ import { normalizePostAuthPath } from "@server/lib/normalizePostAuthPath"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; import type { Metadata } from "next"; -export const metadata: Metadata = { - title: "Resource Access" -}; +export async function generateMetadata(): Promise { + const env = pullEnv(); + const title = + env.branding.resourceAuthPage?.titleText || + env.branding.appName || + "Resource Access"; + + return { title }; +} export const dynamic = "force-dynamic"; diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 724da9cc2..80352bca8 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -53,17 +53,21 @@ export type OrgNavSectionsOptions = { }; // Merged from 'user-management-and-resources' branch -export const orgLangingNavItems: SidebarNavItem[] = [ +export const orgLangingNavItems = (env?: Env): SidebarNavItem[] => [ { title: "sidebarAccount", href: "/{orgId}", icon: }, - { - title: "sidebarMyApiKeys", - href: "/{orgId}/keys", - icon: - } + ...(!env?.flags.disableVirtualApiKeysUi + ? [ + { + title: "sidebarMyApiKeys", + href: "/{orgId}/keys", + icon: + } + ] + : []) ]; export const orgNavSections = ( diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 7adc2b5a0..f196bf780 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,6 +1,7 @@ import React from "react"; import { cn } from "@app/lib/cn"; import { ListUserOrgsResponse } from "@server/routers/org"; +import { Env } from "@app/lib/types/env"; import { orgLangingNavItems, type CommandBarNavSection, @@ -25,6 +26,7 @@ interface LayoutProps { defaultSidebarCollapsed?: boolean; launcherMode?: boolean; showViewAsAdmin?: boolean; + env?: Env; } export async function Layout({ @@ -38,7 +40,8 @@ export async function Layout({ showTopBar = true, defaultSidebarCollapsed = false, launcherMode = false, - showViewAsAdmin = false + showViewAsAdmin = false, + env }: LayoutProps) { const allCookies = await cookies(); const sidebarStateCookie = allCookies.get("pangolin-sidebar-state")?.value; @@ -49,7 +52,7 @@ export async function Layout({ (sidebarStateCookie !== "expanded" && defaultSidebarCollapsed); const launcherNavItems: SidebarNavItem[] = launcherMode - ? orgLangingNavItems + ? orgLangingNavItems(env) : []; return ( diff --git a/src/lib/pullEnv.ts b/src/lib/pullEnv.ts index 21390effc..842ff2967 100644 --- a/src/lib/pullEnv.ts +++ b/src/lib/pullEnv.ts @@ -70,6 +70,10 @@ export function pullEnv(): Env { : false, disableEnterpriseFeatures: process.env.DISABLE_ENTERPRISE_FEATURES === "true" + ? true + : false, + disableVirtualApiKeysUi: + process.env.FLAGS_DISABLE_VIRTUAL_API_KEYS_UI === "true" ? true : false }, diff --git a/src/lib/types/env.ts b/src/lib/types/env.ts index 2a9fd2549..f33bfdd05 100644 --- a/src/lib/types/env.ts +++ b/src/lib/types/env.ts @@ -36,6 +36,7 @@ export type Env = { usePangolinDns: boolean; disableProductHelpBanners: boolean; disableEnterpriseFeatures: boolean; + disableVirtualApiKeysUi: boolean; }; branding: { appName?: string;