@skyhook-io/k8s-ui 1.1.0 → 1.2.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 (58) hide show
  1. package/package.json +8 -2
  2. package/src/components/dock/BottomDock.tsx +14 -15
  3. package/src/components/dock/DockContext.tsx +40 -1
  4. package/src/components/dock/LocalTerminalTab.tsx +241 -0
  5. package/src/components/dock/NodeTerminalTab.tsx +129 -0
  6. package/src/components/dock/TerminalTab.tsx +1 -1
  7. package/src/components/dock/index.ts +2 -0
  8. package/src/components/gitops/GitOpsStatusBadge.tsx +18 -67
  9. package/src/components/logs/LogCore.tsx +30 -4
  10. package/src/components/logs/StructuredLogLine.tsx +168 -0
  11. package/src/components/logs/index.ts +1 -1
  12. package/src/components/logs/useLogBuffer.ts +16 -10
  13. package/src/components/resources/ResourcesView.tsx +186 -67
  14. package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +207 -0
  15. package/src/components/resources/renderers/HPARenderer.tsx +2 -1
  16. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -1
  17. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +2 -1
  18. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +2 -1
  19. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +7 -6
  20. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +2 -1
  21. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +3 -2
  22. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +3 -2
  23. package/src/components/resources/renderers/PodRenderer.tsx +164 -8
  24. package/src/components/resources/renderers/VPARenderer.tsx +2 -1
  25. package/src/components/resources/renderers/WorkloadRenderer.tsx +53 -55
  26. package/src/components/resources/renderers/contour-cells.tsx +48 -0
  27. package/src/components/resources/renderers/index.ts +2 -0
  28. package/src/components/resources/renderers/trivy-shared.tsx +9 -25
  29. package/src/components/resources/resource-utils-contour.ts +34 -0
  30. package/src/components/resources/resource-utils.ts +91 -0
  31. package/src/components/shared/ResourceActionsBar.tsx +276 -144
  32. package/src/components/shared/ResourceRendererDispatch.tsx +59 -5
  33. package/src/components/shared/index.ts +1 -1
  34. package/src/components/timeline/shared.tsx +8 -10
  35. package/src/components/topology/K8sResourceNode.tsx +1 -0
  36. package/src/components/topology/TopologyFilterSidebar.tsx +3 -0
  37. package/src/components/topology/TopologyGraph.tsx +2 -17
  38. package/src/components/topology/layout.ts +1 -0
  39. package/src/components/topology/topology.css +1 -0
  40. package/src/components/ui/CodeViewer.tsx +1 -2
  41. package/src/components/ui/ConfirmDialog.tsx +85 -130
  42. package/src/components/ui/DialogPortal.tsx +82 -0
  43. package/src/components/ui/ForceDeleteConfirmDialog.tsx +84 -10
  44. package/src/components/ui/Toast.tsx +5 -5
  45. package/src/components/ui/drawer-components.tsx +28 -22
  46. package/src/components/ui/index.ts +1 -0
  47. package/src/components/workload/WorkloadView.tsx +30 -2
  48. package/src/theme/index.ts +3 -0
  49. package/src/theme/tailwind-theme.css +61 -0
  50. package/src/theme/variables.css +121 -0
  51. package/src/types/core.ts +13 -2
  52. package/src/utils/animation.ts +1 -1
  53. package/src/utils/badge-colors.ts +158 -123
  54. package/src/utils/log-format.ts +127 -35
  55. package/src/utils/navigation.test.ts +142 -0
  56. package/src/utils/navigation.ts +52 -47
  57. package/src/utils/resource-hierarchy.ts +5 -0
  58. package/src/utils/resource-icons.ts +3 -0
@@ -4,186 +4,192 @@
4
4
  // Kind badge colors - for K8s resource type badges
5
5
  export const KIND_BADGE_COLORS: Record<string, string> = {
6
6
  // Workload controllers
7
- Deployment: 'bg-emerald-500/15 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300',
8
- StatefulSet: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
9
- DaemonSet: 'bg-teal-500/15 text-teal-700 dark:bg-teal-900/50 dark:text-teal-300',
10
- ReplicaSet: 'bg-green-500/15 text-green-700 dark:bg-green-900/50 dark:text-green-300',
11
- Pod: 'bg-lime-500/15 text-lime-700 dark:bg-lime-900/50 dark:text-lime-300',
7
+ Deployment: 'bg-emerald-500/15 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-400',
8
+ StatefulSet: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
9
+ DaemonSet: 'bg-teal-500/15 text-teal-700 dark:bg-teal-900/50 dark:text-teal-400',
10
+ ReplicaSet: 'bg-green-500/15 text-green-700 dark:bg-green-900/50 dark:text-green-400',
11
+ Pod: 'bg-lime-500/15 text-lime-700 dark:bg-lime-900/50 dark:text-lime-400',
12
12
 
13
13
  // Networking
14
- Service: 'bg-blue-500/15 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300',
15
- Ingress: 'bg-violet-500/15 text-violet-700 dark:bg-violet-900/50 dark:text-violet-300',
16
- Gateway: 'bg-violet-500/15 text-violet-700 dark:bg-violet-900/50 dark:text-violet-300',
17
- HTTPRoute: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300',
18
- GRPCRoute: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300',
19
- TCPRoute: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300',
20
- TLSRoute: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300',
14
+ Service: 'bg-blue-500/15 text-blue-700 dark:bg-blue-900/50 dark:text-blue-400',
15
+ Ingress: 'bg-violet-500/15 text-violet-700 dark:bg-violet-900/50 dark:text-violet-400',
16
+ Gateway: 'bg-violet-500/15 text-violet-700 dark:bg-violet-900/50 dark:text-violet-400',
17
+ HTTPRoute: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-400',
18
+ GRPCRoute: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-400',
19
+ TCPRoute: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-400',
20
+ TLSRoute: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-400',
21
21
 
22
22
  // Config
23
- ConfigMap: 'bg-amber-500/15 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300',
24
- Secret: 'bg-red-500/15 text-red-700 dark:bg-red-900/50 dark:text-red-300',
23
+ ConfigMap: 'bg-amber-500/15 text-amber-700 dark:bg-amber-900/50 dark:text-amber-400',
24
+ Secret: 'bg-red-500/15 text-red-800 dark:bg-red-900/50 dark:text-red-400',
25
25
 
26
26
  // Jobs
27
- Job: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300',
28
- CronJob: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300',
27
+ Job: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-400',
28
+ CronJob: 'bg-purple-500/15 text-purple-700 dark:bg-purple-900/50 dark:text-purple-400',
29
29
 
30
30
  // Autoscaling & Storage
31
- HorizontalPodAutoscaler: 'bg-pink-500/15 text-pink-700 dark:bg-pink-900/50 dark:text-pink-300',
32
- PersistentVolumeClaim: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
31
+ HorizontalPodAutoscaler: 'bg-pink-500/15 text-pink-700 dark:bg-pink-900/50 dark:text-pink-400',
32
+ PersistentVolumeClaim: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
33
33
 
34
34
  // Argo Rollouts
35
- Rollout: 'bg-emerald-500/15 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300',
35
+ Rollout: 'bg-emerald-500/15 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-400',
36
36
 
37
37
  // GitOps
38
- Application: 'bg-orange-500/15 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300',
39
- Kustomization: 'bg-indigo-500/15 text-indigo-700 dark:bg-indigo-900/50 dark:text-indigo-300',
40
- GitRepository: 'bg-indigo-500/15 text-indigo-700 dark:bg-indigo-900/50 dark:text-indigo-300',
38
+ Application: 'bg-orange-500/15 text-orange-700 dark:bg-orange-900/50 dark:text-orange-400',
39
+ Kustomization: 'bg-indigo-500/15 text-indigo-700 dark:bg-indigo-900/50 dark:text-indigo-400',
40
+ GitRepository: 'bg-indigo-500/15 text-indigo-700 dark:bg-indigo-900/50 dark:text-indigo-400',
41
41
 
42
42
  // Cluster-scoped
43
- Node: 'bg-sky-500/15 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300',
44
- Namespace: 'bg-gray-500/15 text-gray-700 dark:bg-gray-900/50 dark:text-gray-300',
43
+ Node: 'bg-sky-500/15 text-sky-700 dark:bg-sky-900/50 dark:text-sky-400',
44
+ Namespace: 'bg-gray-500/15 text-gray-700 dark:bg-gray-900/50 dark:text-gray-400',
45
45
 
46
46
  // Knative Serving
47
- KnativeService: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
48
- KnativeConfiguration: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
49
- KnativeRevision: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
50
- KnativeRoute: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
47
+ KnativeService: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
48
+ KnativeConfiguration: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
49
+ KnativeRevision: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
50
+ KnativeRoute: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
51
51
 
52
52
  // Knative Eventing
53
- Broker: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
54
- Trigger: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
53
+ Broker: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
54
+ Trigger: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
55
55
 
56
56
  // Knative Sources
57
- PingSource: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
58
- ApiServerSource: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
59
- ContainerSource: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
60
- SinkBinding: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
61
- Channel: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-300',
57
+ PingSource: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
58
+ ApiServerSource: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
59
+ ContainerSource: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
60
+ SinkBinding: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
61
+ Channel: 'bg-fuchsia-500/15 text-fuchsia-700 dark:bg-fuchsia-900/50 dark:text-fuchsia-400',
62
62
 
63
63
  // Traefik
64
- IngressRoute: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
65
- IngressRouteTCP: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
66
- IngressRouteUDP: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
67
- Middleware: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
68
- MiddlewareTCP: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
69
- TraefikService: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
70
- ServersTransport: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
71
- ServersTransportTCP: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
72
- TLSOption: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
73
- TLSStore: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300',
64
+ IngressRoute: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
65
+ IngressRouteTCP: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
66
+ IngressRouteUDP: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
67
+ Middleware: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
68
+ MiddlewareTCP: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
69
+ TraefikService: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
70
+ ServersTransport: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
71
+ ServersTransportTCP: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
72
+ TLSOption: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
73
+ TLSStore: 'bg-cyan-500/15 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-400',
74
+
75
+ // Contour
76
+ HTTPProxy: 'bg-violet-500/15 text-violet-700 dark:bg-violet-900/50 dark:text-violet-400',
74
77
 
75
78
  // Special
76
- HelmRelease: 'bg-purple-500/20 text-purple-700 dark:text-purple-300',
77
- Event: 'bg-slate-500/15 text-slate-700 dark:bg-slate-900/50 dark:text-slate-300',
79
+ HelmRelease: 'bg-purple-500/20 text-purple-700 dark:text-purple-400',
80
+ Event: 'bg-slate-500/15 text-slate-700 dark:bg-slate-900/50 dark:text-slate-400',
78
81
  }
79
82
 
80
83
  // Kind badge colors with border - for prominent badges in drawers/modals
81
84
  export const KIND_BADGE_BORDERED: Record<string, string> = {
82
85
  // Workload controllers
83
- Deployment: 'bg-emerald-500/20 text-emerald-700 dark:text-emerald-300 border border-emerald-500/30',
84
- StatefulSet: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
85
- DaemonSet: 'bg-teal-500/20 text-teal-700 dark:text-teal-300 border border-teal-500/30',
86
- ReplicaSet: 'bg-green-500/20 text-green-700 dark:text-green-300 border border-green-500/30',
87
- Pod: 'bg-lime-500/20 text-lime-700 dark:text-lime-300 border border-lime-500/30',
88
- PodGroup: 'bg-lime-500/20 text-lime-700 dark:text-lime-300 border border-lime-500/30',
86
+ Deployment: 'bg-emerald-500/20 text-emerald-700 dark:text-emerald-400 border border-emerald-500/30',
87
+ StatefulSet: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
88
+ DaemonSet: 'bg-teal-500/20 text-teal-700 dark:text-teal-400 border border-teal-500/30',
89
+ ReplicaSet: 'bg-green-500/20 text-green-700 dark:text-green-400 border border-green-500/30',
90
+ Pod: 'bg-lime-500/20 text-lime-700 dark:text-lime-400 border border-lime-500/30',
91
+ PodGroup: 'bg-lime-500/20 text-lime-700 dark:text-lime-400 border border-lime-500/30',
89
92
 
90
93
  // Networking
91
- Internet: 'bg-blue-500/20 text-blue-700 dark:text-blue-300 border border-blue-500/30',
92
- Service: 'bg-blue-500/20 text-blue-700 dark:text-blue-300 border border-blue-500/30',
93
- Ingress: 'bg-violet-500/20 text-violet-700 dark:text-violet-300 border border-violet-500/30',
94
- Gateway: 'bg-violet-500/20 text-violet-700 dark:text-violet-300 border border-violet-500/30',
95
- HTTPRoute: 'bg-purple-500/20 text-purple-700 dark:text-purple-300 border border-purple-500/30',
96
- GRPCRoute: 'bg-purple-500/20 text-purple-700 dark:text-purple-300 border border-purple-500/30',
97
- TCPRoute: 'bg-purple-500/20 text-purple-700 dark:text-purple-300 border border-purple-500/30',
98
- TLSRoute: 'bg-purple-500/20 text-purple-700 dark:text-purple-300 border border-purple-500/30',
94
+ Internet: 'bg-blue-500/20 text-blue-700 dark:text-blue-400 border border-blue-500/30',
95
+ Service: 'bg-blue-500/20 text-blue-700 dark:text-blue-400 border border-blue-500/30',
96
+ Ingress: 'bg-violet-500/20 text-violet-700 dark:text-violet-400 border border-violet-500/30',
97
+ Gateway: 'bg-violet-500/20 text-violet-700 dark:text-violet-400 border border-violet-500/30',
98
+ HTTPRoute: 'bg-purple-500/20 text-purple-700 dark:text-purple-400 border border-purple-500/30',
99
+ GRPCRoute: 'bg-purple-500/20 text-purple-700 dark:text-purple-400 border border-purple-500/30',
100
+ TCPRoute: 'bg-purple-500/20 text-purple-700 dark:text-purple-400 border border-purple-500/30',
101
+ TLSRoute: 'bg-purple-500/20 text-purple-700 dark:text-purple-400 border border-purple-500/30',
99
102
 
100
103
  // Config
101
- ConfigMap: 'bg-amber-500/20 text-amber-700 dark:text-amber-300 border border-amber-500/30',
102
- Secret: 'bg-red-500/20 text-red-700 dark:text-red-300 border border-red-500/30',
104
+ ConfigMap: 'bg-amber-500/20 text-amber-700 dark:text-amber-400 border border-amber-500/30',
105
+ Secret: 'bg-red-500/20 text-red-800 dark:text-red-400 border border-red-500/30',
103
106
 
104
107
  // Jobs
105
- Job: 'bg-purple-500/20 text-purple-700 dark:text-purple-300 border border-purple-500/30',
106
- CronJob: 'bg-purple-500/20 text-purple-700 dark:text-purple-300 border border-purple-500/30',
108
+ Job: 'bg-purple-500/20 text-purple-700 dark:text-purple-400 border border-purple-500/30',
109
+ CronJob: 'bg-purple-500/20 text-purple-700 dark:text-purple-400 border border-purple-500/30',
107
110
 
108
111
  // Autoscaling & Storage
109
- HorizontalPodAutoscaler: 'bg-pink-500/20 text-pink-700 dark:text-pink-300 border border-pink-500/30',
110
- PersistentVolumeClaim: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
112
+ HorizontalPodAutoscaler: 'bg-pink-500/20 text-pink-700 dark:text-pink-400 border border-pink-500/30',
113
+ PersistentVolumeClaim: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
111
114
 
112
115
  // Argo Rollouts
113
- Rollout: 'bg-emerald-500/20 text-emerald-700 dark:text-emerald-300 border border-emerald-500/30',
116
+ Rollout: 'bg-emerald-500/20 text-emerald-700 dark:text-emerald-400 border border-emerald-500/30',
114
117
 
115
118
  // GitOps
116
- Application: 'bg-orange-500/20 text-orange-700 dark:text-orange-300 border border-orange-500/30',
117
- Kustomization: 'bg-indigo-500/20 text-indigo-700 dark:text-indigo-300 border border-indigo-500/30',
118
- GitRepository: 'bg-indigo-500/20 text-indigo-700 dark:text-indigo-300 border border-indigo-500/30',
119
+ Application: 'bg-orange-500/20 text-orange-700 dark:text-orange-400 border border-orange-500/30',
120
+ Kustomization: 'bg-indigo-500/20 text-indigo-700 dark:text-indigo-400 border border-indigo-500/30',
121
+ GitRepository: 'bg-indigo-500/20 text-indigo-700 dark:text-indigo-400 border border-indigo-500/30',
119
122
 
120
123
  // Cluster-scoped
121
- Node: 'bg-sky-500/20 text-sky-700 dark:text-sky-300 border border-sky-500/30',
122
- Namespace: 'bg-gray-500/20 text-gray-700 dark:text-gray-300 border border-gray-500/30',
124
+ Node: 'bg-sky-500/20 text-sky-700 dark:text-sky-400 border border-sky-500/30',
125
+ Namespace: 'bg-gray-500/20 text-gray-700 dark:text-gray-400 border border-gray-500/30',
123
126
 
124
127
  // Knative
125
- KnativeService: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
126
- KnativeConfiguration: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
127
- KnativeRevision: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
128
- KnativeRoute: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
129
- Broker: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
130
- Trigger: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
131
- PingSource: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
132
- ApiServerSource: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
133
- ContainerSource: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
134
- SinkBinding: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
135
- Channel: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border border-fuchsia-500/30',
128
+ KnativeService: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
129
+ KnativeConfiguration: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
130
+ KnativeRevision: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
131
+ KnativeRoute: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
132
+ Broker: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
133
+ Trigger: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
134
+ PingSource: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
135
+ ApiServerSource: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
136
+ ContainerSource: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
137
+ SinkBinding: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
138
+ Channel: 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-400 border border-fuchsia-500/30',
136
139
 
137
140
  // Traefik
138
- IngressRoute: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
139
- IngressRouteTCP: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
140
- IngressRouteUDP: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
141
- Middleware: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
142
- MiddlewareTCP: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
143
- TraefikService: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
144
- ServersTransport: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
145
- ServersTransportTCP: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
146
- TLSOption: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
147
- TLSStore: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border border-cyan-500/30',
141
+ IngressRoute: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
142
+ IngressRouteTCP: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
143
+ IngressRouteUDP: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
144
+ Middleware: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
145
+ MiddlewareTCP: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
146
+ TraefikService: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
147
+ ServersTransport: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
148
+ ServersTransportTCP: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
149
+ TLSOption: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
150
+ TLSStore: 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border border-cyan-500/30',
151
+
152
+ // Contour
153
+ HTTPProxy: 'bg-violet-500/20 text-violet-700 dark:text-violet-400 border border-violet-500/30',
148
154
  }
149
155
 
150
156
  // Event type colors - for K8s event types (Normal, Warning)
151
157
  export const EVENT_TYPE_COLORS: Record<string, string> = {
152
- Warning: 'bg-amber-500/20 text-amber-700 dark:text-amber-300',
153
- Normal: 'bg-green-500/20 text-green-700 dark:text-green-300',
158
+ Warning: 'bg-amber-500/20 text-amber-700 dark:text-amber-400',
159
+ Normal: 'bg-green-500/20 text-green-700 dark:text-green-400',
154
160
  }
155
161
 
156
162
  // Operation colors - for change events (add, update, delete)
157
163
  export const OPERATION_COLORS: Record<string, string> = {
158
- add: 'text-green-700 dark:text-green-300',
159
- update: 'text-blue-700 dark:text-blue-300',
160
- delete: 'text-red-700 dark:text-red-300',
164
+ add: 'text-green-700 dark:text-green-400',
165
+ update: 'text-blue-700 dark:text-blue-400',
166
+ delete: 'text-red-800 dark:text-red-400',
161
167
  }
162
168
 
163
169
  // Operation background colors - for badges with background
164
170
  export const OPERATION_BADGE_COLORS: Record<string, string> = {
165
- add: 'bg-green-500/20 text-green-700 dark:text-green-300',
166
- update: 'bg-blue-500/20 text-blue-700 dark:text-blue-300',
167
- delete: 'bg-red-500/20 text-red-700 dark:text-red-300',
171
+ add: 'bg-green-500/20 text-green-700 dark:text-green-400',
172
+ update: 'bg-blue-500/20 text-blue-700 dark:text-blue-400',
173
+ delete: 'bg-red-500/20 text-red-800 dark:text-red-400',
168
174
  }
169
175
 
170
176
  // Health badge colors - for health state indicators
171
177
  export const HEALTH_BADGE_COLORS: Record<string, string> = {
172
- healthy: 'bg-green-500/20 text-green-700 dark:text-green-300',
173
- degraded: 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-300',
174
- unhealthy: 'bg-red-500/20 text-red-700 dark:text-red-300',
178
+ healthy: 'bg-green-500/20 text-green-700 dark:text-green-400',
179
+ degraded: 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-400',
180
+ unhealthy: 'bg-red-500/20 text-red-800 dark:text-red-400',
175
181
  unknown: 'bg-theme-hover/50 text-theme-text-secondary',
176
182
  }
177
183
 
178
184
  // Helm release status colors
179
185
  export const HELM_STATUS_COLORS: Record<string, string> = {
180
- deployed: 'bg-green-500/20 text-green-700 dark:text-green-300',
186
+ deployed: 'bg-green-500/20 text-green-700 dark:text-green-400',
181
187
  superseded: 'bg-theme-hover/50 text-theme-text-secondary',
182
- failed: 'bg-red-500/20 text-red-700 dark:text-red-300',
183
- 'pending-install': 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-300',
184
- 'pending-upgrade': 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-300',
185
- 'pending-rollback': 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-300',
186
- uninstalling: 'bg-orange-500/20 text-orange-700 dark:text-orange-300',
188
+ failed: 'bg-red-500/20 text-red-800 dark:text-red-400',
189
+ 'pending-install': 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-400',
190
+ 'pending-upgrade': 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-400',
191
+ 'pending-rollback': 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-400',
192
+ uninstalling: 'bg-orange-500/20 text-orange-700 dark:text-orange-400',
187
193
  uninstalled: 'bg-theme-hover/50 text-theme-text-secondary',
188
194
  }
189
195
 
@@ -196,19 +202,19 @@ export const DEFAULT_BADGE_COLOR = 'bg-theme-elevated text-theme-text-secondary'
196
202
 
197
203
  // Severity badge colors - with background (for badges, pills, tags)
198
204
  export const SEVERITY_BADGE = {
199
- success: 'bg-green-500/20 text-green-700 dark:text-green-300',
200
- warning: 'bg-amber-500/20 text-amber-700 dark:text-amber-300',
201
- error: 'bg-red-500/20 text-red-700 dark:text-red-300',
202
- info: 'bg-blue-500/20 text-blue-700 dark:text-blue-300',
205
+ success: 'bg-green-500/20 text-green-700 dark:text-green-400',
206
+ warning: 'bg-amber-500/20 text-amber-700 dark:text-amber-400',
207
+ error: 'bg-red-500/20 text-red-800 dark:text-red-400',
208
+ info: 'bg-blue-500/20 text-blue-700 dark:text-blue-400',
203
209
  neutral: 'bg-theme-hover/50 text-theme-text-secondary',
204
210
  } as const
205
211
 
206
212
  // Severity text colors - without background (for inline text, icons)
207
213
  export const SEVERITY_TEXT = {
208
- success: 'text-green-700 dark:text-green-300',
209
- warning: 'text-amber-700 dark:text-amber-300',
210
- error: 'text-red-700 dark:text-red-300',
211
- info: 'text-blue-700 dark:text-blue-300',
214
+ success: 'text-green-700 dark:text-green-400',
215
+ warning: 'text-amber-700 dark:text-amber-400',
216
+ error: 'text-red-800 dark:text-red-400',
217
+ info: 'text-blue-700 dark:text-blue-400',
212
218
  neutral: 'text-theme-text-secondary',
213
219
  } as const
214
220
 
@@ -232,10 +238,11 @@ export const SEVERITY_BORDER = {
232
238
 
233
239
  // Combined severity styles - badge with border (for prominent indicators)
234
240
  export const SEVERITY_BADGE_BORDERED = {
235
- success: 'bg-green-500/20 text-green-700 dark:text-green-300 border border-green-500/30',
236
- warning: 'bg-amber-500/20 text-amber-700 dark:text-amber-300 border border-amber-500/30',
237
- error: 'bg-red-500/20 text-red-700 dark:text-red-300 border border-red-500/30',
238
- info: 'bg-blue-500/20 text-blue-700 dark:text-blue-300 border border-blue-500/30',
241
+ success: 'bg-green-500/20 text-green-700 dark:text-green-400 border border-green-500/30',
242
+ warning: 'bg-amber-500/20 text-amber-700 dark:text-amber-400 border border-amber-500/30',
243
+ error: 'bg-red-500/20 text-red-800 dark:text-red-400 border border-red-500/30',
244
+ info: 'bg-blue-500/20 text-blue-700 dark:text-blue-400 border border-blue-500/30',
245
+ debug: 'bg-gray-500/15 text-gray-600 dark:text-gray-400 border border-gray-400/30 dark:border-gray-500/30',
239
246
  neutral: 'bg-theme-hover/50 text-theme-text-secondary border border-theme-border',
240
247
  } as const
241
248
 
@@ -397,3 +404,31 @@ export function getHelmStatusColor(status: string): string {
397
404
  const statusLower = status.toLowerCase()
398
405
  return HELM_STATUS_COLORS[statusLower] || 'bg-theme-hover/50 text-theme-text-secondary'
399
406
  }
407
+
408
+ // =============================================================================
409
+ // VULNERABILITY SEVERITY COLORS - for Trivy and other security scanners
410
+ // =============================================================================
411
+
412
+ export const VULN_SEVERITY_BADGE: Record<string, string> = {
413
+ CRITICAL: 'bg-red-500/20 text-red-800 dark:text-red-400',
414
+ HIGH: 'bg-orange-500/20 text-orange-700 dark:text-orange-400',
415
+ MEDIUM: 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-400',
416
+ LOW: 'bg-blue-500/20 text-blue-700 dark:text-blue-400',
417
+ UNKNOWN: 'bg-gray-500/20 text-gray-600 dark:text-gray-400',
418
+ }
419
+
420
+ export const VULN_SEVERITY_BAR: Record<string, string> = {
421
+ CRITICAL: 'bg-red-500',
422
+ HIGH: 'bg-orange-500',
423
+ MEDIUM: 'bg-yellow-500',
424
+ LOW: 'bg-blue-500',
425
+ UNKNOWN: 'bg-gray-500',
426
+ }
427
+
428
+ export const VULN_SEVERITY_TEXT: Record<string, string> = {
429
+ CRITICAL: 'text-red-800 dark:text-red-400',
430
+ HIGH: 'text-orange-700 dark:text-orange-400',
431
+ MEDIUM: 'text-yellow-700 dark:text-yellow-400',
432
+ LOW: 'text-blue-700 dark:text-blue-400',
433
+ UNKNOWN: 'text-gray-600 dark:text-gray-400',
434
+ }
@@ -105,50 +105,56 @@ const ANSI_256_COLORS: string[] = (() => {
105
105
  })()
106
106
 
107
107
  // ANSI SGR (Select Graphic Rendition) code → CSS style mapping.
108
- // Colors are chosen to be legible on a dark background (terminal-standard palette).
108
+ // Uses CSS custom properties from variables.css for theme-aware light/dark colors.
109
109
  const SGR_STYLES: Record<number, string> = {
110
110
  1: 'font-weight:bold',
111
111
  2: 'opacity:0.6',
112
112
  3: 'font-style:italic',
113
113
  4: 'text-decoration:underline',
114
114
  // Standard foreground colors (30-37)
115
- 30: 'color:#4c4c4c',
116
- 31: 'color:#cd3131',
117
- 32: 'color:#0dbc79',
118
- 33: 'color:#e5e510',
119
- 34: 'color:#2472c8',
120
- 35: 'color:#bc3fbc',
121
- 36: 'color:#11a8cd',
122
- 37: 'color:#e5e5e5',
115
+ 30: 'color:var(--ansi-black)',
116
+ 31: 'color:var(--ansi-red)',
117
+ 32: 'color:var(--ansi-green)',
118
+ 33: 'color:var(--ansi-yellow)',
119
+ 34: 'color:var(--ansi-blue)',
120
+ 35: 'color:var(--ansi-magenta)',
121
+ 36: 'color:var(--ansi-cyan)',
122
+ 37: 'color:var(--ansi-white)',
123
123
  // Standard background colors (40-47)
124
- 40: 'background-color:#4c4c4c',
125
- 41: 'background-color:#cd3131',
126
- 42: 'background-color:#0dbc79',
127
- 43: 'background-color:#e5e510',
128
- 44: 'background-color:#2472c8',
129
- 45: 'background-color:#bc3fbc',
130
- 46: 'background-color:#11a8cd',
131
- 47: 'background-color:#e5e5e5',
124
+ 40: 'background-color:var(--ansi-black)',
125
+ 41: 'background-color:var(--ansi-red)',
126
+ 42: 'background-color:var(--ansi-green)',
127
+ 43: 'background-color:var(--ansi-yellow)',
128
+ 44: 'background-color:var(--ansi-blue)',
129
+ 45: 'background-color:var(--ansi-magenta)',
130
+ 46: 'background-color:var(--ansi-cyan)',
131
+ 47: 'background-color:var(--ansi-white)',
132
132
  // Bright foreground colors (90-97)
133
- 90: 'color:#767676',
134
- 91: 'color:#f14c4c',
135
- 92: 'color:#23d18b',
136
- 93: 'color:#f5f543',
137
- 94: 'color:#3b8eea',
138
- 95: 'color:#d670d6',
139
- 96: 'color:#29b8db',
140
- 97: 'color:#e5e5e5',
133
+ 90: 'color:var(--ansi-bright-black)',
134
+ 91: 'color:var(--ansi-bright-red)',
135
+ 92: 'color:var(--ansi-bright-green)',
136
+ 93: 'color:var(--ansi-bright-yellow)',
137
+ 94: 'color:var(--ansi-bright-blue)',
138
+ 95: 'color:var(--ansi-bright-magenta)',
139
+ 96: 'color:var(--ansi-bright-cyan)',
140
+ 97: 'color:var(--ansi-bright-white)',
141
141
  // Bright background colors (100-107)
142
- 100: 'background-color:#767676',
143
- 101: 'background-color:#f14c4c',
144
- 102: 'background-color:#23d18b',
145
- 103: 'background-color:#f5f543',
146
- 104: 'background-color:#3b8eea',
147
- 105: 'background-color:#d670d6',
148
- 106: 'background-color:#29b8db',
149
- 107: 'background-color:#e5e5e5',
142
+ 100: 'background-color:var(--ansi-bright-black)',
143
+ 101: 'background-color:var(--ansi-bright-red)',
144
+ 102: 'background-color:var(--ansi-bright-green)',
145
+ 103: 'background-color:var(--ansi-bright-yellow)',
146
+ 104: 'background-color:var(--ansi-bright-blue)',
147
+ 105: 'background-color:var(--ansi-bright-magenta)',
148
+ 106: 'background-color:var(--ansi-bright-cyan)',
149
+ 107: 'background-color:var(--ansi-bright-white)',
150
150
  }
151
151
 
152
+ // Maps 256-color palette indices 0-15 to CSS variable names for theme-aware rendering
153
+ const ANSI_16_NAMES = [
154
+ 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
155
+ 'bright-black', 'bright-red', 'bright-green', 'bright-yellow', 'bright-blue', 'bright-magenta', 'bright-cyan', 'bright-white',
156
+ ]
157
+
152
158
  /**
153
159
  * Resolve an array of SGR codes to a CSS style string.
154
160
  * Handles 256-color (38;5;N / 48;5;N) sequences.
@@ -174,7 +180,10 @@ function resolveSgrStyles(codes: number[]): string {
174
180
  // 256-color foreground: 38;5;N
175
181
  if (codes[i] === 38 && codes[i + 1] === 5 && codes[i + 2] !== undefined) {
176
182
  const colorIdx = codes[i + 2]
177
- if (colorIdx >= 0 && colorIdx < 256) {
183
+ if (colorIdx >= 0 && colorIdx < 16) {
184
+ // Map 256-color indices 0-15 to theme-aware CSS variables
185
+ parts.push(`color:var(--ansi-${ANSI_16_NAMES[colorIdx]})`)
186
+ } else if (colorIdx < 256) {
178
187
  parts.push(`color:${ANSI_256_COLORS[colorIdx]}`)
179
188
  }
180
189
  i += 3
@@ -183,7 +192,9 @@ function resolveSgrStyles(codes: number[]): string {
183
192
  // 256-color background: 48;5;N
184
193
  if (codes[i] === 48 && codes[i + 1] === 5 && codes[i + 2] !== undefined) {
185
194
  const colorIdx = codes[i + 2]
186
- if (colorIdx >= 0 && colorIdx < 256) {
195
+ if (colorIdx >= 0 && colorIdx < 16) {
196
+ parts.push(`background-color:var(--ansi-${ANSI_16_NAMES[colorIdx]})`)
197
+ } else if (colorIdx < 256) {
187
198
  parts.push(`background-color:${ANSI_256_COLORS[colorIdx]}`)
188
199
  }
189
200
  i += 3
@@ -280,6 +291,87 @@ export function parseLogRange(logRange: string): { tailLines?: number; sinceSeco
280
291
  return { tailLines: Number(logRange) }
281
292
  }
282
293
 
294
+ // Syntax highlight colors shared between JSON and logfmt rendering
295
+ export const SYNTAX_COLOR_KEY = '#7cacf8'
296
+ export const SYNTAX_COLOR_STRING = '#73c991'
297
+ const SYNTAX_COLOR_NUMBER = '#e5c07b'
298
+ const SYNTAX_COLOR_BOOLEAN = '#c678dd'
299
+ const SYNTAX_COLOR_NULL = '#808080'
300
+
301
+ /**
302
+ * Syntax-highlight a pretty-printed JSON string for HTML display.
303
+ * Wraps keys, string values, numbers, booleans, and null in colored spans.
304
+ * Applies HTML escaping internally — do not pre-escape the input.
305
+ */
306
+ export function highlightJson(json: string): string {
307
+ const escaped = escapeHtml(json)
308
+ // Single-pass tokenizer: matches all JSON tokens at once so content inside strings
309
+ // is consumed and can't be re-matched by number/boolean/null patterns.
310
+ const tokenRe = /("(?:\\.|[^"\\])*")\s*:|("(?:\\.|[^"\\])*")|(-?\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|\b(true|false)\b|\b(null)\b/g
311
+ return escaped.replace(tokenRe, (match, key, str, num, bool, nil) => {
312
+ if (key) return `<span style="color:${SYNTAX_COLOR_KEY}">${key}</span>:`
313
+ if (str) return `<span style="color:${SYNTAX_COLOR_STRING}">${str}</span>`
314
+ if (num) return `<span style="color:${SYNTAX_COLOR_NUMBER}">${num}</span>`
315
+ if (bool) return `<span style="color:${SYNTAX_COLOR_BOOLEAN}">${bool}</span>`
316
+ if (nil) return `<span style="color:${SYNTAX_COLOR_NULL}">${nil}</span>`
317
+ return match
318
+ })
319
+ }
320
+
321
+ /**
322
+ * Unescape common escape sequences within JSON string values.
323
+ * Converts literal \n, \t, \r\n in pretty-printed JSON to real whitespace,
324
+ * making stack traces and multi-line messages readable in the expanded view.
325
+ */
326
+ export function unescapeJsonStrings(text: string): string {
327
+ return text.replace(/"(?:[^"\\]|\\.)*"/g, (match) => {
328
+ return match
329
+ .replace(/\\r\\n/g, '\r\n')
330
+ .replace(/\\n/g, '\n')
331
+ .replace(/\\t/g, '\t')
332
+ })
333
+ }
334
+
335
+ // logfmt detection and parsing
336
+
337
+ const LOGFMT_PAIR_RE = /(?:^|\s)([a-zA-Z_][\w.]*)=((?:"(?:[^"\\]|\\.)*")|(?:[^\s]*))/g
338
+
339
+ /**
340
+ * Detect if a log line is in logfmt format (key=value pairs).
341
+ * Requires at least 3 pairs AND that the matched pairs cover most of the line,
342
+ * to avoid false positives on prose text that happens to contain a few key=value fragments.
343
+ */
344
+ export function isLogfmt(content: string): boolean {
345
+ const trimmed = content.trimStart()
346
+ if (trimmed[0] === '{') return false
347
+ const matches = trimmed.match(LOGFMT_PAIR_RE)
348
+ if (!matches || matches.length < 3) return false
349
+ // Check that key=value pairs cover at least 60% of the line length
350
+ const matchedLength = matches.reduce((sum, m) => sum + m.trim().length, 0)
351
+ return matchedLength / trimmed.length >= 0.6
352
+ }
353
+
354
+ /**
355
+ * Parse a logfmt line into a key-value record.
356
+ * Handles quoted values with escape sequences.
357
+ */
358
+ export function parseLogfmt(content: string): Record<string, string> | null {
359
+ const result: Record<string, string> = {}
360
+ let count = 0
361
+ let match: RegExpExecArray | null
362
+ const re = new RegExp(LOGFMT_PAIR_RE.source, 'g')
363
+ while ((match = re.exec(content)) !== null) {
364
+ const key = match[1]
365
+ let val = match[2]
366
+ if (val.startsWith('"') && val.endsWith('"')) {
367
+ val = val.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\t/g, '\t')
368
+ }
369
+ result[key] = val
370
+ count++
371
+ }
372
+ return count >= 2 ? result : null
373
+ }
374
+
283
375
  /**
284
376
  * Handle SSE error events from log streams.
285
377
  * Parses server-sent error data and logs it, then calls onClose.