feat(ui): allow setting router fallbacks on key edit

The key edit view exposed every other key-level setting but omitted
router_settings, so users couldn't change fallbacks / retries / etc.
on an existing key from the UI even though /key/update accepted them.

Reuses the same RouterSettingsAccordion the create flow uses, wrapped
in a collapsed antd Collapse so the form stays compact by default.
This commit is contained in:
Ryan Crabbe 2026-05-02 20:23:40 -07:00
parent c94a8d6514
commit 24fc9929c9
No known key found for this signature in database
2 changed files with 40 additions and 1 deletions

View file

@ -98,6 +98,7 @@ export interface KeyResponse {
budget_limits?: Array<{ budget_duration: string; max_budget: number; reset_at?: string }>;
auto_rotate?: boolean;
rotation_interval?: string;
router_settings?: Record<string, unknown> | null;
last_rotation_at?: string;
key_rotation_at?: string;
next_rotation_at?: string;

View file

@ -5,11 +5,14 @@ import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings"
import PolicySelector from "@/components/policies/PolicySelector";
import { InfoCircleOutlined } from "@ant-design/icons";
import { TextInput, Button as TremorButton } from "@tremor/react";
import { Form, Input, Select, Switch, Tooltip } from "antd";
import { Collapse, Form, Input, Select, Switch, Tooltip } from "antd";
import { useEffect, useState } from "react";
import { rolesWithWriteAccess } from "../../utils/roles";
import AgentSelector from "../agent_management/AgentSelector";
import AccessGroupSelector from "../common_components/AccessGroupSelector";
import RouterSettingsAccordion, {
RouterSettingsAccordionValue,
} from "../common_components/RouterSettingsAccordion";
import { mapInternalToDisplayNames } from "../callback_info_helpers";
import KeyLifecycleSettings from "../common_components/KeyLifecycleSettings";
import PassThroughRoutesSelector from "../common_components/PassThroughRoutesSelector";
@ -108,6 +111,14 @@ export function KeyEditView({
const [budgetLimits, setBudgetLimits] = useState<BudgetWindowEntry[]>(
Array.isArray(keyData.budget_limits) ? keyData.budget_limits : []
);
// Router settings (fallbacks, retries, etc.) — local edit state only.
// Initialized once from keyData; the editor remounts on each open
// (key_info_view conditionally renders KeyEditView), so no sync effect is needed.
const [routerSettings, setRouterSettings] = useState<RouterSettingsAccordionValue | null>(
keyData.router_settings && Object.keys(keyData.router_settings).length > 0
? { router_settings: keyData.router_settings as RouterSettingsAccordionValue["router_settings"] }
: null,
);
const { data: organizations, isLoading: isOrganizationsLoading } = useOrganizations();
const { data: projects } = useProjects();
const { data: uiSettingsData } = useUISettings();
@ -283,6 +294,14 @@ export function KeyEditView({
const validWindows = budgetLimits.filter((w) => w.budget_duration && w.max_budget !== null && w.max_budget !== undefined);
values.budget_limits = validWindows.length > 0 ? validWindows : undefined;
// Include router_settings (fallbacks, retries, etc.) when set; send {} to clear.
if (routerSettings?.router_settings) {
const hasValues = Object.values(routerSettings.router_settings).some(
(v) => v !== null && v !== undefined && v !== "",
);
values.router_settings = hasValues ? routerSettings.router_settings : {};
}
await onSubmit(values);
} finally {
setIsKeySaving(false);
@ -724,6 +743,25 @@ export function KeyEditView({
<Input.TextArea rows={10} />
</Form.Item>
{accessToken && (
<Collapse
className="mb-4"
items={[
{
key: "router-settings",
label: <b>Router Settings</b>,
children: (
<RouterSettingsAccordion
accessToken={accessToken}
value={routerSettings || undefined}
onChange={setRouterSettings}
/>
),
},
]}
/>
)}
{/* Auto-Rotation Settings */}
<div className="mb-4">
<KeyLifecycleSettings