diff --git a/ui/bun.lock b/ui/bun.lock index 4d5fd5d2..59c1bef1 100644 --- a/ui/bun.lock +++ b/ui/bun.lock @@ -10,8 +10,10 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.90.12", "@tanstack/react-table": "^8.21.3", @@ -184,6 +186,8 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], @@ -226,10 +230,14 @@ "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], diff --git a/ui/package.json b/ui/package.json index fef4ae5e..ef229add 100644 --- a/ui/package.json +++ b/ui/package.json @@ -21,8 +21,10 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.90.12", "@tanstack/react-table": "^8.21.3", diff --git a/ui/src/components/accounts-table.tsx b/ui/src/components/accounts-table.tsx index 434c6cbe..65865f03 100644 --- a/ui/src/components/accounts-table.tsx +++ b/ui/src/components/accounts-table.tsx @@ -29,11 +29,12 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) { { accessorKey: 'name', header: 'Name', + size: 200, cell: ({ row }) => (
- {row.original.name} + {row.original.name} {row.original.name === defaultAccount && ( - + default )} @@ -43,39 +44,46 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) { { accessorKey: 'type', header: 'Type', - cell: ({ row }) => row.original.type || 'oauth', + size: 100, + cell: ({ row }) => ( + {row.original.type || 'oauth'} + ), }, { accessorKey: 'created', header: 'Created', + size: 150, cell: ({ row }) => { const date = new Date(row.original.created); - return date.toLocaleDateString(); + return {date.toLocaleDateString()}; }, }, { accessorKey: 'last_used', header: 'Last Used', + size: 150, cell: ({ row }) => { - if (!row.original.last_used) return '-'; + if (!row.original.last_used) return -; const date = new Date(row.original.last_used); - return date.toLocaleDateString(); + return {date.toLocaleDateString()}; }, }, { id: 'actions', header: 'Actions', + size: 100, cell: ({ row }) => { const isDefault = row.original.name === defaultAccount; return ( ); }, @@ -111,7 +119,7 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) { type: 'w-[100px]', created: 'w-[150px]', last_used: 'w-[150px]', - actions: 'w-[120px]', + actions: 'w-[100px]', }[header.id] || 'w-auto'; return ( diff --git a/ui/src/components/profiles-table.tsx b/ui/src/components/profiles-table.tsx index 91f50048..c09ef990 100644 --- a/ui/src/components/profiles-table.tsx +++ b/ui/src/components/profiles-table.tsx @@ -20,17 +20,16 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { MoreHorizontal, Trash2, Edit, Settings } from 'lucide-react'; +import { MoreHorizontal, Trash2, Edit } from 'lucide-react'; import { useDeleteProfile } from '@/hooks/use-profiles'; import type { Profile } from '@/lib/api-client'; interface ProfilesTableProps { data: Profile[]; - onEdit?: (profile: Profile) => void; onEditSettings?: (profile: Profile) => void; } -export function ProfilesTable({ data, onEdit, onEditSettings }: ProfilesTableProps) { +export function ProfilesTable({ data, onEditSettings }: ProfilesTableProps) { const deleteMutation = useDeleteProfile(); const columns: ColumnDef[] = [ @@ -64,22 +63,16 @@ export function ProfilesTable({ data, onEdit, onEditSettings }: ProfilesTablePro - - {onEdit && ( - onEdit(row.original)}> - - Edit Profile - - )} + {onEditSettings && ( onEditSettings(row.original)}> - - Edit Settings + + Edit )} deleteMutation.mutate(row.original.name)} > diff --git a/ui/src/components/settings-dialog.tsx b/ui/src/components/settings-dialog.tsx index 4142850c..1e87683c 100644 --- a/ui/src/components/settings-dialog.tsx +++ b/ui/src/components/settings-dialog.tsx @@ -42,6 +42,10 @@ interface SettingsDialogProps { * Inner component that manages local edits state * Gets unmounted/remounted via key prop when dialog closes/opens */ +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; + function SettingsDialogContent({ profileName, onClose, @@ -147,42 +151,94 @@ function SettingsDialogContent({ return ( <> - Environment Settings: {profileName} + Edit Profile: {profileName} - Edit environment variables for this profile. Sensitive keys are masked. + Configure environment variables and settings for this profile. {isLoading ? ( -
- - Loading settings... +
+ + Loading settings...
- ) : currentSettings?.env && Object.keys(currentSettings.env).length > 0 ? ( -
- {Object.entries(currentSettings.env).map(([key, value]) => ( -
- - {isSensitiveKey(key) ? ( - updateEnvValue(key, e.target.value)} /> - ) : ( - updateEnvValue(key, e.target.value)} - className="font-mono" - /> - )} -
- ))} + ) : ( +
+ + + + Environment + + + General + + - {data && ( -
-

Path: {data.path}

-

Last modified: {new Date(data.mtime).toLocaleString()}

-
- )} + + + {currentSettings?.env && Object.keys(currentSettings.env).length > 0 ? ( +
+ {Object.entries(currentSettings.env).map(([key, value]) => ( +
+ + {isSensitiveKey(key) ? ( + updateEnvValue(key, e.target.value)} + className="font-mono text-sm" + /> + ) : ( + updateEnvValue(key, e.target.value)} + className="font-mono text-sm" + /> + )} +
+ ))} +
+ ) : ( +
+

No environment variables configured.

+

Add variables in your settings.json file.

+
+ )} +
+
-
+ + + + Profile Information + Details about this configuration file. + + + {data && ( + <> +
+ Path + + {data.path} + +
+
+ Last Modified + {new Date(data.mtime).toLocaleString()} +
+ + )} +
+
+
+ + +
@@ -193,16 +249,12 @@ function SettingsDialogContent({ ) : ( <> - Save + Save Changes )}
- ) : ( -
- No environment variables configured for this profile. -
)} - + {/* Key prop ensures fresh state on each open */} {open && profileName && ( , + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)); +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; + +const ScrollBar = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = 'vertical', ...props }, ref) => ( + + + +)); +ScrollBar.displayName = ScrollAreaPrimitive.Scrollbar.displayName; + +export { ScrollArea, ScrollBar }; diff --git a/ui/src/components/ui/tabs.tsx b/ui/src/components/ui/tabs.tsx new file mode 100644 index 00000000..afd72a42 --- /dev/null +++ b/ui/src/components/ui/tabs.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import * as TabsPrimitive from '@radix-ui/react-tabs'; + +import { cn } from '@/lib/utils'; + +const Tabs = TabsPrimitive.Root; + +const TabsList = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/ui/src/index.css b/ui/src/index.css index 6cae9b14..62db957d 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -27,6 +27,9 @@ --ring: oklch(0.2 0.02 40); /* Match primary */ --radius: 0.5rem; + --popover: oklch(0.9635 0.0067 97.35); /* Match background */ + --popover-foreground: oklch(0.2 0.02 40); /* Match foreground */ + /* Sidebar colors - Light */ --sidebar: oklch(0.9635 0.0067 97.35); /* Pampas */ --sidebar-foreground: oklch(0.2 0.02 40); @@ -63,6 +66,9 @@ --ring: oklch(0.9635 0.0067 97.35); + --popover: oklch(0.21 0.006 100); /* Match dark bg */ + --popover-foreground: oklch(0.9635 0.0067 97.35); /* Match dark fg */ + /* Sidebar Dark Theme */ --sidebar: oklch(0.21 0.006 100); /* Match bg */ --sidebar-foreground: oklch(0.9635 0.0067 97.35); diff --git a/ui/src/pages/api.tsx b/ui/src/pages/api.tsx index a68df835..101586a5 100644 --- a/ui/src/pages/api.tsx +++ b/ui/src/pages/api.tsx @@ -19,11 +19,6 @@ export function ApiPage() { const [settingsProfileName, setSettingsProfileName] = useState(null); const { data, isLoading } = useProfiles(); - const handleEdit = (profile: Profile) => { - setEditingProfile(profile); - setDialogOpen(true); - }; - const handleEditSettings = (profile: Profile) => { setSettingsProfileName(profile.name); setSettingsDialogOpen(true); @@ -57,11 +52,7 @@ export function ApiPage() { {isLoading ? (
Loading profiles...
) : ( - + )}