@skyhook-io/radar-app 0.2.2 → 0.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/README.md +7 -1
- package/package.json +33 -25
- package/src/App.tsx +1449 -382
- package/src/RadarApp.tsx +132 -19
- package/src/api/apiResources.ts +1 -1
- package/src/api/client.argoResourceSync.test.ts +69 -0
- package/src/api/client.delta.test.ts +89 -0
- package/src/api/client.deltaSync.test.ts +216 -0
- package/src/api/client.metrics.test.ts +106 -0
- package/src/api/client.rightsizing.test.ts +32 -0
- package/src/api/client.ts +2730 -271
- package/src/api/client.yaml.test.ts +45 -0
- package/src/api/diagnose.ts +289 -0
- package/src/api/quotas.ts +16 -0
- package/src/api/rbac.ts +57 -0
- package/src/api/timelineSource.test.ts +217 -0
- package/src/api/timelineSource.ts +582 -0
- package/src/components/ConnectionErrorView.tsx +186 -70
- package/src/components/ContextSwitcher.tsx +63 -18
- package/src/components/DebugOverlay.tsx +5 -3
- package/src/components/NamespaceSwitcher.tsx +41 -0
- package/src/components/UserMenu.tsx +69 -21
- package/src/components/applications/ApplicationsView.tsx +936 -0
- package/src/components/audit/AuditSettingsDialog.tsx +79 -17
- package/src/components/audit/AuditView.tsx +65 -62
- package/src/components/compare/CompareViewRoute.tsx +124 -0
- package/src/components/compare/useCompareCandidates.ts +27 -0
- package/src/components/compare/useCompareLauncher.tsx +79 -0
- package/src/components/cost/ApplicationCostTab.test.ts +204 -0
- package/src/components/cost/ApplicationCostTab.tsx +571 -0
- package/src/components/cost/CostTrendChart.tsx +106 -75
- package/src/components/cost/CostView.test.ts +12 -0
- package/src/components/cost/CostView.tsx +507 -223
- package/src/components/cost/CostViewTabs.test.tsx +21 -0
- package/src/components/cost/CostViewTabs.tsx +40 -0
- package/src/components/cost/CurrentAllocationUse.test.ts +21 -0
- package/src/components/cost/CurrentAllocationUse.tsx +126 -0
- package/src/components/cost/WorkloadCostTab.test.ts +153 -0
- package/src/components/cost/WorkloadCostTab.tsx +372 -0
- package/src/components/cost/cloud-console.test.ts +39 -0
- package/src/components/cost/cloud-console.ts +81 -0
- package/src/components/cost/errors.ts +8 -0
- package/src/components/cost/format.test.ts +27 -0
- package/src/components/cost/format.ts +46 -0
- package/src/components/cost/kinds.ts +5 -0
- package/src/components/curl/ServiceCurlButton.tsx +445 -0
- package/src/components/diagnose/AISettings.tsx +147 -0
- package/src/components/diagnose/DiagnoseContext.tsx +495 -0
- package/src/components/diagnose/DiagnoseSurface.tsx +394 -0
- package/src/components/diagnose/Home.tsx +163 -0
- package/src/components/diagnose/InvestigationView.tsx +622 -0
- package/src/components/diagnose/LocalDiagnoseAction.tsx +162 -0
- package/src/components/diagnose/launch.ts +65 -0
- package/src/components/diagnose/parts.tsx +1756 -0
- package/src/components/dock/BottomDock.tsx +2 -3
- package/src/components/dock/DockContext.tsx +1 -0
- package/src/components/dock/TerminalTab.tsx +1 -1
- package/src/components/dock/WorkloadLogsTab.tsx +21 -5
- package/src/components/dock/index.ts +1 -1
- package/src/components/execution/BatchExecutionView.test.ts +170 -0
- package/src/components/execution/BatchExecutionView.tsx +1329 -0
- package/src/components/execution/batch-run-actions.test.ts +48 -0
- package/src/components/execution/batch-run-actions.ts +24 -0
- package/src/components/execution/batch-timeline.test.ts +57 -0
- package/src/components/execution/batch-timeline.ts +46 -0
- package/src/components/execution/execution-definition.test.ts +208 -0
- package/src/components/execution/execution-definition.ts +245 -0
- package/src/components/gitops/ArgoResourceDiffLoader.tsx +23 -0
- package/src/components/gitops/GitOpsView.tsx +1042 -0
- package/src/components/gitops/RevisionMetaChip.tsx +63 -0
- package/src/components/helm/ChartBrowser.tsx +87 -31
- package/src/components/helm/HelmCompareRoute.tsx +1341 -0
- package/src/components/helm/HelmReleaseDrawer.test.ts +17 -0
- package/src/components/helm/HelmReleaseDrawer.tsx +1073 -102
- package/src/components/helm/HelmView.tsx +237 -96
- package/src/components/helm/InstallWizard.tsx +94 -38
- package/src/components/helm/ManifestDiffViewer.tsx +8 -27
- package/src/components/helm/OwnedResources.tsx +34 -59
- package/src/components/helm/RevisionHistory.tsx +52 -3
- package/src/components/helm/RoleGatedPanel.tsx +3 -3
- package/src/components/helm/TrackChartSourceDialog.tsx +185 -0
- package/src/components/helm/ValuesDiffPreview.tsx +17 -7
- package/src/components/helm/ValuesViewer.tsx +49 -53
- package/src/components/helm/helm-utils.ts +4 -0
- package/src/components/home/ActivitySummary.tsx +4 -1
- package/src/components/home/ClusterHealthCard.tsx +56 -42
- package/src/components/home/CostCard.tsx +21 -36
- package/src/components/home/GitOpsControllersCard.tsx +110 -0
- package/src/components/home/HelmSummary.tsx +3 -1
- package/src/components/home/HomeView.tsx +339 -105
- package/src/components/home/MCPSetupDialog.tsx +29 -87
- package/src/components/home/TrafficSummary.tsx +2 -2
- package/src/components/home/mcpToolCatalog.ts +333 -0
- package/src/components/issues/IssuesPane.tsx +151 -0
- package/src/components/logs/LogsViewer.tsx +4 -1
- package/src/components/logs/ScheduledWorkloadLogsViewer.tsx +135 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +4 -1
- package/src/components/nav/PrimaryNavRail.tsx +285 -0
- package/src/components/portforward/PortForwardButton.tsx +118 -47
- package/src/components/portforward/PortForwardManager.tsx +253 -131
- package/src/components/resource/HPACharts.tsx +237 -0
- package/src/components/resource/PVCUsageBar.tsx +59 -0
- package/src/components/resource/PrometheusCharts.tsx +160 -584
- package/src/components/resource/PrometheusChartsGrid.tsx +270 -0
- package/src/components/resource/RestartChart.tsx +133 -0
- package/src/components/resource/RightsizingStrip.test.ts +109 -0
- package/src/components/resource/RightsizingStrip.tsx +363 -0
- package/src/components/resource-drawer/ResourceDrawer.tsx +3 -1
- package/src/components/resources/CompositeRenderer.tsx +101 -0
- package/src/components/resources/ImageFilesystemModal.tsx +19 -12
- package/src/components/resources/PodFilesystemModal.tsx +6 -5
- package/src/components/resources/ResourceDetailDrawer.tsx +13 -3
- package/src/components/resources/ResourcesView.tsx +194 -17
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +1 -0
- package/src/components/resources/renderers/HPARenderer.tsx +20 -1
- package/src/components/resources/renderers/NamespaceRenderer.tsx +31 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +10 -4
- package/src/components/resources/renderers/PVCRenderer.tsx +19 -1
- package/src/components/resources/renderers/PodRenderer.tsx +30 -6
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +45 -1
- package/src/components/resources/renderers/RoleRenderer.tsx +27 -1
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +28 -1
- package/src/components/resources/renderers/ServiceRenderer.tsx +81 -8
- package/src/components/resources/renderers/WorkloadRenderer.tsx +51 -4
- package/src/components/resources/renderers/index.ts +2 -0
- package/src/components/resources/resource-utils.ts +2 -1
- package/src/components/rightsizing/RightsizingScanView.tsx +938 -0
- package/src/components/rightsizing/copy.test.ts +56 -0
- package/src/components/rightsizing/model.test.ts +227 -0
- package/src/components/rightsizing/model.ts +158 -0
- package/src/components/rightsizing/presentation.test.ts +104 -0
- package/src/components/rightsizing/presentation.ts +94 -0
- package/src/components/settings/MyPermissionsDialog.tsx +241 -0
- package/src/components/settings/SettingsDialog.tsx +1505 -165
- package/src/components/shared/CreateResourceDialog.tsx +9 -2
- package/src/components/shared/LargeClusterNamespacePicker.tsx +3 -3
- package/src/components/timeline/LocalTimelineScrubber.tsx +212 -0
- package/src/components/timeline/RetainedTimelineScrubber.tsx +311 -0
- package/src/components/timeline/TimelineList.tsx +86 -13
- package/src/components/timeline/TimelineSwimlanes.tsx +9 -1299
- package/src/components/timeline/TimelineView.tsx +873 -24
- package/src/components/timeline/TimelineView.urlparams.test.ts +335 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +10 -45
- package/src/components/traffic/TrafficFlowList.tsx +29 -15
- package/src/components/traffic/TrafficGraph.tsx +42 -24
- package/src/components/traffic/TrafficView.tsx +32 -19
- package/src/components/ui/CommandPalette.tsx +8 -215
- package/src/components/ui/DiagnosticsOverlay.tsx +219 -9
- package/src/components/ui/Markdown.tsx +3 -3
- package/src/components/ui/Omnibar.tsx +602 -0
- package/src/components/ui/RadarOmnibar.tsx +52 -0
- package/src/components/ui/SearchSyntaxHelp.tsx +89 -0
- package/src/components/ui/ShortcutHelpOverlay.tsx +3 -2
- package/src/components/ui/UpdateNotification.tsx +48 -36
- package/src/components/ui/command-items.ts +178 -0
- package/src/components/workload/WorkloadView.tsx +1342 -158
- package/src/context/ConnectionContext.tsx +146 -21
- package/src/context/DiagnoseCustomization.tsx +93 -0
- package/src/context/NavCustomization.tsx +75 -0
- package/src/context/TimelineSource.tsx +50 -0
- package/src/contexts/CapabilitiesContext.tsx +32 -8
- package/src/filter/FilterLocationBridge.tsx +30 -0
- package/src/hooks/useClusterLoadState.ts +73 -0
- package/src/hooks/useDocumentTitle.ts +25 -0
- package/src/hooks/useEventSource.ts +6 -0
- package/src/hooks/useKeyboardShortcuts.tsx +1 -0
- package/src/hooks/useMediaQuery.ts +21 -0
- package/src/hooks/useNavRailPinned.ts +46 -0
- package/src/hooks/useRecentResources.ts +49 -0
- package/src/index.css +162 -1
- package/src/index.ts +73 -1
- package/src/main.tsx +7 -5
- package/src/types/clusterLoadState.ts +33 -0
- package/src/types.ts +2 -0
- package/src/utils/auditBadges.ts +53 -0
- package/src/utils/navigation.ts +64 -1
- package/src/components/ui/NamespaceSelector.tsx +0 -436
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useState, useEffect, useMemo, useRef, useCallback } from 'react'
|
|
2
|
-
import { useRefreshAnimation } from '../../hooks/useRefreshAnimation'
|
|
3
2
|
import { useTrafficSources, useTrafficFlows, useTrafficConnect, useSetTrafficSource } from '../../api/traffic'
|
|
4
3
|
import { useClusterInfo } from '../../api/client'
|
|
5
4
|
import type { TrafficWizardState, AggregatedFlow } from '../../types'
|
|
@@ -7,11 +6,13 @@ import { TrafficWizard } from './TrafficWizard'
|
|
|
7
6
|
import { TrafficGraph, type TrafficGraphSelection } from './TrafficGraph'
|
|
8
7
|
import { TrafficFilterSidebar } from './TrafficFilterSidebar'
|
|
9
8
|
import { TrafficFlowListProvider } from './TrafficFlowListContext'
|
|
10
|
-
import { Loader2,
|
|
9
|
+
import { Loader2, Filter, Plug, ChevronDown, List, Activity, AlertTriangle } from 'lucide-react'
|
|
11
10
|
import { clsx } from 'clsx'
|
|
12
11
|
import { useQueryClient } from '@tanstack/react-query'
|
|
13
12
|
import { useDock } from '../dock'
|
|
14
|
-
import { EmptyState, PaneLoader } from '@skyhook-io/k8s-ui'
|
|
13
|
+
import { EmptyState, PaneLoader, FreshnessControl } from '@skyhook-io/k8s-ui'
|
|
14
|
+
import { useConnection } from '../../context/ConnectionContext'
|
|
15
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
15
16
|
|
|
16
17
|
// Addon types for filtering
|
|
17
18
|
export type AddonMode = 'show' | 'group' | 'hide'
|
|
@@ -338,6 +339,7 @@ interface TrafficViewProps {
|
|
|
338
339
|
}
|
|
339
340
|
|
|
340
341
|
export function TrafficView({ namespaces }: TrafficViewProps) {
|
|
342
|
+
const { connection } = useConnection()
|
|
341
343
|
const [wizardState, setWizardState] = useState<TrafficWizardState>('detecting')
|
|
342
344
|
const [timeRange, setTimeRange] = useState<string>('5m')
|
|
343
345
|
const [hideSystem, setHideSystem] = useState(true)
|
|
@@ -420,8 +422,8 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
|
|
|
420
422
|
|
|
421
423
|
const {
|
|
422
424
|
data: flowsData,
|
|
423
|
-
isLoading: flowsLoading,
|
|
424
425
|
isFetching: flowsFetching,
|
|
426
|
+
dataUpdatedAt: flowsUpdatedAt,
|
|
425
427
|
refetch: refetchFlowsRaw,
|
|
426
428
|
} = useTrafficFlows({
|
|
427
429
|
namespaces,
|
|
@@ -429,7 +431,6 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
|
|
|
429
431
|
// Only fetch flows when connected (not connecting and no connection error)
|
|
430
432
|
enabled: wizardState === 'ready' && !isConnecting && !connectionError,
|
|
431
433
|
})
|
|
432
|
-
const [refetchFlows, isRefreshAnimating] = useRefreshAnimation(refetchFlowsRaw)
|
|
433
434
|
|
|
434
435
|
// Auto-retry when flows return with warning but no data (e.g., port-forward not ready yet)
|
|
435
436
|
useEffect(() => {
|
|
@@ -619,13 +620,13 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
|
|
|
619
620
|
|
|
620
621
|
// Toggle L7 filter helpers
|
|
621
622
|
const toggleL7Method = useCallback((method: string) => {
|
|
622
|
-
setL7Methods(prev => { const next = new Set(prev); next.has(method)
|
|
623
|
+
setL7Methods(prev => { const next = new Set(prev); if (next.has(method)) next.delete(method); else next.add(method); return next })
|
|
623
624
|
}, [])
|
|
624
625
|
const toggleL7StatusRange = useCallback((range: string) => {
|
|
625
|
-
setL7StatusRanges(prev => { const next = new Set(prev); next.has(range)
|
|
626
|
+
setL7StatusRanges(prev => { const next = new Set(prev); if (next.has(range)) next.delete(range); else next.add(range); return next })
|
|
626
627
|
}, [])
|
|
627
628
|
const toggleL7Verdict = useCallback((verdict: string) => {
|
|
628
|
-
setL7Verdicts(prev => { const next = new Set(prev); next.has(verdict)
|
|
629
|
+
setL7Verdicts(prev => { const next = new Set(prev); if (next.has(verdict)) next.delete(verdict); else next.add(verdict); return next })
|
|
629
630
|
}, [])
|
|
630
631
|
|
|
631
632
|
// Toggle namespace visibility
|
|
@@ -957,7 +958,11 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
|
|
|
957
958
|
}
|
|
958
959
|
}, [sourcesData, sourcesLoading])
|
|
959
960
|
|
|
960
|
-
// Shared connection handler — used by auto-connect and retry buttons
|
|
961
|
+
// Shared connection handler — used by auto-connect and retry buttons.
|
|
962
|
+
// Deliberately does NOT reset hasAutoConnectedRef on failure: that ref gates
|
|
963
|
+
// the auto-connect effect, and re-arming it would re-fire auto-connect on
|
|
964
|
+
// every failed attempt (unbounded loop). Retries come from the explicit
|
|
965
|
+
// Retry button, which calls handleConnect directly.
|
|
961
966
|
const handleConnect = useCallback(() => {
|
|
962
967
|
setIsConnecting(true)
|
|
963
968
|
setConnectionError(null)
|
|
@@ -968,18 +973,18 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
|
|
|
968
973
|
setIsConnecting(false)
|
|
969
974
|
if (!data.connected && data.error) {
|
|
970
975
|
setConnectionError(data.error)
|
|
971
|
-
hasAutoConnectedRef.current = false // allow retry
|
|
972
976
|
}
|
|
973
977
|
},
|
|
974
978
|
onError: (error) => {
|
|
975
979
|
setIsConnecting(false)
|
|
976
980
|
setConnectionError(error.message)
|
|
977
|
-
hasAutoConnectedRef.current = false // allow retry
|
|
978
981
|
},
|
|
979
982
|
})
|
|
980
983
|
}, [connectMutation, queryClient])
|
|
981
984
|
|
|
982
|
-
// Auto-connect when source is detected
|
|
985
|
+
// Auto-connect once when a source is first detected. Strictly one-shot per
|
|
986
|
+
// mount / cluster (the ref resets on cluster change); failures surface a
|
|
987
|
+
// manual Retry rather than re-arming this effect.
|
|
983
988
|
useEffect(() => {
|
|
984
989
|
if (wizardState === 'ready' && !hasAutoConnectedRef.current && !isConnecting) {
|
|
985
990
|
hasAutoConnectedRef.current = true
|
|
@@ -1117,18 +1122,26 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
|
|
|
1117
1122
|
{/* Top-right: stats + actions */}
|
|
1118
1123
|
<div className="absolute top-3 right-3 z-10 flex items-center gap-2">
|
|
1119
1124
|
{flowsData?.flows && flowsData.flows.length > 0 && (
|
|
1125
|
+
<Tooltip content="Open flow list in dock">
|
|
1120
1126
|
<button onClick={openFlowListDock}
|
|
1121
|
-
className="flex items-center gap-1 px-2 py-1 text-[10px] rounded-lg bg-theme-surface/90 backdrop-blur border border-theme-border text-theme-text-secondary hover:text-theme-text-primary transition-colors"
|
|
1122
|
-
title="Open flow list in dock">
|
|
1127
|
+
className="flex items-center gap-1 px-2 py-1 text-[10px] rounded-lg bg-theme-surface/90 backdrop-blur border border-theme-border text-theme-text-secondary hover:text-theme-text-primary transition-colors">
|
|
1123
1128
|
<List className="w-3 h-3" /> Flows
|
|
1124
1129
|
</button>
|
|
1130
|
+
</Tooltip>
|
|
1125
1131
|
)}
|
|
1126
|
-
<div className="flex items-center
|
|
1132
|
+
<div className="flex items-center px-2 py-1 rounded-lg bg-theme-surface/90 backdrop-blur border border-theme-border text-[10px] text-theme-text-tertiary tabular-nums">
|
|
1127
1133
|
{flowStats.shown}/{flowStats.total}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1134
|
+
</div>
|
|
1135
|
+
{/* Flows are a REST snapshot (no poll, no stream), so this is
|
|
1136
|
+
an honest "Updated N ago" + manual refresh — not "live". */}
|
|
1137
|
+
<div className="flex items-center rounded-lg bg-theme-surface/90 backdrop-blur border border-theme-border px-1.5 py-0.5">
|
|
1138
|
+
<FreshnessControl
|
|
1139
|
+
mode="snapshot"
|
|
1140
|
+
dataUpdatedAt={flowsUpdatedAt}
|
|
1141
|
+
isFetching={flowsFetching}
|
|
1142
|
+
onRefresh={() => refetchFlowsRaw()}
|
|
1143
|
+
connectionState={connection.state}
|
|
1144
|
+
/>
|
|
1132
1145
|
</div>
|
|
1133
1146
|
</div>
|
|
1134
1147
|
</>
|
|
@@ -1,129 +1,27 @@
|
|
|
1
1
|
import { useState, useMemo, useRef, useEffect, useCallback } from 'react'
|
|
2
2
|
import { TRANSITION_BACKDROP, TRANSITION_PANEL } from '../../utils/animation'
|
|
3
3
|
import { Search, X, ChevronRight } from 'lucide-react'
|
|
4
|
-
import { Home, Network, List, Clock, Package, Activity, Sun, Stethoscope, DollarSign, ShieldCheck } from 'lucide-react'
|
|
5
4
|
import { clsx } from 'clsx'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { getResourceIcon } from '../../utils/resource-icons'
|
|
9
|
-
type MainView = 'home' | 'topology' | 'resources' | 'timeline' | 'helm' | 'traffic' | 'cost' | 'audit'
|
|
5
|
+
import { useCommandItems, bestScore, type CommandItem, type CommandItemCallbacks } from './command-items'
|
|
6
|
+
import { Input } from '@skyhook-io/k8s-ui'
|
|
10
7
|
|
|
11
|
-
interface CommandPaletteProps {
|
|
8
|
+
interface CommandPaletteProps extends CommandItemCallbacks {
|
|
12
9
|
onClose: () => void
|
|
13
|
-
onNavigateView: (view: MainView) => void
|
|
14
|
-
onNavigateKind: (kind: string, group: string) => void
|
|
15
|
-
onSwitchContext: (name: string) => void
|
|
16
|
-
onSetNamespaces: (ns: string[]) => void
|
|
17
|
-
onToggleTheme: () => void
|
|
18
|
-
onShowDiagnostics?: () => void
|
|
19
10
|
/** Controls fade-in/out animation (driven by useAnimatedUnmount) */
|
|
20
11
|
isOpen?: boolean
|
|
21
12
|
}
|
|
22
13
|
|
|
23
|
-
interface CommandItem {
|
|
24
|
-
id: string
|
|
25
|
-
label: string
|
|
26
|
-
sublabel?: string
|
|
27
|
-
category: string
|
|
28
|
-
icon?: React.ComponentType<{ className?: string }>
|
|
29
|
-
shortcut?: string
|
|
30
|
-
action: () => void
|
|
31
|
-
/** Extra terms to match against during search (not displayed) */
|
|
32
|
-
searchTerms?: string[]
|
|
33
|
-
/** Small priority bonus added to the final score (only if the item matched). Used to nudge built-in k8s kinds above CRDs on tied queries like "policy" or "event". */
|
|
34
|
-
priorityBonus?: number
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Built-in k8s API groups. Used to nudge these above CRDs on tied matches.
|
|
38
|
-
const CORE_GROUP_BONUS = 10
|
|
39
|
-
const WELL_KNOWN_GROUP_BONUS = 5
|
|
40
|
-
const WELL_KNOWN_GROUPS = new Set([
|
|
41
|
-
'apps',
|
|
42
|
-
'batch',
|
|
43
|
-
'autoscaling',
|
|
44
|
-
'policy',
|
|
45
|
-
'networking.k8s.io',
|
|
46
|
-
'rbac.authorization.k8s.io',
|
|
47
|
-
'storage.k8s.io',
|
|
48
|
-
'scheduling.k8s.io',
|
|
49
|
-
'coordination.k8s.io',
|
|
50
|
-
'apiextensions.k8s.io',
|
|
51
|
-
'admissionregistration.k8s.io',
|
|
52
|
-
'apiregistration.k8s.io',
|
|
53
|
-
'certificates.k8s.io',
|
|
54
|
-
'events.k8s.io',
|
|
55
|
-
'discovery.k8s.io',
|
|
56
|
-
'flowcontrol.apiserver.k8s.io',
|
|
57
|
-
'node.k8s.io',
|
|
58
|
-
'authentication.k8s.io',
|
|
59
|
-
'authorization.k8s.io',
|
|
60
|
-
])
|
|
61
|
-
|
|
62
|
-
function groupPriorityBonus(group: string): number {
|
|
63
|
-
if (!group) return CORE_GROUP_BONUS
|
|
64
|
-
if (WELL_KNOWN_GROUPS.has(group)) return WELL_KNOWN_GROUP_BONUS
|
|
65
|
-
return 0
|
|
66
|
-
}
|
|
67
14
|
|
|
68
|
-
|
|
69
|
-
// Within a tier, a coverage bonus (up to +20) breaks ties in favor of
|
|
70
|
-
// shorter labels — so "serv" picks Service over ServiceAccount, and
|
|
71
|
-
// "service" picks Service (exact) decisively. Bonus is capped below the
|
|
72
|
-
// 25-point tier gap, so tier ordering is preserved.
|
|
73
|
-
function scoreMatch(text: string, query: string): number {
|
|
74
|
-
const lower = text.toLowerCase()
|
|
75
|
-
const q = query.toLowerCase()
|
|
76
|
-
if (!lower.includes(q)) return 0
|
|
77
|
-
let base: number
|
|
78
|
-
if (lower === q) base = 150
|
|
79
|
-
else if (lower.startsWith(q)) base = 100
|
|
80
|
-
else {
|
|
81
|
-
const wordStart = lower.indexOf(q)
|
|
82
|
-
const prev = lower[wordStart - 1]
|
|
83
|
-
base = wordStart > 0 && (prev === ' ' || prev === '/' || prev === '-' || prev === '.') ? 75 : 50
|
|
84
|
-
}
|
|
85
|
-
return base + (q.length / lower.length) * 20
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function bestScore(item: CommandItem, query: string): number {
|
|
89
|
-
// Primary label gets full score; secondary fields are discounted
|
|
90
|
-
// so that e.g. "node" matching the label "Node" ranks above
|
|
91
|
-
// "UpdateInfo" where "node" only matches the group "nodemanagement.gke.io"
|
|
92
|
-
let best = scoreMatch(item.label, query)
|
|
93
|
-
const secondary = Math.floor(Math.max(
|
|
94
|
-
scoreMatch(item.sublabel || '', query),
|
|
95
|
-
scoreMatch(item.category, query)
|
|
96
|
-
) * 0.6)
|
|
97
|
-
best = Math.max(best, secondary)
|
|
98
|
-
if (item.searchTerms) {
|
|
99
|
-
for (const term of item.searchTerms) {
|
|
100
|
-
best = Math.max(best, scoreMatch(term, query))
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// Only apply the priority bonus to items that actually matched, so we don't
|
|
104
|
-
// surface unrelated built-ins ahead of a relevant CRD.
|
|
105
|
-
return best > 0 ? best + (item.priorityBonus || 0) : 0
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function CommandPalette({
|
|
109
|
-
onClose,
|
|
110
|
-
onNavigateView,
|
|
111
|
-
onNavigateKind,
|
|
112
|
-
onSwitchContext,
|
|
113
|
-
onSetNamespaces,
|
|
114
|
-
onToggleTheme,
|
|
115
|
-
onShowDiagnostics,
|
|
116
|
-
isOpen = true,
|
|
117
|
-
}: CommandPaletteProps) {
|
|
15
|
+
export function CommandPalette({ onClose, isOpen = true, ...callbacks }: CommandPaletteProps) {
|
|
118
16
|
const [query, setQuery] = useState('')
|
|
119
17
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
120
18
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
121
19
|
const resultsRef = useRef<HTMLDivElement>(null)
|
|
122
20
|
const isKeyboardNav = useRef(false)
|
|
123
21
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
22
|
+
// The static command items (Views / Kinds / Namespaces / Actions) — shared
|
|
23
|
+
// with the standalone omnibar via useCommandItems so the two never drift.
|
|
24
|
+
const items = useCommandItems(callbacks)
|
|
127
25
|
|
|
128
26
|
// Focus input on mount
|
|
129
27
|
useEffect(() => {
|
|
@@ -143,110 +41,6 @@ export function CommandPalette({
|
|
|
143
41
|
return () => document.removeEventListener('keydown', handler, true)
|
|
144
42
|
}, [onClose])
|
|
145
43
|
|
|
146
|
-
// Build command items
|
|
147
|
-
const items = useMemo<CommandItem[]>(() => {
|
|
148
|
-
const result: CommandItem[] = []
|
|
149
|
-
|
|
150
|
-
// Views
|
|
151
|
-
const viewEntries: { view: MainView; label: string; icon: React.ComponentType<{ className?: string }>; shortcut: string }[] = [
|
|
152
|
-
{ view: 'home', label: 'Home', icon: Home, shortcut: '1' },
|
|
153
|
-
{ view: 'topology', label: 'Topology', icon: Network, shortcut: '2' },
|
|
154
|
-
{ view: 'resources', label: 'Resources', icon: List, shortcut: '3' },
|
|
155
|
-
{ view: 'timeline', label: 'Timeline', icon: Clock, shortcut: '4' },
|
|
156
|
-
{ view: 'helm', label: 'Helm', icon: Package, shortcut: '5' },
|
|
157
|
-
{ view: 'traffic', label: 'Traffic', icon: Activity, shortcut: '6' },
|
|
158
|
-
{ view: 'cost', label: 'Cost', icon: DollarSign, shortcut: '7' },
|
|
159
|
-
{ view: 'audit', label: 'Audit', icon: ShieldCheck, shortcut: '8' },
|
|
160
|
-
]
|
|
161
|
-
for (const v of viewEntries) {
|
|
162
|
-
result.push({
|
|
163
|
-
id: `view-${v.view}`,
|
|
164
|
-
label: `Go to ${v.label}`,
|
|
165
|
-
category: 'Views',
|
|
166
|
-
icon: v.icon,
|
|
167
|
-
shortcut: v.shortcut,
|
|
168
|
-
action: () => { onNavigateView(v.view) },
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Resource kinds (deduplicate by name+group — backend may return multiple API versions)
|
|
173
|
-
const resources = apiResources || CORE_RESOURCES
|
|
174
|
-
const seenKinds = new Set<string>()
|
|
175
|
-
for (const r of resources) {
|
|
176
|
-
if (!r.verbs?.includes('list')) continue
|
|
177
|
-
const kindKey = `${r.name}/${r.group}`
|
|
178
|
-
if (seenKinds.has(kindKey)) continue
|
|
179
|
-
seenKinds.add(kindKey)
|
|
180
|
-
result.push({
|
|
181
|
-
id: `kind-${r.name}-${r.group}`,
|
|
182
|
-
label: r.kind,
|
|
183
|
-
sublabel: r.group || 'core',
|
|
184
|
-
category: 'Resource Kinds',
|
|
185
|
-
icon: getResourceIcon(r.kind),
|
|
186
|
-
action: () => { onNavigateKind(r.name, r.group) },
|
|
187
|
-
searchTerms: [r.name, r.kind],
|
|
188
|
-
priorityBonus: groupPriorityBonus(r.group),
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Contexts
|
|
193
|
-
if (contexts) {
|
|
194
|
-
for (const ctx of contexts) {
|
|
195
|
-
result.push({
|
|
196
|
-
id: `context-${ctx.name}`,
|
|
197
|
-
label: ctx.name,
|
|
198
|
-
sublabel: ctx.isCurrent ? 'current' : ctx.cluster,
|
|
199
|
-
category: 'Contexts',
|
|
200
|
-
action: () => { if (!ctx.isCurrent) onSwitchContext(ctx.name) },
|
|
201
|
-
})
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Namespaces
|
|
206
|
-
if (namespacesData) {
|
|
207
|
-
for (const ns of namespacesData) {
|
|
208
|
-
result.push({
|
|
209
|
-
id: `ns-${ns.name}`,
|
|
210
|
-
label: ns.name,
|
|
211
|
-
category: 'Namespaces',
|
|
212
|
-
action: () => { onSetNamespaces([ns.name]) },
|
|
213
|
-
})
|
|
214
|
-
}
|
|
215
|
-
// "All namespaces" option
|
|
216
|
-
result.push({
|
|
217
|
-
id: 'ns-all',
|
|
218
|
-
label: 'All Namespaces',
|
|
219
|
-
category: 'Namespaces',
|
|
220
|
-
action: () => { onSetNamespaces([]) },
|
|
221
|
-
})
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Actions
|
|
225
|
-
result.push({
|
|
226
|
-
id: 'action-theme',
|
|
227
|
-
label: 'Toggle Theme',
|
|
228
|
-
category: 'Actions',
|
|
229
|
-
icon: Sun,
|
|
230
|
-
shortcut: 't',
|
|
231
|
-
action: () => { onToggleTheme() },
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
if (onShowDiagnostics) {
|
|
235
|
-
result.push({
|
|
236
|
-
id: 'action-diagnostics',
|
|
237
|
-
label: 'Diagnostics',
|
|
238
|
-
category: 'Actions',
|
|
239
|
-
icon: Stethoscope,
|
|
240
|
-
shortcut: 'Ctrl+Shift+D',
|
|
241
|
-
action: () => { onShowDiagnostics() },
|
|
242
|
-
searchTerms: ['debug', 'health', 'status', 'snapshot'],
|
|
243
|
-
})
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return result
|
|
247
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
248
|
-
}, [apiResources, contexts, namespacesData, onNavigateView, onNavigateKind, onSwitchContext, onSetNamespaces, onToggleTheme, onShowDiagnostics])
|
|
249
|
-
|
|
250
44
|
// Filter and rank results
|
|
251
45
|
const filteredItems = useMemo(() => {
|
|
252
46
|
if (!query.trim()) {
|
|
@@ -348,9 +142,8 @@ export function CommandPalette({
|
|
|
348
142
|
{/* Search input */}
|
|
349
143
|
<div className="flex items-center gap-3 px-4 py-3 border-b border-theme-border">
|
|
350
144
|
<Search className="w-5 h-5 text-theme-text-secondary shrink-0" />
|
|
351
|
-
<
|
|
145
|
+
<Input
|
|
352
146
|
ref={inputRef}
|
|
353
|
-
type="text"
|
|
354
147
|
value={query}
|
|
355
148
|
onChange={(e) => setQuery(e.target.value)}
|
|
356
149
|
onKeyDown={handleKeyDown}
|