@skyhook-io/k8s-ui 1.2.4 → 1.3.1
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.
- package/package.json +4 -3
- package/src/components/dock/BottomDock.tsx +24 -6
- package/src/components/dock/DockContext.tsx +9 -1
- package/src/components/dock/LocalTerminalTab.tsx +1 -1
- package/src/components/dock/NodeTerminalTab.tsx +1 -1
- package/src/components/dock/TerminalTab.tsx +2 -2
- package/src/components/gitops/GitOpsActions.tsx +12 -11
- package/src/components/gitops/GitOpsStatusBadge.tsx +6 -6
- package/src/components/logs/LogCore.tsx +8 -8
- package/src/components/logs/LogsViewer.tsx +8 -3
- package/src/components/logs/WorkloadLogsViewer.tsx +37 -9
- package/src/components/logs/useLogStream.ts +20 -0
- package/src/components/resources/ResourcesSidebar.tsx +10 -10
- package/src/components/resources/ResourcesView.tsx +49 -48
- package/src/components/resources/renderers/AlertRenderer.tsx +2 -2
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +2 -2
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +1 -1
- package/src/components/resources/renderers/CertificateRenderer.tsx +3 -3
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +3 -3
- package/src/components/resources/renderers/ChallengeRenderer.tsx +2 -2
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +3 -3
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +3 -3
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +1 -1
- package/src/components/resources/renderers/EventRenderer.tsx +1 -1
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +1 -1
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +5 -5
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +1 -1
- package/src/components/resources/renderers/GatewayRenderer.tsx +3 -3
- package/src/components/resources/renderers/GenericRenderer.tsx +1 -1
- package/src/components/resources/renderers/HPARenderer.tsx +1 -1
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +5 -5
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +1 -1
- package/src/components/resources/renderers/IngressClassRenderer.tsx +1 -1
- package/src/components/resources/renderers/IngressRenderer.tsx +1 -1
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +10 -10
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +1 -1
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +2 -2
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +2 -2
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +6 -6
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -5
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +5 -5
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -2
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +4 -4
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +3 -3
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +3 -3
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +5 -5
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +2 -2
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +5 -5
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +4 -4
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +8 -8
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +3 -3
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +1 -1
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +2 -2
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +6 -6
- package/src/components/resources/renderers/KustomizationRenderer.tsx +1 -1
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +4 -4
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +6 -6
- package/src/components/resources/renderers/NodeRenderer.tsx +5 -5
- package/src/components/resources/renderers/OrderRenderer.tsx +3 -3
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +1 -1
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +1 -1
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +3 -3
- package/src/components/resources/renderers/PodRenderer.tsx +10 -10
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +2 -2
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +3 -3
- package/src/components/resources/renderers/RoleRenderer.tsx +8 -8
- package/src/components/resources/renderers/RolloutRenderer.tsx +1 -1
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +2 -2
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +2 -2
- package/src/components/resources/renderers/SecretRenderer.tsx +3 -3
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +1 -1
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +3 -3
- package/src/components/resources/renderers/ServiceRenderer.tsx +1 -1
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +5 -5
- package/src/components/resources/renderers/StorageClassRenderer.tsx +1 -1
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +1 -1
- package/src/components/resources/renderers/VPARenderer.tsx +2 -2
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +2 -2
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +6 -6
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +5 -5
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +6 -6
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +1 -1
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +10 -10
- package/src/components/resources/renderers/WorkflowRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkloadRenderer.tsx +1 -1
- package/src/components/resources/renderers/argo-cells.tsx +3 -3
- package/src/components/resources/renderers/certmanager-cells.tsx +6 -6
- package/src/components/resources/renderers/cnpg-cells.tsx +5 -5
- package/src/components/resources/renderers/eso-cells.tsx +4 -4
- package/src/components/resources/renderers/flux-cells.tsx +6 -6
- package/src/components/resources/renderers/istio-cells.tsx +9 -9
- package/src/components/resources/renderers/karpenter-cells.tsx +3 -3
- package/src/components/resources/renderers/keda-cells.tsx +2 -2
- package/src/components/resources/renderers/knative-cells.tsx +2 -2
- package/src/components/resources/renderers/kyverno-cells.tsx +3 -3
- package/src/components/resources/renderers/prometheus-cells.tsx +3 -3
- package/src/components/resources/renderers/trivy-cells.tsx +4 -4
- package/src/components/resources/renderers/velero-cells.tsx +4 -4
- package/src/components/shared/EditableYamlView.tsx +1 -1
- package/src/components/shared/ResourceActionsBar.tsx +17 -17
- package/src/components/shared/ResourceRendererDispatch.tsx +8 -7
- package/src/components/timeline/DiffViewer.tsx +131 -0
- package/src/components/timeline/TimelineList.tsx +817 -0
- package/src/components/timeline/index.ts +2 -0
- package/src/components/topology/GroupNode.tsx +189 -79
- package/src/components/topology/K8sResourceNode.tsx +5 -5
- package/src/components/topology/TopologyControls.tsx +2 -2
- package/src/components/topology/TopologyGraph.tsx +362 -62
- package/src/components/topology/layout.ts +288 -92
- package/src/components/topology/topology.css +112 -1
- package/src/components/ui/Badge.tsx +178 -0
- package/src/components/ui/HealthRing.tsx +70 -35
- package/src/components/ui/MetricsChart.tsx +2 -2
- package/src/components/ui/Toast.tsx +74 -27
- package/src/components/ui/drawer-components.tsx +12 -31
- package/src/components/workload/ResourceDetailDrawer.tsx +4 -4
- package/src/components/workload/WorkloadView.tsx +10 -10
- package/src/theme/components.css +195 -0
- package/src/theme/tailwind-theme.css +13 -13
- package/src/theme/variables.css +46 -29
- package/src/types/core.ts +41 -0
- package/src/utils/badge-colors.ts +54 -205
- package/src/utils/download.ts +18 -3
- package/src/utils/log-format.ts +0 -8
- package/tsconfig.json +0 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { clsx } from 'clsx'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// BADGE COMPONENT — single source of truth for all badge rendering
|
|
6
|
+
// All class names are STATIC STRINGS (required for Tailwind content scanning)
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
export type BadgeSeverity = 'success' | 'warning' | 'error' | 'info' | 'neutral'
|
|
10
|
+
export type BadgeSize = 'sm' | 'default'
|
|
11
|
+
|
|
12
|
+
interface BadgeProps {
|
|
13
|
+
/** Severity-based coloring (status badges) */
|
|
14
|
+
severity?: BadgeSeverity
|
|
15
|
+
/** K8s resource kind coloring (kind badges) */
|
|
16
|
+
kind?: string
|
|
17
|
+
/** Explicit color class override (bypasses severity/kind lookup) */
|
|
18
|
+
colorClass?: string
|
|
19
|
+
/** Size variant */
|
|
20
|
+
size?: BadgeSize
|
|
21
|
+
/** Additional classes */
|
|
22
|
+
className?: string
|
|
23
|
+
/** Click handler (renders as button) */
|
|
24
|
+
onClick?: () => void
|
|
25
|
+
/** Title/tooltip */
|
|
26
|
+
title?: string
|
|
27
|
+
children: ReactNode
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// SEVERITY COLORS — static strings for Tailwind scanning
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
const SEVERITY: Record<BadgeSeverity, string> = {
|
|
34
|
+
success: 'bg-emerald-100 text-emerald-700 border-emerald-300 dark:bg-emerald-950/50 dark:text-emerald-400 dark:border-emerald-700/40',
|
|
35
|
+
warning: 'bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950/50 dark:text-amber-400 dark:border-amber-700/40',
|
|
36
|
+
error: 'bg-red-100 text-red-700 border-red-300 dark:bg-red-950/50 dark:text-red-400 dark:border-red-700/40',
|
|
37
|
+
info: 'bg-sky-100 text-sky-700 border-sky-300 dark:bg-sky-950/50 dark:text-sky-400 dark:border-sky-700/40',
|
|
38
|
+
neutral: 'bg-theme-hover/50 text-theme-text-secondary border-theme-border',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// KIND COLORS — static strings, every K8s resource type
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
const KIND: Record<string, string> = {
|
|
45
|
+
// Workloads
|
|
46
|
+
Deployment: 'bg-emerald-100 text-emerald-700 border-emerald-300 dark:bg-emerald-950/50 dark:text-emerald-400 dark:border-emerald-700/40',
|
|
47
|
+
StatefulSet: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
48
|
+
DaemonSet: 'bg-teal-100 text-teal-800 border-teal-300 dark:bg-teal-950/50 dark:text-teal-400 dark:border-teal-700/40',
|
|
49
|
+
ReplicaSet: 'bg-green-100 text-green-800 border-green-300 dark:bg-green-950/50 dark:text-green-400 dark:border-green-700/40',
|
|
50
|
+
Pod: 'bg-lime-100 text-lime-800 border-lime-300 dark:bg-lime-950/50 dark:text-lime-300 dark:border-lime-700/40',
|
|
51
|
+
PodGroup: 'bg-lime-100 text-lime-800 border-lime-300 dark:bg-lime-950/50 dark:text-lime-300 dark:border-lime-700/40',
|
|
52
|
+
Rollout: 'bg-emerald-100 text-emerald-700 border-emerald-300 dark:bg-emerald-950/50 dark:text-emerald-400 dark:border-emerald-700/40',
|
|
53
|
+
|
|
54
|
+
// Networking
|
|
55
|
+
Service: 'bg-blue-100 text-blue-700 border-blue-300 dark:bg-blue-950/50 dark:text-blue-400 dark:border-blue-700/40',
|
|
56
|
+
Internet: 'bg-blue-100 text-blue-700 border-blue-300 dark:bg-blue-950/50 dark:text-blue-400 dark:border-blue-700/40',
|
|
57
|
+
Ingress: 'bg-violet-100 text-violet-700 border-violet-300 dark:bg-violet-950/50 dark:text-violet-400 dark:border-violet-700/40',
|
|
58
|
+
Gateway: 'bg-violet-100 text-violet-700 border-violet-300 dark:bg-violet-950/50 dark:text-violet-400 dark:border-violet-700/40',
|
|
59
|
+
HTTPRoute: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
|
|
60
|
+
GRPCRoute: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
|
|
61
|
+
TCPRoute: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
|
|
62
|
+
TLSRoute: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
|
|
63
|
+
|
|
64
|
+
// Config
|
|
65
|
+
ConfigMap: 'bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950/50 dark:text-amber-300 dark:border-amber-700/40',
|
|
66
|
+
Secret: 'bg-red-100 text-red-700 border-red-300 dark:bg-red-950/50 dark:text-red-400 dark:border-red-700/40',
|
|
67
|
+
|
|
68
|
+
// Jobs
|
|
69
|
+
Job: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
|
|
70
|
+
CronJob: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
|
|
71
|
+
|
|
72
|
+
// Scaling & Storage
|
|
73
|
+
HorizontalPodAutoscaler: 'bg-pink-100 text-pink-800 border-pink-300 dark:bg-pink-950/50 dark:text-pink-300 dark:border-pink-700/40',
|
|
74
|
+
PersistentVolumeClaim: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
75
|
+
PodDisruptionBudget: 'bg-orange-100 text-orange-800 border-orange-300 dark:bg-orange-950/50 dark:text-orange-300 dark:border-orange-700/40',
|
|
76
|
+
|
|
77
|
+
// Cluster-scoped
|
|
78
|
+
Node: 'bg-sky-100 text-sky-700 border-sky-300 dark:bg-sky-950/50 dark:text-sky-400 dark:border-sky-700/40',
|
|
79
|
+
Namespace: 'bg-gray-100 text-gray-700 border-gray-300 dark:bg-gray-950/50 dark:text-gray-400 dark:border-gray-700/40',
|
|
80
|
+
|
|
81
|
+
// GitOps
|
|
82
|
+
Application: 'bg-orange-100 text-orange-800 border-orange-300 dark:bg-orange-950/50 dark:text-orange-300 dark:border-orange-700/40',
|
|
83
|
+
Kustomization: 'bg-indigo-100 text-indigo-700 border-indigo-300 dark:bg-indigo-950/50 dark:text-indigo-400 dark:border-indigo-700/40',
|
|
84
|
+
GitRepository: 'bg-indigo-100 text-indigo-700 border-indigo-300 dark:bg-indigo-950/50 dark:text-indigo-400 dark:border-indigo-700/40',
|
|
85
|
+
HelmRelease: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
|
|
86
|
+
|
|
87
|
+
// Knative
|
|
88
|
+
KnativeService: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
89
|
+
KnativeConfiguration: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
90
|
+
KnativeRevision: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
91
|
+
KnativeRoute: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
92
|
+
Broker: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
93
|
+
Trigger: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
94
|
+
PingSource: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
95
|
+
ApiServerSource: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
96
|
+
ContainerSource: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
97
|
+
SinkBinding: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
98
|
+
Channel: 'bg-fuchsia-100 text-fuchsia-700 border-fuchsia-300 dark:bg-fuchsia-950/50 dark:text-fuchsia-400 dark:border-fuchsia-700/40',
|
|
99
|
+
|
|
100
|
+
// Traefik
|
|
101
|
+
IngressRoute: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
102
|
+
IngressRouteTCP: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
103
|
+
IngressRouteUDP: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
104
|
+
Middleware: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
105
|
+
MiddlewareTCP: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
106
|
+
TraefikService: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
107
|
+
ServersTransport: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
108
|
+
ServersTransportTCP: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
109
|
+
TLSOption: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
110
|
+
TLSStore: 'bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950/50 dark:text-cyan-400 dark:border-cyan-700/40',
|
|
111
|
+
|
|
112
|
+
// Contour
|
|
113
|
+
HTTPProxy: 'bg-violet-100 text-violet-700 border-violet-300 dark:bg-violet-950/50 dark:text-violet-400 dark:border-violet-700/40',
|
|
114
|
+
|
|
115
|
+
// Events
|
|
116
|
+
Event: 'bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-950/50 dark:text-slate-400 dark:border-slate-700/40',
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const DEFAULT_KIND_COLOR = 'bg-fuchsia-50 text-fuchsia-700 border-fuchsia-200 dark:bg-fuchsia-950/40 dark:text-fuchsia-400 dark:border-fuchsia-800/40'
|
|
120
|
+
|
|
121
|
+
// Structure classes
|
|
122
|
+
const SIZE_CLASSES: Record<BadgeSize, string> = {
|
|
123
|
+
default: 'badge',
|
|
124
|
+
sm: 'badge-sm',
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Resolve kind color with fuzzy matching for plural/lowercase forms */
|
|
128
|
+
export function getKindColorClass(kind: string): string {
|
|
129
|
+
// Direct match
|
|
130
|
+
if (KIND[kind]) return KIND[kind]
|
|
131
|
+
|
|
132
|
+
// Fuzzy: try capitalizing
|
|
133
|
+
const capitalized = kind.charAt(0).toUpperCase() + kind.slice(1)
|
|
134
|
+
if (KIND[capitalized]) return KIND[capitalized]
|
|
135
|
+
|
|
136
|
+
// Fuzzy: strip trailing 's' for plural
|
|
137
|
+
const singular = kind.endsWith('s') && !kind.endsWith('ss') ? kind.slice(0, -1) : kind
|
|
138
|
+
const singularCap = singular.charAt(0).toUpperCase() + singular.slice(1)
|
|
139
|
+
if (KIND[singularCap]) return KIND[singularCap]
|
|
140
|
+
|
|
141
|
+
// Fuzzy: known plurals
|
|
142
|
+
const pluralMap: Record<string, string> = {
|
|
143
|
+
pods: 'Pod', deployments: 'Deployment', services: 'Service', ingresses: 'Ingress',
|
|
144
|
+
gateways: 'Gateway', configmaps: 'ConfigMap', secrets: 'Secret',
|
|
145
|
+
daemonsets: 'DaemonSet', statefulsets: 'StatefulSet', replicasets: 'ReplicaSet',
|
|
146
|
+
jobs: 'Job', cronjobs: 'CronJob', nodes: 'Node', namespaces: 'Namespace',
|
|
147
|
+
horizontalpodautoscalers: 'HorizontalPodAutoscaler',
|
|
148
|
+
persistentvolumeclaims: 'PersistentVolumeClaim',
|
|
149
|
+
poddisruptionbudgets: 'PodDisruptionBudget',
|
|
150
|
+
rollouts: 'Rollout', httproutes: 'HTTPRoute', grpcroutes: 'GRPCRoute',
|
|
151
|
+
events: 'Event', helmreleases: 'HelmRelease',
|
|
152
|
+
}
|
|
153
|
+
const mapped = pluralMap[kind.toLowerCase()]
|
|
154
|
+
if (mapped && KIND[mapped]) return KIND[mapped]
|
|
155
|
+
|
|
156
|
+
return DEFAULT_KIND_COLOR
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getSeverityColorClass(severity: BadgeSeverity): string {
|
|
160
|
+
return SEVERITY[severity]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Badge component — the ONE source of truth for badge rendering.
|
|
165
|
+
* Use severity for status badges, kind for resource type badges, or colorClass for custom.
|
|
166
|
+
*/
|
|
167
|
+
export function Badge({ severity, kind, colorClass, size = 'default', className, onClick, title, children }: BadgeProps) {
|
|
168
|
+
const color = colorClass ?? (severity ? SEVERITY[severity] : kind ? getKindColorClass(kind) : '')
|
|
169
|
+
const cls = clsx(SIZE_CLASSES[size], color, className)
|
|
170
|
+
|
|
171
|
+
if (onClick) {
|
|
172
|
+
return <button onClick={onClick} className={cls} title={title}>{children}</button>
|
|
173
|
+
}
|
|
174
|
+
return <span className={cls} title={title}>{children}</span>
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Re-export the raw color maps for backwards compat (used by badge-colors.ts consumers)
|
|
178
|
+
export { SEVERITY as BADGE_SEVERITY_COLORS, KIND as BADGE_KIND_COLORS }
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useId } from 'react'
|
|
2
|
+
|
|
1
3
|
interface Segment {
|
|
2
4
|
value: number
|
|
3
5
|
color: string
|
|
@@ -11,13 +13,29 @@ interface HealthRingProps {
|
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function HealthRing({ segments, size = 48, strokeWidth = 5, label }: HealthRingProps) {
|
|
16
|
+
const filterId = useId()
|
|
14
17
|
const total = segments.reduce((sum, s) => sum + s.value, 0)
|
|
18
|
+
|
|
19
|
+
const radius = 40
|
|
20
|
+
const circumference = 2 * Math.PI * radius
|
|
21
|
+
// 270° arc = 75% of circumference
|
|
22
|
+
const arcLength = 0.75 * circumference
|
|
23
|
+
const gapLength = circumference - arcLength
|
|
24
|
+
|
|
15
25
|
if (total === 0) {
|
|
16
26
|
return (
|
|
17
27
|
<svg width={size} height={size} viewBox="0 0 100 100" className="shrink-0">
|
|
18
|
-
|
|
28
|
+
{/* Background track (270° arc) */}
|
|
29
|
+
<circle
|
|
30
|
+
cx="50" cy="50" r={radius}
|
|
31
|
+
fill="none" stroke="currentColor" strokeWidth={strokeWidth}
|
|
32
|
+
strokeDasharray={`${arcLength} ${gapLength}`}
|
|
33
|
+
strokeLinecap="round"
|
|
34
|
+
transform="rotate(135 50 50)"
|
|
35
|
+
className="text-theme-border"
|
|
36
|
+
/>
|
|
19
37
|
{label && (
|
|
20
|
-
<text x="50" y="
|
|
38
|
+
<text x="50" y="48" textAnchor="middle" dominantBaseline="central" className="fill-theme-text-tertiary text-[22px] font-semibold font-mono">
|
|
21
39
|
0
|
|
22
40
|
</text>
|
|
23
41
|
)}
|
|
@@ -25,45 +43,62 @@ export function HealthRing({ segments, size = 48, strokeWidth = 5, label }: Heal
|
|
|
25
43
|
)
|
|
26
44
|
}
|
|
27
45
|
|
|
28
|
-
|
|
29
|
-
const circumference = 2 * Math.PI * radius
|
|
30
|
-
let accumulatedOffset = 0
|
|
46
|
+
let accumulatedFraction = 0
|
|
31
47
|
|
|
32
|
-
|
|
33
|
-
const arcs =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
48
|
+
const visibleSegments = segments.filter(s => s.value > 0)
|
|
49
|
+
const arcs = visibleSegments.map((seg, i) => {
|
|
50
|
+
const fraction = seg.value / total
|
|
51
|
+
// Scale to 270° arc (75% of circumference)
|
|
52
|
+
const dashLength = fraction * arcLength
|
|
53
|
+
// Offset within the 270° arc range
|
|
54
|
+
const offset = -accumulatedFraction * arcLength
|
|
55
|
+
const isFirst = i === 0
|
|
56
|
+
const isLast = i === visibleSegments.length - 1
|
|
57
|
+
accumulatedFraction += fraction
|
|
58
|
+
return { color: seg.color, dashLength, offset, isFirst, isLast }
|
|
59
|
+
})
|
|
43
60
|
|
|
44
61
|
return (
|
|
45
62
|
<svg width={size} height={size} viewBox="0 0 100 100" className="shrink-0">
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
<defs>
|
|
64
|
+
<filter id={`arcglow-${filterId}`}>
|
|
65
|
+
<feGaussianBlur stdDeviation="2.5" result="blur"/>
|
|
66
|
+
<feMerge>
|
|
67
|
+
<feMergeNode in="blur"/>
|
|
68
|
+
<feMergeNode in="SourceGraphic"/>
|
|
69
|
+
</feMerge>
|
|
70
|
+
</filter>
|
|
71
|
+
</defs>
|
|
72
|
+
{/* Background track (270° arc) */}
|
|
73
|
+
<circle
|
|
74
|
+
cx="50" cy="50" r={radius}
|
|
75
|
+
fill="none" stroke="currentColor" strokeWidth={strokeWidth}
|
|
76
|
+
strokeDasharray={`${arcLength} ${gapLength}`}
|
|
77
|
+
strokeLinecap="round"
|
|
78
|
+
transform="rotate(135 50 50)"
|
|
79
|
+
className="text-theme-border opacity-30"
|
|
80
|
+
/>
|
|
48
81
|
{/* Segments */}
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
<g filter={`url(#arcglow-${filterId})`}>
|
|
83
|
+
{arcs.map((arc, i) => (
|
|
84
|
+
<circle
|
|
85
|
+
key={i}
|
|
86
|
+
cx="50"
|
|
87
|
+
cy="50"
|
|
88
|
+
r={radius}
|
|
89
|
+
fill="none"
|
|
90
|
+
stroke={arc.color}
|
|
91
|
+
strokeWidth={strokeWidth}
|
|
92
|
+
strokeDasharray={`${arc.dashLength} ${circumference - arc.dashLength}`}
|
|
93
|
+
strokeDashoffset={arc.offset}
|
|
94
|
+
strokeLinecap={arc.isFirst || arc.isLast ? 'round' : 'butt'}
|
|
95
|
+
transform="rotate(135 50 50)"
|
|
96
|
+
/>
|
|
97
|
+
))}
|
|
98
|
+
</g>
|
|
99
|
+
{/* Center label — nudged up slightly for visual centering in 270° arc */}
|
|
65
100
|
{label && (
|
|
66
|
-
<text x="50" y="
|
|
101
|
+
<text x="50" y="48" textAnchor="middle" dominantBaseline="central" className="fill-theme-text-primary text-[22px] font-semibold font-mono">
|
|
67
102
|
{label}
|
|
68
103
|
</text>
|
|
69
104
|
)}
|
|
@@ -111,7 +111,7 @@ export function MetricsChart({
|
|
|
111
111
|
{/* Chart with Y-axis */}
|
|
112
112
|
<div className="flex">
|
|
113
113
|
{/* Y-axis labels */}
|
|
114
|
-
<div className="flex flex-col justify-between text-[9px] text-theme-text-tertiary pr-1 w-12 text-right" style={{ height }}>
|
|
114
|
+
<div className="flex flex-col justify-between text-[9px] text-theme-text-tertiary pr-1 w-12 text-right font-mono" style={{ height }}>
|
|
115
115
|
<span className="leading-none">{format(yAxisMax)}</span>
|
|
116
116
|
<span className="leading-none">{format(yAxisMid)}</span>
|
|
117
117
|
<span className="leading-none">0</span>
|
|
@@ -197,7 +197,7 @@ export function MetricsChart({
|
|
|
197
197
|
)}
|
|
198
198
|
|
|
199
199
|
{/* Current value */}
|
|
200
|
-
<div className="flex items-baseline gap-2 mt-1 ml-12">
|
|
200
|
+
<div className="flex items-baseline gap-2 mt-1 ml-12 font-mono">
|
|
201
201
|
<span className={clsx(
|
|
202
202
|
'text-sm font-medium',
|
|
203
203
|
type === 'cpu' ? 'text-blue-400' : 'text-purple-400'
|
|
@@ -11,13 +11,22 @@ interface Toast {
|
|
|
11
11
|
type?: 'success' | 'info' | 'warning' | 'error'
|
|
12
12
|
position?: { x: number; y: number }
|
|
13
13
|
dismissing?: boolean
|
|
14
|
+
action?: { label: string; icon?: ReactNode; onClick: () => void }
|
|
15
|
+
onDetailClick?: () => void
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
interface ToastContextType {
|
|
17
|
-
showToast: (message: string, options?: {
|
|
19
|
+
showToast: (message: string, options?: {
|
|
20
|
+
detail?: string
|
|
21
|
+
command?: string
|
|
22
|
+
type?: Toast['type']
|
|
23
|
+
position?: { x: number; y: number }
|
|
24
|
+
action?: Toast['action']
|
|
25
|
+
onDetailClick?: () => void
|
|
26
|
+
}) => void
|
|
18
27
|
showCopied: (command: string, label?: string, event?: React.MouseEvent) => void
|
|
19
28
|
showError: (message: string, detail?: string) => void
|
|
20
|
-
showSuccess: (message: string, detail?: string) => void
|
|
29
|
+
showSuccess: (message: string, detail?: string, action?: Toast['action'], onDetailClick?: () => void) => void
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
const ToastContext = createContext<ToastContextType | null>(null)
|
|
@@ -26,7 +35,7 @@ const ToastContext = createContext<ToastContextType | null>(null)
|
|
|
26
35
|
class ToastManager {
|
|
27
36
|
private static instance: ToastManager
|
|
28
37
|
private showErrorFn: ((message: string, detail?: string) => void) | null = null
|
|
29
|
-
private showSuccessFn: ((message: string, detail?: string) => void) | null = null
|
|
38
|
+
private showSuccessFn: ((message: string, detail?: string, action?: Toast['action'], onDetailClick?: () => void) => void) | null = null
|
|
30
39
|
|
|
31
40
|
static getInstance(): ToastManager {
|
|
32
41
|
if (!ToastManager.instance) {
|
|
@@ -49,8 +58,8 @@ class ToastManager {
|
|
|
49
58
|
this.showErrorFn ? this.showErrorFn(message, detail) : console.error('[Toast]', message, detail)
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
showSuccess(message: string, detail?: string) {
|
|
53
|
-
this.showSuccessFn ? this.showSuccessFn(message, detail) : console.log('[Toast]', message, detail)
|
|
61
|
+
showSuccess(message: string, detail?: string, action?: Toast['action'], onDetailClick?: () => void) {
|
|
62
|
+
this.showSuccessFn ? this.showSuccessFn(message, detail, action, onDetailClick) : console.log('[Toast]', message, detail)
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
|
|
@@ -60,8 +69,8 @@ export function showApiError(message: string, detail?: string) {
|
|
|
60
69
|
toastManager.showError(message, detail)
|
|
61
70
|
}
|
|
62
71
|
|
|
63
|
-
export function showApiSuccess(message: string, detail?: string) {
|
|
64
|
-
toastManager.showSuccess(message, detail)
|
|
72
|
+
export function showApiSuccess(message: string, detail?: string, action?: Toast['action'], onDetailClick?: () => void) {
|
|
73
|
+
toastManager.showSuccess(message, detail, action, onDetailClick)
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
export function useToast() {
|
|
@@ -82,14 +91,21 @@ export function ToastProvider({ children }: { children: ReactNode }) {
|
|
|
82
91
|
}, DURATION_TOAST_EXIT)
|
|
83
92
|
}, [])
|
|
84
93
|
|
|
85
|
-
const showToast = useCallback((message: string, options?: {
|
|
94
|
+
const showToast = useCallback((message: string, options?: {
|
|
95
|
+
detail?: string
|
|
96
|
+
command?: string
|
|
97
|
+
type?: Toast['type']
|
|
98
|
+
position?: { x: number; y: number }
|
|
99
|
+
action?: Toast['action']
|
|
100
|
+
onDetailClick?: () => void
|
|
101
|
+
}) => {
|
|
86
102
|
const id = Math.random().toString(36).slice(2)
|
|
87
103
|
const toast: Toast = { id, message, ...options }
|
|
88
104
|
|
|
89
105
|
setToasts(prev => [...prev, toast])
|
|
90
106
|
|
|
91
|
-
// Auto-dismiss: errors stay longer (
|
|
92
|
-
const dismissTime = options?.type === 'error' ?
|
|
107
|
+
// Auto-dismiss: errors stay longer (10s), others 7s
|
|
108
|
+
const dismissTime = options?.type === 'error' ? 10000 : 7000
|
|
93
109
|
setTimeout(() => {
|
|
94
110
|
animateDismiss(id)
|
|
95
111
|
}, dismissTime)
|
|
@@ -112,8 +128,8 @@ export function ToastProvider({ children }: { children: ReactNode }) {
|
|
|
112
128
|
showToast(message, { detail, type: 'error' })
|
|
113
129
|
}, [showToast])
|
|
114
130
|
|
|
115
|
-
const showSuccess = useCallback((message: string, detail?: string) => {
|
|
116
|
-
showToast(message, { detail, type: 'success' })
|
|
131
|
+
const showSuccess = useCallback((message: string, detail?: string, action?: Toast['action'], onDetailClick?: () => void) => {
|
|
132
|
+
showToast(message, { detail, type: 'success', action, onDetailClick })
|
|
117
133
|
}, [showToast])
|
|
118
134
|
|
|
119
135
|
const dismissToast = useCallback((id: string) => {
|
|
@@ -143,7 +159,7 @@ function ToastItem({ toast, onDismiss }: { toast: Toast; onDismiss: () => void }
|
|
|
143
159
|
const style: React.CSSProperties = toast.position
|
|
144
160
|
? {
|
|
145
161
|
position: 'fixed',
|
|
146
|
-
left: Math.min(toast.position.x, window.innerWidth -
|
|
162
|
+
left: Math.min(toast.position.x, window.innerWidth - 520),
|
|
147
163
|
top: toast.position.y,
|
|
148
164
|
zIndex: 50,
|
|
149
165
|
}
|
|
@@ -160,13 +176,13 @@ function ToastItem({ toast, onDismiss }: { toast: Toast; onDismiss: () => void }
|
|
|
160
176
|
return (
|
|
161
177
|
<div
|
|
162
178
|
className={clsx(
|
|
163
|
-
'flex items-start gap-3 p-
|
|
164
|
-
'w-[
|
|
179
|
+
'flex items-start gap-3 p-4 rounded-lg shadow-2xl border backdrop-blur-sm',
|
|
180
|
+
'w-[480px] max-w-[calc(100vw-32px)]',
|
|
165
181
|
toast.dismissing ? 'animate-out' : 'animate-in',
|
|
166
182
|
isError
|
|
167
|
-
? 'bg-red-950
|
|
183
|
+
? 'bg-red-950 border-red-800/60'
|
|
168
184
|
: isSuccess
|
|
169
|
-
? 'bg-
|
|
185
|
+
? 'bg-emerald-950 border-emerald-800/50'
|
|
170
186
|
: 'bg-theme-surface border-theme-border'
|
|
171
187
|
)}
|
|
172
188
|
style={style}
|
|
@@ -175,46 +191,77 @@ function ToastItem({ toast, onDismiss }: { toast: Toast; onDismiss: () => void }
|
|
|
175
191
|
<div className={clsx(
|
|
176
192
|
'w-8 h-8 rounded-full flex items-center justify-center shrink-0',
|
|
177
193
|
isError ? 'bg-red-500/20' :
|
|
178
|
-
isSuccess ? 'bg-
|
|
194
|
+
isSuccess ? 'bg-emerald-500/20' : 'bg-blue-500/20'
|
|
179
195
|
)}>
|
|
180
196
|
{isError ? (
|
|
181
197
|
<AlertTriangle className="w-4 h-4 text-red-400" />
|
|
182
198
|
) : toast.command ? (
|
|
183
|
-
<Terminal className={clsx('w-4 h-4', isSuccess ? 'text-
|
|
199
|
+
<Terminal className={clsx('w-4 h-4', isSuccess ? 'text-emerald-400' : 'text-blue-400')} />
|
|
184
200
|
) : (
|
|
185
|
-
<Check className="w-4 h-4 text-
|
|
201
|
+
<Check className="w-4 h-4 text-emerald-400" />
|
|
186
202
|
)}
|
|
187
203
|
</div>
|
|
188
204
|
|
|
189
205
|
{/* Content */}
|
|
190
206
|
<div className="flex-1 min-w-0">
|
|
191
207
|
<div className="flex items-center gap-2">
|
|
192
|
-
<span className={clsx('text-sm font-medium', isError ? 'text-red-200' : isSuccess ? 'text-
|
|
208
|
+
<span className={clsx('text-sm font-medium', isError ? 'text-red-200' : isSuccess ? 'text-emerald-50' : 'text-theme-text-primary')}>
|
|
193
209
|
{toast.message}
|
|
194
210
|
</span>
|
|
195
|
-
{!isError && <Check className={clsx('w-3.5 h-3.5 shrink-0', isSuccess ? 'text-
|
|
211
|
+
{!isError && !toast.action && <Check className={clsx('w-3.5 h-3.5 shrink-0', isSuccess ? 'text-emerald-400' : 'text-green-400')} />}
|
|
196
212
|
</div>
|
|
197
213
|
{toast.detail && (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
214
|
+
toast.onDetailClick ? (
|
|
215
|
+
<button
|
|
216
|
+
onClick={toast.onDetailClick}
|
|
217
|
+
className={clsx(
|
|
218
|
+
'mt-1.5 block text-xs font-mono break-all text-left rounded px-1.5 py-1 -ml-1.5 transition-colors',
|
|
219
|
+
isError
|
|
220
|
+
? 'text-red-300 hover:bg-red-900/50'
|
|
221
|
+
: isSuccess
|
|
222
|
+
? 'text-emerald-300/90 hover:text-emerald-200 hover:bg-emerald-900/50'
|
|
223
|
+
: 'text-theme-text-secondary hover:bg-theme-elevated'
|
|
224
|
+
)}
|
|
225
|
+
title="Click to open file"
|
|
226
|
+
>
|
|
227
|
+
{toast.detail}
|
|
228
|
+
</button>
|
|
229
|
+
) : (
|
|
230
|
+
<p className={clsx('mt-1 text-xs break-all', isError ? 'text-red-300/80' : isSuccess ? 'text-emerald-300/80' : 'text-theme-text-secondary')}>
|
|
231
|
+
{toast.detail}
|
|
232
|
+
</p>
|
|
233
|
+
)
|
|
201
234
|
)}
|
|
202
235
|
{toast.command && (
|
|
203
236
|
<code className="block mt-1.5 text-xs text-theme-text-secondary font-mono bg-theme-base rounded px-2 py-1.5 whitespace-pre-wrap break-all">
|
|
204
237
|
{toast.command}
|
|
205
238
|
</code>
|
|
206
239
|
)}
|
|
240
|
+
{toast.action && (
|
|
241
|
+
<button
|
|
242
|
+
onClick={toast.action.onClick}
|
|
243
|
+
className={clsx(
|
|
244
|
+
'mt-2.5 inline-flex items-center gap-1.5 text-xs font-medium px-3 py-1.5 rounded transition-colors',
|
|
245
|
+
isSuccess
|
|
246
|
+
? 'text-emerald-100 bg-emerald-800/50 hover:bg-emerald-700/50 border border-emerald-700/40'
|
|
247
|
+
: 'text-theme-text-secondary bg-theme-elevated hover:bg-theme-surface border border-theme-border'
|
|
248
|
+
)}
|
|
249
|
+
>
|
|
250
|
+
{toast.action.icon}
|
|
251
|
+
{toast.action.label}
|
|
252
|
+
</button>
|
|
253
|
+
)}
|
|
207
254
|
</div>
|
|
208
255
|
|
|
209
256
|
{/* Dismiss button */}
|
|
210
257
|
<button
|
|
211
258
|
onClick={onDismiss}
|
|
212
259
|
className={clsx(
|
|
213
|
-
'p-1 rounded shrink-0',
|
|
260
|
+
'p-1 rounded shrink-0 transition-colors',
|
|
214
261
|
isError
|
|
215
262
|
? 'text-red-400 hover:text-red-300 hover:bg-red-900/50'
|
|
216
263
|
: isSuccess
|
|
217
|
-
? 'text-
|
|
264
|
+
? 'text-emerald-500 hover:text-emerald-300 hover:bg-emerald-900/50'
|
|
218
265
|
: 'text-theme-text-tertiary hover:text-theme-text-primary hover:bg-theme-elevated'
|
|
219
266
|
)}
|
|
220
267
|
>
|
|
@@ -3,6 +3,7 @@ import { ChevronRight, Copy, Check, Tag, AlertTriangle, CheckCircle, ExternalLin
|
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { formatAge } from '../resources/resource-utils'
|
|
5
5
|
import { Tooltip } from './Tooltip'
|
|
6
|
+
import { getKindColorClass } from '../ui/Badge'
|
|
6
7
|
|
|
7
8
|
// ============================================================================
|
|
8
9
|
// UI COMPONENTS
|
|
@@ -10,7 +11,7 @@ import { Tooltip } from './Tooltip'
|
|
|
10
11
|
|
|
11
12
|
export function KeyValueBadge({ k, v }: { k: string; v: string }) {
|
|
12
13
|
return (
|
|
13
|
-
<span className="
|
|
14
|
+
<span className="badge bg-theme-elevated text-theme-text-secondary">
|
|
14
15
|
{k}={v}
|
|
15
16
|
</span>
|
|
16
17
|
)
|
|
@@ -164,10 +165,10 @@ export function ConditionsSection({ conditions }: { conditions?: any[] }) {
|
|
|
164
165
|
// ============================================================================
|
|
165
166
|
|
|
166
167
|
const ALERT_COLORS = {
|
|
167
|
-
error: { bg: 'bg-red-
|
|
168
|
-
warning: { bg: 'bg-
|
|
169
|
-
info: { bg: 'bg-
|
|
170
|
-
success: { bg: 'bg-
|
|
168
|
+
error: { bg: 'bg-red-50 dark:bg-red-950/30', border: 'border-red-200 dark:border-red-800/30', title: 'text-red-700 dark:text-red-300', message: 'text-red-600/80 dark:text-red-300/70', list: 'text-red-600 dark:text-red-400', bullet: 'text-red-400/60 dark:text-red-400/50' },
|
|
169
|
+
warning: { bg: 'bg-amber-50 dark:bg-amber-950/30', border: 'border-amber-200 dark:border-amber-800/30', title: 'text-amber-700 dark:text-amber-300', message: 'text-amber-600/80 dark:text-amber-300/70', list: 'text-amber-600 dark:text-amber-400', bullet: 'text-amber-400/60 dark:text-amber-400/50' },
|
|
170
|
+
info: { bg: 'bg-sky-50 dark:bg-sky-950/30', border: 'border-sky-200 dark:border-sky-800/30', title: 'text-sky-700 dark:text-sky-300', message: 'text-sky-600/80 dark:text-sky-300/70', list: 'text-sky-600 dark:text-sky-400', bullet: 'text-sky-400/60 dark:text-sky-400/50' },
|
|
171
|
+
success: { bg: 'bg-emerald-50 dark:bg-emerald-950/30', border: 'border-emerald-200 dark:border-emerald-800/30', title: 'text-emerald-700 dark:text-emerald-300', message: 'text-emerald-600/80 dark:text-emerald-300/70', list: 'text-emerald-600 dark:text-emerald-400', bullet: 'text-emerald-400/60 dark:text-emerald-400/50' },
|
|
171
172
|
} as const
|
|
172
173
|
|
|
173
174
|
const DEFAULT_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {
|
|
@@ -313,7 +314,7 @@ export function PodTemplateSection({ template }: { template: any }) {
|
|
|
313
314
|
<>
|
|
314
315
|
<div className="text-xs text-theme-text-tertiary font-medium uppercase tracking-wide">Init Containers</div>
|
|
315
316
|
{initContainers.map((c: any) => (
|
|
316
|
-
<div key={c.name} className="
|
|
317
|
+
<div key={c.name} className="card-inner text-sm border-l-2 border-yellow-500/40">
|
|
317
318
|
<div className="font-medium text-theme-text-primary">{c.name}</div>
|
|
318
319
|
<div className="text-xs text-theme-text-secondary truncate" title={c.image}>{c.image}</div>
|
|
319
320
|
{(c.command || c.args) && (
|
|
@@ -327,7 +328,7 @@ export function PodTemplateSection({ template }: { template: any }) {
|
|
|
327
328
|
</>
|
|
328
329
|
)}
|
|
329
330
|
{containers.map((c: any) => (
|
|
330
|
-
<div key={c.name} className="
|
|
331
|
+
<div key={c.name} className="card-inner text-sm">
|
|
331
332
|
<div className="font-medium text-theme-text-primary">{c.name}</div>
|
|
332
333
|
<div className="text-xs text-theme-text-secondary truncate" title={c.image}>{c.image}</div>
|
|
333
334
|
{c.ports && (
|
|
@@ -450,29 +451,9 @@ export function AppInfoSection({ data }: { data: any }) {
|
|
|
450
451
|
// HELPERS
|
|
451
452
|
// ============================================================================
|
|
452
453
|
|
|
454
|
+
/** Get kind badge color — delegates to Badge.tsx's static color lookup */
|
|
453
455
|
export function getKindColor(kind: string): string {
|
|
454
|
-
|
|
455
|
-
// Use darker text for light mode contrast, brighter for dark mode
|
|
456
|
-
if (k.includes('pod')) return 'bg-lime-500/20 text-lime-700 dark:text-lime-400 border-lime-500/30'
|
|
457
|
-
if (k.includes('deployment')) return 'bg-emerald-500/20 text-emerald-700 dark:text-emerald-400 border-emerald-500/30'
|
|
458
|
-
if (k.includes('service')) return 'bg-blue-500/20 text-blue-700 dark:text-blue-400 border-blue-500/30'
|
|
459
|
-
if (k.includes('gateway')) return 'bg-violet-500/20 text-violet-700 dark:text-violet-400 border-violet-500/30'
|
|
460
|
-
if (k.includes('route')) return 'bg-purple-500/20 text-purple-700 dark:text-purple-400 border-purple-500/30'
|
|
461
|
-
if (k.includes('ingress')) return 'bg-violet-500/20 text-violet-700 dark:text-violet-400 border-violet-500/30'
|
|
462
|
-
if (k.includes('configmap')) return 'bg-amber-500/20 text-amber-700 dark:text-amber-400 border-amber-500/30'
|
|
463
|
-
if (k.endsWith('report')) return 'bg-rose-500/20 text-rose-700 dark:text-rose-400 border-rose-500/30'
|
|
464
|
-
if (k.includes('secret')) return 'bg-red-500/20 text-red-700 dark:text-red-400 border-red-500/30'
|
|
465
|
-
if (k.includes('daemonset')) return 'bg-teal-500/20 text-teal-700 dark:text-teal-400 border-teal-500/30'
|
|
466
|
-
if (k.includes('statefulset')) return 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 border-cyan-500/30'
|
|
467
|
-
if (k.includes('replicaset')) return 'bg-green-500/20 text-green-700 dark:text-green-400 border-green-500/30'
|
|
468
|
-
if (k.includes('hpa') || k.includes('horizontalpodautoscaler')) return 'bg-pink-500/20 text-pink-700 dark:text-pink-400 border-pink-500/30'
|
|
469
|
-
if (k.includes('cronjob')) return 'bg-orange-500/20 text-orange-700 dark:text-orange-400 border-orange-500/30'
|
|
470
|
-
if (k.includes('job')) return 'bg-orange-500/20 text-orange-700 dark:text-orange-400 border-orange-500/30'
|
|
471
|
-
if (k.includes('node')) return 'bg-gray-500/20 text-gray-700 dark:text-gray-400 border-gray-500/30'
|
|
472
|
-
if (k.includes('namespace')) return 'bg-blue-500/20 text-blue-700 dark:text-blue-400 border-blue-500/30'
|
|
473
|
-
if (k.includes('persistentvolume')) return 'bg-purple-500/20 text-purple-700 dark:text-purple-400 border-purple-500/30'
|
|
474
|
-
// Default color for CRDs
|
|
475
|
-
return 'bg-fuchsia-500/20 text-fuchsia-700 dark:text-fuchsia-300 border-fuchsia-500/30'
|
|
456
|
+
return getKindColorClass(kind)
|
|
476
457
|
}
|
|
477
458
|
|
|
478
459
|
export function formatKindName(kind: string): string {
|
|
@@ -648,7 +629,7 @@ export function ResourceRefBadge({ resourceRef, onClick }: ResourceRefBadgeProps
|
|
|
648
629
|
<button
|
|
649
630
|
onClick={() => onClick(resourceRef)}
|
|
650
631
|
className={clsx(
|
|
651
|
-
'
|
|
632
|
+
'badge hover:brightness-[0.92] dark:hover:brightness-125 transition-[filter]',
|
|
652
633
|
kindClass
|
|
653
634
|
)}
|
|
654
635
|
title={`${resourceRef.kind}: ${resourceRef.namespace}/${resourceRef.name}`}
|
|
@@ -661,7 +642,7 @@ export function ResourceRefBadge({ resourceRef, onClick }: ResourceRefBadgeProps
|
|
|
661
642
|
|
|
662
643
|
return (
|
|
663
644
|
<span
|
|
664
|
-
className={clsx('
|
|
645
|
+
className={clsx('badge', kindClass)}
|
|
665
646
|
title={`${resourceRef.kind}: ${resourceRef.namespace}/${resourceRef.name}`}
|
|
666
647
|
>
|
|
667
648
|
<span className="opacity-60">{kindName}/</span>
|