@skyhook-io/k8s-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +63 -0
- package/src/components/dock/BottomDock.tsx +204 -0
- package/src/components/dock/DockContext.tsx +212 -0
- package/src/components/dock/TerminalTab.tsx +333 -0
- package/src/components/dock/index.ts +3 -0
- package/src/components/gitops/GitOpsActions.tsx +227 -0
- package/src/components/gitops/GitOpsStatusBadge.tsx +221 -0
- package/src/components/gitops/ManagedResourcesList.tsx +210 -0
- package/src/components/gitops/SyncCountdown.tsx +146 -0
- package/src/components/gitops/index.ts +4 -0
- package/src/components/logs/JsonLogLine.tsx +90 -0
- package/src/components/logs/LogCore.tsx +553 -0
- package/src/components/logs/LogToolbarSelects.tsx +74 -0
- package/src/components/logs/LogsViewer.tsx +146 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +263 -0
- package/src/components/logs/index.ts +13 -0
- package/src/components/logs/useLogBuffer.ts +138 -0
- package/src/components/logs/useLogSearch.ts +151 -0
- package/src/components/logs/useLogStream.ts +61 -0
- package/src/components/resources/ResourcesView.tsx +5494 -0
- package/src/components/resources/index.ts +17 -0
- package/src/components/resources/renderers/AlertRenderer.tsx +166 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +233 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +89 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +194 -0
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +79 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +68 -0
- package/src/components/resources/renderers/CertificateRenderer.tsx +209 -0
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +123 -0
- package/src/components/resources/renderers/ChallengeRenderer.tsx +111 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +164 -0
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +140 -0
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +153 -0
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +221 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +124 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +86 -0
- package/src/components/resources/renderers/EventRenderer.tsx +175 -0
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +119 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +172 -0
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +242 -0
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +263 -0
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +68 -0
- package/src/components/resources/renderers/GatewayRenderer.tsx +218 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +476 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +154 -0
- package/src/components/resources/renderers/HPARenderer.tsx +164 -0
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +303 -0
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +167 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +49 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +104 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +175 -0
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +176 -0
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +125 -0
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +84 -0
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +112 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +288 -0
- package/src/components/resources/renderers/JobRenderer.tsx +103 -0
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +157 -0
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +188 -0
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +148 -0
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +90 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +136 -0
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +151 -0
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +70 -0
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +309 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +142 -0
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +232 -0
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +97 -0
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +74 -0
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +138 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +220 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +151 -0
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +352 -0
- package/src/components/resources/renderers/LeaseRenderer.tsx +93 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +208 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +282 -0
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +153 -0
- package/src/components/resources/renderers/OrderRenderer.tsx +108 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +98 -0
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +138 -0
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +126 -0
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +63 -0
- package/src/components/resources/renderers/PodRenderer.tsx +677 -0
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +50 -0
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +62 -0
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +71 -0
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +97 -0
- package/src/components/resources/renderers/RoleRenderer.tsx +192 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +227 -0
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +75 -0
- package/src/components/resources/renderers/SbomReportRenderer.tsx +98 -0
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +121 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +321 -0
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +111 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +81 -0
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +65 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +98 -0
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +195 -0
- package/src/components/resources/renderers/StorageClassRenderer.tsx +82 -0
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +257 -0
- package/src/components/resources/renderers/VPARenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +73 -0
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +161 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +134 -0
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +36 -0
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +347 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +156 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +202 -0
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +91 -0
- package/src/components/resources/renderers/WorkloadRenderer.tsx +311 -0
- package/src/components/resources/renderers/argo-cells.tsx +118 -0
- package/src/components/resources/renderers/certmanager-cells.tsx +164 -0
- package/src/components/resources/renderers/cnpg-cells.tsx +161 -0
- package/src/components/resources/renderers/eso-cells.tsx +115 -0
- package/src/components/resources/renderers/flux-cells.tsx +253 -0
- package/src/components/resources/renderers/index.ts +105 -0
- package/src/components/resources/renderers/istio-cells.tsx +210 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +107 -0
- package/src/components/resources/renderers/keda-cells.tsx +94 -0
- package/src/components/resources/renderers/knative-cells.tsx +396 -0
- package/src/components/resources/renderers/kyverno-cells.tsx +85 -0
- package/src/components/resources/renderers/prometheus-cells.tsx +74 -0
- package/src/components/resources/renderers/traefik-cells.tsx +120 -0
- package/src/components/resources/renderers/trivy-cells.tsx +153 -0
- package/src/components/resources/renderers/trivy-shared.tsx +67 -0
- package/src/components/resources/renderers/velero-cells.tsx +164 -0
- package/src/components/resources/resource-utils-argo.ts +155 -0
- package/src/components/resources/resource-utils-certmanager.ts +198 -0
- package/src/components/resources/resource-utils-cnpg.ts +344 -0
- package/src/components/resources/resource-utils-eso.ts +385 -0
- package/src/components/resources/resource-utils-flux.ts +186 -0
- package/src/components/resources/resource-utils-istio.ts +372 -0
- package/src/components/resources/resource-utils-karpenter.ts +150 -0
- package/src/components/resources/resource-utils-keda.ts +203 -0
- package/src/components/resources/resource-utils-knative.ts +218 -0
- package/src/components/resources/resource-utils-kyverno.ts +224 -0
- package/src/components/resources/resource-utils-prometheus.ts +159 -0
- package/src/components/resources/resource-utils-traefik.ts +180 -0
- package/src/components/resources/resource-utils-trivy.ts +146 -0
- package/src/components/resources/resource-utils-velero.ts +293 -0
- package/src/components/resources/resource-utils.ts +1558 -0
- package/src/components/shared/EditableYamlView.tsx +302 -0
- package/src/components/shared/ResourceActionsBar.tsx +924 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +557 -0
- package/src/components/shared/index.ts +3 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/shared.tsx +614 -0
- package/src/components/topology/GroupNode.tsx +198 -0
- package/src/components/topology/K8sResourceNode.tsx +440 -0
- package/src/components/topology/TopologyControls.tsx +62 -0
- package/src/components/topology/TopologyFilterSidebar.tsx +377 -0
- package/src/components/topology/TopologyGraph.tsx +748 -0
- package/src/components/topology/TopologySearch.tsx +304 -0
- package/src/components/topology/index.ts +7 -0
- package/src/components/topology/layout.ts +734 -0
- package/src/components/topology/layout.worker.ts +272 -0
- package/src/components/topology/topology.css +129 -0
- package/src/components/ui/CodeViewer.tsx +451 -0
- package/src/components/ui/ConfirmDialog.tsx +188 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +57 -0
- package/src/components/ui/HealthRing.tsx +72 -0
- package/src/components/ui/MetricsChart.tsx +259 -0
- package/src/components/ui/ResourceBar.tsx +68 -0
- package/src/components/ui/Toast.tsx +225 -0
- package/src/components/ui/Tooltip.tsx +165 -0
- package/src/components/ui/YamlEditor.tsx +280 -0
- package/src/components/ui/drawer-components.tsx +792 -0
- package/src/components/ui/index.ts +10 -0
- package/src/components/workload/ResourceDetailDrawer.tsx +158 -0
- package/src/components/workload/WorkloadView.tsx +1109 -0
- package/src/components/workload/index.ts +2 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useAnimatedUnmount.ts +30 -0
- package/src/hooks/useKeyboardShortcuts.tsx +336 -0
- package/src/hooks/useRefreshAnimation.ts +58 -0
- package/src/index.ts +35 -0
- package/src/types/core.ts +894 -0
- package/src/types/gitops.ts +458 -0
- package/src/types/index.ts +2 -0
- package/src/utils/animation.ts +40 -0
- package/src/utils/api-resources.ts +185 -0
- package/src/utils/badge-colors.ts +399 -0
- package/src/utils/context-name.ts +72 -0
- package/src/utils/download.ts +19 -0
- package/src/utils/format.ts +233 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/log-format.ts +300 -0
- package/src/utils/navigation.ts +119 -0
- package/src/utils/resource-hierarchy.ts +630 -0
- package/src/utils/resource-icons.ts +206 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Shield } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, KeyValueBadgeList } from '../../ui/drawer-components'
|
|
4
|
+
import {
|
|
5
|
+
getPeerAuthenticationMode,
|
|
6
|
+
getPeerAuthenticationSelector,
|
|
7
|
+
getPeerAuthenticationPortLevelMtls,
|
|
8
|
+
} from '../resource-utils-istio'
|
|
9
|
+
|
|
10
|
+
const modeColors: Record<string, string> = {
|
|
11
|
+
STRICT: 'bg-green-500/20 text-green-400',
|
|
12
|
+
PERMISSIVE: 'bg-yellow-500/20 text-yellow-400',
|
|
13
|
+
DISABLE: 'bg-red-500/20 text-red-400',
|
|
14
|
+
UNSET: 'bg-gray-500/20 text-gray-400',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface IstioPeerAuthenticationRendererProps {
|
|
18
|
+
data: any
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function IstioPeerAuthenticationRenderer({ data }: IstioPeerAuthenticationRendererProps) {
|
|
22
|
+
const mode = getPeerAuthenticationMode(data)
|
|
23
|
+
const selector = getPeerAuthenticationSelector(data)
|
|
24
|
+
const portLevelMtls = getPeerAuthenticationPortLevelMtls(data)
|
|
25
|
+
const hasSelector = Object.keys(selector).length > 0
|
|
26
|
+
const hasPortOverrides = Object.keys(portLevelMtls).length > 0
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
{/* mTLS Configuration */}
|
|
31
|
+
<Section title="Peer Authentication" icon={Shield} defaultExpanded>
|
|
32
|
+
<PropertyList>
|
|
33
|
+
<Property label="mTLS Mode" value={
|
|
34
|
+
<span className={clsx(
|
|
35
|
+
'px-2 py-0.5 rounded text-xs font-medium',
|
|
36
|
+
modeColors[mode] || modeColors.UNSET
|
|
37
|
+
)}>
|
|
38
|
+
{mode}
|
|
39
|
+
</span>
|
|
40
|
+
} />
|
|
41
|
+
<Property label="Scope" value={
|
|
42
|
+
hasSelector ? 'Workload-scoped' : 'Namespace-wide'
|
|
43
|
+
} />
|
|
44
|
+
</PropertyList>
|
|
45
|
+
|
|
46
|
+
{/* Mode description */}
|
|
47
|
+
<div className="mt-2 text-xs text-theme-text-tertiary">
|
|
48
|
+
{mode === 'STRICT' && 'All connections must use mTLS. Plaintext connections will be rejected.'}
|
|
49
|
+
{mode === 'PERMISSIVE' && 'Accepts both plaintext and mTLS connections. Useful during migration.'}
|
|
50
|
+
{mode === 'DISABLE' && 'mTLS is disabled. All connections will be plaintext.'}
|
|
51
|
+
{mode === 'UNSET' && 'Inherits mTLS mode from parent (mesh or namespace).'}
|
|
52
|
+
</div>
|
|
53
|
+
</Section>
|
|
54
|
+
|
|
55
|
+
{/* Selector */}
|
|
56
|
+
{hasSelector && (
|
|
57
|
+
<Section title="Workload Selector" defaultExpanded>
|
|
58
|
+
<KeyValueBadgeList items={selector} />
|
|
59
|
+
</Section>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
{/* Port-level mTLS overrides */}
|
|
63
|
+
{hasPortOverrides && (
|
|
64
|
+
<Section title={`Port-Level mTLS (${Object.keys(portLevelMtls).length})`} defaultExpanded>
|
|
65
|
+
<div className="space-y-1">
|
|
66
|
+
{Object.entries(portLevelMtls).map(([port, config]) => (
|
|
67
|
+
<div key={port} className="flex items-center gap-2 text-sm">
|
|
68
|
+
<span className="text-theme-text-secondary font-mono">Port {port}</span>
|
|
69
|
+
<span className={clsx(
|
|
70
|
+
'px-1.5 py-0.5 rounded text-xs font-medium',
|
|
71
|
+
modeColors[config.mode] || modeColors.UNSET
|
|
72
|
+
)}>
|
|
73
|
+
{config.mode}
|
|
74
|
+
</span>
|
|
75
|
+
</div>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
</Section>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
<ConditionsSection conditions={data.status?.conditions || []} />
|
|
82
|
+
</>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Globe } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
import {
|
|
5
|
+
getServiceEntryStatus,
|
|
6
|
+
getServiceEntryHostsList,
|
|
7
|
+
getServiceEntryLocation,
|
|
8
|
+
getServiceEntryPorts,
|
|
9
|
+
getServiceEntryResolution,
|
|
10
|
+
getServiceEntryEndpoints,
|
|
11
|
+
} from '../resource-utils-istio'
|
|
12
|
+
|
|
13
|
+
interface IstioServiceEntryRendererProps {
|
|
14
|
+
data: any
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function IstioServiceEntryRenderer({ data }: IstioServiceEntryRendererProps) {
|
|
18
|
+
const status = getServiceEntryStatus(data)
|
|
19
|
+
const hosts = getServiceEntryHostsList(data)
|
|
20
|
+
const location = getServiceEntryLocation(data)
|
|
21
|
+
const ports = getServiceEntryPorts(data)
|
|
22
|
+
const resolution = getServiceEntryResolution(data)
|
|
23
|
+
const endpoints = getServiceEntryEndpoints(data)
|
|
24
|
+
|
|
25
|
+
const hasNoHosts = hosts.length === 0
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
{hasNoHosts && (
|
|
30
|
+
<AlertBanner
|
|
31
|
+
variant="error"
|
|
32
|
+
title="No Hosts Configured"
|
|
33
|
+
message="This ServiceEntry has no hosts defined."
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
|
|
37
|
+
{/* Service Entry info */}
|
|
38
|
+
<Section title="Service Entry" icon={Globe} defaultExpanded>
|
|
39
|
+
<PropertyList>
|
|
40
|
+
<Property label="Status" value={
|
|
41
|
+
<span className={clsx('px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
42
|
+
{status.text}
|
|
43
|
+
</span>
|
|
44
|
+
} />
|
|
45
|
+
<Property label="Hosts" value={hosts.join(', ') || '-'} />
|
|
46
|
+
<Property label="Location" value={
|
|
47
|
+
<span className={clsx(
|
|
48
|
+
'px-2 py-0.5 rounded text-xs font-medium',
|
|
49
|
+
location === 'MESH_EXTERNAL'
|
|
50
|
+
? 'bg-orange-500/20 text-orange-400'
|
|
51
|
+
: 'bg-blue-500/20 text-blue-400'
|
|
52
|
+
)}>
|
|
53
|
+
{location}
|
|
54
|
+
</span>
|
|
55
|
+
} />
|
|
56
|
+
<Property label="Resolution" value={resolution} />
|
|
57
|
+
</PropertyList>
|
|
58
|
+
</Section>
|
|
59
|
+
|
|
60
|
+
{/* Ports */}
|
|
61
|
+
{ports.length > 0 && (
|
|
62
|
+
<Section title={`Ports (${ports.length})`} defaultExpanded>
|
|
63
|
+
<div className="space-y-1">
|
|
64
|
+
{ports.map((port, i) => (
|
|
65
|
+
<div key={i} className="flex items-center gap-2 text-sm">
|
|
66
|
+
<span className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">
|
|
67
|
+
{port.number}/{port.protocol || 'TCP'}
|
|
68
|
+
</span>
|
|
69
|
+
{port.name && (
|
|
70
|
+
<span className="text-theme-text-tertiary">{port.name}</span>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
))}
|
|
74
|
+
</div>
|
|
75
|
+
</Section>
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
{/* Endpoints */}
|
|
79
|
+
{endpoints.length > 0 && (
|
|
80
|
+
<Section title={`Endpoints (${endpoints.length})`} defaultExpanded>
|
|
81
|
+
<div className="space-y-2">
|
|
82
|
+
{endpoints.map((endpoint, i) => (
|
|
83
|
+
<div key={i} className="bg-theme-elevated/30 rounded p-2">
|
|
84
|
+
<div className="text-sm text-theme-text-primary">{endpoint.address}</div>
|
|
85
|
+
{endpoint.ports && Object.keys(endpoint.ports).length > 0 && (
|
|
86
|
+
<div className="flex flex-wrap gap-1 mt-1">
|
|
87
|
+
{Object.entries(endpoint.ports).map(([name, port]) => (
|
|
88
|
+
<span key={name} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">
|
|
89
|
+
{name}: {port}
|
|
90
|
+
</span>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
{endpoint.labels && Object.keys(endpoint.labels).length > 0 && (
|
|
95
|
+
<div className="flex flex-wrap gap-1 mt-1">
|
|
96
|
+
{Object.entries(endpoint.labels).map(([k, v]) => (
|
|
97
|
+
<span key={k} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">
|
|
98
|
+
{k}={v}
|
|
99
|
+
</span>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
</Section>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
<ConditionsSection conditions={data.status?.conditions || []} />
|
|
110
|
+
</>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { Network, Route } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
4
|
+
import {
|
|
5
|
+
getVirtualServiceStatus,
|
|
6
|
+
getVirtualServiceHostsList,
|
|
7
|
+
getVirtualServiceGatewaysList,
|
|
8
|
+
getVirtualServiceHttpRoutes,
|
|
9
|
+
getVirtualServiceTcpRoutes,
|
|
10
|
+
getVirtualServiceTlsRoutes,
|
|
11
|
+
getVirtualServiceRouteCount,
|
|
12
|
+
} from '../resource-utils-istio'
|
|
13
|
+
|
|
14
|
+
interface IstioVirtualServiceRendererProps {
|
|
15
|
+
data: any
|
|
16
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function IstioVirtualServiceRenderer({ data, onNavigate }: IstioVirtualServiceRendererProps) {
|
|
20
|
+
const status = getVirtualServiceStatus(data)
|
|
21
|
+
const hosts = getVirtualServiceHostsList(data)
|
|
22
|
+
const gateways = getVirtualServiceGatewaysList(data)
|
|
23
|
+
const httpRoutes = getVirtualServiceHttpRoutes(data)
|
|
24
|
+
const tcpRoutes = getVirtualServiceTcpRoutes(data)
|
|
25
|
+
const tlsRoutes = getVirtualServiceTlsRoutes(data)
|
|
26
|
+
const routeCount = getVirtualServiceRouteCount(data)
|
|
27
|
+
const ns = data.metadata?.namespace || ''
|
|
28
|
+
|
|
29
|
+
const hasNoRoutes = routeCount === 0
|
|
30
|
+
const hasFaultInjection = httpRoutes.some((r: any) => r.fault)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
{/* Problem alerts */}
|
|
35
|
+
{hasNoRoutes && (
|
|
36
|
+
<AlertBanner
|
|
37
|
+
variant="error"
|
|
38
|
+
title="No Routes Configured"
|
|
39
|
+
message="This VirtualService has no HTTP, TCP, or TLS routes defined."
|
|
40
|
+
/>
|
|
41
|
+
)}
|
|
42
|
+
{hasFaultInjection && (
|
|
43
|
+
<AlertBanner
|
|
44
|
+
variant="warning"
|
|
45
|
+
title="Fault Injection Active"
|
|
46
|
+
message="One or more routes have fault injection configured (delay or abort)."
|
|
47
|
+
/>
|
|
48
|
+
)}
|
|
49
|
+
|
|
50
|
+
{/* Hosts & Gateways */}
|
|
51
|
+
<Section title="Routing" icon={Network} defaultExpanded>
|
|
52
|
+
<PropertyList>
|
|
53
|
+
<Property label="Status" value={
|
|
54
|
+
<span className={clsx('px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
55
|
+
{status.text}
|
|
56
|
+
</span>
|
|
57
|
+
} />
|
|
58
|
+
<Property label="Hosts" value={hosts.length > 0 ? hosts.join(', ') : '-'} />
|
|
59
|
+
<Property label="Gateways" value={
|
|
60
|
+
gateways.length > 0 ? (
|
|
61
|
+
<div className="flex flex-wrap gap-1">
|
|
62
|
+
{gateways.map((gw, i) => {
|
|
63
|
+
// Gateway reference can be "namespace/name" or just "name" or "mesh"
|
|
64
|
+
if (gw === 'mesh') {
|
|
65
|
+
return <span key={i} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">mesh</span>
|
|
66
|
+
}
|
|
67
|
+
const parts = gw.split('/')
|
|
68
|
+
const gwNs = parts.length === 2 ? parts[0] : ns
|
|
69
|
+
const gwName = parts.length === 2 ? parts[1] : parts[0]
|
|
70
|
+
return (
|
|
71
|
+
<ResourceLink
|
|
72
|
+
key={i}
|
|
73
|
+
name={gwName}
|
|
74
|
+
kind="gateways"
|
|
75
|
+
namespace={gwNs}
|
|
76
|
+
onNavigate={onNavigate}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
})}
|
|
80
|
+
</div>
|
|
81
|
+
) : '-'
|
|
82
|
+
} />
|
|
83
|
+
</PropertyList>
|
|
84
|
+
</Section>
|
|
85
|
+
|
|
86
|
+
{/* HTTP Routes */}
|
|
87
|
+
{httpRoutes.length > 0 && (
|
|
88
|
+
<Section title={`HTTP Routes (${httpRoutes.length})`} icon={Route} defaultExpanded>
|
|
89
|
+
<div className="space-y-3">
|
|
90
|
+
{httpRoutes.map((route: any, i: number) => (
|
|
91
|
+
<div key={i} className="bg-theme-elevated/30 rounded p-3">
|
|
92
|
+
{/* Route header */}
|
|
93
|
+
<div className="flex items-center gap-2 mb-2">
|
|
94
|
+
<span className="text-sm font-medium text-theme-text-primary">
|
|
95
|
+
{route.name || `Route ${i + 1}`}
|
|
96
|
+
</span>
|
|
97
|
+
{route.timeout && (
|
|
98
|
+
<span className="px-1.5 py-0.5 bg-theme-hover rounded text-[10px] text-theme-text-secondary">
|
|
99
|
+
timeout: {route.timeout}
|
|
100
|
+
</span>
|
|
101
|
+
)}
|
|
102
|
+
{route.fault && (
|
|
103
|
+
<span className="px-1.5 py-0.5 bg-yellow-500/20 text-yellow-400 rounded text-[10px] font-medium">
|
|
104
|
+
fault injection
|
|
105
|
+
</span>
|
|
106
|
+
)}
|
|
107
|
+
{route.mirror && (
|
|
108
|
+
<span className="px-1.5 py-0.5 bg-purple-500/20 text-purple-400 rounded text-[10px] font-medium">
|
|
109
|
+
mirroring
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
{/* Match conditions */}
|
|
115
|
+
{route.match && route.match.length > 0 && (
|
|
116
|
+
<div className="mb-2">
|
|
117
|
+
<div className="text-[10px] font-medium text-theme-text-tertiary uppercase tracking-wider mb-1">Match</div>
|
|
118
|
+
<div className="space-y-1">
|
|
119
|
+
{route.match.map((m: any, mi: number) => (
|
|
120
|
+
<div key={mi} className="flex flex-wrap gap-1 text-xs">
|
|
121
|
+
{m.uri && (
|
|
122
|
+
<span className="px-1.5 py-0.5 bg-blue-500/10 text-blue-400 rounded">
|
|
123
|
+
URI {Object.keys(m.uri)[0]}: {Object.values(m.uri)[0] as string}
|
|
124
|
+
</span>
|
|
125
|
+
)}
|
|
126
|
+
{m.method && (
|
|
127
|
+
<span className="px-1.5 py-0.5 bg-blue-500/10 text-blue-400 rounded">
|
|
128
|
+
Method: {m.method.exact || Object.values(m.method)[0]}
|
|
129
|
+
</span>
|
|
130
|
+
)}
|
|
131
|
+
{m.headers && Object.entries(m.headers).map(([hk, hv]: [string, any]) => (
|
|
132
|
+
<span key={hk} className="px-1.5 py-0.5 bg-blue-500/10 text-blue-400 rounded">
|
|
133
|
+
Header {hk}: {typeof hv === 'object' ? Object.values(hv)[0] as string : String(hv)}
|
|
134
|
+
</span>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{/* Destinations with weights */}
|
|
143
|
+
{route.route && route.route.length > 0 && (
|
|
144
|
+
<div className="mb-2">
|
|
145
|
+
<div className="text-[10px] font-medium text-theme-text-tertiary uppercase tracking-wider mb-1">Destinations</div>
|
|
146
|
+
<div className="space-y-1.5">
|
|
147
|
+
{route.route.map((dest: any, di: number) => {
|
|
148
|
+
const weight = dest.weight ?? (route.route.length === 1 ? 100 : undefined)
|
|
149
|
+
const destHost = dest.destination?.host || ''
|
|
150
|
+
const destPort = dest.destination?.port?.number
|
|
151
|
+
const destSubset = dest.destination?.subset
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div key={di} className="flex items-center gap-2 text-xs">
|
|
155
|
+
{weight !== undefined && (
|
|
156
|
+
<div className="flex items-center gap-1 w-16 shrink-0">
|
|
157
|
+
<div className="flex-1 h-1.5 bg-theme-hover rounded-full overflow-hidden">
|
|
158
|
+
<div
|
|
159
|
+
className="h-full bg-blue-500 rounded-full"
|
|
160
|
+
style={{ width: `${weight}%` }}
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
<span className="text-theme-text-secondary font-medium">{weight}%</span>
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
<ResourceLink
|
|
167
|
+
name={destHost.split('.')[0]}
|
|
168
|
+
kind="services"
|
|
169
|
+
namespace={destHost.includes('.') ? destHost.split('.')[1] : ns}
|
|
170
|
+
label={destHost}
|
|
171
|
+
onNavigate={onNavigate}
|
|
172
|
+
/>
|
|
173
|
+
{destPort && (
|
|
174
|
+
<span className="text-theme-text-tertiary">:{destPort}</span>
|
|
175
|
+
)}
|
|
176
|
+
{destSubset && (
|
|
177
|
+
<span className="px-1.5 py-0.5 bg-theme-hover rounded text-theme-text-secondary">
|
|
178
|
+
subset: {destSubset}
|
|
179
|
+
</span>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
)
|
|
183
|
+
})}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{/* Retries */}
|
|
189
|
+
{route.retries && (
|
|
190
|
+
<div className="flex flex-wrap gap-1 text-xs mb-1">
|
|
191
|
+
<span className="text-theme-text-tertiary">Retries:</span>
|
|
192
|
+
<span className="text-theme-text-secondary">
|
|
193
|
+
{route.retries.attempts} attempts
|
|
194
|
+
{route.retries.perTryTimeout && `, ${route.retries.perTryTimeout} per try`}
|
|
195
|
+
{route.retries.retryOn && ` on ${route.retries.retryOn}`}
|
|
196
|
+
</span>
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
|
|
200
|
+
{/* Fault injection details */}
|
|
201
|
+
{route.fault && (
|
|
202
|
+
<div className="flex flex-wrap gap-2 text-xs">
|
|
203
|
+
{route.fault.delay && (
|
|
204
|
+
<span className="px-1.5 py-0.5 bg-yellow-500/10 text-yellow-400 rounded">
|
|
205
|
+
Delay: {route.fault.delay.fixedDelay}
|
|
206
|
+
{route.fault.delay.percentage?.value && ` (${route.fault.delay.percentage.value}%)`}
|
|
207
|
+
</span>
|
|
208
|
+
)}
|
|
209
|
+
{route.fault.abort && (
|
|
210
|
+
<span className="px-1.5 py-0.5 bg-red-500/10 text-red-400 rounded">
|
|
211
|
+
Abort: HTTP {route.fault.abort.httpStatus}
|
|
212
|
+
{route.fault.abort.percentage?.value && ` (${route.fault.abort.percentage.value}%)`}
|
|
213
|
+
</span>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
|
|
218
|
+
{/* Mirror */}
|
|
219
|
+
{route.mirror && (
|
|
220
|
+
<div className="flex items-center gap-1 text-xs mt-1">
|
|
221
|
+
<span className="text-theme-text-tertiary">Mirror:</span>
|
|
222
|
+
<span className="text-theme-text-secondary">
|
|
223
|
+
{route.mirror.host}
|
|
224
|
+
{route.mirror.port?.number && `:${route.mirror.port.number}`}
|
|
225
|
+
</span>
|
|
226
|
+
{route.mirrorPercentage?.value !== undefined && (
|
|
227
|
+
<span className="text-theme-text-tertiary">({route.mirrorPercentage.value}%)</span>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
))}
|
|
233
|
+
</div>
|
|
234
|
+
</Section>
|
|
235
|
+
)}
|
|
236
|
+
|
|
237
|
+
{/* TCP Routes */}
|
|
238
|
+
{tcpRoutes.length > 0 && (
|
|
239
|
+
<Section title={`TCP Routes (${tcpRoutes.length})`} defaultExpanded>
|
|
240
|
+
<div className="space-y-2">
|
|
241
|
+
{tcpRoutes.map((route: any, i: number) => (
|
|
242
|
+
<div key={i} className="bg-theme-elevated/30 rounded p-2">
|
|
243
|
+
{route.match && (
|
|
244
|
+
<div className="text-xs text-theme-text-tertiary mb-1">
|
|
245
|
+
Match: {JSON.stringify(route.match)}
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
{route.route && route.route.map((dest: any, di: number) => (
|
|
249
|
+
<div key={di} className="text-xs text-theme-text-secondary">
|
|
250
|
+
{dest.destination?.host}
|
|
251
|
+
{dest.destination?.port?.number && `:${dest.destination.port.number}`}
|
|
252
|
+
{dest.weight !== undefined && ` (${dest.weight}%)`}
|
|
253
|
+
</div>
|
|
254
|
+
))}
|
|
255
|
+
</div>
|
|
256
|
+
))}
|
|
257
|
+
</div>
|
|
258
|
+
</Section>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
{/* TLS Routes */}
|
|
262
|
+
{tlsRoutes.length > 0 && (
|
|
263
|
+
<Section title={`TLS Routes (${tlsRoutes.length})`} defaultExpanded>
|
|
264
|
+
<div className="space-y-2">
|
|
265
|
+
{tlsRoutes.map((route: any, i: number) => (
|
|
266
|
+
<div key={i} className="bg-theme-elevated/30 rounded p-2">
|
|
267
|
+
{route.match && (
|
|
268
|
+
<div className="text-xs text-theme-text-tertiary mb-1">
|
|
269
|
+
SNI: {route.match.map((m: any) => m.sniHosts?.join(', ')).join('; ')}
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
{route.route && route.route.map((dest: any, di: number) => (
|
|
273
|
+
<div key={di} className="text-xs text-theme-text-secondary">
|
|
274
|
+
{dest.destination?.host}
|
|
275
|
+
{dest.destination?.port?.number && `:${dest.destination.port.number}`}
|
|
276
|
+
{dest.weight !== undefined && ` (${dest.weight}%)`}
|
|
277
|
+
</div>
|
|
278
|
+
))}
|
|
279
|
+
</div>
|
|
280
|
+
))}
|
|
281
|
+
</div>
|
|
282
|
+
</Section>
|
|
283
|
+
)}
|
|
284
|
+
|
|
285
|
+
<ConditionsSection conditions={data.status?.conditions || []} />
|
|
286
|
+
</>
|
|
287
|
+
)
|
|
288
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Clock } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
3
|
+
import { formatDuration } from '../resource-utils'
|
|
4
|
+
|
|
5
|
+
interface JobRendererProps {
|
|
6
|
+
data: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Extract problems from Job status and conditions
|
|
10
|
+
function getJobProblems(data: any): string[] {
|
|
11
|
+
const problems: string[] = []
|
|
12
|
+
const status = data.status || {}
|
|
13
|
+
const spec = data.spec || {}
|
|
14
|
+
const conditions = status.conditions || []
|
|
15
|
+
|
|
16
|
+
// Check for Failed condition
|
|
17
|
+
const failedCondition = conditions.find((c: any) => c.type === 'Failed' && c.status === 'True')
|
|
18
|
+
if (failedCondition) {
|
|
19
|
+
if (failedCondition.reason === 'BackoffLimitExceeded') {
|
|
20
|
+
problems.push(`Job failed: reached backoff limit (${spec.backoffLimit ?? 6} retries)`)
|
|
21
|
+
} else if (failedCondition.reason === 'DeadlineExceeded') {
|
|
22
|
+
problems.push(`Job failed: exceeded active deadline (${spec.activeDeadlineSeconds}s)`)
|
|
23
|
+
} else {
|
|
24
|
+
problems.push(`Job failed: ${failedCondition.reason}${failedCondition.message ? ' - ' + failedCondition.message : ''}`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check for pod failures without terminal condition yet
|
|
29
|
+
if (!failedCondition && status.failed > 0) {
|
|
30
|
+
const remaining = (spec.backoffLimit ?? 6) - status.failed
|
|
31
|
+
if (remaining > 0) {
|
|
32
|
+
problems.push(`${status.failed} pod(s) failed — ${remaining} retries remaining`)
|
|
33
|
+
} else {
|
|
34
|
+
problems.push(`${status.failed} pod(s) failed — no retries remaining`)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for suspended
|
|
39
|
+
if (spec.suspend) {
|
|
40
|
+
problems.push('Job is suspended — pods will not be created')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return problems
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function JobRenderer({ data }: JobRendererProps) {
|
|
47
|
+
const status = data.status || {}
|
|
48
|
+
const spec = data.spec || {}
|
|
49
|
+
const conditions = status.conditions || []
|
|
50
|
+
|
|
51
|
+
const startTime = status.startTime ? new Date(status.startTime) : null
|
|
52
|
+
const completionTime = status.completionTime ? new Date(status.completionTime) : null
|
|
53
|
+
const duration = startTime && completionTime
|
|
54
|
+
? formatDuration(completionTime.getTime() - startTime.getTime(), true)
|
|
55
|
+
: startTime
|
|
56
|
+
? formatDuration(Date.now() - startTime.getTime(), true) + ' (running)'
|
|
57
|
+
: null
|
|
58
|
+
|
|
59
|
+
// Check for problems
|
|
60
|
+
const problems = getJobProblems(data)
|
|
61
|
+
const hasProblems = problems.length > 0
|
|
62
|
+
|
|
63
|
+
// Check if job completed successfully
|
|
64
|
+
const isComplete = conditions.some((c: any) => c.type === 'Complete' && c.status === 'True')
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
{/* Problems alert */}
|
|
69
|
+
{hasProblems && (
|
|
70
|
+
<AlertBanner variant="error" title="Job Issues" items={problems} />
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
{/* Success banner */}
|
|
74
|
+
{isComplete && !hasProblems && (
|
|
75
|
+
<AlertBanner variant="success" title="Job Completed Successfully" />
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
<Section title="Status" icon={Clock}>
|
|
79
|
+
<PropertyList>
|
|
80
|
+
<Property label="Succeeded" value={status.succeeded || 0} />
|
|
81
|
+
<Property label="Failed" value={status.failed || 0} />
|
|
82
|
+
<Property label="Active" value={status.active || 0} />
|
|
83
|
+
<Property label="Completions" value={`${status.succeeded || 0}/${spec.completions || 1}`} />
|
|
84
|
+
{duration && <Property label="Duration" value={duration} />}
|
|
85
|
+
</PropertyList>
|
|
86
|
+
</Section>
|
|
87
|
+
|
|
88
|
+
<Section title="Configuration">
|
|
89
|
+
<PropertyList>
|
|
90
|
+
<Property label="Parallelism" value={spec.parallelism || 1} />
|
|
91
|
+
<Property label="Completions" value={spec.completions || 1} />
|
|
92
|
+
<Property label="Backoff Limit" value={spec.backoffLimit ?? 6} />
|
|
93
|
+
{spec.activeDeadlineSeconds && <Property label="Deadline" value={`${spec.activeDeadlineSeconds}s`} />}
|
|
94
|
+
{spec.ttlSecondsAfterFinished !== undefined && (
|
|
95
|
+
<Property label="TTL After Finish" value={`${spec.ttlSecondsAfterFinished}s`} />
|
|
96
|
+
)}
|
|
97
|
+
</PropertyList>
|
|
98
|
+
</Section>
|
|
99
|
+
|
|
100
|
+
<ConditionsSection conditions={status.conditions} />
|
|
101
|
+
</>
|
|
102
|
+
)
|
|
103
|
+
}
|