@skyhook-io/k8s-ui 1.15.0 → 1.16.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 (37) hide show
  1. package/package.json +3 -3
  2. package/src/components/audit/AuditFindingsTable.tsx +1 -1
  3. package/src/components/checks/ChecksView.tsx +8 -8
  4. package/src/components/gitops/GitOpsDetailLayout.tsx +3 -1
  5. package/src/components/gitops/GitOpsHealthSourceNotice.tsx +12 -2
  6. package/src/components/gitops/index.ts +1 -0
  7. package/src/components/gitops/insights/GitOpsInsightViews.tsx +4 -1
  8. package/src/components/issues/IssuesView.tsx +2 -2
  9. package/src/components/logs/LogCore.tsx +131 -97
  10. package/src/components/logs/StructuredLogLine.tsx +18 -28
  11. package/src/components/logs/log-palette.ts +4 -0
  12. package/src/components/logs/useLogBuffer.ts +8 -39
  13. package/src/components/logs/useLogSearch.ts +52 -24
  14. package/src/components/namespace-switcher/NamespacePicker.test.tsx +140 -0
  15. package/src/components/namespace-switcher/NamespacePicker.tsx +26 -20
  16. package/src/components/resources/renderers/PodRenderer.test.tsx +15 -0
  17. package/src/components/resources/renderers/PodRenderer.tsx +10 -2
  18. package/src/components/resources/renderers/PolicySection.test.tsx +42 -0
  19. package/src/components/resources/renderers/PolicySection.tsx +10 -1
  20. package/src/components/shared/DrainPlanDialog.test.tsx +10 -0
  21. package/src/components/shared/DrainPlanDialog.tsx +14 -3
  22. package/src/components/shared/ResourceActionsBar.tsx +14 -9
  23. package/src/components/ui/FilterPill.tsx +1 -1
  24. package/src/components/ui/RadarUpgradeNote.click.test.tsx +24 -0
  25. package/src/components/ui/RadarUpgradeNote.test.tsx +66 -0
  26. package/src/components/ui/RadarUpgradeNote.tsx +129 -0
  27. package/src/components/ui/ResourceBar.tsx +12 -6
  28. package/src/components/ui/SelectMenu.tsx +62 -19
  29. package/src/components/ui/index.ts +8 -0
  30. package/src/components/workload/WorkloadView.tsx +109 -3
  31. package/src/types/core.ts +24 -4
  32. package/src/types/fetch-error.ts +33 -0
  33. package/src/utils/log-format.ts +1 -1
  34. package/src/utils/log-level.test.ts +269 -0
  35. package/src/utils/log-level.ts +301 -0
  36. package/src/utils/resource-hierarchy.test.ts +69 -0
  37. package/src/utils/resource-hierarchy.ts +87 -52
@@ -91,6 +91,11 @@ export interface HierarchyOptions {
91
91
  topology?: Topology
92
92
  /** If provided, returns only the hierarchy rooted at this resource */
93
93
  rootResource?: { kind: string; group?: string; namespace: string; name: string }
94
+ /** The events were already selected as rootResource's history (the server's
95
+ * scoped query). Every lane is kept: one the hierarchy can't place in the
96
+ * root's tree — a past run whose parent left no events, a second Service —
97
+ * nests under the returned lane instead of being dropped. */
98
+ eventsScopedToRoot?: boolean
94
99
  /** Legacy toggle. Kept for back-compat (WorkloadView): true→'app', false→'owner'
95
100
  * when `grouping` is omitted. Prefer `grouping`. */
96
101
  groupByApp?: boolean
@@ -933,66 +938,96 @@ export function buildResourceHierarchy(options: HierarchyOptions): ResourceLane[
933
938
 
934
939
  // If rootResource is specified, filter to only include lanes related to that resource
935
940
  if (rootResource) {
936
- const rootGroup = canonicalResourceGroup(rootResource.kind, rootResource.group)
937
- const rootLaneId = rootGroup !== undefined
938
- ? laneId(rootResource.kind, rootGroup, rootResource.namespace, rootResource.name)
939
- : resolveId(laneResourceKey(rootResource.kind, rootResource.namespace, rootResource.name))
940
- const rootLane = topLevelLanes.find(l => l.id === rootLaneId)
941
-
942
- if (rootLane) {
943
- // Return the root lane with all its children
944
- return [rootLane]
945
- }
946
-
947
- // If root resource is a child of another lane, find its parent and return that
948
- for (const lane of topLevelLanes) {
949
- if (lane.children?.some(c => c.id === rootLaneId)) {
950
- return [lane]
941
+ const rootLane = pickRootLane(rootResource, topLevelLanes, laneMap, laneParent, findRoot, resolveId)
942
+ if (options.eventsScopedToRoot) {
943
+ const adopted = topLevelLanes.filter((l) => l !== rootLane && !laneContains(l, rootLane))
944
+ if (adopted.length > 0) {
945
+ rootLane.children = [...(rootLane.children ?? []), ...adopted]
946
+ rootLane.childEventCount = (rootLane.childEventCount ?? 0) + adopted.reduce((n, l) => n + l.events.length, 0)
947
+ sortLaneChildrenDeep(rootLane)
948
+ rootLane.allEventsSorted = subtreeEvents(rootLane)
951
949
  }
952
- // Also check if root is the parent and we're looking at the hierarchy from the parent perspective
953
950
  }
951
+ return [rootLane]
952
+ }
954
953
 
955
- // If not found, check if it's a child somewhere in the hierarchy and return its parent
956
- const childLane = laneMap.get(rootLaneId)
957
- if (childLane) {
958
- const parentId = laneParent.get(rootLaneId)
959
- if (parentId) {
960
- const parentLane = topLevelLanes.find(l => l.id === parentId)
961
- if (parentLane) {
962
- return [parentLane]
963
- }
964
- // Walk up to find the top-level ancestor
965
- const rootAncestorId = findRoot(rootLaneId)
966
- const rootAncestor = topLevelLanes.find(l => l.id === rootAncestorId)
967
- if (rootAncestor) {
968
- return [rootAncestor]
969
- }
970
- }
971
- // No parent found, return as a standalone lane. Full-subtree roll-up so a
972
- // nested chain's grandchildren stay in the aggregate.
973
- childLane.allEventsSorted = subtreeEvents(childLane)
974
- return [childLane]
954
+ return topLevelLanes
955
+ }
956
+
957
+
958
+ function laneContains(lane: ResourceLane, target: ResourceLane, seen = new Set<ResourceLane>()): boolean {
959
+ if (seen.has(lane)) return false
960
+ seen.add(lane)
961
+ return (lane.children ?? []).some((c) => c === target || laneContains(c, target, seen))
962
+ }
963
+
964
+ /** The lane a rootResource view shows: the root's own lane, the top-level
965
+ * ancestor it nests under, or a placeholder when it has no events. */
966
+ function pickRootLane(
967
+ rootResource: { kind: string; group?: string; namespace: string; name: string },
968
+ topLevelLanes: ResourceLane[],
969
+ laneMap: Map<string, ResourceLane>,
970
+ laneParent: Map<string, string>,
971
+ findRoot: (id: string) => string,
972
+ resolveId: (resourceKey: string) => string,
973
+ ): ResourceLane {
974
+ const rootGroup = canonicalResourceGroup(rootResource.kind, rootResource.group)
975
+ const rootLaneId = rootGroup !== undefined
976
+ ? laneId(rootResource.kind, rootGroup, rootResource.namespace, rootResource.name)
977
+ : resolveId(laneResourceKey(rootResource.kind, rootResource.namespace, rootResource.name))
978
+ const rootLane = topLevelLanes.find(l => l.id === rootLaneId)
979
+
980
+ if (rootLane) {
981
+ // Return the root lane with all its children
982
+ return rootLane
983
+ }
984
+
985
+ // If root resource is a child of another lane, find its parent and return that
986
+ for (const lane of topLevelLanes) {
987
+ if (lane.children?.some(c => c.id === rootLaneId)) {
988
+ return lane
975
989
  }
990
+ // Also check if root is the parent and we're looking at the hierarchy from the parent perspective
991
+ }
976
992
 
977
- // Resource not found in hierarchy - create a placeholder lane
978
- // This ensures the detail view always has something to show
979
- const placeholderLane: ResourceLane = {
980
- id: rootLaneId,
981
- kind: rootResource.kind,
982
- group: parseLaneId(rootLaneId)?.group || '',
983
- identityResolved: rootGroup !== undefined,
984
- namespace: rootResource.namespace,
985
- name: rootResource.name,
986
- events: [],
987
- isWorkload: isWorkloadKind(rootResource.kind),
988
- children: [],
989
- childEventCount: 0,
990
- allEventsSorted: [],
993
+ // If not found, check if it's a child somewhere in the hierarchy and return its parent
994
+ const childLane = laneMap.get(rootLaneId)
995
+ if (childLane) {
996
+ const parentId = laneParent.get(rootLaneId)
997
+ if (parentId) {
998
+ const parentLane = topLevelLanes.find(l => l.id === parentId)
999
+ if (parentLane) {
1000
+ return parentLane
1001
+ }
1002
+ // Walk up to find the top-level ancestor
1003
+ const rootAncestorId = findRoot(rootLaneId)
1004
+ const rootAncestor = topLevelLanes.find(l => l.id === rootAncestorId)
1005
+ if (rootAncestor) {
1006
+ return rootAncestor
1007
+ }
991
1008
  }
992
- return [placeholderLane]
1009
+ // No parent found, return as a standalone lane. Full-subtree roll-up so a
1010
+ // nested chain's grandchildren stay in the aggregate.
1011
+ childLane.allEventsSorted = subtreeEvents(childLane)
1012
+ return childLane
993
1013
  }
994
1014
 
995
- return topLevelLanes
1015
+ // Resource not found in hierarchy - create a placeholder lane
1016
+ // This ensures the detail view always has something to show
1017
+ const placeholderLane: ResourceLane = {
1018
+ id: rootLaneId,
1019
+ kind: rootResource.kind,
1020
+ group: parseLaneId(rootLaneId)?.group || '',
1021
+ identityResolved: rootGroup !== undefined,
1022
+ namespace: rootResource.namespace,
1023
+ name: rootResource.name,
1024
+ events: [],
1025
+ isWorkload: isWorkloadKind(rootResource.kind),
1026
+ children: [],
1027
+ childEventCount: 0,
1028
+ allEventsSorted: [],
1029
+ }
1030
+ return placeholderLane
996
1031
  }
997
1032
 
998
1033
  // -----------------------------------------------------------------------------