@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,193 @@
|
|
|
1
|
+
import { Cpu } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
4
|
+
|
|
5
|
+
interface VPARendererProps {
|
|
6
|
+
data: any
|
|
7
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatResource(value: string | undefined): string {
|
|
11
|
+
if (!value) return '-'
|
|
12
|
+
return value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function VPARenderer({ data, onNavigate }: VPARendererProps) {
|
|
16
|
+
const spec = data.spec || {}
|
|
17
|
+
const status = data.status || {}
|
|
18
|
+
const targetRef = spec.targetRef || {}
|
|
19
|
+
const updatePolicy = spec.updatePolicy || {}
|
|
20
|
+
const resourcePolicy = spec.resourcePolicy || {}
|
|
21
|
+
const containerPolicies = resourcePolicy.containerPolicies || []
|
|
22
|
+
const recommendations = status.recommendation?.containerRecommendations || []
|
|
23
|
+
const conditions = status.conditions || []
|
|
24
|
+
|
|
25
|
+
const updateMode = updatePolicy.updateMode || 'Auto'
|
|
26
|
+
const isOff = updateMode === 'Off'
|
|
27
|
+
|
|
28
|
+
// Problem detection
|
|
29
|
+
const hasRecommendation = conditions.some((c: any) => c.type === 'RecommendationProvided' && c.status === 'True')
|
|
30
|
+
const noRecommendation = conditions.some((c: any) => c.type === 'RecommendationProvided' && c.status === 'False')
|
|
31
|
+
const configUnsupported = conditions.some((c: any) => c.type === 'ConfigUnsupported' && c.status === 'True')
|
|
32
|
+
const lowConfidence = conditions.some((c: any) => c.type === 'LowConfidence' && c.status === 'True')
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
{/* Problem alerts */}
|
|
37
|
+
{configUnsupported && (
|
|
38
|
+
<AlertBanner
|
|
39
|
+
variant="error"
|
|
40
|
+
title="Configuration Unsupported"
|
|
41
|
+
message={conditions.find((c: any) => c.type === 'ConfigUnsupported')?.message}
|
|
42
|
+
/>
|
|
43
|
+
)}
|
|
44
|
+
|
|
45
|
+
{noRecommendation && !configUnsupported && (
|
|
46
|
+
<AlertBanner
|
|
47
|
+
variant="warning"
|
|
48
|
+
title="No Recommendations"
|
|
49
|
+
message={conditions.find((c: any) => c.type === 'RecommendationProvided')?.message || 'VPA has not produced recommendations yet — insufficient metrics data'}
|
|
50
|
+
/>
|
|
51
|
+
)}
|
|
52
|
+
|
|
53
|
+
{lowConfidence && hasRecommendation && (
|
|
54
|
+
<AlertBanner
|
|
55
|
+
variant="info"
|
|
56
|
+
title="Low Confidence"
|
|
57
|
+
message="Recommendations are based on limited data — consider waiting for more metrics"
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{/* Target & Mode */}
|
|
62
|
+
<Section title="Configuration" icon={Cpu}>
|
|
63
|
+
<PropertyList>
|
|
64
|
+
<Property label="Target" value={
|
|
65
|
+
targetRef.name ? (
|
|
66
|
+
<ResourceLink
|
|
67
|
+
name={targetRef.name}
|
|
68
|
+
kind={(targetRef.kind || 'Deployment').toLowerCase() + 's'}
|
|
69
|
+
namespace={data.metadata?.namespace || ''}
|
|
70
|
+
label={`${targetRef.kind}/${targetRef.name}`}
|
|
71
|
+
onNavigate={onNavigate}
|
|
72
|
+
/>
|
|
73
|
+
) : undefined
|
|
74
|
+
} />
|
|
75
|
+
<Property label="Update Mode" value={
|
|
76
|
+
<span className={clsx(
|
|
77
|
+
updateMode === 'Auto' && 'text-green-400',
|
|
78
|
+
updateMode === 'Recreate' && 'text-yellow-400',
|
|
79
|
+
updateMode === 'Initial' && 'text-blue-400',
|
|
80
|
+
updateMode === 'Off' && 'text-theme-text-tertiary',
|
|
81
|
+
)}>
|
|
82
|
+
{updateMode}
|
|
83
|
+
</span>
|
|
84
|
+
} />
|
|
85
|
+
{isOff && (
|
|
86
|
+
<Property label="" value={
|
|
87
|
+
<span className="text-xs text-theme-text-tertiary">Recommendation-only mode — no automatic updates</span>
|
|
88
|
+
} />
|
|
89
|
+
)}
|
|
90
|
+
</PropertyList>
|
|
91
|
+
</Section>
|
|
92
|
+
|
|
93
|
+
{/* Recommendations */}
|
|
94
|
+
{recommendations.length > 0 && (
|
|
95
|
+
<Section title="Recommendations" defaultExpanded>
|
|
96
|
+
<div className="space-y-3">
|
|
97
|
+
{recommendations.map((rec: any) => (
|
|
98
|
+
<div key={rec.containerName} className="bg-theme-elevated/30 rounded-lg p-3">
|
|
99
|
+
<div className="text-sm font-medium text-theme-text-primary mb-2">{rec.containerName}</div>
|
|
100
|
+
<div className="overflow-x-auto">
|
|
101
|
+
<table className="w-full text-xs">
|
|
102
|
+
<thead>
|
|
103
|
+
<tr className="text-theme-text-tertiary">
|
|
104
|
+
<th className="text-left py-1 pr-3 font-medium w-24"></th>
|
|
105
|
+
<th className="text-right py-1 px-2 font-medium">CPU</th>
|
|
106
|
+
<th className="text-right py-1 px-2 font-medium">Memory</th>
|
|
107
|
+
</tr>
|
|
108
|
+
</thead>
|
|
109
|
+
<tbody>
|
|
110
|
+
<tr>
|
|
111
|
+
<td className="py-1 pr-3 text-theme-text-secondary">Target</td>
|
|
112
|
+
<td className="text-right py-1 px-2 text-green-400 font-medium">{formatResource(rec.target?.cpu)}</td>
|
|
113
|
+
<td className="text-right py-1 px-2 text-green-400 font-medium">{formatResource(rec.target?.memory)}</td>
|
|
114
|
+
</tr>
|
|
115
|
+
<tr>
|
|
116
|
+
<td className="py-1 pr-3 text-theme-text-secondary">Lower Bound</td>
|
|
117
|
+
<td className="text-right py-1 px-2 text-theme-text-secondary">{formatResource(rec.lowerBound?.cpu)}</td>
|
|
118
|
+
<td className="text-right py-1 px-2 text-theme-text-secondary">{formatResource(rec.lowerBound?.memory)}</td>
|
|
119
|
+
</tr>
|
|
120
|
+
<tr>
|
|
121
|
+
<td className="py-1 pr-3 text-theme-text-secondary">Upper Bound</td>
|
|
122
|
+
<td className="text-right py-1 px-2 text-theme-text-secondary">{formatResource(rec.upperBound?.cpu)}</td>
|
|
123
|
+
<td className="text-right py-1 px-2 text-theme-text-secondary">{formatResource(rec.upperBound?.memory)}</td>
|
|
124
|
+
</tr>
|
|
125
|
+
{rec.uncappedTarget && (
|
|
126
|
+
<tr>
|
|
127
|
+
<td className="py-1 pr-3 text-theme-text-tertiary">Uncapped</td>
|
|
128
|
+
<td className="text-right py-1 px-2 text-theme-text-tertiary">{formatResource(rec.uncappedTarget?.cpu)}</td>
|
|
129
|
+
<td className="text-right py-1 px-2 text-theme-text-tertiary">{formatResource(rec.uncappedTarget?.memory)}</td>
|
|
130
|
+
</tr>
|
|
131
|
+
)}
|
|
132
|
+
</tbody>
|
|
133
|
+
</table>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
))}
|
|
137
|
+
</div>
|
|
138
|
+
</Section>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
{/* Resource Policy */}
|
|
142
|
+
{containerPolicies.length > 0 && (
|
|
143
|
+
<Section title="Resource Policy" defaultExpanded>
|
|
144
|
+
<div className="space-y-3">
|
|
145
|
+
{containerPolicies.map((policy: any, i: number) => (
|
|
146
|
+
<div key={i} className="bg-theme-elevated/30 rounded-lg p-3">
|
|
147
|
+
<div className="flex items-center gap-2 mb-2">
|
|
148
|
+
<span className="text-sm font-medium text-theme-text-primary">{policy.containerName || '*'}</span>
|
|
149
|
+
{policy.mode && (
|
|
150
|
+
<span className={clsx(
|
|
151
|
+
'px-1.5 py-0.5 rounded text-[10px] font-medium',
|
|
152
|
+
policy.mode === 'Off' ? 'bg-theme-hover text-theme-text-tertiary' : 'bg-blue-500/20 text-blue-400'
|
|
153
|
+
)}>
|
|
154
|
+
{policy.mode}
|
|
155
|
+
</span>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
<PropertyList>
|
|
159
|
+
{policy.minAllowed && (
|
|
160
|
+
<Property label="Min Allowed" value={
|
|
161
|
+
<span className="text-xs">
|
|
162
|
+
{policy.minAllowed.cpu && `CPU: ${policy.minAllowed.cpu}`}
|
|
163
|
+
{policy.minAllowed.cpu && policy.minAllowed.memory && ' · '}
|
|
164
|
+
{policy.minAllowed.memory && `Mem: ${policy.minAllowed.memory}`}
|
|
165
|
+
</span>
|
|
166
|
+
} />
|
|
167
|
+
)}
|
|
168
|
+
{policy.maxAllowed && (
|
|
169
|
+
<Property label="Max Allowed" value={
|
|
170
|
+
<span className="text-xs">
|
|
171
|
+
{policy.maxAllowed.cpu && `CPU: ${policy.maxAllowed.cpu}`}
|
|
172
|
+
{policy.maxAllowed.cpu && policy.maxAllowed.memory && ' · '}
|
|
173
|
+
{policy.maxAllowed.memory && `Mem: ${policy.maxAllowed.memory}`}
|
|
174
|
+
</span>
|
|
175
|
+
} />
|
|
176
|
+
)}
|
|
177
|
+
{policy.controlledResources && (
|
|
178
|
+
<Property label="Controlled" value={policy.controlledResources.join(', ')} />
|
|
179
|
+
)}
|
|
180
|
+
{policy.controlledValues && (
|
|
181
|
+
<Property label="Controlled Values" value={policy.controlledValues} />
|
|
182
|
+
)}
|
|
183
|
+
</PropertyList>
|
|
184
|
+
</div>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
</Section>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
<ConditionsSection conditions={conditions} />
|
|
191
|
+
</>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { HardDrive, Clock } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
|
|
4
|
+
import {
|
|
5
|
+
getBSLStatus,
|
|
6
|
+
getBSLProvider,
|
|
7
|
+
getBSLBucket,
|
|
8
|
+
getBSLPrefix,
|
|
9
|
+
getBSLRegion,
|
|
10
|
+
getBSLDefault,
|
|
11
|
+
getBSLAccessMode,
|
|
12
|
+
getBSLLastValidation,
|
|
13
|
+
getBSLLastSynced,
|
|
14
|
+
} from '../resource-utils-velero'
|
|
15
|
+
|
|
16
|
+
interface VeleroBSLRendererProps {
|
|
17
|
+
data: any
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function VeleroBSLRenderer({ data }: VeleroBSLRendererProps) {
|
|
21
|
+
const status = data.status || {}
|
|
22
|
+
const conditions = status.conditions || []
|
|
23
|
+
const bslStatus = getBSLStatus(data)
|
|
24
|
+
const config = data.spec?.config || {}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
{/* Status section */}
|
|
29
|
+
<Section title="Status" icon={Clock} defaultExpanded>
|
|
30
|
+
<PropertyList>
|
|
31
|
+
<Property label="Phase" value={
|
|
32
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', bslStatus.color)}>
|
|
33
|
+
{bslStatus.text}
|
|
34
|
+
</span>
|
|
35
|
+
} />
|
|
36
|
+
<Property label="Last Validation" value={getBSLLastValidation(data)} />
|
|
37
|
+
<Property label="Last Synced" value={getBSLLastSynced(data)} />
|
|
38
|
+
{status.lastSyncedRevision && (
|
|
39
|
+
<Property label="Last Synced Revision" value={
|
|
40
|
+
<span className="text-sm font-mono text-theme-text-secondary break-all">{status.lastSyncedRevision}</span>
|
|
41
|
+
} />
|
|
42
|
+
)}
|
|
43
|
+
</PropertyList>
|
|
44
|
+
</Section>
|
|
45
|
+
|
|
46
|
+
{/* Provider section */}
|
|
47
|
+
<Section title="Provider" icon={HardDrive} defaultExpanded>
|
|
48
|
+
<PropertyList>
|
|
49
|
+
<Property label="Provider" value={getBSLProvider(data)} />
|
|
50
|
+
<Property label="Bucket" value={getBSLBucket(data)} />
|
|
51
|
+
<Property label="Prefix" value={getBSLPrefix(data)} />
|
|
52
|
+
<Property label="Region" value={getBSLRegion(data)} />
|
|
53
|
+
<Property label="Access Mode" value={getBSLAccessMode(data)} />
|
|
54
|
+
<Property label="Default" value={getBSLDefault(data) ? 'Yes' : 'No'} />
|
|
55
|
+
</PropertyList>
|
|
56
|
+
{Object.keys(config).length > 0 && (
|
|
57
|
+
<div className="mt-2 pt-2 border-t border-theme-border">
|
|
58
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1">Config</div>
|
|
59
|
+
<div className="flex flex-wrap gap-1">
|
|
60
|
+
{Object.entries(config).map(([k, v]) => (
|
|
61
|
+
<span key={k} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">
|
|
62
|
+
{k}: {String(v)}
|
|
63
|
+
</span>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
</Section>
|
|
69
|
+
|
|
70
|
+
<ConditionsSection conditions={conditions} />
|
|
71
|
+
</>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { Archive, Clock, HardDrive, Filter } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
import {
|
|
5
|
+
getBackupStatus,
|
|
6
|
+
getBackupStorageLocation,
|
|
7
|
+
getBackupIncludedNamespaces,
|
|
8
|
+
getBackupExcludedNamespaces,
|
|
9
|
+
getBackupIncludedResources,
|
|
10
|
+
getBackupExcludedResources,
|
|
11
|
+
getBackupDuration,
|
|
12
|
+
getBackupItemCount,
|
|
13
|
+
getBackupExpiry,
|
|
14
|
+
getBackupErrors,
|
|
15
|
+
getBackupWarnings,
|
|
16
|
+
getBackupValidationErrors,
|
|
17
|
+
getBackupTTL,
|
|
18
|
+
getBackupSnapshotVolumes,
|
|
19
|
+
getBackupDefaultVolumesToFsBackup,
|
|
20
|
+
getBackupVolumeSnapshotLocations,
|
|
21
|
+
} from '../resource-utils-velero'
|
|
22
|
+
import { formatAge } from '../resource-utils'
|
|
23
|
+
|
|
24
|
+
interface VeleroBackupRendererProps {
|
|
25
|
+
data: any
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function VeleroBackupRenderer({ data }: VeleroBackupRendererProps) {
|
|
29
|
+
const status = data.status || {}
|
|
30
|
+
const conditions = status.conditions || []
|
|
31
|
+
|
|
32
|
+
const backupStatus = getBackupStatus(data)
|
|
33
|
+
const errors = getBackupErrors(data)
|
|
34
|
+
const warnings = getBackupWarnings(data)
|
|
35
|
+
const validationErrors = getBackupValidationErrors(data)
|
|
36
|
+
const includedNamespaces = getBackupIncludedNamespaces(data)
|
|
37
|
+
const excludedNamespaces = getBackupExcludedNamespaces(data)
|
|
38
|
+
const includedResources = getBackupIncludedResources(data)
|
|
39
|
+
const excludedResources = getBackupExcludedResources(data)
|
|
40
|
+
const vslLocations = getBackupVolumeSnapshotLocations(data)
|
|
41
|
+
|
|
42
|
+
const isFailed = backupStatus.level === 'unhealthy'
|
|
43
|
+
const isPartiallyFailed = backupStatus.text === 'PartiallyFailed'
|
|
44
|
+
const isInProgress = backupStatus.text === 'InProgress' || backupStatus.text === 'Uploading'
|
|
45
|
+
|
|
46
|
+
// Progress data
|
|
47
|
+
const progress = status.progress
|
|
48
|
+
const itemsBacked = progress?.itemsBackedUp ?? 0
|
|
49
|
+
const totalItems = progress?.totalItems ?? 0
|
|
50
|
+
const progressPercent = totalItems > 0 ? Math.round((itemsBacked / totalItems) * 100) : 0
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
{/* Problem alerts */}
|
|
55
|
+
{(isFailed || isPartiallyFailed) && (
|
|
56
|
+
<AlertBanner
|
|
57
|
+
variant="error"
|
|
58
|
+
title={isFailed ? 'Backup Failed' : 'Backup Partially Failed'}
|
|
59
|
+
message={`${errors} error(s) occurred during backup.`}
|
|
60
|
+
items={validationErrors.length > 0 ? validationErrors : undefined}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
{warnings > 0 && !isFailed && (
|
|
64
|
+
<AlertBanner
|
|
65
|
+
variant="warning"
|
|
66
|
+
title={`${warnings} Warning(s)`}
|
|
67
|
+
message={`Backup completed with ${warnings} warning(s).`}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{/* Status section */}
|
|
72
|
+
<Section title="Status" icon={Archive} defaultExpanded>
|
|
73
|
+
<PropertyList>
|
|
74
|
+
<Property label="Phase" value={
|
|
75
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', backupStatus.color)}>
|
|
76
|
+
{backupStatus.text}
|
|
77
|
+
</span>
|
|
78
|
+
} />
|
|
79
|
+
{status.startTimestamp && (
|
|
80
|
+
<Property label="Started" value={formatAge(status.startTimestamp) + ' ago'} />
|
|
81
|
+
)}
|
|
82
|
+
{status.completionTimestamp && (
|
|
83
|
+
<Property label="Completed" value={formatAge(status.completionTimestamp) + ' ago'} />
|
|
84
|
+
)}
|
|
85
|
+
<Property label="Duration" value={getBackupDuration(data)} />
|
|
86
|
+
<Property label="Expiration" value={getBackupExpiry(data)} />
|
|
87
|
+
{errors > 0 && <Property label="Errors" value={String(errors)} />}
|
|
88
|
+
{warnings > 0 && <Property label="Warnings" value={String(warnings)} />}
|
|
89
|
+
</PropertyList>
|
|
90
|
+
</Section>
|
|
91
|
+
|
|
92
|
+
{/* Progress section (if in progress) */}
|
|
93
|
+
{isInProgress && progress && (
|
|
94
|
+
<Section title="Progress" defaultExpanded>
|
|
95
|
+
<div className="space-y-2">
|
|
96
|
+
<div className="flex items-center justify-between text-sm">
|
|
97
|
+
<span className="text-theme-text-secondary">Items backed up</span>
|
|
98
|
+
<span className="text-theme-text-primary font-medium">{getBackupItemCount(data)}</span>
|
|
99
|
+
</div>
|
|
100
|
+
<div className="w-full bg-theme-elevated rounded-full h-2">
|
|
101
|
+
<div
|
|
102
|
+
className="bg-blue-500 h-2 rounded-full transition-all"
|
|
103
|
+
style={{ width: `${progressPercent}%` }}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
<div className="text-xs text-theme-text-tertiary text-right">{progressPercent}%</div>
|
|
107
|
+
</div>
|
|
108
|
+
</Section>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
{/* Scope section */}
|
|
112
|
+
{(includedNamespaces.length > 0 || excludedNamespaces.length > 0 || includedResources.length > 0 || excludedResources.length > 0 || data.spec?.labelSelector) && (
|
|
113
|
+
<Section title="Scope" icon={Filter} defaultExpanded>
|
|
114
|
+
<PropertyList>
|
|
115
|
+
{includedNamespaces.length > 0 && (
|
|
116
|
+
<Property label="Included Namespaces" value={
|
|
117
|
+
<div className="flex flex-wrap gap-1">
|
|
118
|
+
{includedNamespaces.map((ns: string) => (
|
|
119
|
+
<span key={ns} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">{ns}</span>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
} />
|
|
123
|
+
)}
|
|
124
|
+
{includedNamespaces.length === 0 && (
|
|
125
|
+
<Property label="Included Namespaces" value="* (all)" />
|
|
126
|
+
)}
|
|
127
|
+
{excludedNamespaces.length > 0 && (
|
|
128
|
+
<Property label="Excluded Namespaces" value={
|
|
129
|
+
<div className="flex flex-wrap gap-1">
|
|
130
|
+
{excludedNamespaces.map((ns: string) => (
|
|
131
|
+
<span key={ns} className="px-1.5 py-0.5 bg-red-500/10 rounded text-xs text-red-400">{ns}</span>
|
|
132
|
+
))}
|
|
133
|
+
</div>
|
|
134
|
+
} />
|
|
135
|
+
)}
|
|
136
|
+
{includedResources.length > 0 && (
|
|
137
|
+
<Property label="Included Resources" value={
|
|
138
|
+
<div className="flex flex-wrap gap-1">
|
|
139
|
+
{includedResources.map((r: string) => (
|
|
140
|
+
<span key={r} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">{r}</span>
|
|
141
|
+
))}
|
|
142
|
+
</div>
|
|
143
|
+
} />
|
|
144
|
+
)}
|
|
145
|
+
{excludedResources.length > 0 && (
|
|
146
|
+
<Property label="Excluded Resources" value={
|
|
147
|
+
<div className="flex flex-wrap gap-1">
|
|
148
|
+
{excludedResources.map((r: string) => (
|
|
149
|
+
<span key={r} className="px-1.5 py-0.5 bg-red-500/10 rounded text-xs text-red-400">{r}</span>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
} />
|
|
153
|
+
)}
|
|
154
|
+
{data.spec?.labelSelector && (
|
|
155
|
+
<Property label="Label Selector" value={
|
|
156
|
+
<span className="text-sm font-mono text-theme-text-secondary break-all">
|
|
157
|
+
{JSON.stringify(data.spec.labelSelector)}
|
|
158
|
+
</span>
|
|
159
|
+
} />
|
|
160
|
+
)}
|
|
161
|
+
</PropertyList>
|
|
162
|
+
</Section>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{/* Storage section */}
|
|
166
|
+
<Section title="Storage" icon={HardDrive} defaultExpanded>
|
|
167
|
+
<PropertyList>
|
|
168
|
+
<Property label="Storage Location" value={getBackupStorageLocation(data)} />
|
|
169
|
+
{vslLocations.length > 0 && (
|
|
170
|
+
<Property label="Volume Snapshot Locations" value={
|
|
171
|
+
<div className="flex flex-wrap gap-1">
|
|
172
|
+
{vslLocations.map((loc: string) => (
|
|
173
|
+
<span key={loc} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">{loc}</span>
|
|
174
|
+
))}
|
|
175
|
+
</div>
|
|
176
|
+
} />
|
|
177
|
+
)}
|
|
178
|
+
</PropertyList>
|
|
179
|
+
</Section>
|
|
180
|
+
|
|
181
|
+
{/* Options section */}
|
|
182
|
+
<Section title="Options" icon={Clock}>
|
|
183
|
+
<PropertyList>
|
|
184
|
+
<Property label="TTL" value={getBackupTTL(data)} />
|
|
185
|
+
<Property label="Snapshot Volumes" value={getBackupSnapshotVolumes(data)} />
|
|
186
|
+
<Property label="Default FS Backup" value={getBackupDefaultVolumesToFsBackup(data)} />
|
|
187
|
+
</PropertyList>
|
|
188
|
+
</Section>
|
|
189
|
+
|
|
190
|
+
<ConditionsSection conditions={conditions} />
|
|
191
|
+
</>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { ArchiveRestore, Filter } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
import {
|
|
5
|
+
getRestoreStatus,
|
|
6
|
+
getRestoreBackupName,
|
|
7
|
+
getRestoreIncludedNamespaces,
|
|
8
|
+
getRestoreExcludedNamespaces,
|
|
9
|
+
getRestoreIncludedResources,
|
|
10
|
+
getRestoreExcludedResources,
|
|
11
|
+
getRestoreDuration,
|
|
12
|
+
getRestoreErrors,
|
|
13
|
+
getRestoreWarnings,
|
|
14
|
+
getRestorePVs,
|
|
15
|
+
getRestoreExistingResourcePolicy,
|
|
16
|
+
} from '../resource-utils-velero'
|
|
17
|
+
import { formatAge } from '../resource-utils'
|
|
18
|
+
|
|
19
|
+
interface VeleroRestoreRendererProps {
|
|
20
|
+
data: any
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function VeleroRestoreRenderer({ data }: VeleroRestoreRendererProps) {
|
|
24
|
+
const status = data.status || {}
|
|
25
|
+
const conditions = status.conditions || []
|
|
26
|
+
|
|
27
|
+
const restoreStatus = getRestoreStatus(data)
|
|
28
|
+
const errors = getRestoreErrors(data)
|
|
29
|
+
const warnings = getRestoreWarnings(data)
|
|
30
|
+
const includedNamespaces = getRestoreIncludedNamespaces(data)
|
|
31
|
+
const excludedNamespaces = getRestoreExcludedNamespaces(data)
|
|
32
|
+
const includedResources = getRestoreIncludedResources(data)
|
|
33
|
+
const excludedResources = getRestoreExcludedResources(data)
|
|
34
|
+
|
|
35
|
+
const isFailed = restoreStatus.level === 'unhealthy'
|
|
36
|
+
const isPartiallyFailed = restoreStatus.text === 'PartiallyFailed'
|
|
37
|
+
const isInProgress = restoreStatus.text === 'InProgress'
|
|
38
|
+
|
|
39
|
+
// Progress data
|
|
40
|
+
const progress = status.progress
|
|
41
|
+
const itemsRestored = progress?.itemsRestored ?? 0
|
|
42
|
+
const totalItems = progress?.totalItems ?? 0
|
|
43
|
+
const progressPercent = totalItems > 0 ? Math.round((itemsRestored / totalItems) * 100) : 0
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
{/* Problem alerts */}
|
|
48
|
+
{(isFailed || isPartiallyFailed) && (
|
|
49
|
+
<AlertBanner
|
|
50
|
+
variant="error"
|
|
51
|
+
title={isFailed ? 'Restore Failed' : 'Restore Partially Failed'}
|
|
52
|
+
message={`${errors} error(s) occurred during restore.`}
|
|
53
|
+
/>
|
|
54
|
+
)}
|
|
55
|
+
{warnings > 0 && !isFailed && (
|
|
56
|
+
<AlertBanner
|
|
57
|
+
variant="warning"
|
|
58
|
+
title={`${warnings} Warning(s)`}
|
|
59
|
+
message={`Restore completed with ${warnings} warning(s).`}
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
{/* Status section */}
|
|
64
|
+
<Section title="Status" icon={ArchiveRestore} defaultExpanded>
|
|
65
|
+
<PropertyList>
|
|
66
|
+
<Property label="Phase" value={
|
|
67
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', restoreStatus.color)}>
|
|
68
|
+
{restoreStatus.text}
|
|
69
|
+
</span>
|
|
70
|
+
} />
|
|
71
|
+
<Property label="Backup" value={getRestoreBackupName(data)} />
|
|
72
|
+
{status.startTimestamp && (
|
|
73
|
+
<Property label="Started" value={formatAge(status.startTimestamp) + ' ago'} />
|
|
74
|
+
)}
|
|
75
|
+
{status.completionTimestamp && (
|
|
76
|
+
<Property label="Completed" value={formatAge(status.completionTimestamp) + ' ago'} />
|
|
77
|
+
)}
|
|
78
|
+
<Property label="Duration" value={getRestoreDuration(data)} />
|
|
79
|
+
{errors > 0 && <Property label="Errors" value={String(errors)} />}
|
|
80
|
+
{warnings > 0 && <Property label="Warnings" value={String(warnings)} />}
|
|
81
|
+
</PropertyList>
|
|
82
|
+
</Section>
|
|
83
|
+
|
|
84
|
+
{/* Progress section (if in progress) */}
|
|
85
|
+
{isInProgress && progress && (
|
|
86
|
+
<Section title="Progress" defaultExpanded>
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<div className="flex items-center justify-between text-sm">
|
|
89
|
+
<span className="text-theme-text-secondary">Items restored</span>
|
|
90
|
+
<span className="text-theme-text-primary font-medium">{itemsRestored}/{totalItems}</span>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="w-full bg-theme-elevated rounded-full h-2">
|
|
93
|
+
<div
|
|
94
|
+
className="bg-blue-500 h-2 rounded-full transition-all"
|
|
95
|
+
style={{ width: `${progressPercent}%` }}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="text-xs text-theme-text-tertiary text-right">{progressPercent}%</div>
|
|
99
|
+
</div>
|
|
100
|
+
</Section>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{/* Scope section */}
|
|
104
|
+
{(includedNamespaces.length > 0 || excludedNamespaces.length > 0 || includedResources.length > 0 || excludedResources.length > 0) && (
|
|
105
|
+
<Section title="Scope" icon={Filter} defaultExpanded>
|
|
106
|
+
<PropertyList>
|
|
107
|
+
{includedNamespaces.length > 0 && (
|
|
108
|
+
<Property label="Included Namespaces" value={
|
|
109
|
+
<div className="flex flex-wrap gap-1">
|
|
110
|
+
{includedNamespaces.map((ns: string) => (
|
|
111
|
+
<span key={ns} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">{ns}</span>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
} />
|
|
115
|
+
)}
|
|
116
|
+
{includedNamespaces.length === 0 && (
|
|
117
|
+
<Property label="Included Namespaces" value="* (all)" />
|
|
118
|
+
)}
|
|
119
|
+
{excludedNamespaces.length > 0 && (
|
|
120
|
+
<Property label="Excluded Namespaces" value={
|
|
121
|
+
<div className="flex flex-wrap gap-1">
|
|
122
|
+
{excludedNamespaces.map((ns: string) => (
|
|
123
|
+
<span key={ns} className="px-1.5 py-0.5 bg-red-500/10 rounded text-xs text-red-400">{ns}</span>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
} />
|
|
127
|
+
)}
|
|
128
|
+
{includedResources.length > 0 && (
|
|
129
|
+
<Property label="Included Resources" value={
|
|
130
|
+
<div className="flex flex-wrap gap-1">
|
|
131
|
+
{includedResources.map((r: string) => (
|
|
132
|
+
<span key={r} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">{r}</span>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
} />
|
|
136
|
+
)}
|
|
137
|
+
{excludedResources.length > 0 && (
|
|
138
|
+
<Property label="Excluded Resources" value={
|
|
139
|
+
<div className="flex flex-wrap gap-1">
|
|
140
|
+
{excludedResources.map((r: string) => (
|
|
141
|
+
<span key={r} className="px-1.5 py-0.5 bg-red-500/10 rounded text-xs text-red-400">{r}</span>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
} />
|
|
145
|
+
)}
|
|
146
|
+
</PropertyList>
|
|
147
|
+
</Section>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{/* Options section */}
|
|
151
|
+
<Section title="Options">
|
|
152
|
+
<PropertyList>
|
|
153
|
+
<Property label="Restore PVs" value={getRestorePVs(data)} />
|
|
154
|
+
<Property label="Existing Resource Policy" value={getRestoreExistingResourcePolicy(data)} />
|
|
155
|
+
</PropertyList>
|
|
156
|
+
</Section>
|
|
157
|
+
|
|
158
|
+
<ConditionsSection conditions={conditions} />
|
|
159
|
+
</>
|
|
160
|
+
)
|
|
161
|
+
}
|