@skyhook-io/radar-app 1.3.2 → 1.3.4
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/helm/ChartBrowser.tsx +7 -3
- 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/HomeView.tsx +14 -3
- package/src/components/home/MCPSetupDialog.tsx +4 -4
- 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/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 +188 -6
|
@@ -51,7 +51,7 @@ export function Markdown({ children, className }: MarkdownProps) {
|
|
|
51
51
|
const isInline = !className
|
|
52
52
|
if (isInline) {
|
|
53
53
|
return (
|
|
54
|
-
<code className="
|
|
54
|
+
<code className="inline-code">
|
|
55
55
|
{children}
|
|
56
56
|
</code>
|
|
57
57
|
)
|
|
@@ -147,7 +147,7 @@ export function UpdateNotification() {
|
|
|
147
147
|
onClick={handleCopyCommand}
|
|
148
148
|
className="flex items-center gap-2 mt-2 px-2 py-1.5 bg-theme-elevated rounded font-mono text-theme-text-primary hover:bg-theme-surface-hover transition-colors w-full"
|
|
149
149
|
>
|
|
150
|
-
<code className="flex-1 text-left
|
|
150
|
+
<code className="inline-code flex-1 truncate text-left text-[11px]">{versionInfo.updateCommand}</code>
|
|
151
151
|
<CopyIcon copied={copied} failed={copyFailed} />
|
|
152
152
|
</button>
|
|
153
153
|
</WithTooltip>
|
|
@@ -7,9 +7,14 @@ import {
|
|
|
7
7
|
WorkloadView as BaseWorkloadView,
|
|
8
8
|
type RendererOverrides,
|
|
9
9
|
type GitOpsOwnerRef,
|
|
10
|
+
type GitOpsStatus,
|
|
11
|
+
type HelmOwnerRef,
|
|
10
12
|
gitOpsRouteForOwner,
|
|
13
|
+
gitOpsOwnerFromRelationships,
|
|
14
|
+
getGitOpsResourceStatus,
|
|
15
|
+
resolvedEnvFromKey,
|
|
11
16
|
} from '@skyhook-io/k8s-ui'
|
|
12
|
-
import type { SelectedResource, ResourceRef, ResolvedEnvFrom } from '../../types'
|
|
17
|
+
import type { SelectedResource, ResourceRef, Relationships, ResolvedEnvFrom } from '../../types'
|
|
13
18
|
import { kindToPlural, buildWorkloadPath, type NavigateToResource } from '../../utils/navigation'
|
|
14
19
|
import {
|
|
15
20
|
useChanges, useResourceWithRelationships, usePodLogs, useTopology, useUpdateResource,
|
|
@@ -20,6 +25,7 @@ import {
|
|
|
20
25
|
useCordonNode, useUncordonNode, useDrainNode,
|
|
21
26
|
useCascadeDeletePreview,
|
|
22
27
|
useResourceEvents,
|
|
28
|
+
useResource,
|
|
23
29
|
fetchJSON,
|
|
24
30
|
} from '../../api/client'
|
|
25
31
|
import { PrometheusCharts, isPrometheusSupported } from '../resource/PrometheusCharts'
|
|
@@ -259,6 +265,61 @@ export function WorkloadView({
|
|
|
259
265
|
const resource = resourceResponse?.resource
|
|
260
266
|
const relationships = resourceResponse?.relationships
|
|
261
267
|
const certificateInfo = resourceResponse?.certificateInfo
|
|
268
|
+
const relationshipGitopsOwner = useMemo(() => gitOpsOwnerFromRelationships(relationships), [relationships])
|
|
269
|
+
const inheritedGitOpsLookupRef = useMemo(
|
|
270
|
+
() => findInheritedGitOpsLookupRef(relationships, relationshipGitopsOwner, { kind: kindProp, namespace, name, group: rest.group }),
|
|
271
|
+
[relationships, relationshipGitopsOwner, kindProp, namespace, name, rest.group],
|
|
272
|
+
)
|
|
273
|
+
const inheritedGitOpsResponse = useResourceWithRelationships<any>(
|
|
274
|
+
inheritedGitOpsLookupRef ? kindToPlural(inheritedGitOpsLookupRef.kind) : '',
|
|
275
|
+
inheritedGitOpsLookupRef?.namespace ?? '',
|
|
276
|
+
inheritedGitOpsLookupRef?.name ?? '',
|
|
277
|
+
inheritedGitOpsLookupRef?.group,
|
|
278
|
+
)
|
|
279
|
+
const inheritedGitopsOwner = useMemo(
|
|
280
|
+
() => gitOpsOwnerFromRelationships(inheritedGitOpsResponse.data?.relationships),
|
|
281
|
+
[inheritedGitOpsResponse.data?.relationships],
|
|
282
|
+
)
|
|
283
|
+
const relationshipHelmOwner = useMemo(
|
|
284
|
+
() => nativeHelmOwnerFromRelationships(relationships, resource?.metadata?.namespace ?? namespace),
|
|
285
|
+
[relationships, resource?.metadata?.namespace, namespace],
|
|
286
|
+
)
|
|
287
|
+
const inheritedHelmOwner = useMemo(
|
|
288
|
+
() => nativeHelmOwnerFromRelationships(inheritedGitOpsResponse.data?.relationships, inheritedGitOpsResponse.data?.resource?.metadata?.namespace ?? namespace),
|
|
289
|
+
[inheritedGitOpsResponse.data?.relationships, inheritedGitOpsResponse.data?.resource?.metadata?.namespace, namespace],
|
|
290
|
+
)
|
|
291
|
+
const rawGitopsOwner = relationshipGitopsOwner ?? inheritedGitopsOwner
|
|
292
|
+
const gitOpsSourceResource = relationshipGitopsOwner ? resource : inheritedGitOpsResponse.data?.resource
|
|
293
|
+
const helmOwner = relationshipHelmOwner ?? inheritedHelmOwner
|
|
294
|
+
const helmSourceResource = relationshipHelmOwner ? resource : inheritedGitOpsResponse.data?.resource
|
|
295
|
+
const shouldResolveArgoOwner = rawGitopsOwner?.tool === 'argocd' && !rawGitopsOwner.namespace
|
|
296
|
+
const { data: argoApplications } = useResources<any>('applications', undefined, 'argoproj.io', { enabled: shouldResolveArgoOwner })
|
|
297
|
+
const gitopsOwner = useMemo(
|
|
298
|
+
() => resolveGitOpsOwner(rawGitopsOwner, argoApplications),
|
|
299
|
+
[rawGitopsOwner, argoApplications],
|
|
300
|
+
)
|
|
301
|
+
const gitopsOwnerGroup = gitopsOwner ? gitOpsOwnerGroup(gitopsOwner) : ''
|
|
302
|
+
const shouldFetchGitOpsOwner = Boolean(gitopsOwner?.namespace)
|
|
303
|
+
const gitopsOwnerQuery = useResource<any>(
|
|
304
|
+
shouldFetchGitOpsOwner ? gitopsOwner!.kind : '',
|
|
305
|
+
gitopsOwner?.namespace ?? '',
|
|
306
|
+
gitopsOwner?.name ?? '',
|
|
307
|
+
gitopsOwnerGroup,
|
|
308
|
+
)
|
|
309
|
+
const gitOpsOwnerStatus = useMemo(
|
|
310
|
+
() => deriveGitOpsOwnerStatus(gitopsOwner, gitopsOwnerQuery.data),
|
|
311
|
+
[gitopsOwner, gitopsOwnerQuery.data],
|
|
312
|
+
)
|
|
313
|
+
const gitOpsOwnerVerified = Boolean(gitopsOwner?.namespace && gitopsOwnerQuery.data)
|
|
314
|
+
const gitOpsOwnerPending = Boolean(gitopsOwner?.namespace && gitopsOwnerQuery.isLoading && !gitopsOwnerQuery.data)
|
|
315
|
+
const gitOpsOwnerSource = useMemo(
|
|
316
|
+
() => describeGitOpsOwnerSource(rawGitopsOwner, gitOpsSourceResource),
|
|
317
|
+
[rawGitopsOwner, gitOpsSourceResource],
|
|
318
|
+
)
|
|
319
|
+
const helmOwnerSource = useMemo(
|
|
320
|
+
() => describeHelmOwnerSource(helmOwner, helmSourceResource),
|
|
321
|
+
[helmOwner, helmSourceResource],
|
|
322
|
+
)
|
|
262
323
|
|
|
263
324
|
// For pods: extract envFrom ConfigMap/Secret names and resolve their keys
|
|
264
325
|
const isPod = kindProp.toLowerCase() === 'pods'
|
|
@@ -300,7 +361,7 @@ export function WorkloadView({
|
|
|
300
361
|
envFromConfigMapNames.forEach((n, i) => {
|
|
301
362
|
// Single-resource endpoint returns { resource, relationships } wrapper
|
|
302
363
|
const cm = configMapQueries[i]?.data?.resource ?? configMapQueries[i]?.data
|
|
303
|
-
if (cm) result[n] = { keys: Object.keys(cm.data || {}), values: cm.data || {}, isSecret: false }
|
|
364
|
+
if (cm) result[resolvedEnvFromKey('configmap', n)] = { keys: Object.keys(cm.data || {}), values: cm.data || {}, isSecret: false }
|
|
304
365
|
})
|
|
305
366
|
envFromSecretNames.forEach((n, i) => {
|
|
306
367
|
const secret = secretQueries[i]?.data?.resource ?? secretQueries[i]?.data
|
|
@@ -309,7 +370,7 @@ export function WorkloadView({
|
|
|
309
370
|
for (const [k, v] of Object.entries(secret.data || {})) {
|
|
310
371
|
try { decodedValues[k] = atob(v as string) } catch { decodedValues[k] = v as string }
|
|
311
372
|
}
|
|
312
|
-
result[n] = { keys: Object.keys(decodedValues), values: decodedValues, isSecret: true }
|
|
373
|
+
result[resolvedEnvFromKey('secret', n)] = { keys: Object.keys(decodedValues), values: decodedValues, isSecret: true }
|
|
313
374
|
}
|
|
314
375
|
})
|
|
315
376
|
return Object.keys(result).length > 0 ? result : undefined
|
|
@@ -368,13 +429,28 @@ export function WorkloadView({
|
|
|
368
429
|
|
|
369
430
|
const navigateRouter = useNavigate()
|
|
370
431
|
const handleOpenGitOpsResource = useCallback(
|
|
371
|
-
(ref: GitOpsOwnerRef) =>
|
|
372
|
-
|
|
432
|
+
(ref: GitOpsOwnerRef) => {
|
|
433
|
+
const params = new URLSearchParams()
|
|
434
|
+
const namespaces = searchParams.get('namespaces')
|
|
435
|
+
if (namespaces) params.set('namespaces', namespaces)
|
|
436
|
+
navigateRouter({ pathname: gitOpsRouteForOwner(ref), search: params.toString() })
|
|
437
|
+
},
|
|
438
|
+
[navigateRouter, searchParams],
|
|
373
439
|
)
|
|
374
440
|
const handleNavigateGitOpsPath = useCallback(
|
|
375
441
|
(path: string) => navigateRouter(path),
|
|
376
442
|
[navigateRouter],
|
|
377
443
|
)
|
|
444
|
+
const handleOpenHelmRelease = useCallback(
|
|
445
|
+
(ref: HelmOwnerRef) => {
|
|
446
|
+
const params = new URLSearchParams()
|
|
447
|
+
const namespaces = searchParams.get('namespaces')
|
|
448
|
+
if (namespaces) params.set('namespaces', namespaces)
|
|
449
|
+
params.set('release', `${ref.namespace}/${ref.name}`)
|
|
450
|
+
navigateRouter({ pathname: '/helm', search: params.toString() })
|
|
451
|
+
},
|
|
452
|
+
[navigateRouter, searchParams],
|
|
453
|
+
)
|
|
378
454
|
|
|
379
455
|
// Duplicate dialog
|
|
380
456
|
const [duplicateDialogOpen, setDuplicateDialogOpen] = useState(false)
|
|
@@ -437,7 +513,15 @@ export function WorkloadView({
|
|
|
437
513
|
<FluxSourceConsumersSection kind={k} namespace={ns} name={n} />
|
|
438
514
|
</>
|
|
439
515
|
)}
|
|
440
|
-
onOpenGitOpsResource={handleOpenGitOpsResource}
|
|
516
|
+
onOpenGitOpsResource={gitopsOwnerQuery.data ? handleOpenGitOpsResource : undefined}
|
|
517
|
+
resolvedGitOpsOwner={gitopsOwner}
|
|
518
|
+
gitOpsOwnerVerified={gitOpsOwnerVerified}
|
|
519
|
+
gitOpsOwnerPending={gitOpsOwnerPending}
|
|
520
|
+
gitOpsOwnerSource={gitOpsOwnerSource}
|
|
521
|
+
gitOpsOwnerStatus={gitOpsOwnerStatus}
|
|
522
|
+
helmOwner={helmOwner}
|
|
523
|
+
helmOwnerSource={helmOwnerSource}
|
|
524
|
+
onOpenHelmRelease={handleOpenHelmRelease}
|
|
441
525
|
onNavigateGitOpsPath={handleNavigateGitOpsPath}
|
|
442
526
|
/>
|
|
443
527
|
<CreateResourceDialog
|
|
@@ -454,6 +538,104 @@ export function WorkloadView({
|
|
|
454
538
|
)
|
|
455
539
|
}
|
|
456
540
|
|
|
541
|
+
function resolveGitOpsOwner(owner: GitOpsOwnerRef | null, argoApplications: any[] | undefined): GitOpsOwnerRef | null {
|
|
542
|
+
if (!owner || owner.namespace || owner.tool !== 'argocd') return owner
|
|
543
|
+
const matches = (argoApplications ?? []).filter((app) => app?.metadata?.name === owner.name)
|
|
544
|
+
if (matches.length !== 1) return owner
|
|
545
|
+
const namespace = matches[0]?.metadata?.namespace
|
|
546
|
+
return namespace ? { ...owner, namespace } : owner
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function findInheritedGitOpsLookupRef(
|
|
550
|
+
relationships: Relationships | undefined,
|
|
551
|
+
directOwner: GitOpsOwnerRef | null,
|
|
552
|
+
current: ResourceRef,
|
|
553
|
+
): ResourceRef | null {
|
|
554
|
+
if (directOwner) return null
|
|
555
|
+
const inheritedManagerRefs = (relationships?.managedBy ?? []).filter((ref) =>
|
|
556
|
+
!gitOpsOwnerFromRelationships({ managedBy: [ref] })
|
|
557
|
+
&& !isNativeHelmManager(ref)
|
|
558
|
+
)
|
|
559
|
+
const candidates = [
|
|
560
|
+
relationships?.deployment,
|
|
561
|
+
...inheritedManagerRefs,
|
|
562
|
+
relationships?.owner,
|
|
563
|
+
].filter(Boolean) as ResourceRef[]
|
|
564
|
+
|
|
565
|
+
return candidates.find((ref) => !isCurrentResource(ref, current)) ?? null
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function nativeHelmOwnerFromRelationships(relationships: Relationships | undefined, fallbackNamespace: string): HelmOwnerRef | null {
|
|
569
|
+
const ref = relationships?.managedBy?.[0]
|
|
570
|
+
if (!ref || !isNativeHelmManager(ref)) return null
|
|
571
|
+
return {
|
|
572
|
+
namespace: ref.namespace || fallbackNamespace,
|
|
573
|
+
name: ref.name,
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function isCurrentResource(ref: ResourceRef, current: ResourceRef): boolean {
|
|
578
|
+
return kindToPlural(ref.kind) === kindToPlural(current.kind)
|
|
579
|
+
&& ref.namespace === current.namespace
|
|
580
|
+
&& ref.name === current.name
|
|
581
|
+
&& (ref.group ?? '') === (current.group ?? '')
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function isNativeHelmManager(ref: ResourceRef): boolean {
|
|
585
|
+
return ref.kind === 'HelmRelease' && ref.group !== 'helm.toolkit.fluxcd.io'
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function describeGitOpsOwnerSource(owner: GitOpsOwnerRef | null, resource: any): string | null {
|
|
589
|
+
if (!owner || !resource) return null
|
|
590
|
+
const labels = resource.metadata?.labels ?? {}
|
|
591
|
+
const annotations = resource.metadata?.annotations ?? {}
|
|
592
|
+
|
|
593
|
+
if (owner.tool === 'fluxcd') {
|
|
594
|
+
const nameKey = owner.kind === 'helmreleases' ? 'helm.toolkit.fluxcd.io/name' : 'kustomize.toolkit.fluxcd.io/name'
|
|
595
|
+
const nsKey = owner.kind === 'helmreleases' ? 'helm.toolkit.fluxcd.io/namespace' : 'kustomize.toolkit.fluxcd.io/namespace'
|
|
596
|
+
if (labels[nameKey] || labels[nsKey]) {
|
|
597
|
+
return `${nameKey}=${labels[nameKey] ?? ''}, ${nsKey}=${labels[nsKey] ?? ''}`
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const trackingID = annotations['argocd.argoproj.io/tracking-id']
|
|
602
|
+
if (trackingID) return `argocd.argoproj.io/tracking-id=${trackingID}`
|
|
603
|
+
const argoInstance = labels['argocd.argoproj.io/instance']
|
|
604
|
+
if (argoInstance) return `argocd.argoproj.io/instance=${argoInstance}`
|
|
605
|
+
return null
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function describeHelmOwnerSource(owner: HelmOwnerRef | null, resource: any): string | null {
|
|
609
|
+
if (!owner || !resource) return null
|
|
610
|
+
const annotations = resource.metadata?.annotations ?? {}
|
|
611
|
+
const releaseName = annotations['meta.helm.sh/release-name']
|
|
612
|
+
const releaseNamespace = annotations['meta.helm.sh/release-namespace']
|
|
613
|
+
if (releaseName || releaseNamespace) {
|
|
614
|
+
return `meta.helm.sh/release-name=${releaseName ?? ''}, meta.helm.sh/release-namespace=${releaseNamespace ?? ''}`
|
|
615
|
+
}
|
|
616
|
+
return null
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function gitOpsOwnerGroup(owner: GitOpsOwnerRef): string {
|
|
620
|
+
if (owner.tool === 'argocd') return 'argoproj.io'
|
|
621
|
+
if (owner.kind === 'kustomizations') return 'kustomize.toolkit.fluxcd.io'
|
|
622
|
+
return 'helm.toolkit.fluxcd.io'
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function deriveGitOpsOwnerStatus(owner: GitOpsOwnerRef | null, resource: any): GitOpsStatus | null {
|
|
626
|
+
if (!owner || !resource || !hasGitOpsStatusPayload(owner, resource)) return null
|
|
627
|
+
return getGitOpsResourceStatus(owner.kind, resource)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function hasGitOpsStatusPayload(owner: GitOpsOwnerRef, resource: any): boolean {
|
|
631
|
+
if (owner.kind === 'applications') {
|
|
632
|
+
const status = resource.status ?? {}
|
|
633
|
+
return Boolean(status.sync?.status || status.health?.status || status.operationState?.phase)
|
|
634
|
+
}
|
|
635
|
+
if (resource.spec?.suspend === true) return true
|
|
636
|
+
return Array.isArray(resource.status?.conditions) && resource.status.conditions.length > 0
|
|
637
|
+
}
|
|
638
|
+
|
|
457
639
|
// ============================================================================
|
|
458
640
|
// LOGS TAB — platform-specific (uses data-fetching hooks)
|
|
459
641
|
// ============================================================================
|