@skyhook-io/radar-app 0.1.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/README.md +67 -0
- package/package.json +80 -0
- package/src/App.tsx +1538 -0
- package/src/RadarApp.tsx +145 -0
- package/src/api/apiResources.ts +28 -0
- package/src/api/client.ts +2583 -0
- package/src/api/config.ts +116 -0
- package/src/api/traffic.ts +139 -0
- package/src/components/ConnectionErrorView.tsx +272 -0
- package/src/components/ContextSwitcher.tsx +481 -0
- package/src/components/DebugOverlay.tsx +94 -0
- package/src/components/UserMenu.tsx +87 -0
- package/src/components/audit/AuditSettingsDialog.tsx +162 -0
- package/src/components/audit/AuditView.tsx +123 -0
- package/src/components/cost/CostTrendChart.tsx +388 -0
- package/src/components/cost/CostView.tsx +545 -0
- package/src/components/dock/BottomDock.tsx +96 -0
- package/src/components/dock/DockContext.tsx +11 -0
- package/src/components/dock/LocalTerminalTab.tsx +22 -0
- package/src/components/dock/LogsTab.tsx +26 -0
- package/src/components/dock/NodeTerminalTab.tsx +50 -0
- package/src/components/dock/TerminalTab.tsx +42 -0
- package/src/components/dock/TrafficFlowListTab.tsx +18 -0
- package/src/components/dock/WorkloadLogsTab.tsx +23 -0
- package/src/components/dock/index.ts +2 -0
- package/src/components/gitops/GitOpsActions.tsx +1 -0
- package/src/components/gitops/GitOpsStatusBadge.tsx +1 -0
- package/src/components/gitops/ManagedResourcesList.tsx +1 -0
- package/src/components/gitops/SyncCountdown.tsx +1 -0
- package/src/components/gitops/index.ts +4 -0
- package/src/components/helm/ChartBrowser.tsx +580 -0
- package/src/components/helm/HelmReleaseDrawer.tsx +774 -0
- package/src/components/helm/HelmView.tsx +475 -0
- package/src/components/helm/InstallWizard.tsx +1060 -0
- package/src/components/helm/ManifestDiffViewer.tsx +91 -0
- package/src/components/helm/ManifestViewer.tsx +61 -0
- package/src/components/helm/OwnedResources.tsx +465 -0
- package/src/components/helm/RevisionHistory.tsx +167 -0
- package/src/components/helm/ValuesDiffPreview.tsx +190 -0
- package/src/components/helm/ValuesViewer.tsx +365 -0
- package/src/components/helm/helm-utils.ts +37 -0
- package/src/components/home/ActivitySummary.tsx +262 -0
- package/src/components/home/CertificateHealthCard.tsx +105 -0
- package/src/components/home/ClusterHealthCard.tsx +483 -0
- package/src/components/home/CostCard.tsx +112 -0
- package/src/components/home/HealthRing.tsx +1 -0
- package/src/components/home/HelmSummary.tsx +129 -0
- package/src/components/home/HomeView.tsx +224 -0
- package/src/components/home/MCPSetupDialog.tsx +417 -0
- package/src/components/home/NetworkPolicyCoverageCard.tsx +109 -0
- package/src/components/home/TopologyPreview.tsx +219 -0
- package/src/components/home/TrafficSummary.tsx +154 -0
- package/src/components/logs/JsonLogLine.tsx +1 -0
- package/src/components/logs/LogCore.tsx +2 -0
- package/src/components/logs/LogsViewer.tsx +44 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +40 -0
- package/src/components/logs/useLogBuffer.ts +2 -0
- package/src/components/logs/useLogSearch.ts +1 -0
- package/src/components/portforward/PortForwardButton.tsx +375 -0
- package/src/components/portforward/PortForwardManager.tsx +871 -0
- package/src/components/resource/PrometheusCharts.tsx +687 -0
- package/src/components/resource-drawer/ResourceDrawer.tsx +214 -0
- package/src/components/resources/ImageFilesystemModal.tsx +745 -0
- package/src/components/resources/PodFilesystemModal.tsx +407 -0
- package/src/components/resources/ResourceDetailDrawer.tsx +43 -0
- package/src/components/resources/ResourcesView.tsx +190 -0
- package/src/components/resources/drawer-components.tsx +1 -0
- package/src/components/resources/file-browser-utils.ts +35 -0
- package/src/components/resources/renderers/AlertRenderer.tsx +1 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +17 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +1 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +1 -0
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +1 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +1 -0
- package/src/components/resources/renderers/CertificateRenderer.tsx +1 -0
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +1 -0
- package/src/components/resources/renderers/ChallengeRenderer.tsx +1 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +1 -0
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +1 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +1 -0
- package/src/components/resources/renderers/EventRenderer.tsx +1 -0
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +1 -0
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/GatewayRenderer.tsx +1 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +1 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +1 -0
- package/src/components/resources/renderers/HPARenderer.tsx +1 -0
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +1 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +1 -0
- package/src/components/resources/renderers/JobRenderer.tsx +1 -0
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +1 -0
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +1 -0
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +1 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +1 -0
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +1 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +1 -0
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/LeaseRenderer.tsx +1 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +1 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +44 -0
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +1 -0
- package/src/components/resources/renderers/OrderRenderer.tsx +1 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +1 -0
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +1 -0
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +1 -0
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +1 -0
- package/src/components/resources/renderers/PodRenderer.tsx +94 -0
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +1 -0
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +1 -0
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +1 -0
- package/src/components/resources/renderers/RoleRenderer.tsx +1 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +1 -0
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/SbomReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +1 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +1 -0
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +1 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +26 -0
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/StorageClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/VPARenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +1 -0
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +1 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +1 -0
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +1 -0
- package/src/components/resources/renderers/WorkloadRenderer.tsx +52 -0
- package/src/components/resources/renderers/argo-cells.tsx +1 -0
- package/src/components/resources/renderers/certmanager-cells.tsx +1 -0
- package/src/components/resources/renderers/cnpg-cells.tsx +1 -0
- package/src/components/resources/renderers/eso-cells.tsx +1 -0
- package/src/components/resources/renderers/flux-cells.tsx +1 -0
- package/src/components/resources/renderers/index.ts +91 -0
- package/src/components/resources/renderers/istio-cells.tsx +1 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +1 -0
- package/src/components/resources/renderers/keda-cells.tsx +1 -0
- package/src/components/resources/renderers/knative-cells.tsx +1 -0
- package/src/components/resources/renderers/kyverno-cells.tsx +1 -0
- package/src/components/resources/renderers/prometheus-cells.tsx +1 -0
- package/src/components/resources/renderers/traefik-cells.tsx +1 -0
- package/src/components/resources/renderers/trivy-cells.tsx +1 -0
- package/src/components/resources/renderers/trivy-shared.tsx +1 -0
- package/src/components/resources/renderers/velero-cells.tsx +1 -0
- package/src/components/resources/resource-utils-argo.ts +2 -0
- package/src/components/resources/resource-utils-certmanager.ts +2 -0
- package/src/components/resources/resource-utils-cnpg.ts +2 -0
- package/src/components/resources/resource-utils-eso.ts +2 -0
- package/src/components/resources/resource-utils-flux.ts +2 -0
- package/src/components/resources/resource-utils-istio.ts +2 -0
- package/src/components/resources/resource-utils-karpenter.ts +2 -0
- package/src/components/resources/resource-utils-keda.ts +2 -0
- package/src/components/resources/resource-utils-knative.ts +2 -0
- package/src/components/resources/resource-utils-kyverno.ts +2 -0
- package/src/components/resources/resource-utils-prometheus.ts +2 -0
- package/src/components/resources/resource-utils-traefik.ts +1 -0
- package/src/components/resources/resource-utils-trivy.ts +2 -0
- package/src/components/resources/resource-utils-velero.ts +2 -0
- package/src/components/resources/resource-utils.ts +5 -0
- package/src/components/settings/SettingsDialog.tsx +537 -0
- package/src/components/shared/CreateResourceDialog.tsx +17 -0
- package/src/components/shared/EditableYamlView.tsx +24 -0
- package/src/components/shared/LargeClusterNamespacePicker.tsx +70 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +31 -0
- package/src/components/timeline/DiffViewer.tsx +1 -0
- package/src/components/timeline/TimelineList.tsx +69 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +1308 -0
- package/src/components/timeline/TimelineView.tsx +157 -0
- package/src/components/timeline/shared.tsx +1 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +571 -0
- package/src/components/traffic/TrafficFlowList.tsx +415 -0
- package/src/components/traffic/TrafficFlowListContext.tsx +68 -0
- package/src/components/traffic/TrafficGraph.tsx +1546 -0
- package/src/components/traffic/TrafficView.tsx +1213 -0
- package/src/components/traffic/TrafficWizard.tsx +386 -0
- package/src/components/traffic/index.ts +3 -0
- package/src/components/ui/CodeViewer.tsx +8 -0
- package/src/components/ui/CommandPalette.tsx +460 -0
- package/src/components/ui/ConfirmDialog.tsx +1 -0
- package/src/components/ui/DiagnosticsOverlay.tsx +619 -0
- package/src/components/ui/ErrorBoundary.tsx +46 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +1 -0
- package/src/components/ui/Markdown.tsx +108 -0
- package/src/components/ui/MetricsChart.tsx +1 -0
- package/src/components/ui/NamespaceSelector.tsx +436 -0
- package/src/components/ui/ResourceBar.tsx +1 -0
- package/src/components/ui/ShortcutHelpOverlay.tsx +301 -0
- package/src/components/ui/Toast.tsx +1 -0
- package/src/components/ui/Tooltip.tsx +1 -0
- package/src/components/ui/UpdateNotification.tsx +299 -0
- package/src/components/ui/YamlEditor.tsx +1 -0
- package/src/components/workload/WorkloadView.tsx +532 -0
- package/src/context/ConnectionContext.tsx +173 -0
- package/src/context/ContextSwitchContext.tsx +56 -0
- package/src/context/NavCustomization.tsx +62 -0
- package/src/context/ThemeContext.tsx +97 -0
- package/src/contexts/CapabilitiesContext.tsx +130 -0
- package/src/hooks/useAnimatedUnmount.ts +1 -0
- package/src/hooks/useDesktopDownload.ts +41 -0
- package/src/hooks/useEventSource.ts +262 -0
- package/src/hooks/useFavorites.ts +69 -0
- package/src/hooks/useKeyboardShortcuts.tsx +7 -0
- package/src/hooks/useRefreshAnimation.ts +1 -0
- package/src/index.css +243 -0
- package/src/index.ts +17 -0
- package/src/main.tsx +158 -0
- package/src/types/gitops.ts +2 -0
- package/src/types.ts +3 -0
- package/src/utils/animation.ts +2 -0
- package/src/utils/badge-colors.ts +2 -0
- package/src/utils/context-name.ts +2 -0
- package/src/utils/desktop-download.ts +66 -0
- package/src/utils/desktop-open-folder.ts +21 -0
- package/src/utils/format.ts +2 -0
- package/src/utils/log-format.ts +12 -0
- package/src/utils/navigation.ts +23 -0
- package/src/utils/resource-hierarchy.ts +2 -0
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/skeleton-yaml.ts +2 -0
- package/src/utils/traffic-colors.ts +54 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { OpenCostSummary } from '../../api/client'
|
|
2
|
+
import { useOpenCostSummary } from '../../api/client'
|
|
3
|
+
import { DollarSign } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
export function CostCard({ onNavigate }: { onNavigate?: () => void }) {
|
|
6
|
+
const { data } = useOpenCostSummary()
|
|
7
|
+
|
|
8
|
+
// Only show when OpenCost data is actually available — no placeholder card
|
|
9
|
+
if (!data || !data.available) {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return <CostCardContent data={data} onNavigate={onNavigate} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function CostCardContent({ data, onNavigate }: { data: OpenCostSummary; onNavigate?: () => void }) {
|
|
17
|
+
const hourlyCost = data.totalHourlyCost ?? 0
|
|
18
|
+
const monthlyCost = hourlyCost * 730
|
|
19
|
+
const namespaces = data.namespaces ?? []
|
|
20
|
+
const topNamespaces = namespaces.slice(0, 5)
|
|
21
|
+
|
|
22
|
+
// Find the max cost for bar scaling
|
|
23
|
+
const maxCost = topNamespaces.length > 0 ? topNamespaces[0].hourlyCost : 0
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
onClick={onNavigate}
|
|
28
|
+
className={`h-[260px] rounded-xl bg-theme-surface shadow-theme-sm text-left animate-fade-in-up ${onNavigate ? 'cursor-pointer hover:-translate-y-1 hover:shadow-theme-md transition-all duration-200' : ''}`}
|
|
29
|
+
>
|
|
30
|
+
<div className="flex flex-col h-full w-full">
|
|
31
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50">
|
|
32
|
+
<div className="flex items-center gap-2">
|
|
33
|
+
<DollarSign className="w-4 h-4 text-indigo-500" />
|
|
34
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-indigo-500">Cost Insights</span>
|
|
35
|
+
{namespaces.length > 0 && (
|
|
36
|
+
<span className="badge-sm bg-indigo-100 text-indigo-700 border-indigo-300 dark:bg-indigo-950/50 dark:text-indigo-400 dark:border-indigo-700/40">
|
|
37
|
+
{namespaces.length} ns
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="flex-1 min-h-0 flex flex-col px-4 py-3">
|
|
44
|
+
{/* Hero cost numbers */}
|
|
45
|
+
<div className="flex items-baseline gap-3 mb-3">
|
|
46
|
+
<div className="flex items-baseline gap-1">
|
|
47
|
+
<span className="text-2xl font-bold text-theme-text-primary tabular-nums">
|
|
48
|
+
{formatCost(hourlyCost)}
|
|
49
|
+
</span>
|
|
50
|
+
<span className="text-xs text-theme-text-tertiary">/hr</span>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="flex items-baseline gap-1 text-theme-text-secondary">
|
|
53
|
+
<span className="text-sm font-medium tabular-nums">~{formatCost(monthlyCost)}</span>
|
|
54
|
+
<span className="text-[10px] text-theme-text-tertiary">/mo</span>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
{/* Top namespaces */}
|
|
59
|
+
<div className="flex-1 min-h-0 space-y-1.5">
|
|
60
|
+
{topNamespaces.map((ns) => {
|
|
61
|
+
const pct = maxCost > 0 ? (ns.hourlyCost / maxCost) * 100 : 0
|
|
62
|
+
return (
|
|
63
|
+
<div key={ns.name} className="flex items-center gap-2">
|
|
64
|
+
<span className="text-[11px] text-theme-text-secondary truncate w-24 shrink-0">{ns.name}</span>
|
|
65
|
+
<div className="flex-1 h-2 rounded-full overflow-hidden bg-theme-hover">
|
|
66
|
+
<div
|
|
67
|
+
className="h-full rounded-full bg-indigo-500/60"
|
|
68
|
+
style={{ width: `${Math.max(pct, 2)}%` }}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
<span className="text-[10px] text-theme-text-tertiary tabular-nums w-14 text-right shrink-0">
|
|
72
|
+
{formatCost(ns.hourlyCost)}/h
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
})}
|
|
77
|
+
{namespaces.length > 5 && (
|
|
78
|
+
<span className="text-[10px] text-theme-text-tertiary">
|
|
79
|
+
+{namespaces.length - 5} more namespaces
|
|
80
|
+
</span>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div className="px-4 py-1.5 border-t border-theme-border/50 flex items-center justify-between">
|
|
86
|
+
<span className="text-[10px] text-theme-text-tertiary">
|
|
87
|
+
{data.currency ?? 'USD'} · {data.window ?? '1h'} window
|
|
88
|
+
</span>
|
|
89
|
+
<span className="flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider text-indigo-500">
|
|
90
|
+
OpenCost
|
|
91
|
+
</span>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatCost(value: number): string {
|
|
99
|
+
if (value >= 1000) {
|
|
100
|
+
return `$${(value / 1000).toFixed(1)}k`
|
|
101
|
+
}
|
|
102
|
+
if (value >= 1) {
|
|
103
|
+
return `$${value.toFixed(2)}`
|
|
104
|
+
}
|
|
105
|
+
if (value >= 0.01) {
|
|
106
|
+
return `$${value.toFixed(3)}`
|
|
107
|
+
}
|
|
108
|
+
if (value > 0) {
|
|
109
|
+
return `$${value.toFixed(4)}`
|
|
110
|
+
}
|
|
111
|
+
return '$0.00'
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HealthRing } from '@skyhook-io/k8s-ui/components/ui/HealthRing'
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { DashboardHelmSummary } from '../../api/client'
|
|
2
|
+
import { Package, ArrowRight, Shield } from 'lucide-react'
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
5
|
+
|
|
6
|
+
interface HelmSummaryProps {
|
|
7
|
+
data?: DashboardHelmSummary
|
|
8
|
+
onNavigate: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getStatusBadgeClass(status: string): string {
|
|
12
|
+
switch (status.toLowerCase()) {
|
|
13
|
+
case 'deployed':
|
|
14
|
+
case 'succeeded':
|
|
15
|
+
return 'status-healthy'
|
|
16
|
+
case 'failed':
|
|
17
|
+
return 'status-unhealthy'
|
|
18
|
+
case 'pending-install':
|
|
19
|
+
case 'pending-upgrade':
|
|
20
|
+
case 'pending-rollback':
|
|
21
|
+
return 'status-degraded'
|
|
22
|
+
case 'superseded':
|
|
23
|
+
case 'uninstalled':
|
|
24
|
+
return 'status-unknown'
|
|
25
|
+
default:
|
|
26
|
+
return 'status-unknown'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getHealthDot(health?: string): string {
|
|
31
|
+
switch (health) {
|
|
32
|
+
case 'healthy':
|
|
33
|
+
return 'bg-green-500'
|
|
34
|
+
case 'degraded':
|
|
35
|
+
return 'bg-yellow-500'
|
|
36
|
+
case 'unhealthy':
|
|
37
|
+
return 'bg-red-500'
|
|
38
|
+
default:
|
|
39
|
+
return 'bg-theme-text-tertiary'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function HelmSummary({ data, onNavigate }: HelmSummaryProps) {
|
|
44
|
+
return (
|
|
45
|
+
<button
|
|
46
|
+
onClick={onNavigate}
|
|
47
|
+
className="group h-[260px] rounded-xl bg-theme-surface shadow-theme-sm hover:-translate-y-1 hover:shadow-theme-md transition-all duration-200 text-left"
|
|
48
|
+
>
|
|
49
|
+
<div className="flex flex-col h-full w-full">
|
|
50
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50">
|
|
51
|
+
<div className="flex items-center gap-2">
|
|
52
|
+
<Package className="w-4 h-4 text-theme-text-tertiary" />
|
|
53
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-theme-text-secondary">Helm Releases</span>
|
|
54
|
+
{data && data.total > 0 && (
|
|
55
|
+
<span className="badge-sm bg-theme-elevated text-theme-text-secondary">
|
|
56
|
+
{data.total}
|
|
57
|
+
</span>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className="flex-1 min-h-0 overflow-hidden">
|
|
63
|
+
{!data ? (
|
|
64
|
+
<div className="divide-y divide-theme-border">
|
|
65
|
+
{[...Array(3)].map((_, i) => (
|
|
66
|
+
<div key={i} className="flex items-center justify-between px-3 py-1.5">
|
|
67
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
68
|
+
<span className="w-1.5 h-1.5 rounded-full shrink-0 bg-theme-text-tertiary/30 animate-pulse" />
|
|
69
|
+
<span className="h-3 w-24 rounded bg-theme-text-tertiary/20 animate-pulse" />
|
|
70
|
+
<span className="h-3 w-14 rounded bg-theme-text-tertiary/10 animate-pulse" />
|
|
71
|
+
</div>
|
|
72
|
+
<div className="flex items-center gap-1.5 ml-2">
|
|
73
|
+
<span className="h-3 w-20 rounded bg-theme-text-tertiary/10 animate-pulse hidden sm:inline-block" />
|
|
74
|
+
<span className="h-4 w-14 rounded bg-theme-text-tertiary/15 animate-pulse" />
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
))}
|
|
78
|
+
</div>
|
|
79
|
+
) : data.restricted ? (
|
|
80
|
+
<div className="flex flex-col items-center justify-center h-full py-4 text-theme-text-tertiary">
|
|
81
|
+
<Shield className="w-8 h-8 text-amber-400 mb-2" />
|
|
82
|
+
<span className="text-xs font-medium text-theme-text-secondary">Access Restricted</span>
|
|
83
|
+
<span className="text-[11px] mt-1">Insufficient permissions to list Helm releases</span>
|
|
84
|
+
</div>
|
|
85
|
+
) : !data.releases || data.releases.length === 0 ? (
|
|
86
|
+
<div className="flex items-center justify-center h-full py-4 text-xs text-theme-text-tertiary">
|
|
87
|
+
No Helm releases found
|
|
88
|
+
</div>
|
|
89
|
+
) : (
|
|
90
|
+
<div className="divide-y divide-theme-border">
|
|
91
|
+
{data.releases.map((release) => (
|
|
92
|
+
<div
|
|
93
|
+
key={`${release.namespace}/${release.name}`}
|
|
94
|
+
className="flex items-center justify-between px-3 py-1.5 overflow-hidden"
|
|
95
|
+
>
|
|
96
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
97
|
+
<span className={clsx('w-1.5 h-1.5 rounded-full shrink-0', getHealthDot(release.resourceHealth))} />
|
|
98
|
+
<span className="text-xs text-theme-text-primary truncate">{release.name}</span>
|
|
99
|
+
<span className="text-[10px] text-theme-text-tertiary truncate">{release.namespace}</span>
|
|
100
|
+
</div>
|
|
101
|
+
<div className="flex items-center gap-1.5 ml-2 min-w-0">
|
|
102
|
+
<Tooltip content={`${release.chart} ${release.chartVersion}`} delay={100}>
|
|
103
|
+
<span className="text-[10px] text-theme-text-tertiary hidden sm:inline truncate max-w-[150px]">
|
|
104
|
+
{release.chart} {release.chartVersion}
|
|
105
|
+
</span>
|
|
106
|
+
</Tooltip>
|
|
107
|
+
<span className={clsx('badge-sm shrink-0', getStatusBadgeClass(release.status))}>
|
|
108
|
+
{release.status}
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="px-4 py-1.5 border-t border-theme-border/50 flex items-center justify-between">
|
|
118
|
+
<span className="text-[10px] text-theme-text-tertiary">
|
|
119
|
+
{data && data.releases && data.total > data.releases.length ? `+${data.total - data.releases.length} more` : ''}
|
|
120
|
+
</span>
|
|
121
|
+
<span className="flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider text-theme-text-secondary group-hover:text-theme-text-primary transition-colors">
|
|
122
|
+
Open Helm
|
|
123
|
+
<ArrowRight className="w-3.5 h-3.5 transition-transform group-hover:translate-x-0.5" />
|
|
124
|
+
</span>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</button>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { useDashboard, useDashboardCRDs, useDashboardHelm } from '../../api/client'
|
|
2
|
+
import type { DashboardResponse } from '../../api/client'
|
|
3
|
+
import type { ExtendedMainView, Topology, SelectedResource } from '../../types'
|
|
4
|
+
import { kindToPlural } from '../../utils/navigation'
|
|
5
|
+
import { TopologyPreview } from './TopologyPreview'
|
|
6
|
+
import { HelmSummary } from './HelmSummary'
|
|
7
|
+
import { ActivitySummary } from './ActivitySummary'
|
|
8
|
+
import { TrafficSummary } from './TrafficSummary'
|
|
9
|
+
import { CertificateHealthCard } from './CertificateHealthCard'
|
|
10
|
+
import { NetworkPolicyCoverageCard } from './NetworkPolicyCoverageCard'
|
|
11
|
+
import { CostCard } from './CostCard'
|
|
12
|
+
import { AuditCard } from '@skyhook-io/k8s-ui'
|
|
13
|
+
import { ClusterHealthCard } from './ClusterHealthCard'
|
|
14
|
+
import { AlertTriangle, Loader2, Shield } from 'lucide-react'
|
|
15
|
+
import { clsx } from 'clsx'
|
|
16
|
+
|
|
17
|
+
interface HomeViewProps {
|
|
18
|
+
namespaces: string[]
|
|
19
|
+
topology: Topology | null
|
|
20
|
+
onNavigateToView: (view: ExtendedMainView, params?: Record<string, string>) => void
|
|
21
|
+
onNavigateToResourceKind: (kind: string, group?: string, filters?: Record<string, string[]>) => void
|
|
22
|
+
onNavigateToResource: (resource: SelectedResource) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function HomeView({ namespaces, topology, onNavigateToView, onNavigateToResourceKind, onNavigateToResource }: HomeViewProps) {
|
|
26
|
+
const { data, isLoading, error } = useDashboard(namespaces)
|
|
27
|
+
// CRDs and Helm load lazily after main dashboard to keep initial load fast
|
|
28
|
+
const { data: crdsData } = useDashboardCRDs(namespaces)
|
|
29
|
+
const { data: helmData } = useDashboardHelm(namespaces)
|
|
30
|
+
|
|
31
|
+
if (isLoading) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex-1 flex items-center justify-center">
|
|
34
|
+
<div className="flex flex-col items-center gap-3">
|
|
35
|
+
<Loader2 className="w-6 h-6 animate-spin text-theme-text-tertiary" />
|
|
36
|
+
<span className="text-sm text-theme-text-tertiary">Loading dashboard...</span>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (error || !data) {
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex-1 flex items-center justify-center text-theme-text-secondary">
|
|
45
|
+
<p>Failed to load dashboard data</p>
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (data.accessRestricted) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="flex-1 flex items-center justify-center bg-theme-base">
|
|
53
|
+
<div className="flex flex-col items-center gap-3 max-w-md text-center">
|
|
54
|
+
<div className="w-12 h-12 rounded-full bg-amber-500/10 flex items-center justify-center">
|
|
55
|
+
<Shield className="w-6 h-6 text-amber-500" />
|
|
56
|
+
</div>
|
|
57
|
+
<p className="text-lg font-medium text-theme-text-primary">No Namespace Access</p>
|
|
58
|
+
<p className="text-sm text-theme-text-secondary">
|
|
59
|
+
Your account does not have access to any namespaces in this cluster. Contact your administrator to add a Kubernetes RoleBinding or ClusterRoleBinding for your user.
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const hasProblems = data.problems && data.problems.length > 0
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="flex-1 overflow-y-auto">
|
|
70
|
+
<div className="max-w-[1600px] mx-auto px-6 py-6 space-y-6">
|
|
71
|
+
{/* Row 1: Cluster Health Card (combined health + resource counts) */}
|
|
72
|
+
<ClusterHealthCard
|
|
73
|
+
health={data.health}
|
|
74
|
+
counts={data.resourceCounts}
|
|
75
|
+
cluster={data.cluster}
|
|
76
|
+
metrics={data.metrics}
|
|
77
|
+
metricsServerAvailable={data.metricsServerAvailable}
|
|
78
|
+
topCRDs={crdsData?.topCRDs}
|
|
79
|
+
problems={data.problems ?? []}
|
|
80
|
+
nodeVersionSkew={data.nodeVersionSkew}
|
|
81
|
+
onNavigateToKind={onNavigateToResourceKind}
|
|
82
|
+
onNavigateToView={() => onNavigateToView('resources')}
|
|
83
|
+
onWarningEventsClick={() => onNavigateToView('timeline', { view: 'list', filter: 'warnings', time: 'all' })}
|
|
84
|
+
onUnhealthyClick={() => onNavigateToView('timeline', { view: 'list', filter: 'unhealthy', time: 'all' })}
|
|
85
|
+
/>
|
|
86
|
+
|
|
87
|
+
{/* Row 2: Main content columns — teasers left, problems right (if any) */}
|
|
88
|
+
<div className={clsx(
|
|
89
|
+
'grid gap-6',
|
|
90
|
+
hasProblems ? 'grid-cols-1 lg:grid-cols-[1fr_420px]' : 'grid-cols-1'
|
|
91
|
+
)}>
|
|
92
|
+
{/* Left column: teaser cards */}
|
|
93
|
+
<div className="flex flex-col gap-6 auto-rows-min">
|
|
94
|
+
{/* Primary cards — 2-col grid */}
|
|
95
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
|
96
|
+
<TopologyPreview
|
|
97
|
+
topology={topology}
|
|
98
|
+
summary={data.topologySummary}
|
|
99
|
+
onNavigate={() => onNavigateToView('topology')}
|
|
100
|
+
/>
|
|
101
|
+
<HelmSummary
|
|
102
|
+
data={helmData}
|
|
103
|
+
onNavigate={() => onNavigateToView('helm')}
|
|
104
|
+
/>
|
|
105
|
+
<ActivitySummary
|
|
106
|
+
namespaces={namespaces}
|
|
107
|
+
topology={topology}
|
|
108
|
+
onNavigate={() => onNavigateToView('timeline')}
|
|
109
|
+
/>
|
|
110
|
+
<TrafficSummary
|
|
111
|
+
data={data.trafficSummary}
|
|
112
|
+
onNavigate={() => onNavigateToView('traffic')}
|
|
113
|
+
/>
|
|
114
|
+
<CostCard onNavigate={() => onNavigateToView('cost')} />
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Health & compliance cards — 3-col when enough cards, 2-col fallback */}
|
|
118
|
+
{(data.certificateHealth || data.networkPolicyCoverage || data.audit) && (() => {
|
|
119
|
+
const healthCards = [
|
|
120
|
+
data.certificateHealth && (
|
|
121
|
+
<CertificateHealthCard
|
|
122
|
+
key="certs"
|
|
123
|
+
data={data.certificateHealth}
|
|
124
|
+
onNavigate={() => onNavigateToResourceKind('secrets', undefined, { type: ['TLS'] })}
|
|
125
|
+
/>
|
|
126
|
+
),
|
|
127
|
+
data.networkPolicyCoverage && (
|
|
128
|
+
<NetworkPolicyCoverageCard
|
|
129
|
+
key="netpol"
|
|
130
|
+
data={data.networkPolicyCoverage}
|
|
131
|
+
onNavigate={() => onNavigateToResourceKind('networkpolicies', 'networking.k8s.io')}
|
|
132
|
+
/>
|
|
133
|
+
),
|
|
134
|
+
data.audit && (
|
|
135
|
+
<AuditCard
|
|
136
|
+
key="audit"
|
|
137
|
+
data={data.audit}
|
|
138
|
+
onNavigate={() => onNavigateToView('audit')}
|
|
139
|
+
/>
|
|
140
|
+
),
|
|
141
|
+
].filter(Boolean)
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div className={clsx(
|
|
145
|
+
'grid gap-6',
|
|
146
|
+
healthCards.length >= 3 ? 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3' : 'grid-cols-1 sm:grid-cols-2'
|
|
147
|
+
)}>
|
|
148
|
+
{healthCards}
|
|
149
|
+
</div>
|
|
150
|
+
)
|
|
151
|
+
})()}
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Right column: problems panel */}
|
|
155
|
+
{hasProblems && (
|
|
156
|
+
<ProblemsPanel
|
|
157
|
+
problems={data.problems}
|
|
158
|
+
onResourceClick={onNavigateToResource}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// Problems Panel (right sidebar, scrollable)
|
|
169
|
+
// ============================================================================
|
|
170
|
+
|
|
171
|
+
interface ProblemsPanelProps {
|
|
172
|
+
problems: DashboardResponse['problems']
|
|
173
|
+
onResourceClick: (resource: SelectedResource) => void
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
function ProblemsPanel({ problems, onResourceClick }: ProblemsPanelProps) {
|
|
178
|
+
return (
|
|
179
|
+
<div className="rounded-xl bg-theme-surface shadow-theme-sm flex flex-col lg:max-h-[calc(100vh-280px)] lg:sticky lg:top-0">
|
|
180
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50 shrink-0">
|
|
181
|
+
<div className="flex items-center gap-2">
|
|
182
|
+
<AlertTriangle className="w-4 h-4 text-red-500" />
|
|
183
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-red-500">Unhealthy Workloads</span>
|
|
184
|
+
</div>
|
|
185
|
+
<span className="badge status-unhealthy rounded-full">{problems.length}</span>
|
|
186
|
+
</div>
|
|
187
|
+
<div className="overflow-y-auto flex-1 min-h-0">
|
|
188
|
+
<div className="divide-y divide-theme-border">
|
|
189
|
+
{problems.map((p, i) => (
|
|
190
|
+
<button
|
|
191
|
+
key={`${p.kind}-${p.namespace}-${p.name}-${i}`}
|
|
192
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 hover:bg-theme-hover transition-colors text-left"
|
|
193
|
+
onClick={() => onResourceClick({
|
|
194
|
+
kind: kindToPlural(p.kind),
|
|
195
|
+
namespace: p.namespace,
|
|
196
|
+
name: p.name,
|
|
197
|
+
group: p.group,
|
|
198
|
+
})}
|
|
199
|
+
>
|
|
200
|
+
<span className={clsx(
|
|
201
|
+
'w-1.5 h-1.5 rounded-full shrink-0',
|
|
202
|
+
p.severity === 'critical' ? 'bg-red-400' : p.severity === 'high' ? 'bg-orange-400' : 'bg-yellow-400'
|
|
203
|
+
)} />
|
|
204
|
+
<div className="min-w-0 flex-1">
|
|
205
|
+
<div className="flex items-center gap-1.5">
|
|
206
|
+
<span className="text-[10px] text-theme-text-tertiary bg-theme-elevated px-1 py-0.5 rounded">{p.kind}</span>
|
|
207
|
+
<span className="text-xs text-theme-text-primary truncate font-medium">{p.name}</span>
|
|
208
|
+
<span className="text-[10px] text-theme-text-tertiary ml-auto shrink-0">{p.duration || p.age}</span>
|
|
209
|
+
</div>
|
|
210
|
+
<div className="flex items-center gap-1.5 mt-0.5">
|
|
211
|
+
<span className="text-[11px] text-theme-text-secondary truncate">{p.reason}</span>
|
|
212
|
+
<span className="text-[10px] text-theme-text-tertiary shrink-0">{p.namespace}</span>
|
|
213
|
+
</div>
|
|
214
|
+
{p.message && (
|
|
215
|
+
<div className="text-[10px] text-theme-text-tertiary truncate mt-0.5">{p.message}</div>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
</button>
|
|
219
|
+
))}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
}
|