@skyhook-io/k8s-ui 1.8.14 → 1.9.0
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 +9 -0
- package/package.json +4 -1
- package/src/components/applications/ApplicationDetail.test.tsx +28 -0
- package/src/components/applications/ApplicationDetail.tsx +17 -0
- package/src/components/audit/AuditAlerts.tsx +13 -11
- package/src/components/audit/AuditBadgeTooltip.test.tsx +6 -0
- package/src/components/audit/AuditBadgeTooltip.tsx +9 -6
- package/src/components/audit/AuditCard.tsx +15 -13
- package/src/components/audit/AuditFindingsTable.tsx +33 -31
- package/src/components/audit/AuditSeverityPresentation.test.tsx +64 -0
- package/src/components/checks/types.ts +4 -4
- package/src/components/issues/types.ts +4 -0
- package/src/components/resources/ResourcesView.tsx +9 -7
- package/src/components/resources/get-pod-phase-display.test.ts +204 -0
- package/src/components/resources/resource-utils.ts +101 -0
- package/src/components/shared/CreateResourceDialog.tsx +307 -172
- package/src/components/shared/EditableYamlView.tsx +197 -26
- package/src/components/timeline/TimelineSwimlanes.tsx +28 -15
- package/src/components/timeline/index.ts +0 -2
- package/src/components/timeline/timeline-live.test.ts +0 -37
- package/src/components/timeline/timeline-live.ts +2 -24
- package/src/components/topology/K8sResourceNode.tsx +11 -9
- package/src/components/ui/FilterPill.tsx +3 -1
- package/src/components/ui/YamlEditor.test.ts +148 -0
- package/src/components/ui/YamlEditor.tsx +759 -186
- package/src/components/ui/YamlReview.test.ts +45 -0
- package/src/components/ui/YamlReview.tsx +426 -0
- package/src/components/ui/index.ts +9 -0
- package/src/components/ui/monacoRuntime.ts +41 -0
- package/src/components/ui/monacoYaml.worker.ts +1 -0
- package/src/components/ui/yamlMonacoRuntime.ts +110 -0
- package/src/components/workload/WorkloadView.tsx +27 -2
- package/src/monaco-deep.d.ts +5 -0
- package/src/types/core.ts +8 -0
- package/src/utils/yaml.test.ts +100 -2
- package/src/utils/yaml.ts +86 -1
|
@@ -10,13 +10,14 @@ import {
|
|
|
10
10
|
import { clsx } from 'clsx'
|
|
11
11
|
import type { NodeKind, HealthStatus, PodSummary } from '../../types'
|
|
12
12
|
import { displayKind } from '../../types'
|
|
13
|
-
import { healthToSeverity, SEVERITY_DOT
|
|
13
|
+
import { healthToSeverity, SEVERITY_DOT } from '../../utils/badge-colors'
|
|
14
14
|
import { workloadHue } from '../../utils/workload-colors'
|
|
15
15
|
import { ownershipOf } from '../../utils/topology-neighborhood'
|
|
16
16
|
import { midTruncate } from '../../utils/format'
|
|
17
17
|
import { getTopologyIcon } from '../../utils/resource-icons'
|
|
18
18
|
import { Tooltip } from '../ui/Tooltip'
|
|
19
19
|
import { AuditBadgeTooltip, type AuditBadgeMessage } from '../audit/AuditBadgeTooltip'
|
|
20
|
+
import { SEVERITY_TEXT_CLASS } from '../checks/severity'
|
|
20
21
|
import argoCdLogo from '../../assets/gitops/argocd.png'
|
|
21
22
|
import fluxLogo from '../../assets/gitops/flux.svg'
|
|
22
23
|
|
|
@@ -464,12 +465,13 @@ export const K8sResourceNode = memo(function K8sResourceNode({
|
|
|
464
465
|
// node's data by auditKey). The host only counts "badge-worthy" findings —
|
|
465
466
|
// reference-integrity / lifecycle, "this resource is actually broken" — not the
|
|
466
467
|
// posture/best-practice nags that fire near-universally, so the indicator stays
|
|
467
|
-
// a signal.
|
|
468
|
-
|
|
468
|
+
// a signal. The counters retain the raw transport names, but presentation
|
|
469
|
+
// follows the canonical Checks severity ladder (High, then Medium).
|
|
470
|
+
const auditHigh =
|
|
469
471
|
typeof nodeData.auditDanger === "number" ? nodeData.auditDanger : 0;
|
|
470
|
-
const
|
|
472
|
+
const auditMedium =
|
|
471
473
|
typeof nodeData.auditWarning === "number" ? nodeData.auditWarning : 0;
|
|
472
|
-
const auditTotal =
|
|
474
|
+
const auditTotal = auditHigh + auditMedium;
|
|
473
475
|
const auditMessages = Array.isArray(nodeData.auditMessages)
|
|
474
476
|
? (nodeData.auditMessages as AuditBadgeMessage[])
|
|
475
477
|
: [];
|
|
@@ -652,7 +654,7 @@ export const K8sResourceNode = memo(function K8sResourceNode({
|
|
|
652
654
|
clickHint={false}
|
|
653
655
|
/>
|
|
654
656
|
) : (
|
|
655
|
-
`${auditTotal} audit ${auditTotal === 1 ? "finding" : "findings"}${
|
|
657
|
+
`${auditTotal} audit ${auditTotal === 1 ? "finding" : "findings"}${auditHigh > 0 ? ` · ${auditHigh} high` : ""}${auditMedium > 0 ? ` · ${auditMedium} medium` : ""}`
|
|
656
658
|
)
|
|
657
659
|
}
|
|
658
660
|
position="right"
|
|
@@ -660,9 +662,9 @@ export const K8sResourceNode = memo(function K8sResourceNode({
|
|
|
660
662
|
<TriangleAlert
|
|
661
663
|
className={clsx(
|
|
662
664
|
"w-3 h-3 cursor-help",
|
|
663
|
-
|
|
664
|
-
?
|
|
665
|
-
:
|
|
665
|
+
auditHigh > 0
|
|
666
|
+
? SEVERITY_TEXT_CLASS.high
|
|
667
|
+
: SEVERITY_TEXT_CLASS.medium,
|
|
666
668
|
)}
|
|
667
669
|
/>
|
|
668
670
|
</Tooltip>
|
|
@@ -22,7 +22,7 @@ import { Tooltip } from './Tooltip'
|
|
|
22
22
|
// screen readers announce pressed/unpressed correctly. Optional tooltip
|
|
23
23
|
// describes the toggle action ("Click to stop filtering by danger").
|
|
24
24
|
|
|
25
|
-
export type FilterPillTone = 'neutral' | 'danger' | 'warn' | 'ok' | 'brand'
|
|
25
|
+
export type FilterPillTone = 'neutral' | 'danger' | 'warn' | 'high' | 'medium' | 'ok' | 'brand'
|
|
26
26
|
|
|
27
27
|
interface Props {
|
|
28
28
|
label: ReactNode
|
|
@@ -49,6 +49,8 @@ const TONE_ACTIVE: Record<FilterPillTone, string> = {
|
|
|
49
49
|
neutral: 'bg-theme-text-primary/10 border-theme-text-primary/25 text-theme-text-primary',
|
|
50
50
|
danger: 'bg-red-500/15 border-red-500/40 text-red-700 dark:text-red-300',
|
|
51
51
|
warn: 'bg-amber-500/15 border-amber-500/40 text-amber-800 dark:text-amber-300',
|
|
52
|
+
high: 'bg-orange-500/15 border-orange-500/40 text-orange-800 dark:text-orange-300',
|
|
53
|
+
medium: 'bg-yellow-500/15 border-yellow-500/40 text-yellow-800 dark:text-yellow-300',
|
|
52
54
|
ok: 'bg-emerald-500/15 border-emerald-500/40 text-emerald-700 dark:text-emerald-300',
|
|
53
55
|
brand: 'bg-[var(--color-brand-50)] border-[var(--color-radar-accent)] text-theme-text-primary dark:bg-[var(--color-brand-950)]',
|
|
54
56
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
isBlockingYamlDiagnostic,
|
|
4
|
+
parseFallbackYamlDiagnostics,
|
|
5
|
+
parseYamlDocumentIdentities,
|
|
6
|
+
shouldAutoTriggerYamlSuggestions,
|
|
7
|
+
} from './YamlEditor'
|
|
8
|
+
|
|
9
|
+
describe('parseYamlDocumentIdentities', () => {
|
|
10
|
+
it('preserves ordered identities and document start lines', () => {
|
|
11
|
+
const documents = parseYamlDocumentIdentities(`apiVersion: apps/v1
|
|
12
|
+
kind: Deployment
|
|
13
|
+
metadata:
|
|
14
|
+
name: api
|
|
15
|
+
---
|
|
16
|
+
apiVersion: monitoring.coreos.com/v1
|
|
17
|
+
kind: ServiceMonitor
|
|
18
|
+
metadata:
|
|
19
|
+
name: api
|
|
20
|
+
`)
|
|
21
|
+
expect(documents).toEqual([
|
|
22
|
+
{
|
|
23
|
+
index: 0,
|
|
24
|
+
schemaIndex: 0,
|
|
25
|
+
apiVersion: 'apps/v1',
|
|
26
|
+
kind: 'Deployment',
|
|
27
|
+
startLine: 1,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
index: 1,
|
|
31
|
+
schemaIndex: 1,
|
|
32
|
+
apiVersion: 'monitoring.coreos.com/v1',
|
|
33
|
+
kind: 'ServiceMonitor',
|
|
34
|
+
startLine: 5,
|
|
35
|
+
},
|
|
36
|
+
])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('keeps incomplete documents addressable without inventing an identity', () => {
|
|
40
|
+
expect(parseYamlDocumentIdentities('apiVersion: v1\nkind:')).toEqual([
|
|
41
|
+
{ index: 0, schemaIndex: 0, apiVersion: 'v1', kind: '', startLine: 1 },
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('compacts empty document segments to match preview and apply indices', () => {
|
|
46
|
+
expect(
|
|
47
|
+
parseYamlDocumentIdentities(`apiVersion: apps/v1
|
|
48
|
+
kind: Deployment
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
apiVersion: v1
|
|
53
|
+
kind: ConfigMap
|
|
54
|
+
`),
|
|
55
|
+
).toEqual([
|
|
56
|
+
{
|
|
57
|
+
index: 0,
|
|
58
|
+
schemaIndex: 0,
|
|
59
|
+
apiVersion: 'apps/v1',
|
|
60
|
+
kind: 'Deployment',
|
|
61
|
+
startLine: 1,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
index: 1,
|
|
65
|
+
schemaIndex: 2,
|
|
66
|
+
apiVersion: 'v1',
|
|
67
|
+
kind: 'ConfigMap',
|
|
68
|
+
startLine: 5,
|
|
69
|
+
},
|
|
70
|
+
])
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('isBlockingYamlDiagnostic', () => {
|
|
75
|
+
it('blocks syntax failures when no schema source is present', () => {
|
|
76
|
+
expect(isBlockingYamlDiagnostic('error')).toBe(true)
|
|
77
|
+
expect(isBlockingYamlDiagnostic('warning', '0', 'Incorrect type. Expected integer.')).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('keeps cluster schema diagnostics advisory', () => {
|
|
81
|
+
expect(
|
|
82
|
+
isBlockingYamlDiagnostic(
|
|
83
|
+
'warning',
|
|
84
|
+
'513',
|
|
85
|
+
'Property contianers is not allowed.',
|
|
86
|
+
'yaml-schema: Kubernetes cluster schema',
|
|
87
|
+
),
|
|
88
|
+
).toBe(false)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('keeps deprecations visible but advisory', () => {
|
|
92
|
+
expect(isBlockingYamlDiagnostic('warning', '2', 'Use spec.newField instead.')).toBe(false)
|
|
93
|
+
expect(
|
|
94
|
+
isBlockingYamlDiagnostic(
|
|
95
|
+
'warning',
|
|
96
|
+
undefined,
|
|
97
|
+
'[radar-advisory:deprecated] Use spec.newField instead.',
|
|
98
|
+
),
|
|
99
|
+
).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('parseFallbackYamlDiagnostics', () => {
|
|
104
|
+
it('accepts valid multi-document YAML', () => {
|
|
105
|
+
expect(
|
|
106
|
+
parseFallbackYamlDiagnostics(`apiVersion: v1
|
|
107
|
+
kind: ConfigMap
|
|
108
|
+
---
|
|
109
|
+
apiVersion: apps/v1
|
|
110
|
+
kind: Deployment
|
|
111
|
+
`),
|
|
112
|
+
).toEqual([])
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('reports a blocking syntax error against the correct document', () => {
|
|
116
|
+
const diagnostics = parseFallbackYamlDiagnostics(`apiVersion: v1
|
|
117
|
+
kind: ConfigMap
|
|
118
|
+
---
|
|
119
|
+
apiVersion: v1
|
|
120
|
+
kind: [`)
|
|
121
|
+
|
|
122
|
+
expect(diagnostics).toHaveLength(1)
|
|
123
|
+
expect(diagnostics[0]).toMatchObject({
|
|
124
|
+
severity: 'error',
|
|
125
|
+
line: 5,
|
|
126
|
+
column: 8,
|
|
127
|
+
documentIndex: 1,
|
|
128
|
+
blocking: true,
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('shouldAutoTriggerYamlSuggestions', () => {
|
|
134
|
+
it('triggers after Enter creates an indented blank line', () => {
|
|
135
|
+
expect(shouldAutoTriggerYamlSuggestions(['\n '], ' ', 7)).toBe(true)
|
|
136
|
+
expect(shouldAutoTriggerYamlSuggestions(['\r\n '], ' ', 3)).toBe(true)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('does not trigger for typing, pasted content, or a non-blank line', () => {
|
|
140
|
+
expect(shouldAutoTriggerYamlSuggestions([' '], ' ', 7)).toBe(false)
|
|
141
|
+
expect(shouldAutoTriggerYamlSuggestions(['\n image:'], ' image:', 13)).toBe(false)
|
|
142
|
+
expect(shouldAutoTriggerYamlSuggestions(['\n '], ' ports:', 13)).toBe(false)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('waits until the cursor is at the end of the new indentation', () => {
|
|
146
|
+
expect(shouldAutoTriggerYamlSuggestions(['\n '], ' ', 4)).toBe(false)
|
|
147
|
+
})
|
|
148
|
+
})
|