@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.
Files changed (36) hide show
  1. package/README.md +9 -0
  2. package/package.json +4 -1
  3. package/src/components/applications/ApplicationDetail.test.tsx +28 -0
  4. package/src/components/applications/ApplicationDetail.tsx +17 -0
  5. package/src/components/audit/AuditAlerts.tsx +13 -11
  6. package/src/components/audit/AuditBadgeTooltip.test.tsx +6 -0
  7. package/src/components/audit/AuditBadgeTooltip.tsx +9 -6
  8. package/src/components/audit/AuditCard.tsx +15 -13
  9. package/src/components/audit/AuditFindingsTable.tsx +33 -31
  10. package/src/components/audit/AuditSeverityPresentation.test.tsx +64 -0
  11. package/src/components/checks/types.ts +4 -4
  12. package/src/components/issues/types.ts +4 -0
  13. package/src/components/resources/ResourcesView.tsx +9 -7
  14. package/src/components/resources/get-pod-phase-display.test.ts +204 -0
  15. package/src/components/resources/resource-utils.ts +101 -0
  16. package/src/components/shared/CreateResourceDialog.tsx +307 -172
  17. package/src/components/shared/EditableYamlView.tsx +197 -26
  18. package/src/components/timeline/TimelineSwimlanes.tsx +28 -15
  19. package/src/components/timeline/index.ts +0 -2
  20. package/src/components/timeline/timeline-live.test.ts +0 -37
  21. package/src/components/timeline/timeline-live.ts +2 -24
  22. package/src/components/topology/K8sResourceNode.tsx +11 -9
  23. package/src/components/ui/FilterPill.tsx +3 -1
  24. package/src/components/ui/YamlEditor.test.ts +148 -0
  25. package/src/components/ui/YamlEditor.tsx +759 -186
  26. package/src/components/ui/YamlReview.test.ts +45 -0
  27. package/src/components/ui/YamlReview.tsx +426 -0
  28. package/src/components/ui/index.ts +9 -0
  29. package/src/components/ui/monacoRuntime.ts +41 -0
  30. package/src/components/ui/monacoYaml.worker.ts +1 -0
  31. package/src/components/ui/yamlMonacoRuntime.ts +110 -0
  32. package/src/components/workload/WorkloadView.tsx +27 -2
  33. package/src/monaco-deep.d.ts +5 -0
  34. package/src/types/core.ts +8 -0
  35. package/src/utils/yaml.test.ts +100 -2
  36. 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, SEVERITY_TEXT } from '../../utils/badge-colors'
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. Colored by worst severity (danger red, else warning amber).
468
- const auditDanger =
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 auditWarning =
472
+ const auditMedium =
471
473
  typeof nodeData.auditWarning === "number" ? nodeData.auditWarning : 0;
472
- const auditTotal = auditDanger + auditWarning;
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"}${auditDanger > 0 ? ` · ${auditDanger} danger` : ""}`
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
- auditDanger > 0
664
- ? SEVERITY_TEXT.error
665
- : SEVERITY_TEXT.warning,
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
+ })