@kb-labs/studio-app 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -0
- package/README.md +74 -0
- package/UI-KIT-COMPONENTS.md +783 -0
- package/UI-KIT-PLAN.md +326 -0
- package/eslint-rules/no-direct-antd-import.js +151 -0
- package/eslint.config.ts +27 -0
- package/index.html +17 -0
- package/package.json +72 -0
- package/postcss.config.js +7 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +7 -0
- package/rspack.config.mjs +118 -0
- package/src/App.tsx +46 -0
- package/src/components/devtools/devtools-log-feed.module.css +165 -0
- package/src/components/devtools/devtools-log-feed.tsx +300 -0
- package/src/components/devtools/devtools-panel.module.css +121 -0
- package/src/components/devtools/devtools-panel.tsx +115 -0
- package/src/components/empty-state.tsx +28 -0
- package/src/components/error-boundary.tsx +210 -0
- package/src/components/health-banner.module.css +103 -0
- package/src/components/health-banner.tsx +134 -0
- package/src/components/health-indicator.tsx +12 -0
- package/src/components/page-header.tsx +49 -0
- package/src/components/page-transition.tsx +24 -0
- package/src/components/role-switcher.tsx +148 -0
- package/src/components/run-badge.tsx +13 -0
- package/src/components/ui/icons.tsx +509 -0
- package/src/components/ui/index.ts +25 -0
- package/src/components/ui/kb-config-provider.tsx +130 -0
- package/src/components/ui/kb-header.module.css +51 -0
- package/src/components/ui/kb-header.tsx +112 -0
- package/src/components/ui/kb-notification-bell.module.css +103 -0
- package/src/components/ui/kb-notification-bell.tsx +199 -0
- package/src/components/ui/kb-page-layout.tsx +66 -0
- package/src/components/ui/kb-quick-search.module.css +86 -0
- package/src/components/ui/kb-quick-search.tsx +196 -0
- package/src/components/ui/kb-sidebar.module.css +47 -0
- package/src/components/ui/kb-sidebar.tsx +196 -0
- package/src/components/ui/kb-stat-card.module.css +21 -0
- package/src/components/ui/kb-stat-card.tsx +22 -0
- package/src/components/ui/kb-stat-grid.tsx +22 -0
- package/src/components/ui/kb-status-bar.module.css +49 -0
- package/src/components/ui/kb-status-bar.tsx +145 -0
- package/src/components/ui/kb-system-health-indicator.tsx +229 -0
- package/src/components/ui/kb-theme-toggle.tsx +42 -0
- package/src/components/ui/theme-adapter.ts +812 -0
- package/src/components/ui/theme-transition-overlay.tsx +70 -0
- package/src/components/ui/utils.ts +6 -0
- package/src/components/widget-error-boundary.tsx +172 -0
- package/src/config/env.ts +5 -0
- package/src/config/feature-flags.ts +482 -0
- package/src/config/studio.config.schema.ts +88 -0
- package/src/config/studio.config.ts +84 -0
- package/src/env.d.ts +23 -0
- package/src/hooks/use-command-palette.ts +6 -0
- package/src/hooks/use-devtools-store.ts +25 -0
- package/src/hooks/use-feature-flags.ts +101 -0
- package/src/index.css +10 -0
- package/src/layouts/dashboard-grid.tsx +122 -0
- package/src/layouts/index.ts +12 -0
- package/src/layouts/two-pane.tsx +134 -0
- package/src/main.tsx +15 -0
- package/src/modules/analytics/components/analytics-daily-stats-chart.tsx +257 -0
- package/src/modules/analytics/components/analytics-date-range-picker.tsx +121 -0
- package/src/modules/analytics/pages/analytics-cache-page.tsx +285 -0
- package/src/modules/analytics/pages/analytics-embeddings-page.tsx +274 -0
- package/src/modules/analytics/pages/analytics-events-page.tsx +425 -0
- package/src/modules/analytics/pages/analytics-llm-page.tsx +434 -0
- package/src/modules/analytics/pages/analytics-overview-page.tsx +336 -0
- package/src/modules/analytics/pages/analytics-page.tsx +20 -0
- package/src/modules/analytics/pages/analytics-storage-page.tsx +344 -0
- package/src/modules/analytics/pages/analytics-vectorstore-page.tsx +279 -0
- package/src/modules/analytics/routes/index.tsx +133 -0
- package/src/modules/analytics/utils/formatters.test.ts +148 -0
- package/src/modules/analytics/utils/formatters.ts +100 -0
- package/src/modules/auth/pages/login-page.module.css +113 -0
- package/src/modules/auth/pages/login-page.tsx +80 -0
- package/src/modules/auth/routes/index.tsx +24 -0
- package/src/modules/dashboard/components/ActivityTimelineWidget.tsx +199 -0
- package/src/modules/dashboard/components/AiInsightsWidget.tsx +358 -0
- package/src/modules/dashboard/components/AnimatedMetricCard.css +88 -0
- package/src/modules/dashboard/components/AnimatedMetricCard.tsx +125 -0
- package/src/modules/dashboard/components/HeroMetricCard.css +114 -0
- package/src/modules/dashboard/components/HeroMetricCard.tsx +152 -0
- package/src/modules/dashboard/components/HeroMetricsWidget.tsx +146 -0
- package/src/modules/dashboard/components/SystemResourcesWidget.tsx +197 -0
- package/src/modules/dashboard/components/dashboard-empty.tsx +22 -0
- package/src/modules/dashboard/components/dashboard-kpis-widget.tsx +59 -0
- package/src/modules/dashboard/components/dashboard-kpis.tsx +13 -0
- package/src/modules/dashboard/components/dashboard-latency-percentiles-widget.tsx +85 -0
- package/src/modules/dashboard/components/dashboard-metrics-widget.tsx +78 -0
- package/src/modules/dashboard/components/dashboard-plugins-widget.tsx +67 -0
- package/src/modules/dashboard/components/dashboard-requests-widget.tsx +62 -0
- package/src/modules/dashboard/layouts/dashboard-layout.tsx +16 -0
- package/src/modules/dashboard/pages/ai-insights-page.tsx +563 -0
- package/src/modules/dashboard/pages/dashboard-animations.css +317 -0
- package/src/modules/dashboard/pages/dashboard-page.tsx +13 -0
- package/src/modules/dashboard/routes/index.tsx +70 -0
- package/src/modules/observability/pages/devkit-page.tsx +263 -0
- package/src/modules/observability/pages/incident-detail-page.tsx +571 -0
- package/src/modules/observability/pages/incidents-page.tsx +370 -0
- package/src/modules/observability/pages/log-detail-page.tsx +508 -0
- package/src/modules/observability/pages/logs-page.tsx +1129 -0
- package/src/modules/observability/pages/prometheus-metrics-page.tsx +486 -0
- package/src/modules/observability/pages/state-broker-page.tsx +229 -0
- package/src/modules/observability/pages/system-events-page.tsx +255 -0
- package/src/modules/observability/routes/index.tsx +133 -0
- package/src/modules/plugins/components/marketplace-card.tsx +153 -0
- package/src/modules/plugins/components/plugin-ai-assistant-modal.tsx +374 -0
- package/src/modules/plugins/components/plugin-card.tsx +136 -0
- package/src/modules/plugins/components/plugin-detail/capabilities-summary.tsx +101 -0
- package/src/modules/plugins/components/plugin-detail/cli-commands-table.tsx +101 -0
- package/src/modules/plugins/components/plugin-detail/index.ts +7 -0
- package/src/modules/plugins/components/plugin-detail/jobs-table.tsx +64 -0
- package/src/modules/plugins/components/plugin-detail/permissions/env-permissions.tsx +30 -0
- package/src/modules/plugins/components/plugin-detail/permissions/filesystem-permissions.tsx +68 -0
- package/src/modules/plugins/components/plugin-detail/permissions/index.ts +6 -0
- package/src/modules/plugins/components/plugin-detail/permissions/network-permissions.tsx +43 -0
- package/src/modules/plugins/components/plugin-detail/permissions/platform-services.tsx +242 -0
- package/src/modules/plugins/components/plugin-detail/permissions/resource-quotas.tsx +96 -0
- package/src/modules/plugins/components/plugin-detail/permissions/shell-permissions.tsx +55 -0
- package/src/modules/plugins/components/plugin-detail/permissions-view.tsx +47 -0
- package/src/modules/plugins/components/plugin-detail/plugin-header.tsx +169 -0
- package/src/modules/plugins/components/plugin-detail/rest-routes-table.tsx +159 -0
- package/src/modules/plugins/components/plugin-detail/workflows-table.tsx +40 -0
- package/src/modules/plugins/components/plugin-grid.tsx +20 -0
- package/src/modules/plugins/components/plugin-table.tsx +178 -0
- package/src/modules/plugins/pages/marketplace-page.tsx +200 -0
- package/src/modules/plugins/pages/plugin-detail-page.tsx +1145 -0
- package/src/modules/plugins/routes/index.tsx +48 -0
- package/src/modules/plugins/utils/index.ts +1 -0
- package/src/modules/plugins/utils/schema-helpers.ts +21 -0
- package/src/modules/settings/components/api-routes-viewer.tsx +415 -0
- package/src/modules/settings/components/appearance-settings.tsx +200 -0
- package/src/modules/settings/components/configuration-settings.tsx +321 -0
- package/src/modules/settings/components/data-privacy-settings.tsx +111 -0
- package/src/modules/settings/components/experimental-settings.tsx +243 -0
- package/src/modules/settings/components/navigation-settings.tsx +198 -0
- package/src/modules/settings/components/notifications-settings.tsx +40 -0
- package/src/modules/settings/pages/settings-page.tsx +298 -0
- package/src/modules/settings/routes/index.tsx +47 -0
- package/src/pages/README-assistant.md +91 -0
- package/src/pages/login-page.tsx +177 -0
- package/src/pages/not-found-page.tsx +85 -0
- package/src/providers/auth-provider.tsx +40 -0
- package/src/providers/command-palette-provider.tsx +193 -0
- package/src/providers/data-sources-provider.tsx +27 -0
- package/src/providers/devtools-provider.tsx +72 -0
- package/src/providers/navigation-items-provider.tsx +38 -0
- package/src/providers/registry-v2-provider.tsx +93 -0
- package/src/providers/settings-provider.tsx +158 -0
- package/src/router.tsx +302 -0
- package/src/routes/helpers.tsx +25 -0
- package/src/routes/plugin-page-v2.tsx +52 -0
- package/src/routes/types.ts +18 -0
- package/src/styles/antd-overrides.css +211 -0
- package/src/styles/base.css +190 -0
- package/src/styles/settings.css +75 -0
- package/src/styles/variables.css +94 -0
- package/src/utils/analytics.ts +183 -0
- package/src/utils/apply-navigation-categories.ts +83 -0
- package/src/utils/commands.ts +252 -0
- package/src/utils/export.ts +174 -0
- package/src/utils/format-date.ts +166 -0
- package/src/utils/logger.ts +110 -0
- package/tailwind.config.ts +13 -0
- package/tsconfig.json +13 -0
- package/vitest-setup.ts +1 -0
|
@@ -0,0 +1,1145 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useParams, useNavigate } from 'react-router-dom';
|
|
3
|
+
import {
|
|
4
|
+
UISpin,
|
|
5
|
+
UITag,
|
|
6
|
+
UISpace,
|
|
7
|
+
UITypographyText,
|
|
8
|
+
UITypographyParagraph,
|
|
9
|
+
UITitle,
|
|
10
|
+
UICard,
|
|
11
|
+
UITable,
|
|
12
|
+
UIAccordion,
|
|
13
|
+
UIButton,
|
|
14
|
+
UIAlert,
|
|
15
|
+
UITabs,
|
|
16
|
+
UIEmptyState,
|
|
17
|
+
UIIcon,
|
|
18
|
+
} from '@kb-labs/studio-ui-kit';
|
|
19
|
+
import { useDataSources } from '@/providers/data-sources-provider';
|
|
20
|
+
import type { PluginManifestEntry } from '@kb-labs/studio-data-client';
|
|
21
|
+
import type { UITableColumn } from '@kb-labs/studio-ui-kit';
|
|
22
|
+
import { PluginAIAssistantModal } from '../components/plugin-ai-assistant-modal';
|
|
23
|
+
import { UIPage, UIPageHeader } from '@kb-labs/studio-ui-kit';
|
|
24
|
+
|
|
25
|
+
export function PluginDetailPage() {
|
|
26
|
+
const { pluginId: encodedPluginId } = useParams<{ pluginId: string }>();
|
|
27
|
+
const navigate = useNavigate();
|
|
28
|
+
const { plugins: pluginsSource } = useDataSources();
|
|
29
|
+
const [loading, setLoading] = useState(true);
|
|
30
|
+
const [plugin, setPlugin] = useState<PluginManifestEntry | null>(null);
|
|
31
|
+
const [apiBasePath, setApiBasePath] = useState<string>('');
|
|
32
|
+
const [aiModalOpen, setAiModalOpen] = useState(false);
|
|
33
|
+
|
|
34
|
+
// Decode plugin ID from URL
|
|
35
|
+
const pluginId = encodedPluginId ? decodeURIComponent(encodedPluginId) : '';
|
|
36
|
+
|
|
37
|
+
const loadPlugin = useCallback(async () => {
|
|
38
|
+
try {
|
|
39
|
+
setLoading(true);
|
|
40
|
+
const result = await pluginsSource.getPlugins();
|
|
41
|
+
const found = result.manifests.find((p) => p.pluginId === pluginId);
|
|
42
|
+
setPlugin(found || null);
|
|
43
|
+
setApiBasePath(result.apiBasePath || '');
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('Failed to load plugin:', err);
|
|
46
|
+
} finally {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
}
|
|
49
|
+
}, [pluginId, pluginsSource]);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
loadPlugin();
|
|
53
|
+
}, [loadPlugin]);
|
|
54
|
+
|
|
55
|
+
if (loading) {
|
|
56
|
+
return (
|
|
57
|
+
<UIPage>
|
|
58
|
+
<div style={{ textAlign: 'center', padding: '80px 0' }}>
|
|
59
|
+
<UISpin size="large" />
|
|
60
|
+
</div>
|
|
61
|
+
</UIPage>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!plugin) {
|
|
66
|
+
return (
|
|
67
|
+
<UIPage>
|
|
68
|
+
<UIPageHeader title="Plugin Not Found" />
|
|
69
|
+
<UIEmptyState description="Plugin not found in registry" />
|
|
70
|
+
<div style={{ textAlign: 'center', marginTop: 24 }}>
|
|
71
|
+
<UIButton variant="primary" onClick={() => navigate('/marketplace')}>
|
|
72
|
+
Back to Plugins
|
|
73
|
+
</UIButton>
|
|
74
|
+
</div>
|
|
75
|
+
</UIPage>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { manifest } = plugin;
|
|
80
|
+
const display = manifest.display;
|
|
81
|
+
|
|
82
|
+
const cliCommands = manifest.cli?.commands || [];
|
|
83
|
+
const restRoutes = manifest.rest?.routes || [];
|
|
84
|
+
const workflowHandlers = manifest.workflows?.handlers || [];
|
|
85
|
+
const jobs = manifest.jobs?.handlers || [];
|
|
86
|
+
const permissions = manifest.permissions;
|
|
87
|
+
const platformReqs = manifest.platform;
|
|
88
|
+
|
|
89
|
+
const tabItems = [
|
|
90
|
+
{
|
|
91
|
+
key: 'overview',
|
|
92
|
+
label: 'Overview',
|
|
93
|
+
children: (
|
|
94
|
+
<UISpace direction="vertical" size="large" style={{ width: '100%' }}>
|
|
95
|
+
{/* Validation Errors Alert */}
|
|
96
|
+
{plugin.validation && !plugin.validation.valid && (
|
|
97
|
+
<UIAlert
|
|
98
|
+
variant="error"
|
|
99
|
+
icon={<UIIcon name="CloseCircleOutlined" />}
|
|
100
|
+
message={`Manifest Validation Failed (${plugin.validation.errors.length} error${plugin.validation.errors.length > 1 ? 's' : ''})`}
|
|
101
|
+
description={
|
|
102
|
+
<div style={{ marginTop: 8 }}>
|
|
103
|
+
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
|
104
|
+
{plugin.validation.errors.map((error, idx) => (
|
|
105
|
+
<li key={idx} style={{ marginBottom: 4 }}>
|
|
106
|
+
<UITypographyText code style={{ fontSize: 12 }}>{error}</UITypographyText>
|
|
107
|
+
</li>
|
|
108
|
+
))}
|
|
109
|
+
</ul>
|
|
110
|
+
</div>
|
|
111
|
+
}
|
|
112
|
+
showIcon
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{/* Plugin Header Card */}
|
|
117
|
+
<UICard>
|
|
118
|
+
<UISpace direction="vertical" size="middle" style={{ width: '100%' }}>
|
|
119
|
+
{/* Icon + Name + Version */}
|
|
120
|
+
<UISpace align="start" size="large">
|
|
121
|
+
<div style={{ fontSize: 64, lineHeight: 1 }}>{display?.icon || ''}</div>
|
|
122
|
+
<div style={{ flex: 1 }}>
|
|
123
|
+
<UITitle level={2} style={{ marginBottom: 4 }}>
|
|
124
|
+
{display?.name || manifest.id}
|
|
125
|
+
</UITitle>
|
|
126
|
+
<UISpace size="middle">
|
|
127
|
+
<UITypographyText type="secondary" style={{ fontSize: 16 }}>
|
|
128
|
+
v{manifest.version}
|
|
129
|
+
</UITypographyText>
|
|
130
|
+
<UITag color="blue">
|
|
131
|
+
{typeof plugin.source === 'string'
|
|
132
|
+
? plugin.source
|
|
133
|
+
: plugin.source?.kind || 'unknown'}
|
|
134
|
+
</UITag>
|
|
135
|
+
<UITag>{manifest.schema}</UITag>
|
|
136
|
+
</UISpace>
|
|
137
|
+
</div>
|
|
138
|
+
</UISpace>
|
|
139
|
+
|
|
140
|
+
{/* Description */}
|
|
141
|
+
{display?.description && (
|
|
142
|
+
<UITypographyParagraph style={{ fontSize: 15, marginBottom: 0, color: 'rgba(0,0,0,0.65)' }}>
|
|
143
|
+
{display.description}
|
|
144
|
+
</UITypographyParagraph>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{/* Tags */}
|
|
148
|
+
{display?.tags && display.tags.length > 0 && (
|
|
149
|
+
<UISpace wrap>
|
|
150
|
+
{display.tags.map((tag, idx) => (
|
|
151
|
+
<UITag key={typeof tag === 'string' ? tag : `tag-${idx}`} color="default">
|
|
152
|
+
{typeof tag === 'string' ? tag : JSON.stringify(tag)}
|
|
153
|
+
</UITag>
|
|
154
|
+
))}
|
|
155
|
+
</UISpace>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
{/* Metadata Row */}
|
|
159
|
+
<div
|
|
160
|
+
style={{
|
|
161
|
+
display: 'grid',
|
|
162
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
|
163
|
+
gap: 16,
|
|
164
|
+
marginTop: 8,
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
{display?.author && (
|
|
168
|
+
<div>
|
|
169
|
+
<UITypographyText type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>
|
|
170
|
+
Author
|
|
171
|
+
</UITypographyText>
|
|
172
|
+
<UISpace>
|
|
173
|
+
<UIIcon name="UserOutlined" />
|
|
174
|
+
<UITypographyText>{display.author}</UITypographyText>
|
|
175
|
+
</UISpace>
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
<div>
|
|
179
|
+
<UITypographyText type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>
|
|
180
|
+
Plugin ID
|
|
181
|
+
</UITypographyText>
|
|
182
|
+
<UITypographyText code copyable>
|
|
183
|
+
{manifest.id}
|
|
184
|
+
</UITypographyText>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Links */}
|
|
189
|
+
{(display?.homepage || display?.repository) && (
|
|
190
|
+
<UISpace size="large">
|
|
191
|
+
{display?.homepage && (
|
|
192
|
+
<a href={display.homepage} target="_blank" rel="noopener noreferrer">
|
|
193
|
+
<UISpace>
|
|
194
|
+
<UIIcon name="GlobalOutlined" />
|
|
195
|
+
Homepage
|
|
196
|
+
</UISpace>
|
|
197
|
+
</a>
|
|
198
|
+
)}
|
|
199
|
+
{display?.repository && (
|
|
200
|
+
<a href={display.repository} target="_blank" rel="noopener noreferrer">
|
|
201
|
+
<UISpace>
|
|
202
|
+
<UIIcon name="LinkOutlined" />
|
|
203
|
+
Repository
|
|
204
|
+
</UISpace>
|
|
205
|
+
</a>
|
|
206
|
+
)}
|
|
207
|
+
</UISpace>
|
|
208
|
+
)}
|
|
209
|
+
|
|
210
|
+
{/* Timestamps */}
|
|
211
|
+
{(plugin.discoveredAt || plugin.buildTimestamp) && (
|
|
212
|
+
<div
|
|
213
|
+
style={{
|
|
214
|
+
display: 'grid',
|
|
215
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
|
216
|
+
gap: 16,
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
{plugin.discoveredAt && (
|
|
220
|
+
<div>
|
|
221
|
+
<UITypographyText type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>
|
|
222
|
+
Discovered At
|
|
223
|
+
</UITypographyText>
|
|
224
|
+
<UISpace>
|
|
225
|
+
<UIIcon name="SearchOutlined" />
|
|
226
|
+
<UITypographyText>{new Date(plugin.discoveredAt).toLocaleString()}</UITypographyText>
|
|
227
|
+
</UISpace>
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
{plugin.buildTimestamp && (
|
|
231
|
+
<div>
|
|
232
|
+
<UITypographyText type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>
|
|
233
|
+
Last Built
|
|
234
|
+
</UITypographyText>
|
|
235
|
+
<UISpace>
|
|
236
|
+
<UIIcon name="ClockCircleOutlined" />
|
|
237
|
+
<UITypographyText>{new Date(plugin.buildTimestamp).toLocaleString()}</UITypographyText>
|
|
238
|
+
</UISpace>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
|
|
244
|
+
{/* Plugin Root (collapsible) */}
|
|
245
|
+
<UIAccordion
|
|
246
|
+
ghost
|
|
247
|
+
items={[
|
|
248
|
+
{
|
|
249
|
+
key: 'root',
|
|
250
|
+
label: 'Plugin Root Path',
|
|
251
|
+
children: (
|
|
252
|
+
<UITypographyText code copyable style={{ fontSize: 12 }}>
|
|
253
|
+
{plugin.pluginRoot}
|
|
254
|
+
</UITypographyText>
|
|
255
|
+
),
|
|
256
|
+
},
|
|
257
|
+
]}
|
|
258
|
+
/>
|
|
259
|
+
</UISpace>
|
|
260
|
+
</UICard>
|
|
261
|
+
|
|
262
|
+
{/* Capabilities Summary */}
|
|
263
|
+
<UICard title="Capabilities">
|
|
264
|
+
<UISpace direction="vertical" size="middle" style={{ width: '100%' }}>
|
|
265
|
+
<UISpace wrap size="large">
|
|
266
|
+
{cliCommands.length > 0 && (
|
|
267
|
+
<div>
|
|
268
|
+
<UITag icon={<UIIcon name="CodeOutlined" />} color="blue" style={{ fontSize: 14, padding: '4px 12px' }}>
|
|
269
|
+
{cliCommands.length} CLI Command{cliCommands.length > 1 ? 's' : ''}
|
|
270
|
+
</UITag>
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
{restRoutes.length > 0 && (
|
|
274
|
+
<div>
|
|
275
|
+
<UITag icon={<UIIcon name="ApiOutlined" />} color="green" style={{ fontSize: 14, padding: '4px 12px' }}>
|
|
276
|
+
{restRoutes.length} REST Route{restRoutes.length > 1 ? 's' : ''}
|
|
277
|
+
</UITag>
|
|
278
|
+
</div>
|
|
279
|
+
)}
|
|
280
|
+
{workflowHandlers.length > 0 && (
|
|
281
|
+
<div>
|
|
282
|
+
<UITag icon={<UIIcon name="NodeIndexOutlined" />} color="purple" style={{ fontSize: 14, padding: '4px 12px' }}>
|
|
283
|
+
{workflowHandlers.length} Workflow{workflowHandlers.length > 1 ? 's' : ''}
|
|
284
|
+
</UITag>
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
{jobs.length > 0 && (
|
|
288
|
+
<div>
|
|
289
|
+
<UITag icon={<UIIcon name="ClockCircleOutlined" />} color="orange" style={{ fontSize: 14, padding: '4px 12px' }}>
|
|
290
|
+
{jobs.length} Scheduled Job{jobs.length > 1 ? 's' : ''}
|
|
291
|
+
</UITag>
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
</UISpace>
|
|
295
|
+
|
|
296
|
+
{permissions && (
|
|
297
|
+
<UIAlert
|
|
298
|
+
icon={<UIIcon name="LockOutlined" />}
|
|
299
|
+
variant="warning"
|
|
300
|
+
message="This plugin requires permissions"
|
|
301
|
+
description="See Permissions tab for details"
|
|
302
|
+
showIcon
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
{platformReqs?.requires && platformReqs.requires.length > 0 && (
|
|
306
|
+
<UIAlert
|
|
307
|
+
icon={<UIIcon name="DatabaseOutlined" />}
|
|
308
|
+
variant="info"
|
|
309
|
+
message="Platform Requirements"
|
|
310
|
+
description={
|
|
311
|
+
<UISpace wrap>
|
|
312
|
+
{platformReqs.requires.map((req) => (
|
|
313
|
+
<UITag key={req} color="cyan">
|
|
314
|
+
{req}
|
|
315
|
+
</UITag>
|
|
316
|
+
))}
|
|
317
|
+
</UISpace>
|
|
318
|
+
}
|
|
319
|
+
showIcon
|
|
320
|
+
/>
|
|
321
|
+
)}
|
|
322
|
+
</UISpace>
|
|
323
|
+
</UICard>
|
|
324
|
+
</UISpace>
|
|
325
|
+
),
|
|
326
|
+
},
|
|
327
|
+
cliCommands.length > 0 && {
|
|
328
|
+
key: 'cli',
|
|
329
|
+
label: `CLI Commands (${cliCommands.length})`,
|
|
330
|
+
icon: <UIIcon name="CodeOutlined" />,
|
|
331
|
+
children: <CLICommandsTable commands={cliCommands} />,
|
|
332
|
+
},
|
|
333
|
+
restRoutes.length > 0 && {
|
|
334
|
+
key: 'rest',
|
|
335
|
+
label: `REST API (${restRoutes.length})`,
|
|
336
|
+
icon: <UIIcon name="ApiOutlined" />,
|
|
337
|
+
children: <RestRoutesTable routes={restRoutes} basePath={manifest.rest?.basePath} apiBasePath={apiBasePath} />,
|
|
338
|
+
},
|
|
339
|
+
workflowHandlers.length > 0 && {
|
|
340
|
+
key: 'workflows',
|
|
341
|
+
label: `Workflows (${workflowHandlers.length})`,
|
|
342
|
+
icon: <UIIcon name="NodeIndexOutlined" />,
|
|
343
|
+
children: <WorkflowsTable handlers={workflowHandlers} />,
|
|
344
|
+
},
|
|
345
|
+
jobs.length > 0 && {
|
|
346
|
+
key: 'jobs',
|
|
347
|
+
label: `Jobs (${jobs.length})`,
|
|
348
|
+
icon: <UIIcon name="ClockCircleOutlined" />,
|
|
349
|
+
children: <JobsTable jobs={jobs} />,
|
|
350
|
+
},
|
|
351
|
+
permissions && {
|
|
352
|
+
key: 'permissions',
|
|
353
|
+
label: 'Permissions',
|
|
354
|
+
icon: <UIIcon name="LockOutlined" />,
|
|
355
|
+
children: <PermissionsView permissions={permissions} />,
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
key: 'manifest',
|
|
359
|
+
label: 'Raw Manifest (JSON)',
|
|
360
|
+
children: (
|
|
361
|
+
<UICard>
|
|
362
|
+
<pre style={{ overflow: 'auto', maxHeight: 600 }}>
|
|
363
|
+
{JSON.stringify(manifest, null, 2)}
|
|
364
|
+
</pre>
|
|
365
|
+
</UICard>
|
|
366
|
+
),
|
|
367
|
+
},
|
|
368
|
+
].filter(Boolean);
|
|
369
|
+
|
|
370
|
+
const handleAskAI = async (question: string) => {
|
|
371
|
+
if (!pluginId) {
|
|
372
|
+
throw new Error('Plugin ID is required');
|
|
373
|
+
}
|
|
374
|
+
return pluginsSource.askAboutPlugin(pluginId, { question });
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<UIPage>
|
|
379
|
+
<UISpace style={{ marginBottom: 24, width: '100%', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
380
|
+
<UIButton
|
|
381
|
+
icon={<UIIcon name="ArrowLeftOutlined" />}
|
|
382
|
+
onClick={() => navigate('/marketplace')}
|
|
383
|
+
variant="text"
|
|
384
|
+
>
|
|
385
|
+
Back to Plugins
|
|
386
|
+
</UIButton>
|
|
387
|
+
<UIButton
|
|
388
|
+
icon={<UIIcon name="QuestionCircleOutlined" />}
|
|
389
|
+
onClick={() => setAiModalOpen(true)}
|
|
390
|
+
variant="primary"
|
|
391
|
+
>
|
|
392
|
+
AI Assistant
|
|
393
|
+
</UIButton>
|
|
394
|
+
</UISpace>
|
|
395
|
+
|
|
396
|
+
<UITabs items={tabItems as any} />
|
|
397
|
+
|
|
398
|
+
<PluginAIAssistantModal
|
|
399
|
+
open={aiModalOpen}
|
|
400
|
+
onClose={() => setAiModalOpen(false)}
|
|
401
|
+
pluginId={pluginId}
|
|
402
|
+
pluginName={display?.name || manifest.id}
|
|
403
|
+
onAsk={handleAskAI}
|
|
404
|
+
/>
|
|
405
|
+
</UIPage>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Sub-components for different sections
|
|
410
|
+
|
|
411
|
+
function CLICommandsTable({ commands }: { commands: any[] }) {
|
|
412
|
+
const columns: UITableColumn<any>[] = [
|
|
413
|
+
{
|
|
414
|
+
title: 'Command',
|
|
415
|
+
dataIndex: 'id',
|
|
416
|
+
key: 'id',
|
|
417
|
+
render: (id, record) => (
|
|
418
|
+
<div>
|
|
419
|
+
<UITypographyText code strong>
|
|
420
|
+
{record.group ? `${record.group}:${id}` : id}
|
|
421
|
+
</UITypographyText>
|
|
422
|
+
</div>
|
|
423
|
+
),
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
title: 'Description',
|
|
427
|
+
dataIndex: 'describe',
|
|
428
|
+
key: 'describe',
|
|
429
|
+
render: (desc) => (typeof desc === 'string' ? desc : desc ? JSON.stringify(desc) : '--'),
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
title: 'Flags',
|
|
433
|
+
dataIndex: 'flags',
|
|
434
|
+
key: 'flags',
|
|
435
|
+
render: (flags) =>
|
|
436
|
+
flags?.length > 0 ? (
|
|
437
|
+
<UISpace wrap>
|
|
438
|
+
{flags.map((flag: any) => (
|
|
439
|
+
<UITag key={flag.name}>
|
|
440
|
+
--{flag.name}
|
|
441
|
+
{flag.required && <UITypographyText type="danger">*</UITypographyText>}
|
|
442
|
+
</UITag>
|
|
443
|
+
))}
|
|
444
|
+
</UISpace>
|
|
445
|
+
) : (
|
|
446
|
+
<UITypographyText type="secondary">None</UITypographyText>
|
|
447
|
+
),
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
title: 'Handler',
|
|
451
|
+
dataIndex: 'handler',
|
|
452
|
+
key: 'handler',
|
|
453
|
+
render: (handler) => (
|
|
454
|
+
<UITypographyText code style={{ fontSize: 11 }}>
|
|
455
|
+
{handler}
|
|
456
|
+
</UITypographyText>
|
|
457
|
+
),
|
|
458
|
+
},
|
|
459
|
+
];
|
|
460
|
+
|
|
461
|
+
return (
|
|
462
|
+
<UICard>
|
|
463
|
+
<UITable
|
|
464
|
+
columns={columns}
|
|
465
|
+
dataSource={commands}
|
|
466
|
+
rowKey="id"
|
|
467
|
+
pagination={false}
|
|
468
|
+
expandable={{
|
|
469
|
+
expandedRowRender: (record) =>
|
|
470
|
+
record.flags && record.flags.length > 0 ? (
|
|
471
|
+
<div style={{ margin: 0, paddingLeft: 48 }}>
|
|
472
|
+
<UITitle level={5}>Flags</UITitle>
|
|
473
|
+
{record.flags.map((flag: any) => (
|
|
474
|
+
<div key={flag.name} style={{ marginBottom: 8 }}>
|
|
475
|
+
<UITypographyText code>--{flag.name}</UITypographyText>
|
|
476
|
+
{flag.alias && <UITypographyText type="secondary"> (-{flag.alias})</UITypographyText>}
|
|
477
|
+
{flag.required && <UITag color="red">Required</UITag>}
|
|
478
|
+
<UITag>{flag.type}</UITag>
|
|
479
|
+
{flag.default !== undefined && (
|
|
480
|
+
<UITag color="blue">
|
|
481
|
+
Default: {typeof flag.default === 'object' ? JSON.stringify(flag.default) : String(flag.default)}
|
|
482
|
+
</UITag>
|
|
483
|
+
)}
|
|
484
|
+
{flag.choices && flag.choices.length > 0 && (
|
|
485
|
+
<UITag color="cyan">
|
|
486
|
+
Choices: {flag.choices.join(', ')}
|
|
487
|
+
</UITag>
|
|
488
|
+
)}
|
|
489
|
+
<div>
|
|
490
|
+
<UITypographyText type="secondary">{flag.description || '--'}</UITypographyText>
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
))}
|
|
494
|
+
</div>
|
|
495
|
+
) : null,
|
|
496
|
+
}}
|
|
497
|
+
/>
|
|
498
|
+
</UICard>
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function RestRoutesTable({ routes, basePath, apiBasePath }: { routes: any[]; basePath?: string; apiBasePath?: string }) {
|
|
503
|
+
// Combine API base path with plugin base path, avoiding duplicate /v1
|
|
504
|
+
const fullBasePath = apiBasePath && basePath
|
|
505
|
+
? basePath.startsWith('/v1')
|
|
506
|
+
? apiBasePath + basePath.slice(3) // Remove /v1 from plugin basePath if API already has /api/v1
|
|
507
|
+
: apiBasePath + basePath
|
|
508
|
+
: basePath || '';
|
|
509
|
+
const columns: UITableColumn<any>[] = [
|
|
510
|
+
{
|
|
511
|
+
title: 'Method',
|
|
512
|
+
dataIndex: 'method',
|
|
513
|
+
key: 'method',
|
|
514
|
+
width: 100,
|
|
515
|
+
render: (method) => {
|
|
516
|
+
const colors: Record<string, string> = {
|
|
517
|
+
GET: 'green',
|
|
518
|
+
POST: 'blue',
|
|
519
|
+
PUT: 'orange',
|
|
520
|
+
PATCH: 'purple',
|
|
521
|
+
DELETE: 'red',
|
|
522
|
+
};
|
|
523
|
+
return <UITag color={colors[method] || 'default'}>{method}</UITag>;
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
title: 'Path',
|
|
528
|
+
dataIndex: 'path',
|
|
529
|
+
key: 'path',
|
|
530
|
+
render: (path) => (
|
|
531
|
+
<UITypographyText code>
|
|
532
|
+
{fullBasePath}{path}
|
|
533
|
+
</UITypographyText>
|
|
534
|
+
),
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
title: 'Description',
|
|
538
|
+
dataIndex: 'description',
|
|
539
|
+
key: 'description',
|
|
540
|
+
render: (desc) => (typeof desc === 'string' ? desc : desc ? JSON.stringify(desc) : '--'),
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
title: 'Timeout',
|
|
544
|
+
dataIndex: 'timeoutMs',
|
|
545
|
+
key: 'timeoutMs',
|
|
546
|
+
render: (timeout) => (timeout ? `${timeout}ms` : '--'),
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
title: 'Handler',
|
|
550
|
+
dataIndex: 'handler',
|
|
551
|
+
key: 'handler',
|
|
552
|
+
render: (handler) => (
|
|
553
|
+
<UITypographyText code style={{ fontSize: 11 }}>
|
|
554
|
+
{handler}
|
|
555
|
+
</UITypographyText>
|
|
556
|
+
),
|
|
557
|
+
},
|
|
558
|
+
];
|
|
559
|
+
|
|
560
|
+
const formatSchemaRef = (schema: any): string => {
|
|
561
|
+
if (!schema) {return '--';}
|
|
562
|
+
if (typeof schema === 'string') {return schema;}
|
|
563
|
+
if ('$ref' in schema) {return schema.$ref;}
|
|
564
|
+
if ('zod' in schema) {return schema.zod;}
|
|
565
|
+
return JSON.stringify(schema);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
return (
|
|
569
|
+
<UICard>
|
|
570
|
+
{fullBasePath && (
|
|
571
|
+
<UIAlert
|
|
572
|
+
message={
|
|
573
|
+
<span>
|
|
574
|
+
Base Path: <UITypographyText code>{fullBasePath}</UITypographyText>
|
|
575
|
+
</span>
|
|
576
|
+
}
|
|
577
|
+
variant="info"
|
|
578
|
+
style={{ marginBottom: 16 }}
|
|
579
|
+
/>
|
|
580
|
+
)}
|
|
581
|
+
<UITable
|
|
582
|
+
columns={columns}
|
|
583
|
+
dataSource={routes}
|
|
584
|
+
rowKey={(record) => `${record.method} ${record.path}`}
|
|
585
|
+
pagination={false}
|
|
586
|
+
expandable={{
|
|
587
|
+
expandedRowRender: (record) => {
|
|
588
|
+
const hasInput = record.input;
|
|
589
|
+
const hasOutput = record.output;
|
|
590
|
+
const hasErrors = record.errors && record.errors.length > 0;
|
|
591
|
+
|
|
592
|
+
if (!hasInput && !hasOutput && !hasErrors) {return null;}
|
|
593
|
+
|
|
594
|
+
return (
|
|
595
|
+
<div style={{ margin: 0, paddingLeft: 48 }}>
|
|
596
|
+
{hasInput && (
|
|
597
|
+
<div style={{ marginBottom: 16 }}>
|
|
598
|
+
<UITitle level={5}>
|
|
599
|
+
{record.method === 'GET' ? 'Query Parameters' : 'Request Body'}
|
|
600
|
+
</UITitle>
|
|
601
|
+
<UITypographyText code>{formatSchemaRef(record.input)}</UITypographyText>
|
|
602
|
+
</div>
|
|
603
|
+
)}
|
|
604
|
+
{hasOutput && (
|
|
605
|
+
<div style={{ marginBottom: 16 }}>
|
|
606
|
+
<UITitle level={5}>Response</UITitle>
|
|
607
|
+
<UITypographyText code>{formatSchemaRef(record.output)}</UITypographyText>
|
|
608
|
+
</div>
|
|
609
|
+
)}
|
|
610
|
+
{hasErrors && (
|
|
611
|
+
<div>
|
|
612
|
+
<UITitle level={5}>Error Responses</UITitle>
|
|
613
|
+
{record.errors.map((error: any, idx: number) => (
|
|
614
|
+
<div key={idx} style={{ marginBottom: 8 }}>
|
|
615
|
+
<UITag color="red">{error.code}</UITag>
|
|
616
|
+
<UITypographyText>{error.message || '--'}</UITypographyText>
|
|
617
|
+
</div>
|
|
618
|
+
))}
|
|
619
|
+
</div>
|
|
620
|
+
)}
|
|
621
|
+
</div>
|
|
622
|
+
);
|
|
623
|
+
},
|
|
624
|
+
rowExpandable: (record) => !!(record.input || record.output || (record.errors && record.errors.length > 0)),
|
|
625
|
+
}}
|
|
626
|
+
/>
|
|
627
|
+
</UICard>
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function WorkflowsTable({ handlers }: { handlers: any[] }) {
|
|
632
|
+
const columns: UITableColumn<any>[] = [
|
|
633
|
+
{
|
|
634
|
+
title: 'ID',
|
|
635
|
+
dataIndex: 'id',
|
|
636
|
+
key: 'id',
|
|
637
|
+
render: (id) => <UITypographyText code>{id}</UITypographyText>,
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
title: 'Description',
|
|
641
|
+
dataIndex: 'describe',
|
|
642
|
+
key: 'describe',
|
|
643
|
+
render: (desc) => (typeof desc === 'string' ? desc : desc ? JSON.stringify(desc) : '--'),
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
title: 'Handler',
|
|
647
|
+
dataIndex: 'handler',
|
|
648
|
+
key: 'handler',
|
|
649
|
+
render: (handler) => (
|
|
650
|
+
<UITypographyText code style={{ fontSize: 11 }}>
|
|
651
|
+
{handler}
|
|
652
|
+
</UITypographyText>
|
|
653
|
+
),
|
|
654
|
+
},
|
|
655
|
+
];
|
|
656
|
+
|
|
657
|
+
return (
|
|
658
|
+
<UICard>
|
|
659
|
+
<UITable columns={columns} dataSource={handlers} rowKey="id" pagination={false} />
|
|
660
|
+
</UICard>
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function JobsTable({ jobs }: { jobs: any[] }) {
|
|
665
|
+
const columns: UITableColumn<any>[] = [
|
|
666
|
+
{
|
|
667
|
+
title: 'ID',
|
|
668
|
+
dataIndex: 'id',
|
|
669
|
+
key: 'id',
|
|
670
|
+
render: (id) => <UITypographyText code>{id}</UITypographyText>,
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
title: 'Description',
|
|
674
|
+
dataIndex: 'describe',
|
|
675
|
+
key: 'describe',
|
|
676
|
+
render: (desc) => (typeof desc === 'string' ? desc : desc ? JSON.stringify(desc) : '--'),
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
title: 'Schedule',
|
|
680
|
+
dataIndex: 'schedule',
|
|
681
|
+
key: 'schedule',
|
|
682
|
+
render: (schedule) => (schedule ? <UITag color="orange">{schedule}</UITag> : <UITypographyText type="secondary">Manual</UITypographyText>),
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
title: 'Enabled',
|
|
686
|
+
dataIndex: 'enabled',
|
|
687
|
+
key: 'enabled',
|
|
688
|
+
render: (enabled) =>
|
|
689
|
+
enabled !== false ? <UITag color="green">Yes</UITag> : <UITag color="red">No</UITag>,
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
title: 'Priority',
|
|
693
|
+
dataIndex: 'priority',
|
|
694
|
+
key: 'priority',
|
|
695
|
+
render: (priority) => priority || 5,
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
title: 'Handler',
|
|
699
|
+
dataIndex: 'handler',
|
|
700
|
+
key: 'handler',
|
|
701
|
+
render: (handler) => (
|
|
702
|
+
<UITypographyText code style={{ fontSize: 11 }}>
|
|
703
|
+
{handler}
|
|
704
|
+
</UITypographyText>
|
|
705
|
+
),
|
|
706
|
+
},
|
|
707
|
+
];
|
|
708
|
+
|
|
709
|
+
return (
|
|
710
|
+
<UICard>
|
|
711
|
+
<UITable columns={columns} dataSource={jobs} rowKey="id" pagination={false} />
|
|
712
|
+
</UICard>
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function PermissionsView({ permissions }: { permissions: any }) {
|
|
717
|
+
if (!permissions) {
|
|
718
|
+
return (
|
|
719
|
+
<UICard>
|
|
720
|
+
<UIEmptyState description="No permissions specified for this plugin" />
|
|
721
|
+
</UICard>
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return (
|
|
726
|
+
<UISpace direction="vertical" size="large" style={{ width: '100%' }}>
|
|
727
|
+
{/* Filesystem Permissions */}
|
|
728
|
+
{permissions.fs && (
|
|
729
|
+
<UICard
|
|
730
|
+
title={
|
|
731
|
+
<UISpace>
|
|
732
|
+
<UIIcon name="LockOutlined" />
|
|
733
|
+
<span>Filesystem Permissions</span>
|
|
734
|
+
</UISpace>
|
|
735
|
+
}
|
|
736
|
+
>
|
|
737
|
+
<UITypographyParagraph type="secondary" style={{ marginBottom: 16 }}>
|
|
738
|
+
File and directory access patterns that this plugin is allowed to use. Read access allows viewing files, write access allows creating or modifying files.
|
|
739
|
+
</UITypographyParagraph>
|
|
740
|
+
<UISpace direction="vertical" size="middle" style={{ width: '100%' }}>
|
|
741
|
+
{permissions.fs.read && permissions.fs.read.length > 0 && (
|
|
742
|
+
<div>
|
|
743
|
+
<UITypographyText strong>Read Access:</UITypographyText>
|
|
744
|
+
<div style={{ marginTop: 8 }}>
|
|
745
|
+
{permissions.fs.read.map((path: string, idx: number) => (
|
|
746
|
+
<div key={idx} style={{ marginBottom: 4 }}>
|
|
747
|
+
<UITypographyText code style={{ fontSize: 12 }}>
|
|
748
|
+
{path}
|
|
749
|
+
</UITypographyText>
|
|
750
|
+
</div>
|
|
751
|
+
))}
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
)}
|
|
755
|
+
{permissions.fs.write && permissions.fs.write.length > 0 && (
|
|
756
|
+
<div>
|
|
757
|
+
<UITypographyText strong>Write Access:</UITypographyText>
|
|
758
|
+
<div style={{ marginTop: 8 }}>
|
|
759
|
+
{permissions.fs.write.map((path: string, idx: number) => (
|
|
760
|
+
<div key={idx} style={{ marginBottom: 4 }}>
|
|
761
|
+
<UITypographyText code style={{ fontSize: 12 }}>
|
|
762
|
+
{path}
|
|
763
|
+
</UITypographyText>
|
|
764
|
+
</div>
|
|
765
|
+
))}
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
)}
|
|
769
|
+
</UISpace>
|
|
770
|
+
</UICard>
|
|
771
|
+
)}
|
|
772
|
+
|
|
773
|
+
{/* Network Permissions */}
|
|
774
|
+
{permissions.network?.fetch && permissions.network.fetch.length > 0 && (
|
|
775
|
+
<UICard
|
|
776
|
+
title={
|
|
777
|
+
<UISpace>
|
|
778
|
+
<UIIcon name="GlobalOutlined" />
|
|
779
|
+
<span>Network Permissions</span>
|
|
780
|
+
</UISpace>
|
|
781
|
+
}
|
|
782
|
+
>
|
|
783
|
+
<UITypographyParagraph type="secondary" style={{ marginBottom: 16 }}>
|
|
784
|
+
External hosts and APIs that this plugin can communicate with over the network.
|
|
785
|
+
</UITypographyParagraph>
|
|
786
|
+
<div>
|
|
787
|
+
<UITypographyText strong>Allowed Hosts:</UITypographyText>
|
|
788
|
+
<div style={{ marginTop: 8 }}>
|
|
789
|
+
{permissions.network.fetch.map((host: string, idx: number) => (
|
|
790
|
+
<div key={idx} style={{ marginBottom: 4 }}>
|
|
791
|
+
<UITypographyText code style={{ fontSize: 12 }}>
|
|
792
|
+
{host}
|
|
793
|
+
</UITypographyText>
|
|
794
|
+
</div>
|
|
795
|
+
))}
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
</UICard>
|
|
799
|
+
)}
|
|
800
|
+
|
|
801
|
+
{/* Environment Variables */}
|
|
802
|
+
{permissions.env?.read && permissions.env.read.length > 0 && (
|
|
803
|
+
<UICard title="Environment Variables">
|
|
804
|
+
<UITypographyParagraph type="secondary" style={{ marginBottom: 16 }}>
|
|
805
|
+
System environment variables that this plugin can access. Used for configuration and secrets management.
|
|
806
|
+
</UITypographyParagraph>
|
|
807
|
+
<div>
|
|
808
|
+
<UITypographyText strong>Read Access:</UITypographyText>
|
|
809
|
+
<div style={{ marginTop: 8 }}>
|
|
810
|
+
<UISpace wrap>
|
|
811
|
+
{permissions.env.read.map((env: string, idx: number) => (
|
|
812
|
+
<UITag key={idx} color="cyan">
|
|
813
|
+
{env}
|
|
814
|
+
</UITag>
|
|
815
|
+
))}
|
|
816
|
+
</UISpace>
|
|
817
|
+
</div>
|
|
818
|
+
</div>
|
|
819
|
+
</UICard>
|
|
820
|
+
)}
|
|
821
|
+
|
|
822
|
+
{/* Shell Permissions */}
|
|
823
|
+
{permissions.shell && (
|
|
824
|
+
<UICard
|
|
825
|
+
title={
|
|
826
|
+
<UISpace>
|
|
827
|
+
<UIIcon name="ConsoleSqlOutlined" />
|
|
828
|
+
<span>Shell Permissions</span>
|
|
829
|
+
</UISpace>
|
|
830
|
+
}
|
|
831
|
+
>
|
|
832
|
+
<UITypographyParagraph type="secondary" style={{ marginBottom: 16 }}>
|
|
833
|
+
Shell commands that this plugin is allowed to execute. Shell access is restricted by default for security.
|
|
834
|
+
</UITypographyParagraph>
|
|
835
|
+
{permissions.shell.allow && permissions.shell.allow.length > 0 ? (
|
|
836
|
+
<div>
|
|
837
|
+
<UITypographyText strong>Allowed Commands:</UITypographyText>
|
|
838
|
+
<div style={{ marginTop: 8 }}>
|
|
839
|
+
<UISpace direction="vertical" style={{ width: '100%' }}>
|
|
840
|
+
{permissions.shell.allow.map((cmd: string, idx: number) => (
|
|
841
|
+
<div key={idx} style={{ marginBottom: 4 }}>
|
|
842
|
+
<UITypographyText code style={{ fontSize: 12 }}>
|
|
843
|
+
{cmd}
|
|
844
|
+
</UITypographyText>
|
|
845
|
+
</div>
|
|
846
|
+
))}
|
|
847
|
+
</UISpace>
|
|
848
|
+
</div>
|
|
849
|
+
</div>
|
|
850
|
+
) : (
|
|
851
|
+
<UIAlert
|
|
852
|
+
variant="warning"
|
|
853
|
+
message="Shell access disabled"
|
|
854
|
+
description="This plugin has no shell execution permissions."
|
|
855
|
+
showIcon
|
|
856
|
+
/>
|
|
857
|
+
)}
|
|
858
|
+
</UICard>
|
|
859
|
+
)}
|
|
860
|
+
|
|
861
|
+
{/* Platform Services */}
|
|
862
|
+
{permissions.platform && (
|
|
863
|
+
<UICard
|
|
864
|
+
title={
|
|
865
|
+
<UISpace>
|
|
866
|
+
<UIIcon name="DatabaseOutlined" />
|
|
867
|
+
<span>Platform Services</span>
|
|
868
|
+
</UISpace>
|
|
869
|
+
}
|
|
870
|
+
>
|
|
871
|
+
<UITypographyParagraph type="secondary" style={{ marginBottom: 16 }}>
|
|
872
|
+
Access to KB Labs platform services like LLM, vector store, cache, and analytics.
|
|
873
|
+
</UITypographyParagraph>
|
|
874
|
+
<UISpace direction="vertical" size="middle" style={{ width: '100%' }}>
|
|
875
|
+
<div
|
|
876
|
+
style={{
|
|
877
|
+
display: 'grid',
|
|
878
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
|
|
879
|
+
gap: 12,
|
|
880
|
+
}}
|
|
881
|
+
>
|
|
882
|
+
{/* LLM Service */}
|
|
883
|
+
{permissions.platform.llm !== undefined && (
|
|
884
|
+
<UICard size="small">
|
|
885
|
+
<UISpace direction="vertical" size="small">
|
|
886
|
+
<UISpace>
|
|
887
|
+
{permissions.platform.llm ? (
|
|
888
|
+
<UIIcon name="CheckCircleOutlined" style={{ color: '#52c41a' }} />
|
|
889
|
+
) : (
|
|
890
|
+
<UIIcon name="WarningOutlined" style={{ color: '#d9d9d9' }} />
|
|
891
|
+
)}
|
|
892
|
+
<UITypographyText strong>LLM Service</UITypographyText>
|
|
893
|
+
</UISpace>
|
|
894
|
+
{typeof permissions.platform.llm === 'object' && permissions.platform.llm.models && (
|
|
895
|
+
<div>
|
|
896
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>Models:</UITypographyText>
|
|
897
|
+
<div style={{ marginTop: 4 }}>
|
|
898
|
+
<UISpace wrap size="small">
|
|
899
|
+
{permissions.platform.llm.models.map((model: string, idx: number) => (
|
|
900
|
+
<UITag key={idx} color="blue" style={{ fontSize: 11 }}>
|
|
901
|
+
{model}
|
|
902
|
+
</UITag>
|
|
903
|
+
))}
|
|
904
|
+
</UISpace>
|
|
905
|
+
</div>
|
|
906
|
+
</div>
|
|
907
|
+
)}
|
|
908
|
+
</UISpace>
|
|
909
|
+
</UICard>
|
|
910
|
+
)}
|
|
911
|
+
|
|
912
|
+
{/* Vector Store */}
|
|
913
|
+
{permissions.platform.vectorStore !== undefined && (
|
|
914
|
+
<UICard size="small">
|
|
915
|
+
<UISpace direction="vertical" size="small">
|
|
916
|
+
<UISpace>
|
|
917
|
+
{permissions.platform.vectorStore ? (
|
|
918
|
+
<UIIcon name="CheckCircleOutlined" style={{ color: '#52c41a' }} />
|
|
919
|
+
) : (
|
|
920
|
+
<UIIcon name="WarningOutlined" style={{ color: '#d9d9d9' }} />
|
|
921
|
+
)}
|
|
922
|
+
<UITypographyText strong>Vector Store</UITypographyText>
|
|
923
|
+
</UISpace>
|
|
924
|
+
{typeof permissions.platform.vectorStore === 'object' && permissions.platform.vectorStore.collections && (
|
|
925
|
+
<div>
|
|
926
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>Collections:</UITypographyText>
|
|
927
|
+
<div style={{ marginTop: 4 }}>
|
|
928
|
+
<UISpace wrap size="small">
|
|
929
|
+
{permissions.platform.vectorStore.collections.map((col: string, idx: number) => (
|
|
930
|
+
<UITag key={idx} color="purple" style={{ fontSize: 11 }}>
|
|
931
|
+
{col}
|
|
932
|
+
</UITag>
|
|
933
|
+
))}
|
|
934
|
+
</UISpace>
|
|
935
|
+
</div>
|
|
936
|
+
</div>
|
|
937
|
+
)}
|
|
938
|
+
</UISpace>
|
|
939
|
+
</UICard>
|
|
940
|
+
)}
|
|
941
|
+
|
|
942
|
+
{/* Cache */}
|
|
943
|
+
{permissions.platform.cache !== undefined && (
|
|
944
|
+
<UICard size="small">
|
|
945
|
+
<UISpace direction="vertical" size="small">
|
|
946
|
+
<UISpace>
|
|
947
|
+
{permissions.platform.cache ? (
|
|
948
|
+
<UIIcon name="CheckCircleOutlined" style={{ color: '#52c41a' }} />
|
|
949
|
+
) : (
|
|
950
|
+
<UIIcon name="WarningOutlined" style={{ color: '#d9d9d9' }} />
|
|
951
|
+
)}
|
|
952
|
+
<UITypographyText strong>Cache</UITypographyText>
|
|
953
|
+
</UISpace>
|
|
954
|
+
{typeof permissions.platform.cache === 'object' && permissions.platform.cache.namespaces && (
|
|
955
|
+
<div>
|
|
956
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>Namespaces:</UITypographyText>
|
|
957
|
+
<div style={{ marginTop: 4 }}>
|
|
958
|
+
<UISpace wrap size="small">
|
|
959
|
+
{permissions.platform.cache.namespaces.map((ns: string, idx: number) => (
|
|
960
|
+
<UITag key={idx} color="cyan" style={{ fontSize: 11 }}>
|
|
961
|
+
{ns}
|
|
962
|
+
</UITag>
|
|
963
|
+
))}
|
|
964
|
+
</UISpace>
|
|
965
|
+
</div>
|
|
966
|
+
</div>
|
|
967
|
+
)}
|
|
968
|
+
</UISpace>
|
|
969
|
+
</UICard>
|
|
970
|
+
)}
|
|
971
|
+
|
|
972
|
+
{/* Storage */}
|
|
973
|
+
{permissions.platform.storage !== undefined && (
|
|
974
|
+
<UICard size="small">
|
|
975
|
+
<UISpace direction="vertical" size="small">
|
|
976
|
+
<UISpace>
|
|
977
|
+
{permissions.platform.storage ? (
|
|
978
|
+
<UIIcon name="CheckCircleOutlined" style={{ color: '#52c41a' }} />
|
|
979
|
+
) : (
|
|
980
|
+
<UIIcon name="WarningOutlined" style={{ color: '#d9d9d9' }} />
|
|
981
|
+
)}
|
|
982
|
+
<UITypographyText strong>Storage</UITypographyText>
|
|
983
|
+
</UISpace>
|
|
984
|
+
{typeof permissions.platform.storage === 'object' && permissions.platform.storage.paths && (
|
|
985
|
+
<div>
|
|
986
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>Paths:</UITypographyText>
|
|
987
|
+
<div style={{ marginTop: 4 }}>
|
|
988
|
+
<UISpace wrap size="small">
|
|
989
|
+
{permissions.platform.storage.paths.map((path: string, idx: number) => (
|
|
990
|
+
<UITag key={idx} color="orange" style={{ fontSize: 11 }}>
|
|
991
|
+
{path}
|
|
992
|
+
</UITag>
|
|
993
|
+
))}
|
|
994
|
+
</UISpace>
|
|
995
|
+
</div>
|
|
996
|
+
</div>
|
|
997
|
+
)}
|
|
998
|
+
</UISpace>
|
|
999
|
+
</UICard>
|
|
1000
|
+
)}
|
|
1001
|
+
|
|
1002
|
+
{/* Analytics */}
|
|
1003
|
+
{permissions.platform.analytics !== undefined && (
|
|
1004
|
+
<UICard size="small">
|
|
1005
|
+
<UISpace>
|
|
1006
|
+
{permissions.platform.analytics ? (
|
|
1007
|
+
<UIIcon name="CheckCircleOutlined" style={{ color: '#52c41a' }} />
|
|
1008
|
+
) : (
|
|
1009
|
+
<UIIcon name="WarningOutlined" style={{ color: '#d9d9d9' }} />
|
|
1010
|
+
)}
|
|
1011
|
+
<UITypographyText strong>Analytics</UITypographyText>
|
|
1012
|
+
</UISpace>
|
|
1013
|
+
</UICard>
|
|
1014
|
+
)}
|
|
1015
|
+
|
|
1016
|
+
{/* Embeddings */}
|
|
1017
|
+
{permissions.platform.embeddings !== undefined && (
|
|
1018
|
+
<UICard size="small">
|
|
1019
|
+
<UISpace>
|
|
1020
|
+
{permissions.platform.embeddings ? (
|
|
1021
|
+
<UIIcon name="CheckCircleOutlined" style={{ color: '#52c41a' }} />
|
|
1022
|
+
) : (
|
|
1023
|
+
<UIIcon name="WarningOutlined" style={{ color: '#d9d9d9' }} />
|
|
1024
|
+
)}
|
|
1025
|
+
<UITypographyText strong>Embeddings</UITypographyText>
|
|
1026
|
+
</UISpace>
|
|
1027
|
+
</UICard>
|
|
1028
|
+
)}
|
|
1029
|
+
|
|
1030
|
+
{/* Events */}
|
|
1031
|
+
{permissions.platform.events !== undefined && (
|
|
1032
|
+
<UICard size="small">
|
|
1033
|
+
<UISpace direction="vertical" size="small">
|
|
1034
|
+
<UISpace>
|
|
1035
|
+
{permissions.platform.events ? (
|
|
1036
|
+
<UIIcon name="CheckCircleOutlined" style={{ color: '#52c41a' }} />
|
|
1037
|
+
) : (
|
|
1038
|
+
<UIIcon name="WarningOutlined" style={{ color: '#d9d9d9' }} />
|
|
1039
|
+
)}
|
|
1040
|
+
<UITypographyText strong>Event Bus</UITypographyText>
|
|
1041
|
+
</UISpace>
|
|
1042
|
+
{typeof permissions.platform.events === 'object' && (
|
|
1043
|
+
<div>
|
|
1044
|
+
{permissions.platform.events.publish && permissions.platform.events.publish.length > 0 && (
|
|
1045
|
+
<div style={{ marginBottom: 8 }}>
|
|
1046
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>Publish:</UITypographyText>
|
|
1047
|
+
<div style={{ marginTop: 4 }}>
|
|
1048
|
+
<UISpace wrap size="small">
|
|
1049
|
+
{permissions.platform.events.publish.map((evt: string, idx: number) => (
|
|
1050
|
+
<UITag key={idx} color="green" style={{ fontSize: 11 }}>
|
|
1051
|
+
{evt}
|
|
1052
|
+
</UITag>
|
|
1053
|
+
))}
|
|
1054
|
+
</UISpace>
|
|
1055
|
+
</div>
|
|
1056
|
+
</div>
|
|
1057
|
+
)}
|
|
1058
|
+
{permissions.platform.events.subscribe && permissions.platform.events.subscribe.length > 0 && (
|
|
1059
|
+
<div>
|
|
1060
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>Subscribe:</UITypographyText>
|
|
1061
|
+
<div style={{ marginTop: 4 }}>
|
|
1062
|
+
<UISpace wrap size="small">
|
|
1063
|
+
{permissions.platform.events.subscribe.map((evt: string, idx: number) => (
|
|
1064
|
+
<UITag key={idx} color="blue" style={{ fontSize: 11 }}>
|
|
1065
|
+
{evt}
|
|
1066
|
+
</UITag>
|
|
1067
|
+
))}
|
|
1068
|
+
</UISpace>
|
|
1069
|
+
</div>
|
|
1070
|
+
</div>
|
|
1071
|
+
)}
|
|
1072
|
+
</div>
|
|
1073
|
+
)}
|
|
1074
|
+
</UISpace>
|
|
1075
|
+
</UICard>
|
|
1076
|
+
)}
|
|
1077
|
+
</div>
|
|
1078
|
+
</UISpace>
|
|
1079
|
+
</UICard>
|
|
1080
|
+
)}
|
|
1081
|
+
|
|
1082
|
+
{/* Quotas */}
|
|
1083
|
+
{permissions.quotas && (
|
|
1084
|
+
<UICard title="Resource Quotas">
|
|
1085
|
+
<UITypographyParagraph type="secondary" style={{ marginBottom: 16 }}>
|
|
1086
|
+
Maximum resource limits for this plugin to prevent runaway processes and ensure system stability.
|
|
1087
|
+
</UITypographyParagraph>
|
|
1088
|
+
<div
|
|
1089
|
+
style={{
|
|
1090
|
+
display: 'grid',
|
|
1091
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
|
1092
|
+
gap: 16,
|
|
1093
|
+
}}
|
|
1094
|
+
>
|
|
1095
|
+
{permissions.quotas.timeoutMs && (
|
|
1096
|
+
<UICard size="small">
|
|
1097
|
+
<div style={{ textAlign: 'center' }}>
|
|
1098
|
+
<UITypographyText type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 8 }}>
|
|
1099
|
+
Timeout
|
|
1100
|
+
</UITypographyText>
|
|
1101
|
+
<UITypographyText strong style={{ fontSize: 20 }}>
|
|
1102
|
+
{permissions.quotas.timeoutMs}
|
|
1103
|
+
</UITypographyText>
|
|
1104
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>
|
|
1105
|
+
ms
|
|
1106
|
+
</UITypographyText>
|
|
1107
|
+
</div>
|
|
1108
|
+
</UICard>
|
|
1109
|
+
)}
|
|
1110
|
+
{permissions.quotas.memoryMb && (
|
|
1111
|
+
<UICard size="small">
|
|
1112
|
+
<div style={{ textAlign: 'center' }}>
|
|
1113
|
+
<UITypographyText type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 8 }}>
|
|
1114
|
+
Memory
|
|
1115
|
+
</UITypographyText>
|
|
1116
|
+
<UITypographyText strong style={{ fontSize: 20 }}>
|
|
1117
|
+
{permissions.quotas.memoryMb}
|
|
1118
|
+
</UITypographyText>
|
|
1119
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>
|
|
1120
|
+
MB
|
|
1121
|
+
</UITypographyText>
|
|
1122
|
+
</div>
|
|
1123
|
+
</UICard>
|
|
1124
|
+
)}
|
|
1125
|
+
{permissions.quotas.cpuMs && (
|
|
1126
|
+
<UICard size="small">
|
|
1127
|
+
<div style={{ textAlign: 'center' }}>
|
|
1128
|
+
<UITypographyText type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 8 }}>
|
|
1129
|
+
CPU Time
|
|
1130
|
+
</UITypographyText>
|
|
1131
|
+
<UITypographyText strong style={{ fontSize: 20 }}>
|
|
1132
|
+
{permissions.quotas.cpuMs}
|
|
1133
|
+
</UITypographyText>
|
|
1134
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>
|
|
1135
|
+
ms
|
|
1136
|
+
</UITypographyText>
|
|
1137
|
+
</div>
|
|
1138
|
+
</UICard>
|
|
1139
|
+
)}
|
|
1140
|
+
</div>
|
|
1141
|
+
</UICard>
|
|
1142
|
+
)}
|
|
1143
|
+
</UISpace>
|
|
1144
|
+
);
|
|
1145
|
+
}
|