@skyhook-io/k8s-ui 1.5.13 → 1.6.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 (45) hide show
  1. package/package.json +3 -3
  2. package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -3
  3. package/src/components/dock/BottomDock.tsx +24 -17
  4. package/src/components/dock/DockContext.tsx +39 -0
  5. package/src/components/gitops/index.ts +4 -0
  6. package/src/components/gitops/insights/GitOpsInsightViews.tsx +1456 -0
  7. package/src/components/gitops/insights/index.ts +6 -0
  8. package/src/components/gitops/insights/insights-helpers.test.ts +98 -0
  9. package/src/components/gitops/insights/insights-helpers.ts +99 -0
  10. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +799 -0
  11. package/src/components/gitops/tree/index.ts +4 -0
  12. package/src/components/gitops/tree/tree-helpers.ts +42 -0
  13. package/src/components/resources/ResourcesView.tsx +71 -18
  14. package/src/components/resources/index.ts +1 -1
  15. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -1
  16. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -1
  17. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -1
  18. package/src/components/resources/renderers/PodRenderer.tsx +4 -3
  19. package/src/components/resources/renderers/SecretRenderer.tsx +4 -10
  20. package/src/components/shared/EditableYamlView.tsx +28 -17
  21. package/src/components/shared/ManagedByChip.tsx +45 -0
  22. package/src/components/shared/index.ts +1 -0
  23. package/src/components/timeline/TimelineList.tsx +3 -3
  24. package/src/components/topology/TopologyGraph.tsx +3 -2
  25. package/src/components/ui/Tooltip.tsx +10 -1
  26. package/src/components/ui/drawer-components.tsx +1 -1
  27. package/src/components/workload/ResourceDetailDrawer.tsx +5 -3
  28. package/src/components/workload/WorkloadView.tsx +66 -0
  29. package/src/hooks/useKeyboardShortcuts.tsx +3 -2
  30. package/src/index.ts +3 -0
  31. package/src/types/core.ts +19 -2
  32. package/src/types/gitops-insights.ts +193 -0
  33. package/src/types/gitops-tree.ts +57 -0
  34. package/src/types/index.ts +2 -0
  35. package/src/utils/badge-colors.ts +6 -1
  36. package/src/utils/format.ts +28 -0
  37. package/src/utils/gitops-owner.test.ts +136 -0
  38. package/src/utils/gitops-owner.ts +92 -0
  39. package/src/utils/gitops-route.test.ts +78 -0
  40. package/src/utils/gitops-route.ts +104 -0
  41. package/src/utils/index.ts +2 -0
  42. package/src/utils/navigation.ts +14 -0
  43. package/src/utils/resource-hierarchy.ts +47 -3
  44. package/src/utils/yaml.test.ts +101 -0
  45. package/src/utils/yaml.ts +26 -0
@@ -0,0 +1,101 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { parse as yamlParse } from 'yaml'
3
+ import { cleanResourceForYaml, resourceToYaml } from './yaml'
4
+
5
+ function makePod() {
6
+ return {
7
+ apiVersion: 'v1',
8
+ kind: 'Pod',
9
+ metadata: {
10
+ name: 'nginx',
11
+ namespace: 'default',
12
+ uid: 'abcd-1234',
13
+ resourceVersion: '99999',
14
+ creationTimestamp: '2026-01-01T00:00:00Z',
15
+ generation: 1,
16
+ managedFields: [{ manager: 'kubectl', operation: 'Update' }],
17
+ labels: { app: 'nginx' },
18
+ annotations: { 'kubectl.kubernetes.io/last-applied-configuration': '{}' },
19
+ ownerReferences: [{ kind: 'ReplicaSet', name: 'nginx-rs', uid: 'rs-uid' }],
20
+ },
21
+ spec: {
22
+ containers: [{ name: 'nginx', image: 'nginx:1.25' }],
23
+ },
24
+ status: {
25
+ phase: 'Running',
26
+ podIP: '10.0.0.1',
27
+ },
28
+ }
29
+ }
30
+
31
+ describe('cleanResourceForYaml', () => {
32
+ it('strips status', () => {
33
+ const cleaned = cleanResourceForYaml(makePod())
34
+ expect(cleaned.status).toBeUndefined()
35
+ })
36
+
37
+ it('strips all 5 server-generated metadata fields', () => {
38
+ const cleaned = cleanResourceForYaml(makePod())
39
+ expect(cleaned.metadata.uid).toBeUndefined()
40
+ expect(cleaned.metadata.resourceVersion).toBeUndefined()
41
+ expect(cleaned.metadata.creationTimestamp).toBeUndefined()
42
+ expect(cleaned.metadata.generation).toBeUndefined()
43
+ expect(cleaned.metadata.managedFields).toBeUndefined()
44
+ })
45
+
46
+ it('preserves user metadata', () => {
47
+ const cleaned = cleanResourceForYaml(makePod())
48
+ expect(cleaned.metadata.name).toBe('nginx')
49
+ expect(cleaned.metadata.namespace).toBe('default')
50
+ expect(cleaned.metadata.labels).toEqual({ app: 'nginx' })
51
+ expect(cleaned.metadata.annotations).toEqual({
52
+ 'kubectl.kubernetes.io/last-applied-configuration': '{}',
53
+ })
54
+ expect(cleaned.metadata.ownerReferences).toHaveLength(1)
55
+ })
56
+
57
+ it('preserves spec', () => {
58
+ const cleaned = cleanResourceForYaml(makePod())
59
+ expect(cleaned.spec).toEqual({
60
+ containers: [{ name: 'nginx', image: 'nginx:1.25' }],
61
+ })
62
+ })
63
+
64
+ it('does not mutate the input', () => {
65
+ const input = makePod()
66
+ cleanResourceForYaml(input)
67
+ expect(input.status).toBeDefined()
68
+ expect(input.metadata.uid).toBe('abcd-1234')
69
+ expect(input.metadata.managedFields).toHaveLength(1)
70
+ })
71
+
72
+ it('returns null/undefined/primitives unchanged', () => {
73
+ expect(cleanResourceForYaml(null)).toBeNull()
74
+ expect(cleanResourceForYaml(undefined)).toBeUndefined()
75
+ expect(cleanResourceForYaml('not an object' as any)).toBe('not an object')
76
+ expect(cleanResourceForYaml(42 as any)).toBe(42)
77
+ })
78
+
79
+ it('handles object with no metadata field', () => {
80
+ const cleaned = cleanResourceForYaml({ kind: 'Pod', spec: {} })
81
+ expect(cleaned).toEqual({ kind: 'Pod', spec: {} })
82
+ })
83
+
84
+ it('handles metadata present but null', () => {
85
+ const cleaned = cleanResourceForYaml({ kind: 'Pod', metadata: null, spec: {} })
86
+ expect(cleaned).toEqual({ kind: 'Pod', metadata: null, spec: {} })
87
+ })
88
+ })
89
+
90
+ describe('resourceToYaml', () => {
91
+ it('returns empty string for null/undefined', () => {
92
+ expect(resourceToYaml(null)).toBe('')
93
+ expect(resourceToYaml(undefined)).toBe('')
94
+ })
95
+
96
+ it('round-trips through yaml.parse to the cleaned object', () => {
97
+ const cleaned = cleanResourceForYaml(makePod())
98
+ const yaml = resourceToYaml(makePod())
99
+ expect(yamlParse(yaml)).toEqual(cleaned)
100
+ })
101
+ })
@@ -0,0 +1,26 @@
1
+ import { stringify as yamlStringify } from 'yaml'
2
+
3
+ const SERVER_GENERATED_METADATA = [
4
+ 'managedFields',
5
+ 'resourceVersion',
6
+ 'uid',
7
+ 'creationTimestamp',
8
+ 'generation',
9
+ ] as const
10
+
11
+ export function cleanResourceForYaml<T = any>(data: T): T {
12
+ if (!data || typeof data !== 'object') return data
13
+ const cleaned = structuredClone(data) as any
14
+ delete cleaned.status
15
+ if (cleaned.metadata && typeof cleaned.metadata === 'object') {
16
+ for (const field of SERVER_GENERATED_METADATA) {
17
+ delete cleaned.metadata[field]
18
+ }
19
+ }
20
+ return cleaned
21
+ }
22
+
23
+ export function resourceToYaml(data: any): string {
24
+ if (!data) return ''
25
+ return yamlStringify(cleanResourceForYaml(data), { lineWidth: 0, indent: 2 })
26
+ }