@skyhook-io/radar-app 1.3.4 → 1.3.5
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/api/client.ts +17 -4
- package/src/components/home/mcpToolCatalog.ts +19 -2
- package/src/components/resource/PrometheusChartsGrid.tsx +1 -3
- package/src/components/resources/renderers/ServiceRenderer.tsx +28 -1
- package/src/components/workload/WorkloadView.tsx +1 -1
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -926,7 +926,12 @@ export function useResourceWithRelationships<T>(kind: string, namespace: string,
|
|
|
926
926
|
}
|
|
927
927
|
|
|
928
928
|
// List resources - queryKey includes group for cache sharing with ResourcesView
|
|
929
|
-
export function useResources<T>(
|
|
929
|
+
export function useResources<T>(
|
|
930
|
+
kind: string,
|
|
931
|
+
namespace?: string,
|
|
932
|
+
group?: string,
|
|
933
|
+
options?: { enabled?: boolean; refetchInterval?: number | false },
|
|
934
|
+
) {
|
|
930
935
|
const params = new URLSearchParams()
|
|
931
936
|
if (namespace) params.set('namespace', namespace)
|
|
932
937
|
if (group) params.set('group', group)
|
|
@@ -937,6 +942,7 @@ export function useResources<T>(kind: string, namespace?: string, group?: string
|
|
|
937
942
|
queryFn: () => fetchJSON(`/resources/${kind}${queryString ? `?${queryString}` : ''}`),
|
|
938
943
|
enabled: (options?.enabled ?? true) && Boolean(kind),
|
|
939
944
|
staleTime: 30000, // 30 seconds - matches refetchInterval in ResourcesView
|
|
945
|
+
refetchInterval: options?.refetchInterval,
|
|
940
946
|
})
|
|
941
947
|
}
|
|
942
948
|
|
|
@@ -1625,8 +1631,12 @@ export function useUpdateResource() {
|
|
|
1625
1631
|
const queryClient = useQueryClient()
|
|
1626
1632
|
|
|
1627
1633
|
return useMutation({
|
|
1628
|
-
mutationFn: async ({ kind, namespace, name, yaml }: { kind: string; namespace: string; name: string; yaml: string }) => {
|
|
1629
|
-
const
|
|
1634
|
+
mutationFn: async ({ kind, namespace, name, yaml, force = true }: { kind: string; namespace: string; name: string; yaml: string; force?: boolean }) => {
|
|
1635
|
+
const url = new URL(`${getApiBase()}/resources/${kind}/${namespace}/${name}`, window.location.origin)
|
|
1636
|
+
if (!force) {
|
|
1637
|
+
url.searchParams.set('force', 'false')
|
|
1638
|
+
}
|
|
1639
|
+
const response = await apiFetch(url.toString(), {
|
|
1630
1640
|
method: 'PUT',
|
|
1631
1641
|
headers: { 'Content-Type': 'text/plain' },
|
|
1632
1642
|
body: yaml,
|
|
@@ -1723,12 +1733,15 @@ export function useApplyResource() {
|
|
|
1723
1733
|
const queryClient = useQueryClient()
|
|
1724
1734
|
|
|
1725
1735
|
return useMutation({
|
|
1726
|
-
mutationFn: async ({ yaml, mode = 'apply', dryRun = false }: { yaml: string; mode?: 'apply' | 'create'; dryRun?: boolean }) => {
|
|
1736
|
+
mutationFn: async ({ yaml, mode = 'apply', dryRun = false, force = false }: { yaml: string; mode?: 'apply' | 'create'; dryRun?: boolean; force?: boolean }) => {
|
|
1727
1737
|
const url = new URL(`${getApiBase()}/resources/apply`, window.location.origin)
|
|
1728
1738
|
url.searchParams.set('mode', mode)
|
|
1729
1739
|
if (dryRun) {
|
|
1730
1740
|
url.searchParams.set('dryRun', 'true')
|
|
1731
1741
|
}
|
|
1742
|
+
if (force) {
|
|
1743
|
+
url.searchParams.set('force', 'true')
|
|
1744
|
+
}
|
|
1732
1745
|
const response = await apiFetch(url.toString(), {
|
|
1733
1746
|
method: 'POST',
|
|
1734
1747
|
headers: { 'Content-Type': 'text/plain' },
|
|
@@ -253,12 +253,29 @@ export const MCP_TOOL_CATALOG: MCPToolInfo[] = [
|
|
|
253
253
|
{
|
|
254
254
|
name: 'apply_resource',
|
|
255
255
|
write: true,
|
|
256
|
-
desc: 'Create or update a resource from YAML. apply mode is
|
|
256
|
+
desc: 'Create or update a resource from YAML. apply mode is server-side apply and reports field ownership conflicts by default; create mode fails if it exists. Multi-document YAML returns per-document status on partial failure.',
|
|
257
257
|
params: [
|
|
258
258
|
{ arg: 'yaml', required: true, desc: 'YAML manifest (multi-document with --- supported)' },
|
|
259
259
|
{ arg: 'mode', desc: 'apply (default) or create' },
|
|
260
|
-
{ arg: 'dry_run', desc: 'validate without persisting' },
|
|
260
|
+
{ arg: 'dry_run', desc: 'validate and preview without persisting' },
|
|
261
261
|
{ arg: 'namespace', desc: 'override namespace for the resource' },
|
|
262
|
+
{ arg: 'verify', desc: 'return post-mutation state; on dry_run return preview diff (default true)' },
|
|
263
|
+
{ arg: 'force', desc: 'force SSA field ownership conflicts and take ownership from other managers (default false)' },
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: 'patch_resource',
|
|
268
|
+
write: true,
|
|
269
|
+
desc: 'Patch one existing resource with JSON Patch, JSON Merge Patch, or built-in-kind strategic merge patch for precise edits without rewriting the full manifest.',
|
|
270
|
+
params: [
|
|
271
|
+
{ arg: 'kind', required: true, desc: 'resource kind, e.g. Deployment, Service, ConfigMap' },
|
|
272
|
+
{ arg: 'name', required: true, desc: 'resource name' },
|
|
273
|
+
{ arg: 'namespace', desc: 'resource namespace; omit for cluster-scoped resources' },
|
|
274
|
+
{ arg: 'group', desc: 'API group when the kind is ambiguous' },
|
|
275
|
+
{ arg: 'patch_type', desc: 'json (default), merge, or strategic' },
|
|
276
|
+
{ arg: 'patch', required: true, desc: 'JSON patch body' },
|
|
277
|
+
{ arg: 'dry_run', desc: 'validate and preview without persisting' },
|
|
278
|
+
{ arg: 'verify', desc: 'return post-patch state; on dry_run return preview diff (default true)' },
|
|
262
279
|
],
|
|
263
280
|
},
|
|
264
281
|
{
|
|
@@ -48,14 +48,12 @@ export function PrometheusChartsGrid({
|
|
|
48
48
|
name,
|
|
49
49
|
resource,
|
|
50
50
|
}: PrometheusChartsGridProps) {
|
|
51
|
-
// Node-kind workloads don't have container restart semantics — KSM would
|
|
52
|
-
// return empty series anyway, but suppressing the lane spares the query.
|
|
53
|
-
const showRestartLane = kind !== 'Node'
|
|
54
51
|
useAutoPromConnect()
|
|
55
52
|
const { data: status, isLoading: statusLoading } = usePrometheusStatus()
|
|
56
53
|
const connectMutation = usePrometheusConnect()
|
|
57
54
|
const isConnected = status?.connected === true
|
|
58
55
|
const isSupported = SUPPORTED_KINDS.has(kind)
|
|
56
|
+
const showRestartLane = isSupported && kind !== 'Node'
|
|
59
57
|
|
|
60
58
|
const [timeRange, setTimeRange] = useState<PrometheusTimeRange>('1h')
|
|
61
59
|
const [diskExpanded, setDiskExpanded] = useState(false)
|
|
@@ -1,18 +1,45 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
1
2
|
import { ServiceRenderer as BaseServiceRenderer } from '@skyhook-io/k8s-ui/components/resources/renderers/ServiceRenderer'
|
|
2
3
|
import { PortForwardInlineButton } from '../../portforward/PortForwardButton'
|
|
4
|
+
import { useResources } from '../../../api/client'
|
|
5
|
+
import type { ResourceRef } from '../../../types'
|
|
3
6
|
|
|
4
7
|
interface ServiceRendererProps {
|
|
5
8
|
data: any
|
|
6
9
|
onCopy: (text: string, label: string) => void
|
|
7
10
|
copied: string | null
|
|
11
|
+
onNavigate?: (ref: ResourceRef) => void
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
export function ServiceRenderer({ data, onCopy, copied }: ServiceRendererProps) {
|
|
14
|
+
export function ServiceRenderer({ data, onCopy, copied, onNavigate }: ServiceRendererProps) {
|
|
15
|
+
const namespace = data.metadata?.namespace
|
|
16
|
+
const serviceName = data.metadata?.name
|
|
17
|
+
const spec = data.spec || {}
|
|
18
|
+
const shouldLoadEndpointSlices = Boolean(
|
|
19
|
+
namespace &&
|
|
20
|
+
serviceName &&
|
|
21
|
+
spec.type !== 'ExternalName' &&
|
|
22
|
+
(!spec.selector || Object.keys(spec.selector).length === 0)
|
|
23
|
+
)
|
|
24
|
+
const { data: endpointSlices, isLoading: endpointSlicesLoading } = useResources<any>(
|
|
25
|
+
'endpointslices',
|
|
26
|
+
namespace,
|
|
27
|
+
'discovery.k8s.io',
|
|
28
|
+
{ enabled: shouldLoadEndpointSlices, refetchInterval: 30000 }
|
|
29
|
+
)
|
|
30
|
+
const matchingEndpointSlices = useMemo(
|
|
31
|
+
() => (endpointSlices || []).filter((slice: any) => slice.metadata?.labels?.['kubernetes.io/service-name'] === serviceName),
|
|
32
|
+
[endpointSlices, serviceName]
|
|
33
|
+
)
|
|
34
|
+
|
|
11
35
|
return (
|
|
12
36
|
<BaseServiceRenderer
|
|
13
37
|
data={data}
|
|
14
38
|
onCopy={onCopy}
|
|
15
39
|
copied={copied}
|
|
40
|
+
endpointSlices={matchingEndpointSlices}
|
|
41
|
+
endpointSlicesLoading={endpointSlicesLoading}
|
|
42
|
+
onNavigate={onNavigate}
|
|
16
43
|
renderPortAction={({ namespace, serviceName, port, protocol }) => (
|
|
17
44
|
<PortForwardInlineButton
|
|
18
45
|
namespace={namespace}
|
|
@@ -922,7 +922,7 @@ function DrawerMetricsContent({ kind, namespace, name, resource }: {
|
|
|
922
922
|
resource: any
|
|
923
923
|
}) {
|
|
924
924
|
const [chartRange, setChartRange] = useState<import('../../api/client').PrometheusTimeRange>('1h')
|
|
925
|
-
const showRestartLane = kind !== 'Node'
|
|
925
|
+
const showRestartLane = isPrometheusSupported(kind) && kind !== 'Node'
|
|
926
926
|
|
|
927
927
|
return (
|
|
928
928
|
<div className="flex flex-col h-full">
|