Skip to content

Commit 0a59505

Browse files
author
colinmcneil
committed
Add UI for new oauth provider CLI
1 parent 8c97948 commit 0a59505

File tree

6 files changed

+134
-5
lines changed

6 files changed

+134
-5
lines changed

src/extension/ui/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useMCPClient } from './queries/useMCPClient';
99
import { useRequiredImages } from './queries/useRequiredImages';
1010
import { useSecrets } from './queries/useSecrets';
1111
import { syncRegistryWithConfig } from './Registry';
12-
12+
import useOAuthProvider from './queries/useOAuthProvider';
1313
export const client = createDockerDesktopClient();
1414

1515
// Memoize the CatalogGrid component to prevent unnecessary re-renders
@@ -24,7 +24,7 @@ export function App() {
2424
const secrets = useSecrets(client);
2525
const mcpClient = useMCPClient(client);
2626

27-
// Create a memoized callback for syncing registry with config
27+
// Create a memoized callback for syncing registry with config we can call later
2828
const syncRegistry = useCallback(async () => {
2929
if (config.config && catalogAll.registryItems) {
3030
await syncRegistryWithConfig(client, catalogAll.registryItems, config.config);

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
Box,
88
Button,
99
CircularProgress,
10-
Divider,
1110
FormControlLabel,
1211
FormGroup,
1312
IconButton,
@@ -25,8 +24,8 @@ import React, { Suspense, useMemo, useState } from 'react';
2524
import { CATALOG_LAYOUT_SX } from '../Constants';
2625
import { CatalogItemRichened } from '../types/catalog';
2726
import YourClients from './tabs/YourClients';
28-
29-
const ToolCatalog = React.lazy(() => import('./tabs/ToolCatalog'));
27+
import OAuthProviders from './tabs/OAuthProviders';
28+
import ToolCatalog from './tabs/ToolCatalog';
3029

3130
// Initialize the Docker Desktop client
3231
const client = createDockerDesktopClient();
@@ -126,6 +125,7 @@ export const CatalogGrid: React.FC<CatalogGridProps> = ({ appProps }) => {
126125
>
127126
<Tab label="MCP Catalog" />
128127
<Tab label="Clients" />
128+
<Tab label="OAuth Providers" />
129129
</Tabs>
130130
{tab === 0 && (
131131
<Stack
@@ -253,6 +253,17 @@ export const CatalogGrid: React.FC<CatalogGridProps> = ({ appProps }) => {
253253
)}
254254
{tab === 1 && <YourClients appProps={appProps} />}
255255
</Suspense>
256+
<Suspense
257+
fallback={
258+
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
259+
<CircularProgress />
260+
</Box>
261+
}
262+
>
263+
{tab === 2 && <OAuthProviders client={client} />}
264+
</Suspense>
256265
</Stack>
257266
);
258267
};
268+
269+
export default CatalogGrid;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { v1 } from "@docker/extension-api-client-types";
2+
import useOAuthProvider from "../../queries/useOAuthProvider";
3+
import { Alert, Box, Button, Card, CardContent, CardHeader, Chip, CircularProgress, Collapse, Link, Stack, Typography } from "@mui/material";
4+
import { useState } from "react";
5+
import { OAuthProvider } from "../../types/oauth/Provider";
6+
7+
const OAuthProviders = ({ client }: { client: v1.DockerDesktopClient }) => {
8+
const { data, isLoading, error, authorizeOAuthProvider, unauthorizeOAuthProvider } = useOAuthProvider(client);
9+
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
10+
if (isLoading) {
11+
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
12+
<Typography>Loading OAuth Providers...</Typography>
13+
<CircularProgress />
14+
</Box>;
15+
}
16+
if (error) {
17+
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
18+
<Alert severity="error">Error: {error.message || JSON.stringify(error, null, 2)}</Alert>
19+
</Box>;
20+
}
21+
if (!data) {
22+
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
23+
<Alert severity="info">No supported OAuth Providers detected</Alert>
24+
</Box>;
25+
}
26+
if (data.length > 0) {
27+
return <>
28+
{data.map((provider: OAuthProvider) => {
29+
return <Card>
30+
<CardHeader title={provider.app} action={provider.authorized ? <Button variant="contained" color="error" onClick={() => {
31+
unauthorizeOAuthProvider.mutateAsync(provider.app);
32+
}}>Unauthorize</Button> : <Button variant="contained" color="success" onClick={() => {
33+
authorizeOAuthProvider.mutateAsync(provider.app);
34+
}}>Authorize</Button>} />
35+
<CardContent>
36+
<Stack direction="row" alignItems="center" spacing={1}>
37+
<Typography>{provider.provider}</Typography>
38+
{provider.authorized ? <Chip label="Authorized" color="success" size="small" /> : <Chip label="Unauthorized" color="error" size="small" />}
39+
</Stack>
40+
</CardContent>
41+
</Card>
42+
})}
43+
</>
44+
}
45+
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
46+
<Alert severity="info">No supported OAuth Providers detected</Alert>
47+
</Box>;
48+
};
49+
50+
export default OAuthProviders;
51+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { useMutation, useQuery } from "@tanstack/react-query";
2+
import { v1 } from "@docker/extension-api-client-types";
3+
import {
4+
authorizeOAuthApp,
5+
listOAuthApps,
6+
unauthorizeOAuthApp,
7+
} from "../utils/OAuth";
8+
9+
const useOAuthProvider = (client: v1.DockerDesktopClient) => {
10+
const { data, isLoading, error } = useQuery({
11+
queryKey: ["oauth-providers"],
12+
queryFn: () => listOAuthApps(client),
13+
});
14+
const authorizeOAuthProvider = useMutation({
15+
mutationFn: (appId: string) => authorizeOAuthApp(client, appId),
16+
});
17+
const unauthorizeOAuthProvider = useMutation({
18+
mutationFn: (appId: string) => unauthorizeOAuthApp(client, appId),
19+
});
20+
return {
21+
data,
22+
isLoading,
23+
error,
24+
authorizeOAuthProvider,
25+
unauthorizeOAuthProvider,
26+
};
27+
};
28+
29+
export default useOAuthProvider;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface OAuthProvider {
2+
app: string;
3+
authorized: boolean;
4+
provider: string;
5+
tools: string[] | null;
6+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { v1 } from "@docker/extension-api-client-types";
2+
import { OAuthProvider } from "../types/oauth/Provider";
3+
export const listOAuthApps = async (client: v1.DockerDesktopClient) => {
4+
const output = await client.extension.host?.cli.exec("host-binary", [
5+
"list-oauth-apps",
6+
]);
7+
return JSON.parse(output?.stdout || "[]") as OAuthProvider[];
8+
};
9+
10+
export const authorizeOAuthApp = async (
11+
client: v1.DockerDesktopClient,
12+
appId: string
13+
) => {
14+
const output = await client.extension.host?.cli.exec("host-binary", [
15+
"authorize",
16+
"--name",
17+
appId,
18+
]);
19+
return output?.stdout;
20+
};
21+
22+
export const unauthorizeOAuthApp = async (
23+
client: v1.DockerDesktopClient,
24+
appId: string
25+
) => {
26+
const output = await client.extension.host?.cli.exec("host-binary", [
27+
"unauthorize",
28+
"--name",
29+
appId,
30+
]);
31+
return output?.stdout;
32+
};

0 commit comments

Comments
 (0)