@skyhook-io/radar-app 1.3.1 → 1.3.3
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/package.json +1 -1
- package/src/App.tsx +111 -58
- package/src/api/client.ts +29 -1
- package/src/components/ConnectionErrorView.tsx +2 -2
- package/src/components/gitops/GitOpsView.tsx +127 -27
- package/src/components/helm/ChartBrowser.tsx +7 -3
- package/src/components/helm/HelmReleaseDrawer.tsx +4 -6
- package/src/components/helm/InstallWizard.tsx +1 -1
- package/src/components/helm/RoleGatedPanel.tsx +2 -2
- package/src/components/home/ClusterHealthCard.tsx +1 -1
- package/src/components/home/GitOpsControllersCard.tsx +14 -12
- package/src/components/home/HomeView.tsx +84 -56
- package/src/components/home/MCPSetupDialog.tsx +20 -86
- package/src/components/home/mcpToolCatalog.ts +276 -0
- package/src/components/issues/IssuesPane.tsx +78 -0
- package/src/components/portforward/PortForwardButton.tsx +1 -1
- package/src/components/portforward/PortForwardManager.tsx +1 -1
- package/src/components/resource/PrometheusCharts.tsx +18 -159
- package/src/components/resources/ImageFilesystemModal.tsx +1 -2
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +5 -3
- package/src/components/resources/renderers/WorkloadRenderer.tsx +6 -2
- package/src/components/settings/MyPermissionsDialog.tsx +1 -1
- package/src/components/settings/SettingsDialog.tsx +22 -2
- package/src/components/timeline/TimelineSwimlanes.tsx +8 -1311
- package/src/components/ui/Markdown.tsx +1 -1
- package/src/components/ui/UpdateNotification.tsx +1 -1
- package/src/components/workload/WorkloadView.tsx +190 -7
|
@@ -41,21 +41,23 @@ export function GitOpsControllersCard({ data, onNavigate }: GitOpsControllersCar
|
|
|
41
41
|
<button
|
|
42
42
|
type="button"
|
|
43
43
|
onClick={onNavigate}
|
|
44
|
-
className="
|
|
44
|
+
className="group h-[260px] rounded-xl bg-theme-surface shadow-theme-sm hover:-translate-y-1 hover:shadow-theme-md transition-all duration-200 text-left animate-fade-in-up"
|
|
45
45
|
>
|
|
46
|
-
<div className="flex
|
|
47
|
-
<div className="flex items-center
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
<div className="flex flex-col h-full w-full">
|
|
47
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50">
|
|
48
|
+
<div className="flex items-center gap-2">
|
|
49
|
+
<GitBranch className={clsx('h-4 w-4', headerTone)} />
|
|
50
|
+
<span className={clsx('text-xs font-semibold uppercase tracking-wider', headerTone)}>
|
|
51
|
+
GitOps Controllers
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
<span className={clsx('text-[11px] font-medium', headerTone)}>{headerLabel}</span>
|
|
52
55
|
</div>
|
|
53
|
-
<span className={clsx('text-[11px] font-medium', headerTone)}>{headerLabel}</span>
|
|
54
|
-
</div>
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
<div className="flex-1 min-h-0 overflow-y-auto px-5 py-3 flex flex-col gap-3">
|
|
58
|
+
{argo.length > 0 && <ControllerSection label="Argo CD" controllers={argo} />}
|
|
59
|
+
{flux.length > 0 && <ControllerSection label="Flux CD" controllers={flux} />}
|
|
60
|
+
</div>
|
|
59
61
|
</div>
|
|
60
62
|
</button>
|
|
61
63
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
1
|
+
import { useMemo, type ReactNode } from 'react'
|
|
2
2
|
import { useDashboard, useDashboardCRDs, useDashboardHelm } from '../../api/client'
|
|
3
3
|
import type { DashboardResponse } from '../../api/client'
|
|
4
4
|
import type { ExtendedMainView, Topology, SelectedResource } from '../../types'
|
|
@@ -113,77 +113,88 @@ export function HomeView({ namespaces, topology, onNavigateToView, onNavigateToR
|
|
|
113
113
|
)}>
|
|
114
114
|
{/* Left column: teaser cards */}
|
|
115
115
|
<div className="flex flex-col gap-6 auto-rows-min">
|
|
116
|
-
{/*
|
|
116
|
+
{/* Live band — Topology + Timeline always render, so a fixed 2-up never strands.
|
|
117
|
+
These are the richest visuals and the most-used live views, so they get the width. */}
|
|
117
118
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
|
118
119
|
<TopologyPreview
|
|
119
120
|
topology={scopedTopology}
|
|
120
121
|
summary={data.topologySummary}
|
|
121
122
|
onNavigate={() => onNavigateToView('topology')}
|
|
122
123
|
/>
|
|
123
|
-
<HelmSummary
|
|
124
|
-
data={helmData}
|
|
125
|
-
onNavigate={() => onNavigateToView('helm')}
|
|
126
|
-
/>
|
|
127
124
|
<ActivitySummary
|
|
128
125
|
namespaces={namespaces}
|
|
129
126
|
topology={scopedTopology}
|
|
130
127
|
onNavigate={() => onNavigateToView('timeline')}
|
|
131
128
|
/>
|
|
132
|
-
<TrafficSummary
|
|
133
|
-
data={data.trafficSummary}
|
|
134
|
-
onNavigate={() => onNavigateToView('traffic')}
|
|
135
|
-
/>
|
|
136
|
-
<CostCard onNavigate={() => onNavigateToView('cost')} />
|
|
137
129
|
</div>
|
|
138
130
|
|
|
139
|
-
{/*
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
131
|
+
{/* Explore band — flex-grow wrap so the row always fills. The conditional
|
|
132
|
+
Cost card self-hides via BandItem's empty:hidden when OpenCost is absent,
|
|
133
|
+
leaving Traffic + Helm to stretch rather than stranding an empty cell. */}
|
|
134
|
+
<div className="flex flex-wrap gap-6">
|
|
135
|
+
<BandItem>
|
|
136
|
+
<TrafficSummary
|
|
137
|
+
data={data.trafficSummary}
|
|
138
|
+
onNavigate={() => onNavigateToView('traffic')}
|
|
139
|
+
/>
|
|
140
|
+
</BandItem>
|
|
141
|
+
<BandItem>
|
|
142
|
+
<HelmSummary
|
|
143
|
+
data={helmData}
|
|
144
|
+
onNavigate={() => onNavigateToView('helm')}
|
|
145
|
+
/>
|
|
146
|
+
</BandItem>
|
|
147
|
+
<BandItem>
|
|
148
|
+
<CostCard onNavigate={() => onNavigateToView('cost')} />
|
|
149
|
+
</BandItem>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{/* Posture band — same flex-grow wrap so any subset of compliance cards
|
|
153
|
+
fills its row instead of stranding the last one (the old 3-col grid
|
|
154
|
+
left Cluster Audit alone with two empty cells beside it). */}
|
|
155
|
+
{(data.certificateHealth || data.networkPolicyCoverage || data.audit || data.gitopsControllers) && (
|
|
156
|
+
<div className="flex flex-wrap gap-6">
|
|
157
|
+
{data.certificateHealth && (
|
|
158
|
+
<BandItem>
|
|
159
|
+
<CertificateHealthCard
|
|
160
|
+
data={data.certificateHealth}
|
|
161
|
+
onNavigate={() => onNavigateToResourceKind('secrets', undefined, { type: ['TLS'] })}
|
|
162
|
+
/>
|
|
163
|
+
</BandItem>
|
|
164
|
+
)}
|
|
165
|
+
{data.networkPolicyCoverage && (
|
|
166
|
+
<BandItem>
|
|
167
|
+
<NetworkPolicyCoverageCard
|
|
168
|
+
data={data.networkPolicyCoverage}
|
|
169
|
+
onNavigate={() => onNavigateToResourceKind('networkpolicies', 'networking.k8s.io')}
|
|
170
|
+
/>
|
|
171
|
+
</BandItem>
|
|
172
|
+
)}
|
|
173
|
+
{data.gitopsControllers && (
|
|
174
|
+
<BandItem>
|
|
175
|
+
<GitOpsControllersCard
|
|
176
|
+
data={data.gitopsControllers}
|
|
177
|
+
onNavigate={() => onNavigateToView('gitops')}
|
|
178
|
+
/>
|
|
179
|
+
</BandItem>
|
|
180
|
+
)}
|
|
181
|
+
{data.audit && (
|
|
182
|
+
<BandItem>
|
|
183
|
+
<AuditCard
|
|
184
|
+
data={data.audit}
|
|
185
|
+
onNavigate={() => onNavigateToView('audit')}
|
|
186
|
+
/>
|
|
187
|
+
</BandItem>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
181
191
|
</div>
|
|
182
192
|
|
|
183
193
|
{/* Right column: problems panel */}
|
|
184
194
|
{hasProblems && (
|
|
185
195
|
<ProblemsPanel
|
|
186
196
|
problems={data.problems}
|
|
197
|
+
onNavigateToIssues={() => onNavigateToView('issues')}
|
|
187
198
|
onResourceClick={onNavigateToResource}
|
|
188
199
|
/>
|
|
189
200
|
)}
|
|
@@ -193,25 +204,42 @@ export function HomeView({ namespaces, topology, onNavigateToView, onNavigateToR
|
|
|
193
204
|
)
|
|
194
205
|
}
|
|
195
206
|
|
|
207
|
+
// A self-tiling flex item: grows to share the row, clamps to a sensible min
|
|
208
|
+
// width, and removes itself (empty:hidden) when its card renders null — so a
|
|
209
|
+
// data-gated card (e.g. Cost without OpenCost) can't leave a phantom column.
|
|
210
|
+
function BandItem({ children }: { children: ReactNode }) {
|
|
211
|
+
return <div className="flex-1 min-w-[260px] empty:hidden [&>*]:w-full">{children}</div>
|
|
212
|
+
}
|
|
213
|
+
|
|
196
214
|
// ============================================================================
|
|
197
215
|
// Problems Panel (right sidebar, scrollable)
|
|
198
216
|
// ============================================================================
|
|
199
217
|
|
|
200
218
|
interface ProblemsPanelProps {
|
|
201
219
|
problems: DashboardResponse['problems']
|
|
220
|
+
onNavigateToIssues: () => void
|
|
202
221
|
onResourceClick: (resource: SelectedResource) => void
|
|
203
222
|
}
|
|
204
223
|
|
|
205
224
|
|
|
206
|
-
function ProblemsPanel({ problems, onResourceClick }: ProblemsPanelProps) {
|
|
225
|
+
function ProblemsPanel({ problems, onNavigateToIssues, onResourceClick }: ProblemsPanelProps) {
|
|
207
226
|
return (
|
|
208
227
|
<div className="rounded-xl bg-theme-surface shadow-theme-sm flex flex-col lg:max-h-[calc(100vh-280px)] lg:sticky lg:top-0">
|
|
209
228
|
<div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50 shrink-0">
|
|
210
229
|
<div className="flex items-center gap-2">
|
|
211
230
|
<AlertTriangle className="w-4 h-4 text-red-500" />
|
|
212
|
-
<span className="text-xs font-semibold uppercase tracking-wider text-red-500">
|
|
231
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-red-500">Active Issues</span>
|
|
232
|
+
</div>
|
|
233
|
+
<div className="flex items-center gap-2">
|
|
234
|
+
<button
|
|
235
|
+
type="button"
|
|
236
|
+
className="rounded-md px-2 py-1 text-xs font-medium text-accent-text transition-colors hover:bg-accent-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-radar-accent)]/40"
|
|
237
|
+
onClick={onNavigateToIssues}
|
|
238
|
+
>
|
|
239
|
+
View all
|
|
240
|
+
</button>
|
|
241
|
+
<span className="badge status-unhealthy rounded-full">{problems.length}</span>
|
|
213
242
|
</div>
|
|
214
|
-
<span className="badge status-unhealthy rounded-full">{problems.length}</span>
|
|
215
243
|
</div>
|
|
216
244
|
<div className="overflow-y-auto flex-1 min-h-0">
|
|
217
245
|
<div className="divide-y divide-theme-border">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useRef, useEffect, useState, useCallback } from 'react'
|
|
2
2
|
import { X, Copy, Check, Radio, Terminal, MessageSquare, Code2, ChevronRight, Pin } from 'lucide-react'
|
|
3
3
|
import { apiUrl, getAuthHeaders, getCredentialsMode } from '../../api/config'
|
|
4
|
+
import { MCP_TOOL_CATALOG } from './mcpToolCatalog'
|
|
4
5
|
|
|
5
6
|
interface MCPSetupDialogProps {
|
|
6
7
|
open: boolean
|
|
@@ -208,8 +209,9 @@ export function MCPSetupDialog({ open, onClose, mcpUrl }: MCPSetupDialogProps) {
|
|
|
208
209
|
verbose YAML output.
|
|
209
210
|
</p>
|
|
210
211
|
<p className="text-sm text-theme-text-secondary leading-relaxed">
|
|
211
|
-
Read tools are strictly read-only. Write tools (restart, scale, sync
|
|
212
|
-
|
|
212
|
+
Read tools are strictly read-only. Write tools (restart, scale, sync, apply,
|
|
213
|
+
node drain) are annotated as destructive so your AI client can flag them and
|
|
214
|
+
prompt before running.
|
|
213
215
|
</p>
|
|
214
216
|
</div>
|
|
215
217
|
|
|
@@ -219,7 +221,7 @@ export function MCPSetupDialog({ open, onClose, mcpUrl }: MCPSetupDialogProps) {
|
|
|
219
221
|
<div className="relative">
|
|
220
222
|
<div className="flex items-center gap-3 bg-theme-base rounded-md px-3 py-2.5">
|
|
221
223
|
<span className="badge text-purple-400 bg-purple-500/10">HTTP</span>
|
|
222
|
-
<code className="
|
|
224
|
+
<code className="inline-code text-sm">{mcpUrl}</code>
|
|
223
225
|
</div>
|
|
224
226
|
<CopyButton text={mcpUrl} />
|
|
225
227
|
</div>
|
|
@@ -296,95 +298,27 @@ export function MCPSetupDialog({ open, onClose, mcpUrl }: MCPSetupDialogProps) {
|
|
|
296
298
|
|
|
297
299
|
{/* Available tools */}
|
|
298
300
|
<div className="space-y-2">
|
|
299
|
-
<
|
|
301
|
+
<div className="flex items-baseline justify-between">
|
|
302
|
+
<h4 className="text-sm font-semibold text-theme-text-primary">Tools</h4>
|
|
303
|
+
<span className="text-[11px] text-theme-text-tertiary">{MCP_TOOL_CATALOG.length} tools</span>
|
|
304
|
+
</div>
|
|
300
305
|
<div className="grid grid-cols-1 gap-1.5">
|
|
301
|
-
{
|
|
302
|
-
{ name: 'get_dashboard', desc: 'Get cluster health overview including resource counts, problems (failing pods, unhealthy deployments), recent warning events, and Helm release status. Start here to understand cluster state before drilling into specific resources.', params: [
|
|
303
|
-
{ name: 'namespace', required: false, desc: 'filter to a specific namespace' },
|
|
304
|
-
]},
|
|
305
|
-
{ name: 'list_resources', desc: 'List Kubernetes resources of a given kind with minified summaries. Supports all built-in kinds (pods, deployments, services, etc.) and CRDs. Use to discover what\'s running before inspecting individual resources.', params: [
|
|
306
|
-
{ name: 'kind', required: true, desc: 'resource kind, e.g. pods, deployments, services' },
|
|
307
|
-
{ name: 'namespace', required: false, desc: 'filter to a specific namespace' },
|
|
308
|
-
]},
|
|
309
|
-
{ name: 'get_resource', desc: 'Get a single Kubernetes resource: minified spec/status/metadata plus default-on resourceContext (managedBy, exposes, selectedBy, uses, runsOn, issue/audit rollups). Optionally include heavier sidecars (events, metrics, logs).', params: [
|
|
310
|
-
{ name: 'kind', required: true, desc: 'resource kind, e.g. pod, deployment, service' },
|
|
311
|
-
{ name: 'namespace', required: false, desc: 'omit for cluster-scoped kinds (Node, ClusterRole, IngressClass, etc.)' },
|
|
312
|
-
{ name: 'name', required: true, desc: 'resource name' },
|
|
313
|
-
{ name: 'group', required: false, desc: 'API group when the kind is ambiguous (e.g. serving.knative.dev for Knative Service vs core Service)' },
|
|
314
|
-
{ name: 'include', required: false, desc: 'events, metrics, logs' },
|
|
315
|
-
{ name: 'context', required: false, desc: 'resourceContext tier: basic (default) or none (bare minified)' },
|
|
316
|
-
]},
|
|
317
|
-
{ name: 'get_topology', desc: 'Get the topology graph showing relationships between Kubernetes resources. Returns nodes and edges representing Deployments, Services, Ingresses, Pods, etc. Use \'traffic\' view for network flow or \'resources\' view for ownership hierarchy. Use \'summary\' format for LLM-friendly text descriptions.', params: [
|
|
318
|
-
{ name: 'namespace', required: false, desc: 'filter to a specific namespace' },
|
|
319
|
-
{ name: 'view', required: false, desc: 'traffic or resources' },
|
|
320
|
-
{ name: 'format', required: false, desc: 'graph (default) or summary (text)' },
|
|
321
|
-
]},
|
|
322
|
-
{ name: 'get_events', desc: 'Get recent Kubernetes warning events, deduplicated and sorted by recency. Useful for diagnosing issues — shows event reason, message, and occurrence count. Filter by resource kind/name to scope to a specific resource.', params: [
|
|
323
|
-
{ name: 'namespace', required: false, desc: 'filter to a specific namespace' },
|
|
324
|
-
{ name: 'limit', required: false, desc: 'max events to return (default 20)' },
|
|
325
|
-
{ name: 'kind', required: false, desc: 'filter to events for this resource kind' },
|
|
326
|
-
{ name: 'name', required: false, desc: 'filter to events for this resource name' },
|
|
327
|
-
]},
|
|
328
|
-
{ name: 'get_pod_logs', desc: 'Get filtered log lines from a pod, prioritizing errors and warnings. Returns diagnostically relevant lines (errors, panics, stack traces) or falls back to the last 20 lines if no error patterns match.', params: [
|
|
329
|
-
{ name: 'namespace', required: true, desc: 'pod namespace' },
|
|
330
|
-
{ name: 'name', required: true, desc: 'pod name' },
|
|
331
|
-
{ name: 'container', required: false, desc: 'container name (defaults to first)' },
|
|
332
|
-
{ name: 'tail_lines', required: false, desc: 'lines from end (default 200)' },
|
|
333
|
-
]},
|
|
334
|
-
{ name: 'list_namespaces', desc: 'List all Kubernetes namespaces with their status. Use to discover available namespaces before filtering other queries.', params: [] },
|
|
335
|
-
{ name: 'get_changes', desc: 'Get recent resource changes (creates, updates, deletes) from the cluster timeline. Use to investigate what changed before an incident. Filter by namespace, resource kind, or specific resource name.', params: [
|
|
336
|
-
{ name: 'namespace', required: false, desc: 'filter to a specific namespace' },
|
|
337
|
-
{ name: 'kind', required: false, desc: 'filter to a resource kind (e.g. Deployment)' },
|
|
338
|
-
{ name: 'name', required: false, desc: 'filter to a specific resource name' },
|
|
339
|
-
{ name: 'since', required: false, desc: 'lookback duration, e.g. 1h, 30m (default 1h)' },
|
|
340
|
-
{ name: 'limit', required: false, desc: 'max changes to return (default 20, max 50)' },
|
|
341
|
-
]},
|
|
342
|
-
{ name: 'list_helm_releases', desc: 'List all Helm releases in the cluster with their status and health. Returns release name, namespace, chart, version, status, and resource health.', params: [
|
|
343
|
-
{ name: 'namespace', required: false, desc: 'filter to a specific namespace' },
|
|
344
|
-
]},
|
|
345
|
-
{ name: 'get_helm_release', desc: 'Get detailed information about a specific Helm release including owned resources and their status. Optionally include values, revision history, or manifest diff between revisions.', params: [
|
|
346
|
-
{ name: 'namespace', required: true, desc: 'release namespace' },
|
|
347
|
-
{ name: 'name', required: true, desc: 'release name' },
|
|
348
|
-
{ name: 'include', required: false, desc: 'values, history, diff' },
|
|
349
|
-
{ name: 'diff_revision_1', required: false, desc: 'first revision for diff' },
|
|
350
|
-
{ name: 'diff_revision_2', required: false, desc: 'second revision for diff (defaults to current)' },
|
|
351
|
-
]},
|
|
352
|
-
{ name: 'get_workload_logs', desc: 'Get aggregated, AI-filtered logs from all pods of a workload (Deployment, StatefulSet, or DaemonSet). Logs are collected from all matching pods, filtered for errors/warnings, and deduplicated.', params: [
|
|
353
|
-
{ name: 'kind', required: true, desc: 'deployment, statefulset, or daemonset' },
|
|
354
|
-
{ name: 'namespace', required: true, desc: 'workload namespace' },
|
|
355
|
-
{ name: 'name', required: true, desc: 'workload name' },
|
|
356
|
-
{ name: 'container', required: false, desc: 'specific container name' },
|
|
357
|
-
{ name: 'tail_lines', required: false, desc: 'lines per pod (default 100)' },
|
|
358
|
-
]},
|
|
359
|
-
{ name: 'manage_workload', desc: 'Perform operations on a workload. \'restart\' triggers a rolling restart, \'scale\' changes the replica count, \'rollback\' reverts to a previous revision.', params: [
|
|
360
|
-
{ name: 'action', required: true, desc: 'restart, scale, or rollback' },
|
|
361
|
-
{ name: 'kind', required: true, desc: 'deployment, statefulset, or daemonset' },
|
|
362
|
-
{ name: 'namespace', required: true, desc: 'workload namespace' },
|
|
363
|
-
{ name: 'name', required: true, desc: 'workload name' },
|
|
364
|
-
{ name: 'replicas', required: false, desc: 'target replica count (for scale)' },
|
|
365
|
-
{ name: 'revision', required: false, desc: 'target revision (for rollback)' },
|
|
366
|
-
]},
|
|
367
|
-
{ name: 'manage_cronjob', desc: 'Perform operations on a CronJob. \'trigger\' creates a manual Job run, \'suspend\' pauses the schedule, \'resume\' re-enables it.', params: [
|
|
368
|
-
{ name: 'action', required: true, desc: 'trigger, suspend, or resume' },
|
|
369
|
-
{ name: 'namespace', required: true, desc: 'cronjob namespace' },
|
|
370
|
-
{ name: 'name', required: true, desc: 'cronjob name' },
|
|
371
|
-
]},
|
|
372
|
-
{ name: 'manage_gitops', desc: 'Perform operations on GitOps resources. ArgoCD: sync, suspend, resume. FluxCD: reconcile, suspend, resume.', params: [
|
|
373
|
-
{ name: 'action', required: true, desc: 'sync/reconcile, suspend, or resume' },
|
|
374
|
-
{ name: 'tool', required: true, desc: 'argocd or fluxcd' },
|
|
375
|
-
{ name: 'namespace', required: true, desc: 'resource namespace' },
|
|
376
|
-
{ name: 'name', required: true, desc: 'resource name' },
|
|
377
|
-
{ name: 'kind', required: false, desc: 'FluxCD resource kind (e.g. kustomization, helmrelease)' },
|
|
378
|
-
]},
|
|
379
|
-
].map((tool) => (
|
|
306
|
+
{MCP_TOOL_CATALOG.map((tool) => (
|
|
380
307
|
<div key={tool.name} className="card-inner space-y-1.5">
|
|
381
|
-
<
|
|
308
|
+
<div className="flex items-center gap-2">
|
|
309
|
+
<code className="inline-code text-[11px]">{tool.name}</code>
|
|
310
|
+
{tool.write && (
|
|
311
|
+
<span className="badge-sm bg-amber-500/10 text-amber-600 dark:text-amber-400" title="Write tool — annotated as destructive">
|
|
312
|
+
write
|
|
313
|
+
</span>
|
|
314
|
+
)}
|
|
315
|
+
</div>
|
|
382
316
|
<p className="text-[11px] text-theme-text-tertiary leading-relaxed">{tool.desc}</p>
|
|
383
317
|
{tool.params.length > 0 && (
|
|
384
318
|
<div className="flex flex-wrap gap-1.5 pt-0.5">
|
|
385
319
|
{tool.params.map((p) => (
|
|
386
|
-
<span key={p.
|
|
387
|
-
<span
|
|
320
|
+
<span key={p.arg} className="inline-code text-[11px]" title={p.desc}>
|
|
321
|
+
<span>{p.arg}</span>
|
|
388
322
|
{p.required && <span className="text-red-400">*</span>}
|
|
389
323
|
</span>
|
|
390
324
|
))}
|