@skyhook-io/k8s-ui 1.17.1 → 1.18.1
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/README.md +13 -0
- package/package.json +4 -4
- package/src/components/compare/CompareResourcePicker.tsx +18 -4
- package/src/components/gitops/GitOpsDetailLayout.test.tsx +64 -0
- package/src/components/gitops/GitOpsDetailLayout.tsx +22 -15
- package/src/components/gitops/GitOpsGraphFilterRail.tsx +2 -2
- package/src/components/gitops/GitOpsTableView.coverage.test.tsx +27 -0
- package/src/components/gitops/GitOpsTableView.tsx +33 -4
- package/src/components/gitops/detail-helpers.test.ts +58 -13
- package/src/components/gitops/detail-helpers.ts +37 -6
- package/src/components/gitops/index.ts +1 -0
- package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +27 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +16 -10
- package/src/components/gitops/insights/insights-helpers.test.ts +18 -0
- package/src/components/gitops/insights/insights-helpers.ts +7 -0
- package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +73 -9
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +45 -28
- package/src/components/gitops/tree/tree-helpers.test.ts +6 -0
- package/src/components/gitops/tree/tree-helpers.ts +5 -1
- package/src/components/issues/IssuesView.tsx +18 -10
- package/src/components/issues/ResourceIssuesSection.tsx +5 -2
- package/src/components/issues/diagnostic.ts +2 -0
- package/src/components/issues/index.ts +1 -1
- package/src/components/issues/issues.test.ts +26 -0
- package/src/components/issues/severity.ts +9 -2
- package/src/components/issues/types.ts +32 -4
- package/src/components/logs/LogCore.theme.test.tsx +126 -1
- package/src/components/logs/LogCore.tsx +159 -40
- package/src/components/logs/LogsViewer.test.tsx +183 -60
- package/src/components/logs/LogsViewer.tsx +49 -12
- package/src/components/logs/StructuredLogLine.tsx +1 -1
- package/src/components/logs/WorkloadLogsViewer.tsx +7 -1
- package/src/components/logs/useLogBuffer.reading.test.tsx +149 -0
- package/src/components/logs/useLogBuffer.ts +48 -18
- package/src/components/logs/useLogSearch.ts +3 -1
- package/src/components/namespace-switcher/NamespacePicker.test.tsx +236 -2
- package/src/components/namespace-switcher/NamespacePicker.tsx +42 -8
- package/src/components/resources/ColumnPickerList.test.tsx +46 -0
- package/src/components/resources/ColumnPickerList.tsx +174 -0
- package/src/components/resources/ResourcesSidebar.test.tsx +49 -0
- package/src/components/resources/ResourcesSidebar.tsx +36 -21
- package/src/components/resources/ResourcesView.kind-view.test.tsx +149 -2
- package/src/components/resources/ResourcesView.selected-kind.test.ts +14 -0
- package/src/components/resources/ResourcesView.trivy-search.test.tsx +59 -0
- package/src/components/resources/ResourcesView.tsx +198 -93
- package/src/components/resources/column-order.test.ts +104 -0
- package/src/components/resources/column-order.ts +64 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.test.tsx +15 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +4 -1
- package/src/components/resources/renderers/CronJobRenderer.test.tsx +31 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +28 -20
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +22 -9
- package/src/components/resources/renderers/PodRenderer.sidecar-terminal.test.tsx +200 -0
- package/src/components/resources/renderers/PodRenderer.test.tsx +115 -0
- package/src/components/resources/renderers/PodRenderer.tsx +72 -20
- package/src/components/resources/resource-utils-trivy.test.ts +87 -0
- package/src/components/resources/resource-utils-trivy.ts +122 -0
- package/src/components/resources/resource-utils.ts +15 -2
- package/src/components/resources/summarize-scheduler-message.test.ts +1 -0
- package/src/components/shared/DetailShell.tsx +9 -7
- package/src/components/shared/DrainPlanDialog.test.tsx +7 -0
- package/src/components/shared/DrainPlanDialog.tsx +6 -2
- package/src/components/shared/ManagedByChip.test.tsx +22 -0
- package/src/components/shared/ManagedByChip.tsx +24 -4
- package/src/components/shared/ResourceActionsBar.identity.test.tsx +64 -0
- package/src/components/shared/ResourceActionsBar.tsx +12 -6
- package/src/components/shared/ResourceRendererDispatch.test.tsx +49 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +48 -31
- package/src/components/timeline/DiffViewer.test.tsx +69 -0
- package/src/components/timeline/DiffViewer.tsx +113 -4
- package/src/components/timeline/TimelineList.tsx +5 -1
- package/src/components/timeline/shared.test.tsx +48 -0
- package/src/components/timeline/shared.tsx +14 -19
- package/src/components/ui/FetchResult.test.tsx +19 -2
- package/src/components/ui/FetchResult.tsx +13 -1
- package/src/components/ui/MultiSelectPicker.test.tsx +20 -0
- package/src/components/ui/MultiSelectPicker.tsx +20 -2
- package/src/components/ui/PageHeader.tsx +6 -4
- package/src/components/ui/Toast.tsx +48 -30
- package/src/components/ui/drawer-components.test.tsx +253 -0
- package/src/components/ui/drawer-components.tsx +315 -11
- package/src/components/ui/drawer-older-replicasets.test.tsx +45 -0
- package/src/components/ui/index.ts +1 -1
- package/src/components/workload/WorkloadView.relationships.test.tsx +33 -0
- package/src/components/workload/WorkloadView.timelineFocus.test.tsx +96 -0
- package/src/components/workload/WorkloadView.tsx +68 -14
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useMediaQuery.ts +21 -0
- package/src/hooks/usePhoneLayout.ts +12 -0
- package/src/theme/tailwind-theme.css +5 -0
- package/src/types/core.ts +91 -3
- package/src/types/gitops-flux-inventory.test.ts +24 -0
- package/src/types/gitops-insights.ts +1 -0
- package/src/types/gitops-tree.ts +2 -0
- package/src/types/gitops.ts +5 -5
- package/src/types/index.ts +3 -0
- package/src/types/read-coverage.ts +65 -0
- package/src/types/resource-coverage.ts +40 -0
- package/src/utils/format.test.ts +7 -0
- package/src/utils/format.ts +6 -1
- package/src/utils/gitops-owner.test.ts +42 -1
- package/src/utils/gitops-owner.ts +22 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/read-actions.test.ts +22 -0
- package/src/utils/read-actions.ts +56 -0
- package/src/utils/resource-coverage.test.ts +121 -0
- package/src/utils/resource-coverage.ts +117 -0
|
@@ -19,6 +19,7 @@ export interface DrainPlanPod {
|
|
|
19
19
|
pdb?: string
|
|
20
20
|
pdbChecked: boolean
|
|
21
21
|
terminating?: boolean
|
|
22
|
+
expectedSkip?: boolean
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface DrainPlan {
|
|
@@ -95,7 +96,8 @@ const OUTCOME_LABEL: Record<DrainOutcome, string> = {
|
|
|
95
96
|
|
|
96
97
|
const ESTIMATE_CAVEAT =
|
|
97
98
|
'An estimate, not a guarantee: the drain re-lists live state when it runs, and pods, budgets and permissions can change until then. ' +
|
|
98
|
-
'
|
|
99
|
+
'Drain normally waits up to 2 minutes for evictions and pod deletion, retrying when a disruption budget temporarily blocks eviction. ' +
|
|
100
|
+
'Older Radar versions may return before pods are deleted. A pod covered by more than one budget is refused outright.'
|
|
99
101
|
|
|
100
102
|
interface DrainPlanContentProps {
|
|
101
103
|
nodeName: string
|
|
@@ -217,7 +219,9 @@ export function DrainPlanContent({
|
|
|
217
219
|
{current.pods.map((p) => (
|
|
218
220
|
<tr key={`${p.namespace}/${p.name}`} className="border-b border-theme-border last:border-b-0 align-top">
|
|
219
221
|
<td className="px-2 py-1.5 whitespace-nowrap">
|
|
220
|
-
<Badge severity={OUTCOME_SEVERITY[p.outcome]} size="sm">
|
|
222
|
+
<Badge severity={p.outcome === 'skip' && p.expectedSkip === false ? 'warning' : OUTCOME_SEVERITY[p.outcome]} size="sm">
|
|
223
|
+
{p.outcome === 'skip' && p.expectedSkip !== undefined ? p.expectedSkip ? 'left by design' : 'requires attention' : OUTCOME_LABEL[p.outcome]}
|
|
224
|
+
</Badge>
|
|
221
225
|
</td>
|
|
222
226
|
<td className="px-2 py-1.5 font-mono whitespace-nowrap text-theme-text-primary">
|
|
223
227
|
{p.namespace}/{p.name}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
|
|
4
|
+
import { ManagedByChip } from './ManagedByChip'
|
|
5
|
+
|
|
6
|
+
const owner = { tool: 'argocd' as const, kind: 'applications' as const, namespace: 'argocd', name: 'billing' }
|
|
7
|
+
|
|
8
|
+
describe('ManagedByChip', () => {
|
|
9
|
+
it('says Managed by for a verified owner its status confirms', () => {
|
|
10
|
+
const html = renderToString(<ManagedByChip owner={owner} verified listing={{ state: 'confirmed' }} />)
|
|
11
|
+
expect(html).toContain('Managed by')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('says Tracked by, with a warning, for an owner whose status does not list the resource while another does', () => {
|
|
15
|
+
const html = renderToString(
|
|
16
|
+
<ManagedByChip owner={owner} verified listing={{ state: 'conflict', listedBy: [{ tool: 'argocd', kind: 'applications', namespace: 'argocd', name: 'hub' }] }} />,
|
|
17
|
+
)
|
|
18
|
+
expect(html).toContain('Tracked by')
|
|
19
|
+
expect(html).not.toContain('Managed by')
|
|
20
|
+
expect(html).toMatch(/rounded-full[^"]*bg-amber/)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GitBranch, Package } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
|
-
import type { GitOpsOwnerRef } from '../../utils/gitops-owner'
|
|
3
|
+
import type { GitOpsOwnerRef, ManagerListing } from '../../utils/gitops-owner'
|
|
4
4
|
import type { GitOpsStatus } from '../../types/gitops'
|
|
5
5
|
import { StatusDot, type StatusTone } from '../ui/status-tone'
|
|
6
6
|
import { Tooltip } from '../ui/Tooltip'
|
|
@@ -11,7 +11,10 @@ export type HelmOwnerRef = {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// ManagedByChip renders the "Managed by <ArgoCD/FluxCD app>" affordance for
|
|
14
|
-
// resources detected (via labels/annotations) to be GitOps-managed.
|
|
14
|
+
// resources detected (via labels/annotations) to be GitOps-managed. `listing`
|
|
15
|
+
// compares that claim with the managers whose status lists the resource: a
|
|
16
|
+
// manager that doesn't list it while another does is shown as "Tracked by"
|
|
17
|
+
// with a warning, since the claim is all that names it. The chip
|
|
15
18
|
// is clickable when the host wires `onOpen`; integrators that don't surface
|
|
16
19
|
// a GitOps tab can omit the handler and the chip degrades to a passive badge
|
|
17
20
|
// so the relationship is still visible.
|
|
@@ -26,6 +29,7 @@ export function ManagedByChip({
|
|
|
26
29
|
verified = true,
|
|
27
30
|
pending = false,
|
|
28
31
|
source,
|
|
32
|
+
listing,
|
|
29
33
|
variant = 'inline',
|
|
30
34
|
}: {
|
|
31
35
|
owner: GitOpsOwnerRef
|
|
@@ -34,13 +38,15 @@ export function ManagedByChip({
|
|
|
34
38
|
verified?: boolean
|
|
35
39
|
pending?: boolean
|
|
36
40
|
source?: string | null
|
|
41
|
+
listing?: ManagerListing
|
|
37
42
|
variant?: 'inline' | 'block'
|
|
38
43
|
}) {
|
|
39
44
|
const toolLabel = owner.tool === 'argocd' ? 'ArgoCD' : 'FluxCD'
|
|
40
45
|
const ownerKindLabel = gitOpsOwnerKindLabel(owner)
|
|
41
46
|
const label = owner.namespace ? `${owner.namespace}/${owner.name}` : owner.name
|
|
42
47
|
const statusLabel = status ? gitOpsStatusLabel(status) : null
|
|
43
|
-
const
|
|
48
|
+
const conflict = listing?.state === 'conflict' ? listing.listedBy : null
|
|
49
|
+
const prefix = pending ? 'Resolving' : verified && !conflict ? 'Managed by' : 'Tracked by'
|
|
44
50
|
const tooltipContent = (
|
|
45
51
|
<span className="flex flex-col gap-1">
|
|
46
52
|
<span>
|
|
@@ -48,10 +54,23 @@ export function ManagedByChip({
|
|
|
48
54
|
<>
|
|
49
55
|
Looking up matching {ownerKindLabel} <TooltipCode>{label}</TooltipCode> in this cluster.
|
|
50
56
|
</>
|
|
57
|
+
) : conflict ? (
|
|
58
|
+
<>
|
|
59
|
+
This resource's {toolLabel} tracking metadata names <TooltipCode>{label}</TooltipCode>, but that {ownerKindLabel}'s status doesn't list it.{' '}
|
|
60
|
+
{conflict.length === 1 ? 'The status of ' : 'The statuses of '}
|
|
61
|
+
{conflict.map((m, i) => (
|
|
62
|
+
<span key={`${m.namespace}/${m.name}`}>
|
|
63
|
+
{i > 0 && ', '}
|
|
64
|
+
<TooltipCode>{m.namespace ? `${m.namespace}/${m.name}` : m.name}</TooltipCode>
|
|
65
|
+
</span>
|
|
66
|
+
))}{' '}
|
|
67
|
+
{conflict.length === 1 ? 'does.' : 'do.'}
|
|
68
|
+
</>
|
|
51
69
|
) : verified ? (
|
|
52
70
|
<>
|
|
53
71
|
Managed by {toolLabel} · <TooltipCode>{label}</TooltipCode>
|
|
54
72
|
{statusLabel && <> · {statusLabel}{status?.message ? `: ${status.message}` : ''}</>}
|
|
73
|
+
{listing?.state === 'confirmed' && <>. Its status lists this resource too.</>}
|
|
55
74
|
</>
|
|
56
75
|
) : (
|
|
57
76
|
<>
|
|
@@ -80,6 +99,7 @@ export function ManagedByChip({
|
|
|
80
99
|
)}
|
|
81
100
|
>
|
|
82
101
|
<GitBranch className="h-3 w-3 shrink-0" />
|
|
102
|
+
{conflict && <StatusDot tone="degraded" size="xs" />}
|
|
83
103
|
<span className="shrink-0 text-theme-text-tertiary">{prefix}</span>
|
|
84
104
|
<span className="truncate max-w-[180px]">{label}</span>
|
|
85
105
|
{statusLabel && (
|
|
@@ -141,7 +161,7 @@ export function HelmManagedByChip({
|
|
|
141
161
|
)
|
|
142
162
|
}
|
|
143
163
|
|
|
144
|
-
function gitOpsOwnerKindLabel(owner: GitOpsOwnerRef): string {
|
|
164
|
+
export function gitOpsOwnerKindLabel(owner: GitOpsOwnerRef): string {
|
|
145
165
|
if (owner.tool === 'argocd') return 'Application'
|
|
146
166
|
if (owner.kind === 'helmreleases') return 'HelmRelease'
|
|
147
167
|
if (owner.kind === 'kustomizations') return 'Kustomization'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import { ResourceActionsBar } from './ResourceActionsBar'
|
|
4
|
+
|
|
5
|
+
const noop = () => {}
|
|
6
|
+
|
|
7
|
+
function actions(kind: string, apiVersion: string) {
|
|
8
|
+
return renderToString(
|
|
9
|
+
<ResourceActionsBar
|
|
10
|
+
resource={{ kind, name: 'example', namespace: 'test' }}
|
|
11
|
+
data={{ apiVersion, metadata: { name: 'example', namespace: 'test' } }}
|
|
12
|
+
onFluxReconcile={noop}
|
|
13
|
+
onFluxSuspend={noop}
|
|
14
|
+
onFluxResume={noop}
|
|
15
|
+
onArgoSync={noop}
|
|
16
|
+
onArgoRefresh={noop}
|
|
17
|
+
onArgoSuspend={noop}
|
|
18
|
+
onArgoResume={noop}
|
|
19
|
+
canPortForward
|
|
20
|
+
renderPortForward={() => <button>Port forward</button>}
|
|
21
|
+
/>,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('resource actions use the loaded API group', () => {
|
|
26
|
+
it.each(['app.k8s.io/v1beta1', 'notargoproj.io/v1alpha1', ''])('does not offer Argo actions for Application in %s', (apiVersion) => {
|
|
27
|
+
const html = actions('applications', apiVersion)
|
|
28
|
+
expect(html).not.toContain('>Sync<')
|
|
29
|
+
expect(html).not.toContain('>Refresh<')
|
|
30
|
+
expect(html).not.toContain('Enable Auto-Sync')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('retains Argo actions for its exact Application group', () => {
|
|
34
|
+
const html = actions('applications', 'argoproj.io/v1alpha1')
|
|
35
|
+
expect(html).toContain('>Sync<')
|
|
36
|
+
expect(html).toContain('>Refresh<')
|
|
37
|
+
expect(html).toContain('Enable Auto-Sync')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it.each(['serving.knative.dev/v1', 'vendor.serving.knative.dev/v1', 'vendor.io/v1', ''])('does not port-forward a non-core Service in %s', (apiVersion) => {
|
|
41
|
+
expect(actions('services', apiVersion)).not.toContain('Port forward')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('retains port forwarding for core Service', () => {
|
|
45
|
+
expect(actions('services', 'v1')).toContain('Port forward')
|
|
46
|
+
})
|
|
47
|
+
it.each([
|
|
48
|
+
['gitrepositories', 'source.toolkit.fluxcd.io'],
|
|
49
|
+
['ocirepositories', 'source.toolkit.fluxcd.io'],
|
|
50
|
+
['helmrepositories', 'source.toolkit.fluxcd.io'],
|
|
51
|
+
['kustomizations', 'kustomize.toolkit.fluxcd.io'],
|
|
52
|
+
['helmreleases', 'helm.toolkit.fluxcd.io'],
|
|
53
|
+
['alerts', 'notification.toolkit.fluxcd.io'],
|
|
54
|
+
])('only offers Flux actions for %s in its exact group', (kind, group) => {
|
|
55
|
+
expect(actions(kind, `${group}/v1`)).toContain('>Reconcile<')
|
|
56
|
+
for (const apiVersion of ['', 'vendor.io/v1', `foreign.${group}/v1`, 'argoproj.io/v1alpha1']) {
|
|
57
|
+
const html = actions(kind, apiVersion)
|
|
58
|
+
expect(html).not.toContain('>Reconcile<')
|
|
59
|
+
expect(html).not.toContain('>Suspend<')
|
|
60
|
+
expect(html).not.toContain('>Resume<')
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
})
|
|
@@ -28,10 +28,13 @@ import { DialogPortal } from '../ui/DialogPortal'
|
|
|
28
28
|
import type { SelectedResource, WorkloadRevision } from '../../types'
|
|
29
29
|
import type { RadarUpgradeRequirement } from '../../types/fetch-error'
|
|
30
30
|
import { displayKindName } from '../ui/drawer-components'
|
|
31
|
-
import { getDefaultContainerName } from '../resources/resource-utils'
|
|
31
|
+
import { getDefaultContainerName, getExecContainerNames } from '../resources/resource-utils'
|
|
32
32
|
import { SetImageDialog, type SetImageOwnership } from './SetImageDialog'
|
|
33
33
|
import { DrainPlanDialog, DEFAULT_DRAIN_DIALOG_OPTIONS, type DrainDialogOptions, type DrainPlan } from './DrainPlanDialog'
|
|
34
34
|
import type { WorkloadImageInventory, WorkloadImageUpdate } from '../../types/core'
|
|
35
|
+
import { getGitOpsTool } from '../gitops/detail-helpers'
|
|
36
|
+
import { apiVersionToGroup } from '../../utils/navigation'
|
|
37
|
+
import { isApiGroup } from '../resources/resource-utils-cnpg'
|
|
35
38
|
import { isArgoRolloutResource } from '../../utils/workload-rollout'
|
|
36
39
|
import { isCoreBatchJob } from '../../utils/api-resources'
|
|
37
40
|
import { formatRelativeAgeTime } from '../../utils/format'
|
|
@@ -40,6 +43,8 @@ import { formatRelativeAgeTime } from '../../utils/format'
|
|
|
40
43
|
// ACTIONS BAR - Interactive buttons that change based on resource kind
|
|
41
44
|
// ============================================================================
|
|
42
45
|
|
|
46
|
+
const FLUX_ACTION_KINDS = new Set(['gitrepositories', 'ocirepositories', 'helmrepositories', 'kustomizations', 'helmreleases', 'alerts'])
|
|
47
|
+
|
|
43
48
|
interface ResourceActionsBarProps {
|
|
44
49
|
resource: SelectedResource
|
|
45
50
|
leadingActions?: React.ReactNode
|
|
@@ -187,6 +192,7 @@ export function ResourceActionsBar({
|
|
|
187
192
|
onPlanDrain, onPlanDrainReset, drainPlan, isPlanningDrain, drainPlanError, drainPlanUpgrade,
|
|
188
193
|
}: ResourceActionsBarProps) {
|
|
189
194
|
const kind = resource.kind.toLowerCase()
|
|
195
|
+
const isFluxAction = FLUX_ACTION_KINDS.has(kind) && getGitOpsTool(kind, apiVersionToGroup(data?.apiVersion)) === 'flux'
|
|
190
196
|
const coreBatchJob = isCoreBatchJob(kind, resource.group)
|
|
191
197
|
const supportsWorkloadActions = ['deployments', 'statefulsets', 'daemonsets'].includes(kind) ||
|
|
192
198
|
(kind === 'rollouts' && isArgoRolloutResource(data))
|
|
@@ -254,7 +260,7 @@ export function ResourceActionsBar({
|
|
|
254
260
|
namespace: resource.namespace,
|
|
255
261
|
podName: resource.name,
|
|
256
262
|
containerName: getDefaultContainerName(data) || containers[0],
|
|
257
|
-
containers,
|
|
263
|
+
containers: getExecContainerNames(data),
|
|
258
264
|
})
|
|
259
265
|
}
|
|
260
266
|
}
|
|
@@ -370,7 +376,7 @@ export function ResourceActionsBar({
|
|
|
370
376
|
onUncordonNode && (
|
|
371
377
|
<button
|
|
372
378
|
onClick={() => onUncordonNode({ name: resource.name })}
|
|
373
|
-
disabled={isUncordoningNode}
|
|
379
|
+
disabled={isUncordoningNode || isDrainingNode}
|
|
374
380
|
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-white bg-emerald-600 hover:bg-emerald-700 rounded-lg transition-colors disabled:opacity-50"
|
|
375
381
|
>
|
|
376
382
|
<Play className={`w-3.5 h-3.5 ${isUncordoningNode ? 'animate-pulse' : ''}`} />
|
|
@@ -406,7 +412,7 @@ export function ResourceActionsBar({
|
|
|
406
412
|
)}
|
|
407
413
|
|
|
408
414
|
{/* Service actions */}
|
|
409
|
-
{kind === 'services' &&
|
|
415
|
+
{kind === 'services' && data?.apiVersion === 'v1' && canPortForward && resource.namespace && resource.name && renderPortForward && (
|
|
410
416
|
renderPortForward({
|
|
411
417
|
type: 'service',
|
|
412
418
|
namespace: resource.namespace,
|
|
@@ -516,7 +522,7 @@ export function ResourceActionsBar({
|
|
|
516
522
|
)}
|
|
517
523
|
|
|
518
524
|
{/* FluxCD actions */}
|
|
519
|
-
{
|
|
525
|
+
{isFluxAction && (
|
|
520
526
|
<FluxActions
|
|
521
527
|
resource={resource}
|
|
522
528
|
data={data}
|
|
@@ -532,7 +538,7 @@ export function ResourceActionsBar({
|
|
|
532
538
|
)}
|
|
533
539
|
|
|
534
540
|
{/* ArgoCD actions */}
|
|
535
|
-
{kind === 'applications' && (
|
|
541
|
+
{kind === 'applications' && isApiGroup(data?.apiVersion, 'argoproj.io') && (
|
|
536
542
|
<ArgoActions
|
|
537
543
|
resource={resource}
|
|
538
544
|
data={data}
|
|
@@ -1087,3 +1087,52 @@ it('uses RayJob native detail only for the supported exact API', () => {
|
|
|
1087
1087
|
if (apiVersion !== 'ray.io/v1') { expect(html).toContain('native-fallback'); expect(getResourceStatus('rayjobs', data)).toBeNull() }
|
|
1088
1088
|
}
|
|
1089
1089
|
})
|
|
1090
|
+
|
|
1091
|
+
describe('API group identity', () => {
|
|
1092
|
+
it.each([
|
|
1093
|
+
['applications', 'app.k8s.io/v1beta1', 'Application'],
|
|
1094
|
+
['applications', 'notargoproj.io/v1alpha1', 'Application'],
|
|
1095
|
+
['services', 'vendor.serving.knative.dev/v1', 'Service'],
|
|
1096
|
+
['ingresses', 'vendor.networking.internal.knative.dev/v1alpha1', 'Ingress'],
|
|
1097
|
+
['certificates', 'vendor.networking.internal.knative.dev/v1alpha1', 'Certificate'],
|
|
1098
|
+
['gateways', 'vendor.networking.istio.io/v1', 'Gateway'],
|
|
1099
|
+
])('keeps %s in %s generic', (plural, apiVersion, kind) => {
|
|
1100
|
+
const data = { apiVersion, kind, metadata: { name: 'foreign', namespace: 'test' }, spec: { vendorSetting: 'custom-value' }, status: { phase: 'VendorReady' } }
|
|
1101
|
+
expect(renderKind(plural, data, 'test')).toContain('custom-value')
|
|
1102
|
+
expect(getResourceStatus(plural, data)?.text).toBe('VendorReady')
|
|
1103
|
+
})
|
|
1104
|
+
|
|
1105
|
+
it('retains Argo CD status for its exact Application group', () => {
|
|
1106
|
+
const data = { apiVersion: 'argoproj.io/v1alpha1', kind: 'Application', metadata: { name: 'real' }, status: { sync: { status: 'Synced' }, health: { status: 'Healthy' } } }
|
|
1107
|
+
expect(renderKind('applications', data)).toContain('Synced')
|
|
1108
|
+
expect(getResourceStatus('applications', data)?.text).toBe('Healthy')
|
|
1109
|
+
})
|
|
1110
|
+
|
|
1111
|
+
it('retains Knative Service status and core Service presentation', () => {
|
|
1112
|
+
expect(getResourceStatus('services', { apiVersion: 'serving.knative.dev/v1', status: { conditions: [{ type: 'Ready', status: 'True' }] } })?.text).toBe('Ready')
|
|
1113
|
+
expect(renderKind('services', { apiVersion: 'v1', metadata: { name: 'core' }, spec: { type: 'ClusterIP', clusterIP: '10.0.0.1' } })).toContain('10.0.0.1')
|
|
1114
|
+
})
|
|
1115
|
+
})
|
|
1116
|
+
|
|
1117
|
+
describe('Flux kind collisions', () => {
|
|
1118
|
+
it.each([
|
|
1119
|
+
['gitrepositories', 'source.toolkit.fluxcd.io'],
|
|
1120
|
+
['ocirepositories', 'source.toolkit.fluxcd.io'],
|
|
1121
|
+
['helmrepositories', 'source.toolkit.fluxcd.io'],
|
|
1122
|
+
['kustomizations', 'kustomize.toolkit.fluxcd.io'],
|
|
1123
|
+
['helmreleases', 'helm.toolkit.fluxcd.io'],
|
|
1124
|
+
['alerts', 'notification.toolkit.fluxcd.io'],
|
|
1125
|
+
])('keeps foreign %s generic and preserves its real Flux renderer', (kind, group) => {
|
|
1126
|
+
const resource = {
|
|
1127
|
+
apiVersion: `${group}/v1`, metadata: { name: 'example', namespace: 'test' },
|
|
1128
|
+
spec: { vendorSetting: 'custom-value' }, status: { phase: 'VendorReady' },
|
|
1129
|
+
}
|
|
1130
|
+
expect(renderKind(kind, resource, 'test')).not.toContain('custom-value')
|
|
1131
|
+
expect(getResourceStatus(kind, resource)?.text).not.toBe('VendorReady')
|
|
1132
|
+
for (const apiVersion of ['vendor.io/v1', `foreign.${group}/v1`, 'argoproj.io/v1alpha1']) {
|
|
1133
|
+
const foreign = { ...resource, apiVersion }
|
|
1134
|
+
expect(renderKind(kind, foreign, 'test')).toContain('custom-value')
|
|
1135
|
+
expect(getResourceStatus(kind, foreign)?.text).toBe('VendorReady')
|
|
1136
|
+
}
|
|
1137
|
+
})
|
|
1138
|
+
})
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getGitOpsTool } from '../gitops/detail-helpers'
|
|
2
|
+
import { apiVersionToGroup } from '../../utils/navigation'
|
|
1
3
|
import { hasReflectorDetails } from '../resources/renderers/ReflectorSection'
|
|
2
4
|
import { clsx } from 'clsx'
|
|
3
5
|
import { SEVERITY_BADGE, HEALTH_BADGE_COLORS } from '../../utils/badge-colors'
|
|
@@ -314,6 +316,7 @@ import type { ScalerDiagnosis } from '../resources/renderers/WorkloadRenderer'
|
|
|
314
316
|
* When an override is not provided, the base (shared) renderer is used.
|
|
315
317
|
*/
|
|
316
318
|
export interface RendererOverrides {
|
|
319
|
+
FluxHelmReleaseRenderer?: React.ComponentType<React.ComponentProps<typeof FluxHelmReleaseRenderer>>
|
|
317
320
|
JobRenderer?: React.ComponentType<{ data: any; onNavigate?: (ref: ResourceRef) => void }>
|
|
318
321
|
JobSetRenderer?: React.ComponentType<{ data: any; onNavigate?: (ref: ResourceRef) => void }>
|
|
319
322
|
RayJobRenderer?: React.ComponentType<{ data: any; onNavigate?: (ref: ResourceRef) => void }>
|
|
@@ -551,6 +554,8 @@ interface ResourceRendererDispatchProps {
|
|
|
551
554
|
resource: SelectedResource
|
|
552
555
|
data: any
|
|
553
556
|
relationships?: Relationships
|
|
557
|
+
/** See RelatedResourcesSection. */
|
|
558
|
+
trackedBy?: string
|
|
554
559
|
certificateInfo?: SecretCertificateInfo
|
|
555
560
|
hpaDiagnosis?: HPADiagnosis
|
|
556
561
|
scalerDiagnostics?: ScalerDiagnosis[]
|
|
@@ -597,6 +602,7 @@ export function ResourceRendererDispatch({
|
|
|
597
602
|
resource,
|
|
598
603
|
data,
|
|
599
604
|
relationships,
|
|
605
|
+
trackedBy,
|
|
600
606
|
certificateInfo,
|
|
601
607
|
hpaDiagnosis,
|
|
602
608
|
scalerDiagnostics,
|
|
@@ -700,8 +706,11 @@ export function ResourceRendererDispatch({
|
|
|
700
706
|
// foreign CRD render a BLANK drawer rather than the generic one.
|
|
701
707
|
// CAPI's `machines` and `machinesets` need the same protection because another
|
|
702
708
|
// API group can publish either plural without inheriting CAPI presentation.
|
|
709
|
+
const isFluxKind = ['gitrepositories', 'ocirepositories', 'helmrepositories', 'kustomizations', 'helmreleases', 'alerts'].includes(kind)
|
|
710
|
+
const isFluxResource = getGitOpsTool(kind, apiVersionToGroup(data?.apiVersion)) === 'flux'
|
|
703
711
|
const isGroupGatedKind =
|
|
704
|
-
kind === '
|
|
712
|
+
isFluxKind || kind === 'applications' || kind === 'services' || kind === 'ingresses' || kind === 'certificates' || kind === 'gateways'
|
|
713
|
+
|| kind === 'clusters' || kind === 'backups' || kind === 'scheduledbackups' || kind === 'poolers'
|
|
705
714
|
|| kind === 'objectstores' || kind === 'databases' || kind === 'publications'
|
|
706
715
|
|| kind === 'subscriptions' || kind === 'imagecatalogs' || kind === 'clusterimagecatalogs' || kind === 'databaseroles' || kind === 'jobsets'
|
|
707
716
|
|| kind === 'policies' || kind === 'rollouts' || kind === 'experiments'
|
|
@@ -709,7 +718,13 @@ export function ResourceRendererDispatch({
|
|
|
709
718
|
|| kind === 'machines' || kind === 'machinesets' || kind === 'workloads' || kind === 'localqueues' || kind === 'clusterqueues'
|
|
710
719
|
const isCNPGApiVersion = isApiGroup(data?.apiVersion, CNPG_GROUP)
|
|
711
720
|
const groupGatedMatched =
|
|
712
|
-
(
|
|
721
|
+
(isFluxKind && isFluxResource)
|
|
722
|
+
|| (kind === 'applications' && isApiGroup(data?.apiVersion, 'argoproj.io'))
|
|
723
|
+
|| (kind === 'services' && (data?.apiVersion === 'v1' || isApiGroup(data?.apiVersion, 'serving.knative.dev')))
|
|
724
|
+
|| (kind === 'ingresses' && ['networking.k8s.io', 'extensions', 'networking.internal.knative.dev'].some(group => isApiGroup(data?.apiVersion, group)))
|
|
725
|
+
|| (kind === 'certificates' && ['cert-manager.io', 'networking.internal.knative.dev'].some(group => isApiGroup(data?.apiVersion, group)))
|
|
726
|
+
|| (kind === 'gateways' && ['gateway.networking.k8s.io', 'networking.istio.io'].some(group => isApiGroup(data?.apiVersion, group)))
|
|
727
|
+
|| (kind === 'clusters' && (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'cluster.x-k8s.io')))
|
|
713
728
|
|| (kind === 'backups' && (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'velero.io')))
|
|
714
729
|
|| ((kind === 'scheduledbackups' || kind === 'poolers') && isCNPGApiVersion)
|
|
715
730
|
|| (kind === 'objectstores' && isApiGroup(data?.apiVersion, 'barmancloud.cnpg.io'))
|
|
@@ -755,6 +770,7 @@ export function ResourceRendererDispatch({
|
|
|
755
770
|
const isKnownKind = KNOWN_KINDS.has(kind) || isCrossplaneMR || isCrossplaneClaim || isCrossplaneXR
|
|
756
771
|
|
|
757
772
|
const JobComp = rendererOverrides?.JobRenderer ?? JobRenderer
|
|
773
|
+
const FluxHelmReleaseComp = rendererOverrides?.FluxHelmReleaseRenderer ?? FluxHelmReleaseRenderer
|
|
758
774
|
const JobSetComp = rendererOverrides?.JobSetRenderer ?? JobSetRenderer
|
|
759
775
|
const RayJobComp = rendererOverrides?.RayJobRenderer ?? RayJobRenderer
|
|
760
776
|
const RayClusterComp = rendererOverrides?.RayClusterRenderer ?? RayClusterRenderer
|
|
@@ -797,7 +813,7 @@ export function ResourceRendererDispatch({
|
|
|
797
813
|
|
|
798
814
|
const sidebarContent = showCommonSections && (
|
|
799
815
|
<>
|
|
800
|
-
<RelatedResourcesSection relationships={sidebarRelationships} onNavigate={onNavigate} />
|
|
816
|
+
<RelatedResourcesSection relationships={sidebarRelationships} onNavigate={onNavigate} kind={kind} trackedBy={trackedBy} />
|
|
801
817
|
{kind !== 'events' && <EventsSection fullscreen={!!renderSidebar} events={events || []} updates={updates || []} isLoading={eventsLoading ?? false} eventsError={eventsError ?? null} updatesError={updatesError ?? null} hint={eventsHint} />}
|
|
802
818
|
<LabelsSection data={data} />
|
|
803
819
|
<AnnotationsSection data={data} />
|
|
@@ -822,9 +838,9 @@ export function ResourceRendererDispatch({
|
|
|
822
838
|
/>
|
|
823
839
|
)}
|
|
824
840
|
{kind === 'replicasets' && <ReplicaSetRenderer data={data} />}
|
|
825
|
-
{kind === 'services' &&
|
|
841
|
+
{kind === 'services' && data?.apiVersion === 'v1' && <ServiceComp data={data} onCopy={onCopy} copied={copied} onNavigate={onNavigate} />}
|
|
826
842
|
{kind === 'endpointslices' && <EndpointSliceRenderer data={data} onNavigate={onNavigate} />}
|
|
827
|
-
{kind === 'ingresses' &&
|
|
843
|
+
{kind === 'ingresses' && (isApiGroup(data?.apiVersion, 'networking.k8s.io') || isApiGroup(data?.apiVersion, 'extensions')) && <IngressRenderer data={data} onNavigate={onNavigate} />}
|
|
828
844
|
{kind === 'configmaps' && <ConfigMapRenderer data={data} relationships={relationships} onNavigate={onNavigate} />}
|
|
829
845
|
{kind === 'secrets' && <SecretRenderer data={data} relationships={relationships} onNavigate={onNavigate} certificateInfo={certificateInfo} resourceData={data} onSaveSecretValue={onSaveSecretValue} isSaving={isSavingSecret} />}
|
|
830
846
|
{kind === 'jobs' && !nonCoreJobFallthrough && <JobComp data={data} onNavigate={onNavigate} />}
|
|
@@ -846,7 +862,7 @@ export function ResourceRendererDispatch({
|
|
|
846
862
|
{kind === 'analysisruns' && <AnalysisRunRenderer data={data} onNavigate={onNavigate} />}
|
|
847
863
|
{(kind === 'analysistemplates' || kind === 'clusteranalysistemplates') && <AnalysisTemplateRenderer data={data} />}
|
|
848
864
|
{kind === 'experiments' && isApiGroup(data?.apiVersion, 'argoproj.io') && <ExperimentRenderer data={data} onNavigate={onNavigate} />}
|
|
849
|
-
{kind === 'certificates' &&
|
|
865
|
+
{kind === 'certificates' && isApiGroup(data?.apiVersion, 'cert-manager.io') && <CertificateRenderer data={data} />}
|
|
850
866
|
{kind === 'workflows' && <WorkflowRenderer data={data} onNavigate={onNavigate} />}
|
|
851
867
|
{kind === 'persistentvolumes' && <PersistentVolumeRenderer data={data} onNavigate={onNavigate} />}
|
|
852
868
|
{kind === 'storageclasses' && <StorageClassRenderer data={data} />}
|
|
@@ -855,7 +871,7 @@ export function ResourceRendererDispatch({
|
|
|
855
871
|
{kind === 'issuers' && <IssuerRenderer data={data} />}
|
|
856
872
|
{kind === 'orders' && <OrderRenderer data={data} />}
|
|
857
873
|
{kind === 'challenges' && <ChallengeRenderer data={data} />}
|
|
858
|
-
{kind === 'gateways' && (data.apiVersion
|
|
874
|
+
{kind === 'gateways' && groupGatedMatched && (isApiGroup(data.apiVersion, 'networking.istio.io') ? <IstioGatewayRenderer data={data} /> : <GatewayRenderer data={data} onNavigate={onNavigate} />)}
|
|
859
875
|
{kind === 'gatewayclasses' && <GatewayClassRenderer data={data} />}
|
|
860
876
|
{kind === 'httproutes' && <HTTPRouteRenderer data={data} onNavigate={onNavigate} />}
|
|
861
877
|
{kind === 'grpcroutes' && <GRPCRouteRenderer data={data} onNavigate={onNavigate} />}
|
|
@@ -875,13 +891,13 @@ export function ResourceRendererDispatch({
|
|
|
875
891
|
{(kind === 'roles' || kind === 'clusterroles') && <RoleComp data={data} onNavigate={onNavigate} />}
|
|
876
892
|
{(kind === 'rolebindings' || kind === 'clusterrolebindings') && <RoleBindingComp data={data} onNavigate={onNavigate} />}
|
|
877
893
|
{kind === 'events' && <EventRenderer data={data} onNavigate={onNavigate} />}
|
|
878
|
-
{kind === 'gitrepositories' && <GitRepositoryRenderer data={data} />}
|
|
879
|
-
{kind === 'ocirepositories' && <OCIRepositoryRenderer data={data} />}
|
|
880
|
-
{kind === 'helmrepositories' && <HelmRepositoryRenderer data={data} />}
|
|
881
|
-
{kind === 'kustomizations' && <KustomizationRenderer data={data} onNavigate={onNavigate} />}
|
|
882
|
-
{kind === 'helmreleases' && <
|
|
883
|
-
{kind === 'alerts' && <AlertRenderer data={data} />}
|
|
884
|
-
{kind === 'applications' && <ArgoApplicationRenderer data={data} />}
|
|
894
|
+
{kind === 'gitrepositories' && isFluxResource && <GitRepositoryRenderer data={data} />}
|
|
895
|
+
{kind === 'ocirepositories' && isFluxResource && <OCIRepositoryRenderer data={data} />}
|
|
896
|
+
{kind === 'helmrepositories' && isFluxResource && <HelmRepositoryRenderer data={data} />}
|
|
897
|
+
{kind === 'kustomizations' && isFluxResource && <KustomizationRenderer data={data} onNavigate={onNavigate} />}
|
|
898
|
+
{kind === 'helmreleases' && isFluxResource && <FluxHelmReleaseComp data={data} onNavigate={onNavigate} />}
|
|
899
|
+
{kind === 'alerts' && isFluxResource && <AlertRenderer data={data} />}
|
|
900
|
+
{kind === 'applications' && isApiGroup(data?.apiVersion, 'argoproj.io') && <ArgoApplicationRenderer data={data} />}
|
|
885
901
|
{kind === 'nodepools' && <KarpenterNodePoolComp data={data} onNavigate={onNavigate} />}
|
|
886
902
|
{kind === 'nodeclaims' && <KarpenterNodeClaimRenderer data={data} onNavigate={onNavigate} />}
|
|
887
903
|
{kind === 'ec2nodeclasses' && <KarpenterEC2NodeClassRenderer data={data} />}
|
|
@@ -987,14 +1003,14 @@ export function ResourceRendererDispatch({
|
|
|
987
1003
|
{kind === 'leases' && <LeaseRenderer data={data} />}
|
|
988
1004
|
{kind === 'limitranges' && <LimitRangeRenderer data={data} />}
|
|
989
1005
|
{/* Knative Serving */}
|
|
990
|
-
{(kind === 'services' && data?.apiVersion
|
|
1006
|
+
{(kind === 'services' && isApiGroup(data?.apiVersion, 'serving.knative.dev')) && <KnativeServiceRenderer data={data} onNavigate={onNavigate} />}
|
|
991
1007
|
{kind === 'knativeservices' && <KnativeServiceRenderer data={data} onNavigate={onNavigate} />}
|
|
992
1008
|
{kind === 'knativeconfigurations' && <KnativeConfigurationRenderer data={data} onNavigate={onNavigate} />}
|
|
993
1009
|
{kind === 'knativerevisions' && <KnativeRevisionRenderer data={data} onNavigate={onNavigate} />}
|
|
994
1010
|
{kind === 'knativeroutes' && <KnativeRouteRenderer data={data} onNavigate={onNavigate} />}
|
|
995
|
-
{(kind === 'ingresses' && data?.apiVersion
|
|
1011
|
+
{(kind === 'ingresses' && isApiGroup(data?.apiVersion, 'networking.internal.knative.dev')) && <KnativeIngressRenderer data={data} />}
|
|
996
1012
|
{kind === 'knativeingresses' && <KnativeIngressRenderer data={data} />}
|
|
997
|
-
{(kind === 'certificates' && data?.apiVersion
|
|
1013
|
+
{(kind === 'certificates' && isApiGroup(data?.apiVersion, 'networking.internal.knative.dev')) && <KnativeCertificateRenderer data={data} />}
|
|
998
1014
|
{kind === 'knativecertificates' && <KnativeCertificateRenderer data={data} />}
|
|
999
1015
|
{kind === 'serverlessservices' && <ServerlessServiceRenderer data={data} onNavigate={onNavigate} />}
|
|
1000
1016
|
{kind === 'domainmappings' && <DomainMappingRenderer data={data} onNavigate={onNavigate} />}
|
|
@@ -1099,11 +1115,11 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
|
|
|
1099
1115
|
}
|
|
1100
1116
|
if (k === 'replicasets') return getWorkloadStatus(data, k)
|
|
1101
1117
|
if (k === 'services') {
|
|
1102
|
-
if (data.apiVersion
|
|
1118
|
+
if (isApiGroup(data.apiVersion, 'serving.knative.dev')) {
|
|
1103
1119
|
const status = getKnativeConditionStatus(data)
|
|
1104
1120
|
return { text: status.text, color: status.color }
|
|
1105
1121
|
}
|
|
1106
|
-
return getServiceStatus(data)
|
|
1122
|
+
if (data.apiVersion === 'v1') return getServiceStatus(data)
|
|
1107
1123
|
}
|
|
1108
1124
|
if (k === 'endpointslices') {
|
|
1109
1125
|
const endpoints = data.endpoints || []
|
|
@@ -1176,11 +1192,11 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
|
|
|
1176
1192
|
if (k === 'workflows') return getWorkflowStatus(data)
|
|
1177
1193
|
if (k === 'cronworkflows') return getCronWorkflowStatus(data)
|
|
1178
1194
|
if (k === 'certificates') {
|
|
1179
|
-
if (data.apiVersion
|
|
1195
|
+
if (isApiGroup(data.apiVersion, 'networking.internal.knative.dev')) {
|
|
1180
1196
|
const status = getKnativeConditionStatus(data)
|
|
1181
1197
|
return { text: status.text, color: status.color }
|
|
1182
1198
|
}
|
|
1183
|
-
return getCertificateStatus(data)
|
|
1199
|
+
if (isApiGroup(data.apiVersion, 'cert-manager.io')) return getCertificateStatus(data)
|
|
1184
1200
|
}
|
|
1185
1201
|
if (k === 'persistentvolumes') return getPVStatus(data)
|
|
1186
1202
|
if (k === 'certificaterequests') return getCertificateRequestStatus(data)
|
|
@@ -1189,20 +1205,21 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
|
|
|
1189
1205
|
if (k === 'orders') return getOrderState(data)
|
|
1190
1206
|
if (k === 'challenges') return getChallengeState(data)
|
|
1191
1207
|
if (k === 'gateways') {
|
|
1192
|
-
if (data.apiVersion
|
|
1193
|
-
return getGatewayStatus(data)
|
|
1208
|
+
if (isApiGroup(data.apiVersion, 'networking.istio.io')) return getIstioGatewayStatus(data)
|
|
1209
|
+
if (isApiGroup(data.apiVersion, 'gateway.networking.k8s.io')) return getGatewayStatus(data)
|
|
1194
1210
|
}
|
|
1195
1211
|
if (k === 'gatewayclasses') return getGatewayClassStatus(data)
|
|
1196
1212
|
if (k === 'httproutes' || k === 'grpcroutes' || k === 'tcproutes' || k === 'tlsroutes' || k === 'udproutes') return getRouteStatus(data)
|
|
1197
1213
|
if (k === 'sealedsecrets') return getSealedSecretStatus(data)
|
|
1198
1214
|
if (k === 'poddisruptionbudgets') return getPDBStatus(data)
|
|
1199
|
-
|
|
1200
|
-
if (k === '
|
|
1201
|
-
if (k === '
|
|
1202
|
-
if (k === '
|
|
1203
|
-
if (k === '
|
|
1204
|
-
if (k === '
|
|
1205
|
-
if (k === '
|
|
1215
|
+
const isFluxResource = getGitOpsTool(k, apiVersionToGroup(data.apiVersion)) === 'flux'
|
|
1216
|
+
if (k === 'gitrepositories' && isFluxResource) return getGitRepositoryStatus(data)
|
|
1217
|
+
if (k === 'ocirepositories' && isFluxResource) return getOCIRepositoryStatus(data)
|
|
1218
|
+
if (k === 'helmrepositories' && isFluxResource) return getHelmRepositoryStatus(data)
|
|
1219
|
+
if (k === 'kustomizations' && isFluxResource) return getKustomizationStatus(data)
|
|
1220
|
+
if (k === 'helmreleases' && isFluxResource) return getFluxHelmReleaseStatus(data)
|
|
1221
|
+
if (k === 'alerts' && isFluxResource) return getFluxAlertStatus(data)
|
|
1222
|
+
if (k === 'applications' && isApiGroup(data.apiVersion, 'argoproj.io')) return getArgoApplicationStatus(data)
|
|
1206
1223
|
if (k === 'nodepools') return getNodePoolStatus(data)
|
|
1207
1224
|
if (k === 'nodeclaims') return getNodeClaimStatus(data)
|
|
1208
1225
|
if (k === 'ec2nodeclasses') return getEC2NodeClassStatus(data)
|
|
@@ -1347,7 +1364,7 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
|
|
|
1347
1364
|
if (
|
|
1348
1365
|
knativeConditionKinds.includes(k)
|
|
1349
1366
|
|| isKnativeSubscription
|
|
1350
|
-
|| (k === 'ingresses' && data.apiVersion
|
|
1367
|
+
|| (k === 'ingresses' && isApiGroup(data.apiVersion, 'networking.internal.knative.dev'))
|
|
1351
1368
|
) {
|
|
1352
1369
|
const status = getKnativeConditionStatus(data)
|
|
1353
1370
|
return { text: status.text, color: status.color }
|
|
@@ -23,4 +23,73 @@ describe("DiffViewer", () => {
|
|
|
23
23
|
expect(html).toContain("Old value:");
|
|
24
24
|
expect(html).toContain("New value:");
|
|
25
25
|
});
|
|
26
|
+
|
|
27
|
+
it("renders values kept out of the feed as a badge, not as struck-through text", () => {
|
|
28
|
+
const html = renderToStaticMarkup(
|
|
29
|
+
<DiffViewer
|
|
30
|
+
diff={{
|
|
31
|
+
summary: "pod template annotation checksum/config changed",
|
|
32
|
+
fields: [
|
|
33
|
+
{ path: "spec.template.metadata.annotations[checksum/config]", oldValue: "(value hidden)", newValue: "(value hidden)" },
|
|
34
|
+
{ path: "spec.template.metadata.annotations[example.com/new]", oldValue: null, newValue: "(set)" },
|
|
35
|
+
{ path: "spec.template.metadata.annotations[example.com/old]", oldValue: "(set)", newValue: null },
|
|
36
|
+
{ path: "spec.template.spec.affinity", oldValue: "changed", newValue: "changed" },
|
|
37
|
+
],
|
|
38
|
+
}}
|
|
39
|
+
/>,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(html).not.toContain("line-through");
|
|
43
|
+
expect(html).not.toContain("(value hidden)");
|
|
44
|
+
expect(html).not.toContain("(set)");
|
|
45
|
+
expect(html.match(/value hidden/g)).toHaveLength(4);
|
|
46
|
+
expect(html).toContain("Set");
|
|
47
|
+
expect(html).toContain("Removed");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("names key changes instead of striking through an unchanged key list", () => {
|
|
51
|
+
const html = renderToStaticMarkup(
|
|
52
|
+
<DiffViewer
|
|
53
|
+
diff={{
|
|
54
|
+
summary: "data modified keys: [password]",
|
|
55
|
+
fields: [{ path: "data (modified keys)", oldValue: ["password"], newValue: ["password"] }],
|
|
56
|
+
}}
|
|
57
|
+
/>,
|
|
58
|
+
);
|
|
59
|
+
expect(html).toContain("keys changed: <span class=\"font-mono\">password</span>");
|
|
60
|
+
expect(html).not.toContain("line-through");
|
|
61
|
+
expect(html).not.toContain("Old value:");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("merges added, removed and modified key lists of one field into one row", () => {
|
|
65
|
+
const html = renderToStaticMarkup(
|
|
66
|
+
<DiffViewer
|
|
67
|
+
diff={{
|
|
68
|
+
summary: "",
|
|
69
|
+
fields: [
|
|
70
|
+
{ path: "data (added keys)", oldValue: null, newValue: ["token"] },
|
|
71
|
+
{ path: "data (removed keys)", oldValue: ["old"], newValue: null },
|
|
72
|
+
{ path: "data (modified keys)", oldValue: ["a", "b"], newValue: ["a", "b"] },
|
|
73
|
+
{ path: "stringData (added keys)", oldValue: null, newValue: ["extra"] },
|
|
74
|
+
{ path: "type", oldValue: "Opaque", newValue: "kubernetes.io/tls" },
|
|
75
|
+
],
|
|
76
|
+
}}
|
|
77
|
+
/>,
|
|
78
|
+
);
|
|
79
|
+
expect(html.match(/>data</g)).toHaveLength(1);
|
|
80
|
+
expect(html).toMatch(/keys changed: <span[^>]*>a, b<\/span>.*added: <span[^>]*>token<\/span>.*removed: <span[^>]*>old<\/span>/);
|
|
81
|
+
expect(html).toContain(">stringData<");
|
|
82
|
+
expect(html).toContain("added: <span class=\"font-mono\">extra</span>");
|
|
83
|
+
// Ordinary fields keep the old→new rendering.
|
|
84
|
+
expect(html).toContain("Old value:");
|
|
85
|
+
expect(html).toContain("kubernetes.io/tls");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("keeps the old→new rendering when a key-list path does not carry a list", () => {
|
|
89
|
+
const html = renderToStaticMarkup(
|
|
90
|
+
<DiffViewer diff={{ summary: "", fields: [{ path: "data (modified keys)", oldValue: "x", newValue: "y" }] }} />,
|
|
91
|
+
);
|
|
92
|
+
expect(html).toContain("Old value:");
|
|
93
|
+
expect(html).not.toContain("keys changed");
|
|
94
|
+
});
|
|
26
95
|
});
|