Skip to content

Commit bae2933

Browse files
authored
0.2.38 (#126)
* Enabled tool toggle copy change * Resolve #96 - Pretty-serialize mcp configs * Attempt secret deletion in host binary * Update toggle lock state * Improve loading state * Improve tile modal design & overflow
1 parent 79cd58f commit bae2933

File tree

14 files changed

+276
-232
lines changed

14 files changed

+276
-232
lines changed

src/extension/host-binary/cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func DeleteSecret(ctx context.Context) *cobra.Command {
8989
cmd := &cobra.Command{
9090
Use: "delete",
9191
Short: "Delete a secret",
92-
Args: cobra.ExactArgs(1),
92+
Args: cobra.NoArgs,
9393
RunE: func(*cobra.Command, []string) error {
9494
return runDeleteSecret(ctx, *opts)
9595
},

src/extension/ui/src/App.tsx

Lines changed: 26 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import { POLL_INTERVAL } from './Constants';
88
import { CatalogProvider, useCatalogContext } from './context/CatalogContext';
99
import { ConfigProvider } from './context/ConfigContext';
1010
import { MCPClientProvider, useMCPClientContext } from './context/MCPClientContext';
11+
import { RequiredImagesProvider, useRequiredImagesContext } from './context/RequiredImageContext';
1112
import ConfigurationModal from './components/tile/Modal';
13+
import LoadingState from './components/LoadingState';
1214

1315
export const client = createDockerDesktopClient();
1416

@@ -23,16 +25,18 @@ export function App() {
2325
// Wrap the entire application with our providers
2426
return (
2527
<ConfigProvider client={client}>
26-
<CatalogProvider client={client}>
27-
<MCPClientProvider client={client}>
28-
<AppContent
29-
settings={settings}
30-
setSettings={setSettings}
31-
configuringItem={configuringItem}
32-
setConfiguringItem={setConfiguringItem}
33-
/>
34-
</MCPClientProvider>
35-
</CatalogProvider>
28+
<RequiredImagesProvider client={client}>
29+
<CatalogProvider client={client}>
30+
<MCPClientProvider client={client}>
31+
<AppContent
32+
settings={settings}
33+
setSettings={setSettings}
34+
configuringItem={configuringItem}
35+
setConfiguringItem={setConfiguringItem}
36+
/>
37+
</MCPClientProvider>
38+
</CatalogProvider>
39+
</RequiredImagesProvider>
3640
</ConfigProvider>
3741
);
3842
}
@@ -44,53 +48,22 @@ interface AppContentProps {
4448
setConfiguringItem: React.Dispatch<React.SetStateAction<CatalogItemWithName | null>>;
4549
}
4650

47-
function AppContent({ settings, setSettings, configuringItem, setConfiguringItem }: AppContentProps) {
48-
const {
49-
imagesLoadingResults,
50-
loadImagesIfNeeded,
51-
catalogItems,
52-
} = useCatalogContext();
51+
function AppContent({ settings, setSettings, setConfiguringItem }: AppContentProps) {
52+
const { secretsLoading, catalogLoading, registryLoading } = useCatalogContext();
53+
const { isLoading: imagesLoading } = useRequiredImagesContext();
5354

54-
// Instead of showing full-page loading states for each resource, let's implement a more unified approach
55-
// Only show full-page loading during initial load, not during background refetching
56-
const isInitialLoading = !catalogItems;
55+
const isLoading = secretsLoading || catalogLoading || registryLoading || imagesLoading;
5756

58-
// Critical error check - only for images as they're required for the app to function
59-
if (imagesLoadingResults?.stderr) {
60-
return (
61-
<Paper sx={{ padding: 2, height: '90vh', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }}>
62-
<Alert
63-
sx={{ fontSize: '1.5em' }}
64-
action={
65-
<Button variant='outlined' color='secondary' onClick={() => loadImagesIfNeeded()}>
66-
Retry
67-
</Button>
68-
}
69-
title="Error loading images"
70-
severity="error"
71-
>
72-
{imagesLoadingResults.stderr}
73-
</Alert>
74-
<Typography>{imagesLoadingResults?.stdout}</Typography>
75-
</Paper>
76-
);
77-
}
78-
79-
// Show one unified loading screen during initial load
80-
if (isInitialLoading) {
81-
return (
82-
<Paper sx={{ padding: 2, height: '90vh', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }}>
83-
<CircularProgress sx={{ marginBottom: 2 }} />
84-
<Typography>Loading application...</Typography>
85-
</Paper>
86-
);
87-
}
8857
return (
8958
<>
90-
<CatalogGrid
91-
setConfiguringItem={setConfiguringItem}
92-
showSettings={() => setSettings({ ...settings, showModal: true })}
93-
/>
59+
{isLoading ? (
60+
<LoadingState />
61+
) : (
62+
<CatalogGrid
63+
setConfiguringItem={setConfiguringItem}
64+
showSettings={() => setSettings({ ...settings, showModal: true })}
65+
/>
66+
)}
9467
</>
9568
);
9669
}

src/extension/ui/src/Constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const DOCKER_MCP_IMAGE = 'alpine/socat'
1010
export const DOCKER_MCP_CONTAINER_ARGS = 'STDIO TCP:host.docker.internal:8811'
1111
export const DOCKER_MCP_COMMAND = `docker run -i --rm ${DOCKER_MCP_IMAGE} ${DOCKER_MCP_CONTAINER_ARGS}`
1212

13-
export const TILE_DESCRIPTION_MAX_LENGTH = 180;
13+
export const TILE_DESCRIPTION_MAX_LENGTH = 120;
1414

1515
export const CATALOG_LAYOUT_SX = {
1616
width: '90vw',

src/extension/ui/src/Secrets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ namespace Secrets {
3737

3838
export async function deleteSecret(client: v1.DockerDesktopClient, name: string): Promise<void> {
3939
try {
40-
const response = await client.extension.host?.cli.exec('host-binary', ['delete', name]);
40+
const response = await client.extension.host?.cli.exec('host-binary', ['delete', '--name', name]);
4141
client.desktopUI.toast.success('Secret deleted successfully')
4242
if (!response) {
4343
client.desktopUI.toast.error('Failed to delete secret. Could not get response from host-binary.')

src/extension/ui/src/components/CatalogGrid.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export const CatalogGrid: React.FC<CatalogGridProps> = ({
167167
<FormControlLabel control={<Switch checked={showMine} onChange={(e) => {
168168
setShowMine(e.target.checked)
169169
localStorage.setItem('showMine', e.target.checked.toString())
170-
}} />} label="Show only my tools" />
170+
}} />} label="Show only enabled tools" />
171171
</Stack>
172172
</FormGroup>
173173

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { LinearProgress, LinearProgressProps, Box, Typography, Paper, Stack } from "@mui/material";
2+
import { useConfigContext } from "../context/ConfigContext";
3+
import { useEffect, useState } from "react";
4+
import { useCatalogContext } from "../context/CatalogContext";
5+
import { useRequiredImagesContext } from "../context/RequiredImageContext";
6+
7+
const LoadingState = () => {
8+
const { secretsLoading, catalogLoading, registryLoading } = useCatalogContext();
9+
const { imageStates, isLoading: imagesLoading } = useRequiredImagesContext();
10+
const { configLoading } = useConfigContext();
11+
12+
// Determine if any loading is happening
13+
const isLoading = secretsLoading || catalogLoading || registryLoading || imagesLoading || configLoading;
14+
15+
const [progress, setProgress] = useState(0);
16+
17+
useEffect(() => {
18+
const imageProgressTotal = (() => {
19+
const total = 5;
20+
const loaded = Object.values(imageStates).filter(state => state.status === 'success').length;
21+
return Math.round((loaded / total) * 100);
22+
})()
23+
24+
const configProgressTotal = configLoading ? 100 : 0;
25+
26+
const secretsProgressTotal = secretsLoading ? 100 : 0;
27+
28+
const catalogProgressTotal = catalogLoading ? 100 : 0;
29+
30+
const registryProgressTotal = registryLoading ? 100 : 0;
31+
32+
const totalLoadedProgress = imageProgressTotal + configProgressTotal + secretsProgressTotal + catalogProgressTotal + registryProgressTotal;
33+
const totalPossibleProgress = 500;
34+
setProgress(Math.round((totalLoadedProgress / totalPossibleProgress) * 100));
35+
}, [isLoading]);
36+
37+
if (!isLoading) return null;
38+
39+
const getLoadingText = () => {
40+
if (imagesLoading) return 'Loading required Docker images';
41+
if (configLoading) return 'Loading configuration';
42+
if (secretsLoading) return 'Loading secrets';
43+
if (catalogLoading) return 'Loading catalog';
44+
if (registryLoading) return 'Loading registry';
45+
}
46+
47+
return (
48+
<Box sx={{ maxWidth: '500px', width: '100%', position: 'sticky', zIndex: 1000, border: '1px solid red', left: '50%', transform: 'translateX(-50%)', top: '10vh', p: 2 }}>
49+
<Stack direction="column" alignItems="center" width="100%" justifyContent="space-between">
50+
<LinearProgress sx={{ width: '100%' }} variant="determinate" value={progress} />
51+
<Typography>
52+
{progress}%
53+
</Typography>
54+
</Stack>
55+
{imagesLoading && (
56+
<Paper elevation={2} sx={{ m: 2, p: 2, maxWidth: '500px', mx: 'auto' }}>
57+
<Typography variant="subtitle1" gutterBottom>
58+
{getLoadingText()}
59+
</Typography>
60+
61+
<Stack spacing={1}>
62+
{Object.entries(imageStates).map(([imageName, state]) => (
63+
<Box key={imageName} sx={{ display: 'flex', justifyContent: 'space-between' }}>
64+
<Typography variant="body2">{imageName}:</Typography>
65+
<Typography variant="body2" color={
66+
state.status === 'loading' ? 'primary' :
67+
state.status === 'error' ? 'error' :
68+
state.status === 'success' ? 'success' :
69+
'text.secondary'
70+
}>
71+
{state.status}
72+
</Typography>
73+
</Box>
74+
))}
75+
</Stack>
76+
</Paper>
77+
)}
78+
</Box>
79+
);
80+
}
81+
82+
export default LoadingState;

src/extension/ui/src/components/tile/Bottom.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ type BottomProps = {
1010
const Bottom = ({ item, needsConfiguration }: BottomProps) => {
1111
return (
1212
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
13-
{<Chip label={
14-
<Stack direction="row" spacing={1} alignItems="center" sx={{ fontSize: '1.2em' }}>
15-
<Hardware sx={{ fontSize: '1.2em' }} />
16-
{`${(item.tools || []).length || 1} tool` + ((item.tools || []).length || 1 !== 1 ? 's' : '')}
17-
13+
{<Chip sx={{ fontSize: '1.2em', p: '4px 8px' }} label={
14+
<Stack direction="row" alignItems="center" sx={{ fontSize: '1.2em' }}>
15+
<Hardware sx={{ fontSize: '1.2em', mr: '2px' }} />
16+
{item.tools?.length || 1}
1817
</Stack>
1918
} color="primary" />}
2019
{!item.tools?.length && !!item.prompts && <Chip label={`${item.prompts} prompt` + (item.prompts !== 1 ? 's' : '')} color="secondary" />}

src/extension/ui/src/components/tile/Index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Center from "./Center";
1111
import Bottom from "./Bottom";
1212
import { Secret } from "../../types";
1313
import { v1 } from "@docker/extension-api-client-types";
14+
import { useConfigContext } from "../../context/ConfigContext";
1415

1516

1617
type TileProps = {
@@ -33,8 +34,7 @@ const Tile = ({ item, registered, onSecretChange, secrets, client, unAssignedCon
3334
const [changedSecrets, setChangedSecrets] = useState<{ [key: string]: string | undefined }>({})
3435
const [secretLoading, setSecretLoading] = useState(false)
3536

36-
const { registryLoading } = useCatalogContext()
37-
const { registerCatalogItem, unregisterCatalogItem } = useCatalogContext();
37+
const { registryLoading, registerCatalogItem, unregisterCatalogItem } = useCatalogContext()
3838
const [showConfigModal, setShowConfigModal] = useState(false)
3939

4040
useEffect(() => {
@@ -104,6 +104,7 @@ const Tile = ({ item, registered, onSecretChange, secrets, client, unAssignedCon
104104
}
105105
}}
106106
onSecretChange={onSecretChange}
107+
unAssignedSecrets={unAssignedSecrets}
107108
/>
108109
<Card onClick={(e) => {
109110
if ((e.target as HTMLElement).tagName !== 'INPUT') {

0 commit comments

Comments
 (0)