mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
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:
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user