@skyhook-io/k8s-ui 1.2.4 → 1.3.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 (119) hide show
  1. package/package.json +3 -2
  2. package/src/components/dock/BottomDock.tsx +1 -1
  3. package/src/components/dock/LocalTerminalTab.tsx +1 -1
  4. package/src/components/dock/NodeTerminalTab.tsx +1 -1
  5. package/src/components/dock/TerminalTab.tsx +2 -2
  6. package/src/components/gitops/GitOpsActions.tsx +12 -11
  7. package/src/components/gitops/GitOpsStatusBadge.tsx +6 -6
  8. package/src/components/logs/LogCore.tsx +8 -8
  9. package/src/components/logs/WorkloadLogsViewer.tsx +29 -5
  10. package/src/components/logs/useLogStream.ts +20 -0
  11. package/src/components/resources/ResourcesSidebar.tsx +10 -10
  12. package/src/components/resources/ResourcesView.tsx +49 -48
  13. package/src/components/resources/renderers/AlertRenderer.tsx +2 -2
  14. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +2 -2
  15. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +1 -1
  16. package/src/components/resources/renderers/CertificateRenderer.tsx +3 -3
  17. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +3 -3
  18. package/src/components/resources/renderers/ChallengeRenderer.tsx +2 -2
  19. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +3 -3
  20. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +3 -3
  21. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +2 -2
  22. package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +1 -1
  23. package/src/components/resources/renderers/EventRenderer.tsx +1 -1
  24. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
  25. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
  26. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +1 -1
  27. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +5 -5
  28. package/src/components/resources/renderers/GatewayClassRenderer.tsx +1 -1
  29. package/src/components/resources/renderers/GatewayRenderer.tsx +3 -3
  30. package/src/components/resources/renderers/GenericRenderer.tsx +1 -1
  31. package/src/components/resources/renderers/HPARenderer.tsx +1 -1
  32. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +5 -5
  33. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +1 -1
  34. package/src/components/resources/renderers/IngressClassRenderer.tsx +1 -1
  35. package/src/components/resources/renderers/IngressRenderer.tsx +1 -1
  36. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +10 -10
  37. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +1 -1
  38. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +2 -2
  39. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +2 -2
  40. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +6 -6
  41. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -5
  42. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +5 -5
  43. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -2
  44. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +4 -4
  45. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +3 -3
  46. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +3 -3
  47. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +5 -5
  48. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +2 -2
  49. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +5 -5
  50. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +4 -4
  51. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +8 -8
  52. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +3 -3
  53. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +1 -1
  54. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +2 -2
  55. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +6 -6
  56. package/src/components/resources/renderers/KustomizationRenderer.tsx +1 -1
  57. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +4 -4
  58. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +6 -6
  59. package/src/components/resources/renderers/NodeRenderer.tsx +5 -5
  60. package/src/components/resources/renderers/OrderRenderer.tsx +3 -3
  61. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +1 -1
  62. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +1 -1
  63. package/src/components/resources/renderers/PodMonitorRenderer.tsx +3 -3
  64. package/src/components/resources/renderers/PodRenderer.tsx +10 -10
  65. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +2 -2
  66. package/src/components/resources/renderers/RoleBindingRenderer.tsx +3 -3
  67. package/src/components/resources/renderers/RoleRenderer.tsx +8 -8
  68. package/src/components/resources/renderers/RolloutRenderer.tsx +1 -1
  69. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +2 -2
  70. package/src/components/resources/renderers/SealedSecretRenderer.tsx +2 -2
  71. package/src/components/resources/renderers/SecretRenderer.tsx +3 -3
  72. package/src/components/resources/renderers/ServiceAccountRenderer.tsx +1 -1
  73. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +3 -3
  74. package/src/components/resources/renderers/ServiceRenderer.tsx +1 -1
  75. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +5 -5
  76. package/src/components/resources/renderers/StorageClassRenderer.tsx +1 -1
  77. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +1 -1
  78. package/src/components/resources/renderers/VPARenderer.tsx +2 -2
  79. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +2 -2
  80. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +6 -6
  81. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +5 -5
  82. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +6 -6
  83. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +1 -1
  84. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +10 -10
  85. package/src/components/resources/renderers/WorkflowRenderer.tsx +2 -2
  86. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +2 -2
  87. package/src/components/resources/renderers/WorkloadRenderer.tsx +1 -1
  88. package/src/components/resources/renderers/argo-cells.tsx +3 -3
  89. package/src/components/resources/renderers/certmanager-cells.tsx +6 -6
  90. package/src/components/resources/renderers/cnpg-cells.tsx +5 -5
  91. package/src/components/resources/renderers/eso-cells.tsx +4 -4
  92. package/src/components/resources/renderers/flux-cells.tsx +6 -6
  93. package/src/components/resources/renderers/istio-cells.tsx +9 -9
  94. package/src/components/resources/renderers/karpenter-cells.tsx +3 -3
  95. package/src/components/resources/renderers/keda-cells.tsx +2 -2
  96. package/src/components/resources/renderers/knative-cells.tsx +2 -2
  97. package/src/components/resources/renderers/kyverno-cells.tsx +3 -3
  98. package/src/components/resources/renderers/prometheus-cells.tsx +3 -3
  99. package/src/components/resources/renderers/trivy-cells.tsx +4 -4
  100. package/src/components/resources/renderers/velero-cells.tsx +4 -4
  101. package/src/components/shared/EditableYamlView.tsx +1 -1
  102. package/src/components/shared/ResourceActionsBar.tsx +17 -17
  103. package/src/components/shared/ResourceRendererDispatch.tsx +8 -7
  104. package/src/components/topology/K8sResourceNode.tsx +5 -5
  105. package/src/components/topology/TopologyControls.tsx +2 -2
  106. package/src/components/topology/TopologyGraph.tsx +89 -23
  107. package/src/components/topology/topology.css +1 -1
  108. package/src/components/ui/Badge.tsx +178 -0
  109. package/src/components/ui/HealthRing.tsx +70 -35
  110. package/src/components/ui/MetricsChart.tsx +2 -2
  111. package/src/components/ui/drawer-components.tsx +12 -31
  112. package/src/components/workload/ResourceDetailDrawer.tsx +4 -4
  113. package/src/components/workload/WorkloadView.tsx +10 -10
  114. package/src/theme/components.css +195 -0
  115. package/src/theme/tailwind-theme.css +13 -13
  116. package/src/theme/variables.css +46 -29
  117. package/src/types/core.ts +2 -0
  118. package/src/utils/badge-colors.ts +54 -205
  119. package/src/utils/log-format.ts +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyhook-io/k8s-ui",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/skyhook-io/radar",
@@ -22,6 +22,7 @@
22
22
  "./topology": "./src/components/topology/index.ts",
23
23
  "./theme/variables.css": "./src/theme/variables.css",
24
24
  "./theme/tailwind-theme.css": "./src/theme/tailwind-theme.css",
25
+ "./theme/components.css": "./src/theme/components.css",
25
26
  "./theme": "./src/theme/index.ts",
26
27
  "./topology.css": "./src/components/topology/topology.css"
27
28
  },
@@ -65,6 +66,6 @@
65
66
  "react-dom": "^19.2.4",
66
67
  "typescript": "^5.9.3",
67
68
  "vitest": "^4.0.18",
68
- "yaml": "^2.7.1"
69
+ "yaml": "^2.8.3"
69
70
  }
70
71
  }
@@ -91,7 +91,7 @@ export function BottomDock({ renderTabContent, leftOffset = 0 }: BottomDockProps
91
91
  >
92
92
  {isExpanded && !isMaximized && (
93
93
  <div
94
- className="absolute top-0 left-0 right-0 h-1 cursor-ns-resize hover:bg-blue-500/50 transition-colors"
94
+ className="absolute top-0 left-0 right-0 h-1 cursor-ns-resize hover:bg-skyhook-500/50 transition-colors"
95
95
  onMouseDown={handleMouseDown}
96
96
  />
97
97
  )}
@@ -227,7 +227,7 @@ export function LocalTerminalTab({
227
227
  <div className="text-xs text-theme-text-disabled mb-3">{error}</div>
228
228
  <button
229
229
  onClick={connect}
230
- className="flex items-center gap-2 px-3 py-1.5 bg-blue-600 text-white text-xs rounded hover:bg-blue-700"
230
+ className="flex items-center gap-2 px-3 py-1.5 btn-brand text-xs rounded"
231
231
  >
232
232
  <RefreshCw className="w-3 h-3" />
233
233
  Retry
@@ -101,7 +101,7 @@ export function NodeTerminalTab({
101
101
  <div className="text-xs text-theme-text-tertiary text-center max-w-md break-all">{error}</div>
102
102
  <button
103
103
  onClick={createPod}
104
- className="flex items-center gap-2 px-3 py-1.5 bg-blue-600 text-white text-xs rounded hover:bg-blue-700"
104
+ className="flex items-center gap-2 px-3 py-1.5 btn-brand text-xs rounded"
105
105
  >
106
106
  <RefreshCw className="w-3 h-3" />
107
107
  Retry
@@ -293,7 +293,7 @@ export function TerminalTab({
293
293
  <button
294
294
  onClick={handleCreateDebugContainer}
295
295
  disabled={isCreatingDebug}
296
- className="flex items-center gap-2 px-3 py-1.5 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 disabled:opacity-50"
296
+ className="flex items-center gap-2 px-3 py-1.5 btn-brand text-xs rounded"
297
297
  >
298
298
  {isCreatingDebug ? (
299
299
  <><RefreshCw className="w-3 h-3 animate-spin" />Creating debug container...</>
@@ -317,7 +317,7 @@ export function TerminalTab({
317
317
  <div className="text-xs text-theme-text-disabled mb-3">{error}</div>
318
318
  <button
319
319
  onClick={connect}
320
- className="flex items-center gap-2 px-3 py-1.5 bg-blue-600 text-white text-xs rounded hover:bg-blue-700"
320
+ className="flex items-center gap-2 px-3 py-1.5 btn-brand text-xs rounded"
321
321
  >
322
322
  <RefreshCw className="w-3 h-3" />
323
323
  Retry
@@ -1,5 +1,6 @@
1
1
  import { RefreshCw, Pause, Play, Loader2 } from 'lucide-react'
2
2
  import { clsx } from 'clsx'
3
+ import { SEVERITY_BADGE } from '../../utils/badge-colors'
3
4
 
4
5
  interface GitOpsActionsProps {
5
6
  /** The GitOps tool type */
@@ -53,10 +54,10 @@ export function GitOpsActions({
53
54
  'flex items-center rounded font-medium transition-colors',
54
55
  buttonClass,
55
56
  isSyncing
56
- ? 'bg-blue-500/20 text-blue-400 cursor-wait'
57
+ ? `${SEVERITY_BADGE.info} cursor-wait`
57
58
  : suspended
58
59
  ? 'bg-theme-elevated text-theme-text-tertiary cursor-not-allowed'
59
- : 'bg-blue-500/20 text-blue-400 hover:bg-blue-500/30'
60
+ : `${SEVERITY_BADGE.info} hover:bg-sky-500/30`
60
61
  )}
61
62
  title={suspended ? 'Cannot sync while suspended' : syncLabel}
62
63
  >
@@ -79,8 +80,8 @@ export function GitOpsActions({
79
80
  'flex items-center rounded font-medium transition-colors',
80
81
  buttonClass,
81
82
  isSuspending
82
- ? 'bg-green-500/20 text-green-400 cursor-wait'
83
- : 'bg-green-500/20 text-green-400 hover:bg-green-500/30'
83
+ ? `${SEVERITY_BADGE.success} cursor-wait`
84
+ : `${SEVERITY_BADGE.success} hover:bg-emerald-500/30`
84
85
  )}
85
86
  >
86
87
  {isSuspending ? (
@@ -100,8 +101,8 @@ export function GitOpsActions({
100
101
  'flex items-center rounded font-medium transition-colors',
101
102
  buttonClass,
102
103
  isSuspending
103
- ? 'bg-yellow-500/20 text-yellow-400 cursor-wait'
104
- : 'bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30'
104
+ ? `${SEVERITY_BADGE.warning} cursor-wait`
105
+ : `${SEVERITY_BADGE.warning} hover:bg-amber-500/30`
105
106
  )}
106
107
  >
107
108
  {isSuspending ? (
@@ -147,7 +148,7 @@ export function SyncButton({
147
148
  buttonClass,
148
149
  loading || disabled
149
150
  ? 'bg-theme-elevated text-theme-text-tertiary cursor-not-allowed'
150
- : 'bg-blue-500/20 text-blue-400 hover:bg-blue-500/30'
151
+ : `${SEVERITY_BADGE.info} hover:bg-sky-500/30`
151
152
  )}
152
153
  >
153
154
  {loading ? (
@@ -190,8 +191,8 @@ export function SuspendToggle({
190
191
  'flex items-center rounded font-medium transition-colors',
191
192
  buttonClass,
192
193
  loading
193
- ? 'bg-green-500/20 text-green-400 cursor-wait'
194
- : 'bg-green-500/20 text-green-400 hover:bg-green-500/30'
194
+ ? `${SEVERITY_BADGE.success} cursor-wait`
195
+ : `${SEVERITY_BADGE.success} hover:bg-emerald-500/30`
195
196
  )}
196
197
  >
197
198
  {loading ? (
@@ -212,8 +213,8 @@ export function SuspendToggle({
212
213
  'flex items-center rounded font-medium transition-colors',
213
214
  buttonClass,
214
215
  loading
215
- ? 'bg-yellow-500/20 text-yellow-400 cursor-wait'
216
- : 'bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30'
216
+ ? `${SEVERITY_BADGE.warning} cursor-wait`
217
+ : `${SEVERITY_BADGE.warning} hover:bg-amber-500/30`
217
218
  )}
218
219
  >
219
220
  {loading ? (
@@ -21,7 +21,7 @@ export function GitOpsStatusBadge({ status, showHealth = true, compact = false }
21
21
  if (compact) {
22
22
  return (
23
23
  <span
24
- className={clsx('px-2 py-0.5 rounded text-xs font-medium inline-flex items-center gap-1', colorClass)}
24
+ className={clsx('badge', colorClass)}
25
25
  title={status.message}
26
26
  >
27
27
  <Icon className="w-3 h-3" />
@@ -37,7 +37,7 @@ export function GitOpsStatusBadge({ status, showHealth = true, compact = false }
37
37
  return (
38
38
  <div className="flex items-center gap-2">
39
39
  <span
40
- className={clsx('px-2 py-0.5 rounded text-xs font-medium inline-flex items-center gap-1', colorClass)}
40
+ className={clsx('badge', colorClass)}
41
41
  title={status.message}
42
42
  >
43
43
  <Icon className="w-3.5 h-3.5" />
@@ -94,7 +94,7 @@ function HealthIndicator({ health }: HealthIndicatorProps) {
94
94
 
95
95
  return (
96
96
  <span
97
- className={clsx('px-1.5 py-0.5 rounded text-xs inline-flex items-center gap-1', color)}
97
+ className={clsx('badge-sm', color)}
98
98
  title={`Health: ${label}`}
99
99
  >
100
100
  <Icon className="w-3 h-3" />
@@ -126,7 +126,7 @@ function getHealthInfo(health: GitOpsHealthStatus) {
126
126
  export function SyncStatusBadge({ sync, suspended }: { sync: SyncStatus; suspended?: boolean }) {
127
127
  if (suspended) {
128
128
  return (
129
- <span className={clsx('px-2 py-0.5 rounded text-xs font-medium inline-flex items-center gap-1', SEVERITY_BADGE_BORDERED.warning)}>
129
+ <span className={clsx('badge', SEVERITY_BADGE_BORDERED.warning)}>
130
130
  <Pause className="w-3 h-3" />
131
131
  Suspended
132
132
  </span>
@@ -137,7 +137,7 @@ export function SyncStatusBadge({ sync, suspended }: { sync: SyncStatus; suspend
137
137
  const Icon = config.icon
138
138
 
139
139
  return (
140
- <span className={clsx('px-2 py-0.5 rounded text-xs font-medium inline-flex items-center gap-1', config.color)}>
140
+ <span className={clsx('badge', config.color)}>
141
141
  <Icon className={clsx('w-3 h-3', sync === 'Reconciling' && 'animate-spin')} />
142
142
  {config.label}
143
143
  </span>
@@ -164,7 +164,7 @@ export function HealthStatusBadge({ health }: { health: GitOpsHealthStatus }) {
164
164
  const { icon: Icon, color, label } = getHealthInfo(health)
165
165
 
166
166
  return (
167
- <span className={clsx('px-2 py-0.5 rounded text-xs font-medium inline-flex items-center gap-1', color)}>
167
+ <span className={clsx('badge', color)}>
168
168
  <Icon className={clsx('w-3 h-3', health === 'Progressing' && 'animate-spin')} />
169
169
  {label}
170
170
  </span>
@@ -240,7 +240,7 @@ export function LogCore({
240
240
  <button
241
241
  onClick={() => setExpandAllStructured(prev => !prev)}
242
242
  className={`p-1.5 rounded transition-colors ${
243
- expandAllStructured ? 'bg-blue-600/50 text-theme-text-primary' : 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated'
243
+ expandAllStructured ? 'btn-brand-toggle' : 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated'
244
244
  }`}
245
245
  >
246
246
  <Braces className="w-4 h-4" />
@@ -253,7 +253,7 @@ export function LogCore({
253
253
  <button
254
254
  onClick={toggleTimestamps}
255
255
  className={`p-1.5 rounded transition-colors ${
256
- showTimestamps ? 'bg-blue-600/50 text-theme-text-primary' : 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated'
256
+ showTimestamps ? 'btn-brand-toggle' : 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated'
257
257
  }`}
258
258
  >
259
259
  <Clock className="w-4 h-4" />
@@ -265,7 +265,7 @@ export function LogCore({
265
265
  <button
266
266
  onClick={toggleWrap}
267
267
  className={`p-1.5 rounded transition-colors ${
268
- wordWrap ? 'bg-blue-600/50 text-theme-text-primary' : 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated'
268
+ wordWrap ? 'btn-brand-toggle' : 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated'
269
269
  }`}
270
270
  >
271
271
  <WrapText className="w-4 h-4" />
@@ -277,7 +277,7 @@ export function LogCore({
277
277
  <button
278
278
  onClick={() => search.isOpen ? search.close() : search.open()}
279
279
  className={`p-1.5 rounded transition-colors ${
280
- search.isOpen ? 'bg-blue-600 text-theme-text-primary' : 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated'
280
+ search.isOpen ? 'btn-brand-toggle' : 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated'
281
281
  }`}
282
282
  >
283
283
  <Search className="w-4 h-4" />
@@ -365,7 +365,7 @@ export function LogCore({
365
365
  <button
366
366
  onClick={search.toggleRegex}
367
367
  className={`p-1 rounded transition-colors ${
368
- search.isRegex ? 'bg-blue-600 text-theme-text-primary' : 'text-theme-text-tertiary hover:text-theme-text-secondary'
368
+ search.isRegex ? 'btn-brand-toggle' : 'text-theme-text-tertiary hover:text-theme-text-secondary'
369
369
  }`}
370
370
  >
371
371
  <Regex className="w-3.5 h-3.5" />
@@ -377,7 +377,7 @@ export function LogCore({
377
377
  <button
378
378
  onClick={search.toggleCaseSensitive}
379
379
  className={`p-1 rounded transition-colors ${
380
- search.isCaseSensitive ? 'bg-blue-600 text-theme-text-primary' : 'text-theme-text-tertiary hover:text-theme-text-secondary'
380
+ search.isCaseSensitive ? 'btn-brand-toggle' : 'text-theme-text-tertiary hover:text-theme-text-secondary'
381
381
  }`}
382
382
  >
383
383
  <CaseSensitive className="w-3.5 h-3.5" />
@@ -389,7 +389,7 @@ export function LogCore({
389
389
  <button
390
390
  onClick={search.toggleFilterMode}
391
391
  className={`p-1 rounded transition-colors ${
392
- search.isFilterMode ? 'bg-blue-600 text-theme-text-primary' : 'text-theme-text-tertiary hover:text-theme-text-secondary'
392
+ search.isFilterMode ? 'btn-brand-toggle' : 'text-theme-text-tertiary hover:text-theme-text-secondary'
393
393
  }`}
394
394
  >
395
395
  <Filter className="w-3.5 h-3.5" />
@@ -484,7 +484,7 @@ export function LogCore({
484
484
  {!atBottom && (
485
485
  <button
486
486
  onClick={scrollToBottom}
487
- className="absolute bottom-4 right-14 px-3 py-1.5 bg-blue-600 text-white text-xs rounded-full shadow-lg hover:bg-blue-700 z-10"
487
+ className="absolute bottom-4 right-14 px-3 py-1.5 btn-brand text-xs rounded-full shadow-lg z-10"
488
488
  >
489
489
  Scroll to bottom
490
490
  </button>
@@ -131,6 +131,29 @@ export function WorkloadLogsViewer({ name, fetchAll, createStream, forceDark }:
131
131
  })
132
132
  }
133
133
  },
134
+ onPodAdded: (data: any) => {
135
+ if (data?.pods) {
136
+ const newPods = data.pods as WorkloadPodInfo[]
137
+ setPods(prev => {
138
+ const existing = new Set(prev.map(p => p.name))
139
+ const toAdd = newPods.filter(p => !existing.has(p.name))
140
+ return toAdd.length > 0 ? [...prev, ...toAdd] : prev
141
+ })
142
+ setSelectedPods(prev => {
143
+ const next = new Set(prev)
144
+ newPods.forEach(p => next.add(p.name))
145
+ return next
146
+ })
147
+ }
148
+ },
149
+ onPodRemoved: (data: any) => {
150
+ if (data?.pod) {
151
+ const removedName = data.pod as string
152
+ setPods(prev => prev.filter(p => p.name !== removedName))
153
+ // Don't remove from selectedPods — keep old log entries visible
154
+ // while new pod logs start flowing in
155
+ }
156
+ },
134
157
  },
135
158
  'Workload log stream error',
136
159
  )
@@ -152,8 +175,9 @@ export function WorkloadLogsViewer({ name, fetchAll, createStream, forceDark }:
152
175
  }, [])
153
176
 
154
177
  const toggleAllPods = useCallback(() => {
155
- setSelectedPods(selectedPods.size === pods.length ? new Set() : new Set(pods.map(p => p.name)))
156
- }, [selectedPods.size, pods])
178
+ const allSelected = pods.every(p => selectedPods.has(p.name))
179
+ setSelectedPods(allSelected ? new Set() : new Set(pods.map(p => p.name)))
180
+ }, [selectedPods, pods])
157
181
 
158
182
  const filteredEntries = useMemo(
159
183
  () => entries.filter(e => !e.pod || selectedPods.has(e.pod)),
@@ -196,11 +220,11 @@ export function WorkloadLogsViewer({ name, fetchAll, createStream, forceDark }:
196
220
  <button
197
221
  onClick={() => setShowPodFilter(v => !v)}
198
222
  className={`flex items-center gap-1.5 px-2 py-1.5 text-xs rounded transition-colors ${
199
- showPodFilter ? 'bg-blue-600 text-theme-text-primary' : 'bg-theme-elevated text-theme-text-secondary hover:bg-theme-hover'
223
+ showPodFilter ? 'btn-brand-toggle' : 'bg-theme-elevated text-theme-text-secondary hover:bg-theme-hover'
200
224
  }`}
201
225
  >
202
226
  <Filter className="w-3 h-3" />
203
- <span>{selectedPods.size}/{pods.length} pods</span>
227
+ <span>{pods.filter(p => selectedPods.has(p.name)).length}/{pods.length} pods</span>
204
228
  <ChevronDown className="w-3 h-3" />
205
229
  </button>
206
230
 
@@ -208,7 +232,7 @@ export function WorkloadLogsViewer({ name, fetchAll, createStream, forceDark }:
208
232
  <div className="absolute top-full left-0 mt-1 w-64 bg-theme-elevated border border-theme-border rounded-lg shadow-lg z-50 max-h-64 overflow-y-auto">
209
233
  <div className="p-2 border-b border-theme-border">
210
234
  <button onClick={toggleAllPods} className="text-xs text-blue-400 hover:text-blue-300">
211
- {selectedPods.size === pods.length ? 'Deselect all' : 'Select all'}
235
+ {pods.every(p => selectedPods.has(p.name)) ? 'Deselect all' : 'Select all'}
212
236
  </button>
213
237
  </div>
214
238
  {pods.map(pod => (
@@ -6,6 +6,10 @@ export interface LogStreamHandlers {
6
6
  onConnected?: (data: unknown) => void
7
7
  /** Called for each log event with parsed event data */
8
8
  onLog: (data: unknown) => void
9
+ /** Called when new pods are discovered during streaming (workload logs only) */
10
+ onPodAdded?: (data: unknown) => void
11
+ /** Called when pods are terminated during streaming (workload logs only) */
12
+ onPodRemoved?: (data: unknown) => void
9
13
  }
10
14
 
11
15
  /**
@@ -45,6 +49,22 @@ export function useLogStream() {
45
49
  }
46
50
  })
47
51
 
52
+ es.addEventListener('pod_added', (event) => {
53
+ if (handlers.onPodAdded) {
54
+ try { handlers.onPodAdded(JSON.parse((event as MessageEvent).data)) } catch (e) {
55
+ console.error('Failed to parse pod_added event:', e)
56
+ }
57
+ }
58
+ })
59
+
60
+ es.addEventListener('pod_removed', (event) => {
61
+ if (handlers.onPodRemoved) {
62
+ try { handlers.onPodRemoved(JSON.parse((event as MessageEvent).data)) } catch (e) {
63
+ console.error('Failed to parse pod_removed event:', e)
64
+ }
65
+ }
66
+ })
67
+
48
68
  es.addEventListener('end', () => setIsStreaming(false))
49
69
 
50
70
  es.addEventListener('error', (event) => {
@@ -109,7 +109,7 @@ const ResourceTypeButton = forwardRef<HTMLButtonElement, ResourceTypeButtonProps
109
109
  className={clsx(
110
110
  'w-full flex items-center gap-2 px-2 xl:px-3 py-1.5 rounded-lg text-sm transition-colors group/kind min-w-0',
111
111
  isSelected
112
- ? 'bg-blue-500/20 text-blue-700 dark:text-blue-300'
112
+ ? 'selection-strong selection-text'
113
113
  : forbidden
114
114
  ? 'text-theme-text-disabled hover:bg-theme-elevated hover:text-theme-text-secondary'
115
115
  : 'text-theme-text-secondary hover:bg-theme-elevated hover:text-theme-text-primary'
@@ -146,8 +146,8 @@ const ResourceTypeButton = forwardRef<HTMLButtonElement, ResourceTypeButtonProps
146
146
  </Tooltip>
147
147
  ) : (
148
148
  <span className={clsx(
149
- 'text-xs py-0.5 rounded text-center',
150
- isSelected ? 'bg-blue-500/30 text-blue-700 dark:text-blue-300' : 'bg-theme-elevated',
149
+ 'text-xs py-0.5 rounded text-center font-mono',
150
+ isSelected ? 'bg-skyhook-500/30 selection-text' : 'bg-theme-elevated',
151
151
  count < 1000 ? 'w-8' : 'w-9'
152
152
  )}>
153
153
  {count}
@@ -357,7 +357,7 @@ export function ResourcesSidebar({
357
357
  }, [effectiveSelectedKind.name, effectiveSelectedKind.group])
358
358
 
359
359
  return (
360
- <div className={clsx('w-56 2xl:w-72 bg-theme-surface border-r border-theme-border overflow-y-auto overflow-x-hidden shrink-0', className)}>
360
+ <div className={clsx('w-56 2xl:w-72 bg-theme-surface dark:bg-theme-base border-r border-theme-border overflow-y-auto overflow-x-hidden shrink-0', className)}>
361
361
  <div className="px-2 py-2 border-b border-theme-border">
362
362
  <div className="relative">
363
363
  <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-theme-text-tertiary" />
@@ -367,7 +367,7 @@ export function ResourcesSidebar({
367
367
  value={kindFilter}
368
368
  onChange={(e) => setKindFilter(e.target.value)}
369
369
  onKeyDown={(e) => { if (e.key === 'Escape') { setKindFilter(''); (e.target as HTMLInputElement).blur() } }}
370
- className="w-full pl-7 pr-7 py-2 bg-theme-elevated border border-theme-border-light rounded-lg text-sm text-theme-text-primary placeholder-theme-text-disabled focus:outline-none focus:ring-2 focus:ring-blue-500"
370
+ className="w-full pl-7 pr-7 py-2 bg-theme-elevated border border-theme-border-light rounded-lg text-sm text-theme-text-primary placeholder-theme-text-disabled focus:outline-none focus:ring-2 focus:ring-skyhook-500"
371
371
  />
372
372
  {kindFilter && (
373
373
  <button
@@ -393,7 +393,7 @@ export function ResourcesSidebar({
393
393
  )}
394
394
  <span className="flex-1 text-left">Favorites</span>
395
395
  {!favoritesExpanded && pinned.length > 0 && (
396
- <span className={clsx('text-xs py-0.5 rounded bg-theme-elevated text-theme-text-secondary font-normal normal-case text-center', pinned.length < 1000 ? 'w-8' : 'w-9')}>
396
+ <span className={clsx('text-xs py-0.5 rounded bg-theme-elevated text-theme-text-secondary font-normal normal-case text-center font-mono', pinned.length < 1000 ? 'w-8' : 'w-9')}>
397
397
  {pinned.length}
398
398
  </span>
399
399
  )}
@@ -444,7 +444,7 @@ export function ResourcesSidebar({
444
444
  )}
445
445
  <span className="flex-1 text-left">{category.name}</span>
446
446
  {!isExpanded && (
447
- <span className={clsx('text-xs py-0.5 rounded bg-theme-elevated text-theme-text-secondary font-normal normal-case text-center', category.total < 1000 ? 'w-8' : 'w-9')}>
447
+ <span className={clsx('text-xs py-0.5 rounded bg-theme-elevated text-theme-text-secondary font-normal normal-case text-center font-mono', category.total < 1000 ? 'w-8' : 'w-9')}>
448
448
  {category.total}
449
449
  </span>
450
450
  )}
@@ -497,15 +497,15 @@ export function ResourcesSidebar({
497
497
  className={clsx(
498
498
  'w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors',
499
499
  isSelected
500
- ? 'bg-blue-500/20 text-blue-700 dark:text-blue-300'
500
+ ? 'selection-strong selection-text'
501
501
  : 'text-theme-text-secondary hover:bg-theme-elevated hover:text-theme-text-primary'
502
502
  )}
503
503
  >
504
504
  <Icon className="w-4 h-4 shrink-0" />
505
505
  <span className="flex-1 text-left">{type.label}</span>
506
506
  <span className={clsx(
507
- 'text-xs px-2 py-0.5 rounded',
508
- isSelected ? 'bg-blue-500/30 text-blue-700 dark:text-blue-300' : 'bg-theme-elevated'
507
+ 'badge font-mono',
508
+ isSelected ? 'bg-skyhook-500/30 selection-text' : 'bg-theme-elevated'
509
509
  )}>
510
510
  {count}
511
511
  </span>