feat(site): promote AI settings to a top-level section (#25582)

> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

Linear: [DEVEX-355](https://linear.app/coder/issue/DEVEX-355)

Fifth and final PR in a 5-PR stack splitting #25328. Surfaces the AI
settings section in the dashboard chrome and moves the existing AI
Governance page out of `/deployment`.

- `Navbar` / `NavbarView` / `DeploymentDropdown` gain a
`canViewAISettings` prop sourced from the `viewAnyAIProvider` permission
added in PR 2. The deployment dropdown gets a new AI entry that links to
`/ai/settings`.
- `DeploymentSidebarView` drops the AI-related entries that now live
under `/ai/settings`.
- `AISettingsSidebarView` expands to include AI Governance and a
cross-section link to Manage Coder Agents.
- `router.tsx` removes the `/deployment/ai-governance` route and mounts
the matching `/ai/settings/governance` child route under the new AI
settings layout.
- `ChatsSidebar` settings panel repoints the Providers link from
`/deployment/ai-providers` to `/ai/settings`.

<details>
<summary>Stack</summary>

1. #25579 jakehwll/DEVEX-355/01-primitives, primitives
2. #25580 jakehwll/DEVEX-355/02-api, API client and query layer
3. #25581 jakehwll/DEVEX-355/03-components, provider form components
4. #25583 jakehwll/DEVEX-355/04-pages, pages and routes
5. **jakehwll/DEVEX-355/05-section, section reshuffle (this PR)**

Replaces #25328 once the stack lands.
</details>
This commit is contained in:
Jake Howell
2026-05-27 02:50:36 +10:00
committed by GitHub
parent e9f0f81d76
commit d80b484487
9 changed files with 67 additions and 29 deletions
@@ -15,8 +15,9 @@ interface DeploymentDropdownProps {
canViewOrganizations: boolean;
canViewAuditLog: boolean;
canViewConnectionLog: boolean;
canViewHealth: boolean;
canViewAIBridge: boolean;
canViewAISettings: boolean;
canViewHealth: boolean;
}
export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
@@ -24,16 +25,18 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
canViewOrganizations,
canViewAuditLog,
canViewConnectionLog,
canViewHealth,
canViewAIBridge,
canViewAISettings,
canViewHealth,
}) => {
if (
!canViewAuditLog &&
!canViewConnectionLog &&
!canViewDeployment &&
!canViewOrganizations &&
!canViewHealth &&
!canViewAIBridge
!canViewAIBridge &&
!canViewAISettings &&
!canViewHealth
) {
return null;
}
@@ -53,8 +56,9 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
canViewOrganizations={canViewOrganizations}
canViewAuditLog={canViewAuditLog}
canViewConnectionLog={canViewConnectionLog}
canViewHealth={canViewHealth}
canViewAIBridge={canViewAIBridge}
canViewAISettings={canViewAISettings}
canViewHealth={canViewHealth}
/>
</DropdownMenuContent>
</DropdownMenu>
@@ -64,9 +68,10 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
canViewDeployment,
canViewAuditLog,
canViewHealth,
canViewConnectionLog,
canViewAIBridge,
canViewAISettings,
canViewHealth,
}) => {
return (
<nav>
@@ -78,6 +83,11 @@ const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
<DropdownMenuItem asChild>
<Link to="/organizations">Organizations</Link>
</DropdownMenuItem>
{canViewAISettings && (
<DropdownMenuItem asChild>
<Link to="/ai/settings">AI</Link>
</DropdownMenuItem>
)}
{canViewAuditLog && (
<DropdownMenuItem asChild>
<Link to={linkToAuditing}>Audit Logs</Link>
@@ -27,6 +27,7 @@ export const Navbar: FC = () => {
featureVisibility.connection_log && permissions.viewAnyConnectionLog;
const canViewAIBridge =
featureVisibility.aibridge && permissions.viewAnyAIBridgeInterception;
const canViewAISettings = permissions.viewAnyAIProvider;
const canCreateChat = permissions.createChat;
const uniqueLinks = new Map<string, LinkConfig>();
@@ -47,6 +48,7 @@ export const Navbar: FC = () => {
canViewAuditLog={canViewAuditLog}
canViewConnectionLog={canViewConnectionLog}
canViewAIBridge={canViewAIBridge}
canViewAISettings={canViewAISettings}
canCreateChat={canCreateChat}
proxyContextValue={proxyContextValue}
/>
@@ -33,6 +33,7 @@ const meta: Meta<typeof NavbarView> = {
canViewAuditLog: true,
canViewDeployment: true,
canViewHealth: true,
canViewAISettings: true,
canViewOrganizations: true,
canCreateChat: true,
supportLinks: [],
@@ -58,6 +59,7 @@ export const ForAuditor: Story = {
canViewAuditLog: true,
canViewDeployment: false,
canViewHealth: false,
canViewAISettings: false,
canViewOrganizations: false,
},
play: async ({ canvasElement }) => {
@@ -74,6 +76,7 @@ export const ForOrgAdmin: Story = {
canViewAuditLog: true,
canViewDeployment: false,
canViewHealth: false,
canViewAISettings: false,
canViewOrganizations: true,
},
play: async ({ canvasElement }) => {
@@ -90,6 +93,7 @@ export const ForSingleOrgOSSAdmin: Story = {
canViewOrganizations: false,
canViewConnectionLog: false,
canViewAIBridge: false,
canViewAISettings: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@@ -105,6 +109,7 @@ export const ForMember: Story = {
canViewAuditLog: false,
canViewDeployment: false,
canViewHealth: false,
canViewAISettings: false,
canViewOrganizations: false,
canCreateChat: false,
},
@@ -116,6 +121,7 @@ export const ForMemberWithAgentsAccess: Story = {
canViewAuditLog: false,
canViewDeployment: false,
canViewHealth: false,
canViewAISettings: false,
canViewOrganizations: false,
canCreateChat: true,
},
@@ -138,6 +144,7 @@ export const SupportLinks: Story = {
canViewAuditLog: false,
canViewDeployment: false,
canViewHealth: false,
canViewAISettings: false,
canViewOrganizations: false,
supportLinks: [
{
@@ -178,6 +185,7 @@ export const DefaultSupportLinks: Story = {
canViewAuditLog: false,
canViewDeployment: false,
canViewHealth: false,
canViewAISettings: false,
canViewOrganizations: false,
supportLinks: [
{ icon: "docs", name: "Documentation", target: "" },
@@ -33,6 +33,7 @@ interface NavbarViewProps {
canViewConnectionLog: boolean;
canViewHealth: boolean;
canViewAIBridge: boolean;
canViewAISettings: boolean;
canCreateChat: boolean;
proxyContextValue?: ProxyContextValue;
}
@@ -54,6 +55,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
canViewAuditLog,
canViewConnectionLog,
canViewAIBridge,
canViewAISettings,
canCreateChat,
proxyContextValue,
}) => {
@@ -128,9 +130,10 @@ export const NavbarView: FC<NavbarViewProps> = ({
canViewAuditLog={canViewAuditLog}
canViewOrganizations={canViewOrganizations}
canViewDeployment={canViewDeployment}
canViewHealth={canViewHealth}
canViewConnectionLog={canViewConnectionLog}
canViewAIBridge={canViewAIBridge}
canViewAISettings={canViewAISettings}
canViewHealth={canViewHealth}
/>
</div>
@@ -1,8 +1,11 @@
import type { FC } from "react";
import { useAuthenticated } from "#/hooks/useAuthenticated";
import AISettingsSidebarView from "#/modules/management/AISettingsSidebarView";
/**
* A sidebar for AI settings.
*/
export const AISettingsSidebar: React.FC = () => {
return <AISettingsSidebarView />;
export const AISettingsSidebar: FC = () => {
const { permissions } = useAuthenticated();
return <AISettingsSidebarView permissions={permissions} />;
};
@@ -1,13 +1,35 @@
import { ArrowUpRightIcon } from "lucide-react";
import type { FC } from "react";
import {
Sidebar as BaseSidebar,
SettingsSidebarNavItem as SidebarNavItem,
} from "#/components/Sidebar/Sidebar";
import type { Permissions } from "#/modules/permissions";
const AISettingsSidebarView: React.FC = () => {
interface AISettingsSidebarViewProps {
/** Site-wide permissions. */
permissions: Permissions;
}
const AISettingsSidebarView: FC<AISettingsSidebarViewProps> = ({
permissions,
}) => {
return (
<BaseSidebar>
<div className="flex flex-col gap-1">
{permissions.viewDeploymentConfig && (
<SidebarNavItem href="/ai/settings/governance">
AI Governance
</SidebarNavItem>
)}
<SidebarNavItem href="/ai/settings">Providers</SidebarNavItem>
{permissions.editDeploymentConfig && (
<SidebarNavItem href="/agents/settings/agents">
<div className="flex flex-row items-center gap-1">
Manage Coder Agents <ArrowUpRightIcon size={16} />
</div>
</SidebarNavItem>
)}
</div>
</BaseSidebar>
);
@@ -75,11 +75,7 @@ export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({
Observability
</SidebarNavItem>
)}
{permissions.viewDeploymentConfig && (
<SidebarNavItem href="/deployment/ai-governance">
AI Governance
</SidebarNavItem>
)}
{permissions.viewAllUsers && (
<SidebarNavItem href="/deployment/users">Users</SidebarNavItem>
)}
@@ -105,13 +101,6 @@ export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({
{!hasPremiumLicense && (
<SidebarNavItem href="/deployment/premium">Premium</SidebarNavItem>
)}
{permissions.editDeploymentConfig && (
<SidebarNavItem href="/agents/settings/agents">
<div className="flex flex-row items-center gap-1">
Manage Coder Agents <ArrowUpRightIcon size={16} />
</div>
</SidebarNavItem>
)}
</div>
</BaseSidebar>
);
@@ -1,5 +1,6 @@
import {
ArrowLeftIcon,
ArrowUpRightIcon,
BotIcon,
BoxesIcon,
ChevronRightIcon,
@@ -165,9 +166,9 @@ export const SettingsPanel: FC<SettingsPanelProps> = ({
<SettingsNavItem
icon={PlugIcon}
label="Providers"
active={settingsSection === "providers"}
to="/agents/settings/providers"
state={location.state}
active={false}
to="/ai/settings"
trailingIcon={ArrowUpRightIcon}
/>
<SettingsNavItem
icon={BoxesIcon}
+4 -4
View File
@@ -600,10 +600,7 @@ export const router = createBrowserRouter(
path="observability"
element={<ObservabilitySettingsPage />}
/>
<Route
path="ai-governance"
element={<AIGovernanceSettingsPage />}
/>
<Route path="network" element={<NetworkSettingsPage />} />
<Route path="userauth" element={<UserAuthSettingsPage />} />
<Route
@@ -698,6 +695,9 @@ export const router = createBrowserRouter(
</Route>
<Route path="/ai/settings" element={<AISettingsLayout />}>
<Route element={<DeploymentConfigProvider />}>
<Route path="governance" element={<AIGovernanceSettingsPage />} />
</Route>
<Route index element={<AISettingsProvidersPage />} />
<Route path="add" element={<AISettingsAddProviderPage />} />
<Route