Skip to main content
Glama
AccessControls.tsx7.68 kB
import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useUpdateAppConfig } from "@/data/app-config"; import { cn } from "@/lib/utils"; import { DEFAULT_PROFILE_NAME, initAccessControlsStore, useAccessControlsStore, useSocketStore, } from "@/store"; import { Group, Save, Shield, Undo } from "lucide-react"; import { useCallback, useEffect, useMemo } from "react"; import { AgentProfiles } from "../components/access-controls/AgentProfiles"; import { ToolGroups } from "../components/access-controls/ToolGroups"; export default function AccessControls() { const { mutateAsync: updateAppConfigAsync, isPending } = useUpdateAppConfig(); const { systemState } = useSocketStore((s) => ({ systemState: s.systemState, })); const { agentsList, appConfigUpdates, hasPendingChanges, profiles, resetAppConfigUpdates, setAgentsList, setProfiles, setToolGroups, toolGroups, } = useAccessControlsStore((state) => ({ agentsList: state.agentsList, appConfigUpdates: state.appConfigUpdates, hasPendingChanges: state.hasPendingChanges, profiles: state.profiles, resetAppConfigUpdates: state.resetAppConfigUpdates, setAgentsList: state.setAgentsList, setProfiles: state.setProfiles, setToolGroups: state.setToolGroups, toolGroups: state.toolGroups, })); const saveConfiguration = useCallback(async () => { await updateAppConfigAsync(appConfigUpdates as Record<string, unknown>); resetAppConfigUpdates(); }, [appConfigUpdates, resetAppConfigUpdates, updateAppConfigAsync]); const mcpServers = useMemo(() => { if (!systemState?.targetServers_new) return []; return systemState.targetServers_new.map((server) => ({ name: server.name, tools: server.tools.map((tool) => ({ name: tool.name, description: tool.description, })), })); }, [systemState?.targetServers_new]); // Reset the state when the page unmounts useEffect(() => initAccessControlsStore, []); return ( <div className=" w-full bg-[var(--color-bg-app)]"> <div className="container mx-auto px-4 py-8"> <div className="flex justify-between items-start gap-12 whitespace-nowrap"> <h1 className="text-3xl font-bold mb-8 tracking-tight"> AI Agent Control Interface </h1> <div className="flex justify-between items-center gap-6"> <div className="flex items-center gap-3"> <Button onClick={initAccessControlsStore} variant="secondary" disabled={isPending || !hasPendingChanges} className={`${ hasPendingChanges ? "bg-[var(--color-bg-interactive)] hover:bg-[var(--color-bg-interactive-hover)] text-[var(--color-fg-interactive)] hover:text-[var(--color-fg-interactive-hover)]" : "bg-[var(--color-bg-secondary)] hover:bg-[var(--color-bg-secondary)] text-[var(--color-text-secondary)] hover:text-[var(--color-text-secondary)] cursor-not-allowed" }`} > <Undo className="w-4 h-4 mr-2" /> Discard </Button> <Button onClick={saveConfiguration} variant="secondary" className={`${ hasPendingChanges ? "bg-[var(--color-bg-interactive)] hover:bg-[var(--color-bg-interactive-hover)] text-[var(--color-fg-interactive)] hover:text-[var(--color-fg-interactive-hover)]" : "bg-[var(--color-bg-secondary)] hover:bg-[var(--color-bg-secondary)] text-[var(--color-text-secondary)] hover:text-[var(--color-text-secondary)] cursor-not-allowed" }`} disabled={isPending || !hasPendingChanges} > <Save className="w-4 h-4 mr-2" /> Save </Button> </div> <div className={cn("text-[var(--color-text-secondary)] text-sm w-32", { "text-[var(--color-fg-interactive)]": isPending || hasPendingChanges, })} > <div className="flex items-center gap-2"> {hasPendingChanges ? ( "Unsaved changes" ) : isPending ? ( <> Saving...{" "} <Spinner className="w-4 h-4 mr-2 text-[var(--color-fg-interactive)]" /> </> ) : ( "No changes" )} </div> </div> </div> </div> <div className="w-full"> <div className="bg-[var(--color-bg-container)] rounded-xl border border-[var(--color-border-primary)] shadow-xl"> <Tabs defaultValue="profile" className="w-full"> <div className="p-5 border-b border-[var(--color-border-primary)]"> <TabsList className="grid grid-cols-2 rounded-lg gap-1.5"> <TabsTrigger value="profile" className="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] data-[state=active]:bg-[var(--color-fg-interactive)] data-[state=active]:text-[var(--color-text-primary-inverted)] data-[state=active]:shadow" > <Shield className="w-4 h-4 mr-2" /> Agent Profiles </TabsTrigger> <TabsTrigger value="data" className="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] data-[state=active]:bg-[var(--color-fg-interactive)] data-[state=active]:text-[var(--color-text-primary-inverted)] data-[state=active]:shadow" > <Group className="w-4 h-4 mr-2" /> Tool Groups </TabsTrigger> </TabsList> </div> <div className="p-6 w-full h-[calc(100vh-211px)] overflow-x-hidden"> <TabsContent value="profile" className="mt-0"> {Boolean(agentsList && mcpServers) && ( <AgentProfiles agents={agentsList} getIsAgentDisabledForProfile={({ profileId, agentId }) => profiles.some( (p) => p.id !== profileId && p.name !== DEFAULT_PROFILE_NAME && p.agents.includes(agentId), ) } isPendingUpdateAppConfig={isPending} mcpServers={mcpServers} profiles={profiles} setAgentsList={setAgentsList} setProfiles={setProfiles} setToolGroups={setToolGroups} toolGroups={toolGroups} /> )} </TabsContent> <TabsContent value="data" className="mt-0"> <ToolGroups isPendingUpdateAppConfig={isPending} mcpServers={mcpServers} setProfiles={setProfiles} setToolGroups={setToolGroups} toolGroups={toolGroups} /> </TabsContent> </div> </Tabs> </div> </div> </div> </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TheLunarCompany/lunar'

If you have feedback or need assistance with the MCP directory API, please join our Discord server