Skip to content

Commit b5fefe9

Browse files
authored
Merge pull request #189 from docker/fix-enable-server
Fix dancing Enable/Disable
2 parents 964386a + fde4a3f commit b5fefe9

File tree

4 files changed

+73
-83
lines changed

4 files changed

+73
-83
lines changed

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
Typography,
2626
useTheme,
2727
} from '@mui/material';
28-
import { capitalize } from 'lodash-es';
2928
import { useEffect, useState } from 'react';
3029

3130
import { ASSIGNED_SECRET_PLACEHOLDER, MCP_POLICY_NAME } from '../../Constants';
@@ -34,6 +33,7 @@ import { useConfig } from '../../queries/useConfig';
3433
import { useSecrets } from '../../queries/useSecrets';
3534
import { CatalogItemRichened } from '../../types/catalog';
3635
import ConfigEditor from './ConfigEditor';
36+
import { formatName } from '../../formatName';
3737

3838
interface TabPanelProps {
3939
children?: React.ReactNode;
@@ -154,15 +154,7 @@ const ConfigurationModal = ({
154154
borderRadius: 1,
155155
}}
156156
/>
157-
{
158-
// Lodash doesn't have a capitalize function that works with strings
159-
catalogItem.name
160-
.replace(/-/g, ' ')
161-
.replace(/_/g, ' ')
162-
.split(' ')
163-
.map(capitalize)
164-
.join(' ')
165-
}
157+
{formatName(catalogItem.name)}
166158

167159
<Tooltip
168160
placement="right"

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

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Avatar, CardHeader, Switch, Tooltip, Typography } from '@mui/material';
2-
import { capitalize } from 'lodash-es';
32

43
import { CatalogItemRichened } from '../../types/catalog';
4+
import { formatName } from '../../formatName';
5+
import { format } from 'path';
56

67
type TopProps = {
78
onToggleRegister: (checked: boolean) => void;
@@ -26,24 +27,16 @@ export default function Top({ item, onToggleRegister }: TopProps) {
2627
}
2728
title={
2829
<Typography sx={{ justifySelf: 'flex-start', fontWeight: 'bold' }}>
29-
{
30-
// Lodash doesn't have a capitalize function that works with strings
31-
item.name
32-
.replace(/-/g, ' ')
33-
.replace(/_/g, ' ')
34-
.split(' ')
35-
.map(capitalize)
36-
.join(' ')
37-
}
30+
{formatName(item.name)}
3831
</Typography>
3932
}
4033
action={
4134
item.canRegister ? (
4235
<Tooltip
4336
title={
4437
item.registered
45-
? 'Unregistering this server will hide it from MCP clients.'
46-
: 'Registering this server will expose it to MCP clients.'
38+
? `Disable ${formatName(item.name)}`
39+
: `Enable ${formatName(item.name)}`
4740
}
4841
>
4942
<Switch
@@ -56,7 +49,9 @@ export default function Top({ item, onToggleRegister }: TopProps) {
5649
/>
5750
</Tooltip>
5851
) : (
59-
<Tooltip title="This server needs configuration before it can be used.">
52+
<Tooltip
53+
title={`Enabling ${formatName(item.name)} requires configuration`}
54+
>
6055
<span>
6156
<Switch checked={false} disabled />
6257
</span>

src/extension/ui/src/formatName.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { capitalize } from 'lodash-es';
2+
3+
// This function is used to format the name of a catalog item, as this name isn't always presentable
4+
export function formatName(name: string): string {
5+
// Lodash doesn't have a capitalize function that works with strings
6+
return name
7+
.replace(/-/g, ' ')
8+
.replace(/_/g, ' ')
9+
.split(' ')
10+
.map(capitalize)
11+
.join(' ');
12+
}

src/extension/ui/src/queries/useCatalog.ts

Lines changed: 51 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
import { v1 } from "@docker/extension-api-client-types";
1+
import { v1 } from '@docker/extension-api-client-types';
22
import {
33
CatalogItem,
44
CatalogItemRichened,
55
CatalogItemWithName,
6-
} from "../types/catalog";
7-
import { getRegistry, syncRegistryWithConfig } from "../Registry";
8-
import Secrets from "../Secrets";
9-
import { parse, stringify } from "yaml";
6+
} from '../types/catalog';
7+
import { getRegistry, syncRegistryWithConfig } from '../Registry';
8+
import Secrets from '../Secrets';
9+
import { parse, stringify } from 'yaml';
1010
import {
1111
CATALOG_URL,
1212
POLL_INTERVAL,
1313
UNASSIGNED_SECRET_PLACEHOLDER,
14-
} from "../Constants";
15-
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
16-
import { getTemplateForItem } from "./useConfig";
17-
import { useState, useEffect, useCallback, useMemo } from "react";
18-
import { escapeJSONForPlatformShell, tryRunImageSync } from "../FileUtils";
19-
import { useConfig } from "./useConfig";
20-
import { useSecrets } from "./useSecrets";
14+
} from '../Constants';
15+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
16+
import { getTemplateForItem } from './useConfig';
17+
import { useState, useEffect, useCallback, useMemo } from 'react';
18+
import { escapeJSONForPlatformShell, tryRunImageSync } from '../FileUtils';
19+
import { useConfig } from './useConfig';
20+
import { useSecrets } from './useSecrets';
2121

2222
const STORAGE_KEYS = {
23-
catalog: "docker-catalog-catalog",
24-
registry: "docker-catalog-registry",
23+
catalog: 'docker-catalog-catalog',
24+
registry: 'docker-catalog-registry',
2525
};
2626

2727
function useCatalog(client: v1.DockerDesktopClient) {
@@ -73,23 +73,20 @@ function useCatalog(client: v1.DockerDesktopClient) {
7373
isLoading: catalogLoading,
7474
refetch: refetchCatalog,
7575
} = useQuery({
76-
queryKey: ["catalog"],
76+
queryKey: ['catalog'],
7777
enabled: !secretsLoading && !registryLoading && !configLoading,
7878
queryFn: async () => {
7979
const response = await fetch(
80-
localStorage.getItem("catalogUrl") || CATALOG_URL
80+
localStorage.getItem('catalogUrl') || CATALOG_URL
8181
);
8282
const catalog = await response.text();
83-
const items = parse(catalog)["registry"] as { [key: string]: any };
83+
const items = parse(catalog)['registry'] as { [key: string]: any };
8484
const enrichedItems = Object.entries(items).map(([name, item]) => ({
8585
name,
8686
...item,
8787
})) as CatalogItemWithName[];
8888
return enrichedItems.reverse().map(enrichCatalogItem);
8989
},
90-
refetchInterval: POLL_INTERVAL,
91-
staleTime: 60000,
92-
gcTime: 300000,
9390
});
9491

9592
// This effect will re-enrich catalog items whenever secrets, config, or registry items change
@@ -106,7 +103,7 @@ function useCatalog(client: v1.DockerDesktopClient) {
106103
// Use deep comparison for determining if updates are needed
107104
if (JSON.stringify(enrichedItems) !== JSON.stringify(catalogItems)) {
108105
// Use a stable reference for query data updates
109-
queryClient.setQueryData(["catalog"], [...enrichedItems]);
106+
queryClient.setQueryData(['catalog'], [...enrichedItems]);
110107
}
111108
}
112109
}, [
@@ -120,7 +117,7 @@ function useCatalog(client: v1.DockerDesktopClient) {
120117

121118
// Persist catalog to localStorage when it changes (for fallback only)
122119
useQuery({
123-
queryKey: ["catalog", "persist", catalogItems],
120+
queryKey: ['catalog', 'persist', catalogItems],
124121
queryFn: async () => {
125122
if (catalogItems && catalogItems.length > 0) {
126123
localStorage.setItem(
@@ -158,7 +155,7 @@ function useRegistry(client: v1.DockerDesktopClient) {
158155
refetch: refetchRegistry,
159156
isLoading: registryLoading,
160157
} = useQuery({
161-
queryKey: ["registry"],
158+
queryKey: ['registry'],
162159
queryFn: async () => {
163160
setCanRegister(false);
164161
try {
@@ -168,11 +165,11 @@ function useRegistry(client: v1.DockerDesktopClient) {
168165
} catch (error) {
169166
if (error instanceof Error) {
170167
client.desktopUI.toast.error(
171-
"Failed to get prompt registry: " + error.message
168+
'Failed to get prompt registry: ' + error.message
172169
);
173170
} else {
174171
client.desktopUI.toast.error(
175-
"Failed to get prompt registry: " + JSON.stringify(error)
172+
'Failed to get prompt registry: ' + JSON.stringify(error)
176173
);
177174
}
178175
setCanRegister(true);
@@ -185,15 +182,15 @@ function useRegistry(client: v1.DockerDesktopClient) {
185182
});
186183

187184
useQuery({
188-
queryKey: ["registry", "init"],
185+
queryKey: ['registry', 'init'],
189186
queryFn: async () => {
190187
const cachedRegistry = localStorage.getItem(STORAGE_KEYS.registry);
191188
if (cachedRegistry && queryClient && !registryItems) {
192189
try {
193190
const parsedRegistry = JSON.parse(cachedRegistry);
194-
queryClient.setQueryData(["registry"], parsedRegistry);
191+
queryClient.setQueryData(['registry'], parsedRegistry);
195192
} catch (e) {
196-
console.error("Failed to parse cached registry:", e);
193+
console.error('Failed to parse cached registry:', e);
197194
}
198195
}
199196
return null;
@@ -208,7 +205,7 @@ function useRegistry(client: v1.DockerDesktopClient) {
208205
);
209206

210207
useQuery({
211-
queryKey: ["registry", "persist"],
208+
queryKey: ['registry', 'persist'],
212209
queryFn: async () => {
213210
if (registryItemsString) {
214211
localStorage.setItem(STORAGE_KEYS.registry, registryItemsString);
@@ -228,7 +225,7 @@ function useRegistry(client: v1.DockerDesktopClient) {
228225
{
229226
files: [
230227
{
231-
path: "registry.yaml",
228+
path: 'registry.yaml',
232229
content: stringify({ registry: newRegistry }),
233230
},
234231
],
@@ -237,12 +234,12 @@ function useRegistry(client: v1.DockerDesktopClient) {
237234
);
238235

239236
await tryRunImageSync(client, [
240-
"--rm",
241-
"-v",
242-
"docker-prompts:/docker-prompts",
243-
"--workdir",
244-
"/docker-prompts",
245-
"vonwig/function_write_files:latest",
237+
'--rm',
238+
'-v',
239+
'docker-prompts:/docker-prompts',
240+
'--workdir',
241+
'/docker-prompts',
242+
'vonwig/function_write_files:latest',
246243
payload,
247244
]);
248245

@@ -292,7 +289,7 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
292289
{
293290
files: [
294291
{
295-
path: "registry.yaml",
292+
path: 'registry.yaml',
296293
content: stringify({ registry: newRegistry }),
297294
},
298295
],
@@ -301,12 +298,12 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
301298
);
302299

303300
await tryRunImageSync(client, [
304-
"--rm",
305-
"-v",
306-
"docker-prompts:/docker-prompts",
307-
"--workdir",
308-
"/docker-prompts",
309-
"vonwig/function_write_files:latest",
301+
'--rm',
302+
'-v',
303+
'docker-prompts:/docker-prompts',
304+
'--workdir',
305+
'/docker-prompts',
306+
'vonwig/function_write_files:latest',
310307
payload,
311308
]);
312309

@@ -318,7 +315,7 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
318315
return { success: true, newRegistry };
319316
} catch (error) {
320317
client.desktopUI.toast.error(
321-
"Failed to register catalog item: " + error
318+
'Failed to register catalog item: ' + error
322319
);
323320
// Treat YAML file write failures as fatal, no rollback
324321
throw error;
@@ -327,10 +324,7 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
327324
// Only need one update of registry data, not both onMutate and onSuccess
328325
onSuccess: async (data) => {
329326
// Update the registry data after successful registration
330-
queryClient.setQueryData(["registry"], data.newRegistry);
331-
332-
// Also invalidate catalog to refresh the registered state
333-
await queryClient.invalidateQueries({ queryKey: ["catalog"] });
327+
queryClient.setQueryData(['registry'], data.newRegistry);
334328
},
335329
});
336330

@@ -350,7 +344,7 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
350344
{
351345
files: [
352346
{
353-
path: "registry.yaml",
347+
path: 'registry.yaml',
354348
content: stringify({ registry: currentRegistry }),
355349
},
356350
],
@@ -359,12 +353,12 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
359353
);
360354

361355
await tryRunImageSync(client, [
362-
"--rm",
363-
"-v",
364-
"docker-prompts:/docker-prompts",
365-
"--workdir",
366-
"/docker-prompts",
367-
"vonwig/function_write_files:latest",
356+
'--rm',
357+
'-v',
358+
'docker-prompts:/docker-prompts',
359+
'--workdir',
360+
'/docker-prompts',
361+
'vonwig/function_write_files:latest',
368362
payload,
369363
]);
370364

@@ -374,7 +368,7 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
374368
return { success: true, newRegistry: currentRegistry };
375369
} catch (error) {
376370
client.desktopUI.toast.error(
377-
"Failed to unregister catalog item: " + error
371+
'Failed to unregister catalog item: ' + error
378372
);
379373
// Treat YAML file write failures as fatal, no rollback
380374
throw error;
@@ -383,10 +377,7 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
383377
// Only need one update of registry data, not both onMutate and onSuccess
384378
onSuccess: async (data) => {
385379
// Update the registry data after successful unregistration
386-
queryClient.setQueryData(["registry"], data.newRegistry);
387-
388-
// Also invalidate catalog to refresh the registered state
389-
await queryClient.invalidateQueries({ queryKey: ["catalog"] });
380+
queryClient.setQueryData(['registry'], data.newRegistry);
390381
},
391382
});
392383

0 commit comments

Comments
 (0)