@skyhook-io/radar-app 1.3.3 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyhook-io/radar-app",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "Radar's full web UI as a reusable React component. Used by Radar's own binary and by external consumers like Radar Cloud.",
5
5
  "repository": {
6
6
  "type": "git",
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>(kind: string, namespace?: string, group?: string, options?: { enabled?: boolean }) {
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 response = await apiFetch(`${getApiBase()}/resources/${kind}/${namespace}/${name}`, {
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 a server-side apply with Force=true (can take field ownership from Helm/Flux); create mode fails if it exists. Multi-document YAML supported.',
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">