Skip to content

Commit 9ca1a53

Browse files
DMontgomery40Faxbot Agent
andauthored
p4(ui): wire Marketplace to backend with disabled-state banner (ADMIN_MARKETPLACE_ENABLED=false) (#24)
Co-authored-by: Faxbot Agent <[email protected]>
1 parent df5b48d commit 9ca1a53

File tree

2 files changed

+66
-8
lines changed

2 files changed

+66
-8
lines changed

api/admin_ui/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,7 @@ function AppContent() {
832832
)}
833833
{toolsTab === 2 && <Logs client={client!} />}
834834
{toolsTab === 3 && <Plugins client={client!} readOnly={!hasTrait('role.admin')} />}
835-
{toolsTab === 4 && <PluginMarketplace />}
835+
{toolsTab === 4 && <PluginMarketplace client={client!} docsBase={uiConfig?.docs_base || adminConfig?.branding?.docs_base} />}
836836
{toolsTab === 5 && <ScriptsTests client={client!} docsBase={uiConfig?.docs_base || adminConfig?.branding?.docs_base} canSend={canSend} readOnly={!isAdmin} />}
837837
{toolsTab === 6 && (
838838
<TunnelSettings

api/admin_ui/src/components/PluginMarketplace.tsx

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,46 @@
1-
import React, { useState } from 'react';
2-
import { Box, Typography, TextField, InputAdornment, Grid, Card, CardContent } from '@mui/material';
1+
import React, { useEffect, useState } from 'react';
2+
import { Box, Typography, TextField, InputAdornment, Grid, Card, CardContent, Alert, CircularProgress } from '@mui/material';
33
import SearchIcon from '@mui/icons-material/Search';
4+
import { AdminAPIClient } from '../api/client';
45

56
/**
67
* Minimal placeholder for the Admin Console Plugin Marketplace.
78
* - Strict TypeScript compliant (no unused vars)
89
* - Not wired into the App shell yet; safe to compile
910
* - No provider name checks; trait‑gated wiring will come in later PRs
1011
*/
11-
export default function PluginMarketplace(): JSX.Element {
12+
type Props = { client: AdminAPIClient; docsBase?: string };
13+
14+
export default function PluginMarketplace({ client, docsBase }: Props): JSX.Element {
1215
const [query, setQuery] = useState('');
16+
const [loading, setLoading] = useState(false);
17+
const [plugins, setPlugins] = useState<Array<{ id: string; name?: string; description?: string }>>([]);
18+
const [disabled, setDisabled] = useState(false);
19+
20+
useEffect(() => {
21+
let cancelled = false;
22+
const run = async () => {
23+
setLoading(true);
24+
try {
25+
const res = await fetch(`${window.location.origin}/admin/marketplace/plugins`, {
26+
headers: { 'X-API-Key': (client as any).apiKey || '' },
27+
});
28+
if (res.status === 404) {
29+
if (!cancelled) setDisabled(true);
30+
return;
31+
}
32+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
33+
const data = await res.json();
34+
if (!cancelled) setPlugins(Array.isArray(data?.plugins) ? data.plugins : []);
35+
} catch {
36+
if (!cancelled) setDisabled(true);
37+
} finally {
38+
if (!cancelled) setLoading(false);
39+
}
40+
};
41+
run();
42+
return () => { cancelled = true; };
43+
}, [client]);
1344

1445
return (
1546
<Box sx={{ px: { xs: 1, sm: 2, md: 3 }, py: 2 }}>
@@ -32,19 +63,46 @@ export default function PluginMarketplace(): JSX.Element {
3263
sx={{ mb: 3 }}
3364
/>
3465

66+
{disabled && (
67+
<Alert severity="info" sx={{ mb: 2 }}>
68+
Plugin Marketplace is disabled. Enable by setting <code>ADMIN_MARKETPLACE_ENABLED=true</code>.
69+
{docsBase && (
70+
<>
71+
{' '}Learn more at <a href={`${docsBase}/admin/marketplace`} target="_blank" rel="noreferrer">docs</a>.
72+
</>
73+
)}
74+
</Alert>
75+
)}
76+
77+
{loading && <CircularProgress size={20} sx={{ mb: 2 }} />}
78+
3579
<Grid container spacing={2}>
3680
{/* Empty state placeholder; results will be populated in later PRs */}
3781
<Grid item xs={12}>
3882
<Card variant="outlined">
3983
<CardContent>
40-
<Typography color="text.secondary">
41-
Marketplace results will appear here. Use traits to gate provider‑specific UI.
42-
</Typography>
84+
{plugins.length === 0 ? (
85+
<Typography color="text.secondary">
86+
{disabled ? 'Marketplace is currently disabled.' : 'No plugins found.'}
87+
</Typography>
88+
) : (
89+
<>
90+
{plugins
91+
.filter(p => (query ? (p.name || '').toLowerCase().includes(query.toLowerCase()) : true))
92+
.map(p => (
93+
<Box key={p.id} sx={{ mb: 1 }}>
94+
<Typography variant="subtitle1">{p.name || p.id}</Typography>
95+
{p.description && (
96+
<Typography variant="body2" color="text.secondary">{p.description}</Typography>
97+
)}
98+
</Box>
99+
))}
100+
</>
101+
)}
43102
</CardContent>
44103
</Card>
45104
</Grid>
46105
</Grid>
47106
</Box>
48107
);
49108
}
50-

0 commit comments

Comments
 (0)