@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,352 @@
|
|
|
1
|
+
import { Shield, ShieldCheck, ShieldAlert, FileWarning, ListChecks, ChevronDown, ChevronRight } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
5
|
+
import {
|
|
6
|
+
getPolicyReportSummary,
|
|
7
|
+
getPolicyReportResults,
|
|
8
|
+
getPolicyReportScope,
|
|
9
|
+
getPolicyReportSource,
|
|
10
|
+
getKyvernoPolicyAction,
|
|
11
|
+
getKyvernoPolicyRuleCount,
|
|
12
|
+
getKyvernoPolicyBackground,
|
|
13
|
+
getKyvernoPolicyRules,
|
|
14
|
+
getKyvernoPolicyRuleCountByType,
|
|
15
|
+
getKyvernoPolicyAutogenRules,
|
|
16
|
+
} from '../resource-utils-kyverno'
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// PolicyReport / ClusterPolicyReport Renderer
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
interface PolicyReportRendererProps {
|
|
23
|
+
data: any
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resultColorMap: Record<string, string> = {
|
|
27
|
+
pass: 'bg-green-500/20 text-green-400',
|
|
28
|
+
fail: 'bg-red-500/20 text-red-400',
|
|
29
|
+
warn: 'bg-yellow-500/20 text-yellow-400',
|
|
30
|
+
error: 'bg-red-500/20 text-red-400',
|
|
31
|
+
skip: 'bg-blue-500/20 text-blue-400',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const severityColorMap: Record<string, string> = {
|
|
35
|
+
critical: 'bg-red-500/20 text-red-400',
|
|
36
|
+
high: 'bg-orange-500/20 text-orange-400',
|
|
37
|
+
medium: 'bg-yellow-500/20 text-yellow-400',
|
|
38
|
+
low: 'bg-blue-500/20 text-blue-400',
|
|
39
|
+
info: 'bg-theme-hover text-theme-text-tertiary',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ResultRow({ result }: { result: any }) {
|
|
43
|
+
const [expanded, setExpanded] = useState(false)
|
|
44
|
+
const message = result.message || ''
|
|
45
|
+
const hasMessage = message.length > 0
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="border-b border-theme-border/30 last:border-b-0">
|
|
49
|
+
<div
|
|
50
|
+
className={clsx(
|
|
51
|
+
'flex items-center gap-2 py-1.5 px-2 text-xs',
|
|
52
|
+
hasMessage && 'cursor-pointer hover:bg-theme-hover/50',
|
|
53
|
+
)}
|
|
54
|
+
onClick={() => hasMessage && setExpanded(!expanded)}
|
|
55
|
+
>
|
|
56
|
+
{hasMessage ? (
|
|
57
|
+
expanded ? <ChevronDown className="w-3 h-3 text-theme-text-tertiary shrink-0" /> : <ChevronRight className="w-3 h-3 text-theme-text-tertiary shrink-0" />
|
|
58
|
+
) : (
|
|
59
|
+
<span className="w-3 shrink-0" />
|
|
60
|
+
)}
|
|
61
|
+
{/* Result status */}
|
|
62
|
+
<span className={clsx('px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0', resultColorMap[result.result] || 'bg-theme-hover text-theme-text-tertiary')}>
|
|
63
|
+
{result.result || '-'}
|
|
64
|
+
</span>
|
|
65
|
+
{/* Severity */}
|
|
66
|
+
{result.severity && (
|
|
67
|
+
<span className={clsx('px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0', severityColorMap[result.severity?.toLowerCase()] || 'bg-theme-hover text-theme-text-tertiary')}>
|
|
68
|
+
{result.severity}
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
{/* Policy / Rule */}
|
|
72
|
+
<span className="text-theme-text-secondary truncate">{result.policy || '-'}</span>
|
|
73
|
+
{result.rule && (
|
|
74
|
+
<span className="text-theme-text-tertiary truncate">/ {result.rule}</span>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
{expanded && message && (
|
|
78
|
+
<div className="px-2 pb-2 pl-7">
|
|
79
|
+
<div className="text-xs text-theme-text-secondary break-all bg-theme-elevated/30 rounded p-2">
|
|
80
|
+
{message}
|
|
81
|
+
</div>
|
|
82
|
+
{result.category && (
|
|
83
|
+
<div className="mt-1 text-[10px] text-theme-text-tertiary">Category: {result.category}</div>
|
|
84
|
+
)}
|
|
85
|
+
{result.source && (
|
|
86
|
+
<div className="text-[10px] text-theme-text-tertiary">Source: {result.source}</div>
|
|
87
|
+
)}
|
|
88
|
+
{result.resources && result.resources.length > 0 && (
|
|
89
|
+
<div className="mt-1 text-[10px] text-theme-text-tertiary">
|
|
90
|
+
Resources: {result.resources.map((r: any) => `${r.kind}/${r.namespace ? r.namespace + '/' : ''}${r.name}`).join(', ')}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function PolicyReportRenderer({ data }: PolicyReportRendererProps) {
|
|
100
|
+
const summary = getPolicyReportSummary(data)
|
|
101
|
+
const results = getPolicyReportResults(data)
|
|
102
|
+
const scope = getPolicyReportScope(data)
|
|
103
|
+
const source = getPolicyReportSource(data)
|
|
104
|
+
const total = summary.pass + summary.fail + summary.warn + summary.error + summary.skip
|
|
105
|
+
|
|
106
|
+
const failResults = results.filter((r: any) => r.result === 'fail')
|
|
107
|
+
const errorResults = results.filter((r: any) => r.result === 'error')
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
{/* Problem alerts */}
|
|
112
|
+
{summary.fail > 0 && (
|
|
113
|
+
<AlertBanner
|
|
114
|
+
variant="error"
|
|
115
|
+
title={`${summary.fail} policy check${summary.fail > 1 ? 's' : ''} failed`}
|
|
116
|
+
message={failResults.length <= 3
|
|
117
|
+
? failResults.map((r: any) => `${r.policy}${r.rule ? '/' + r.rule : ''}: ${r.message || 'failed'}`).join('; ')
|
|
118
|
+
: `${failResults.slice(0, 2).map((r: any) => r.policy).join(', ')} and ${failResults.length - 2} more`
|
|
119
|
+
}
|
|
120
|
+
/>
|
|
121
|
+
)}
|
|
122
|
+
{summary.error > 0 && summary.fail === 0 && (
|
|
123
|
+
<AlertBanner
|
|
124
|
+
variant="error"
|
|
125
|
+
title={`${summary.error} policy check${summary.error > 1 ? 's' : ''} errored`}
|
|
126
|
+
message={errorResults.length <= 3
|
|
127
|
+
? errorResults.map((r: any) => `${r.policy}: ${r.message || 'error'}`).join('; ')
|
|
128
|
+
: undefined
|
|
129
|
+
}
|
|
130
|
+
/>
|
|
131
|
+
)}
|
|
132
|
+
{summary.warn > 0 && summary.fail === 0 && summary.error === 0 && (
|
|
133
|
+
<AlertBanner
|
|
134
|
+
variant="warning"
|
|
135
|
+
title={`${summary.warn} policy warning${summary.warn > 1 ? 's' : ''}`}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
{/* Summary */}
|
|
140
|
+
<Section title="Summary" icon={ShieldCheck}>
|
|
141
|
+
<PropertyList>
|
|
142
|
+
{scope !== '-' && <Property label="Scope" value={scope} />}
|
|
143
|
+
{source !== '-' && <Property label="Source" value={source} />}
|
|
144
|
+
</PropertyList>
|
|
145
|
+
|
|
146
|
+
{/* Visual summary bar */}
|
|
147
|
+
{total > 0 && (
|
|
148
|
+
<div className="mt-3">
|
|
149
|
+
<div className="flex rounded overflow-hidden h-5 w-full">
|
|
150
|
+
{summary.pass > 0 && (
|
|
151
|
+
<div
|
|
152
|
+
className="bg-green-500/60 flex items-center justify-center text-[10px] font-medium text-white"
|
|
153
|
+
style={{ width: `${(summary.pass / total) * 100}%` }}
|
|
154
|
+
title={`Pass: ${summary.pass}`}
|
|
155
|
+
>
|
|
156
|
+
{summary.pass > 0 && total > 3 ? summary.pass : ''}
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
{summary.fail > 0 && (
|
|
160
|
+
<div
|
|
161
|
+
className="bg-red-500/60 flex items-center justify-center text-[10px] font-medium text-white"
|
|
162
|
+
style={{ width: `${(summary.fail / total) * 100}%` }}
|
|
163
|
+
title={`Fail: ${summary.fail}`}
|
|
164
|
+
>
|
|
165
|
+
{summary.fail > 0 && total > 3 ? summary.fail : ''}
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
{summary.warn > 0 && (
|
|
169
|
+
<div
|
|
170
|
+
className="bg-yellow-500/60 flex items-center justify-center text-[10px] font-medium text-white"
|
|
171
|
+
style={{ width: `${(summary.warn / total) * 100}%` }}
|
|
172
|
+
title={`Warn: ${summary.warn}`}
|
|
173
|
+
>
|
|
174
|
+
{summary.warn > 0 && total > 3 ? summary.warn : ''}
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
{summary.error > 0 && (
|
|
178
|
+
<div
|
|
179
|
+
className="bg-red-400/60 flex items-center justify-center text-[10px] font-medium text-white"
|
|
180
|
+
style={{ width: `${(summary.error / total) * 100}%` }}
|
|
181
|
+
title={`Error: ${summary.error}`}
|
|
182
|
+
>
|
|
183
|
+
{summary.error > 0 && total > 3 ? summary.error : ''}
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
{summary.skip > 0 && (
|
|
187
|
+
<div
|
|
188
|
+
className="bg-blue-500/40 flex items-center justify-center text-[10px] font-medium text-white"
|
|
189
|
+
style={{ width: `${(summary.skip / total) * 100}%` }}
|
|
190
|
+
title={`Skip: ${summary.skip}`}
|
|
191
|
+
>
|
|
192
|
+
{summary.skip > 0 && total > 3 ? summary.skip : ''}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
<div className="flex gap-3 mt-1.5 text-[10px]">
|
|
197
|
+
{summary.pass > 0 && <span className="text-green-400">Pass: {summary.pass}</span>}
|
|
198
|
+
{summary.fail > 0 && <span className="text-red-400">Fail: {summary.fail}</span>}
|
|
199
|
+
{summary.warn > 0 && <span className="text-yellow-400">Warn: {summary.warn}</span>}
|
|
200
|
+
{summary.error > 0 && <span className="text-red-400">Error: {summary.error}</span>}
|
|
201
|
+
{summary.skip > 0 && <span className="text-blue-400">Skip: {summary.skip}</span>}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
</Section>
|
|
206
|
+
|
|
207
|
+
{/* Results */}
|
|
208
|
+
{results.length > 0 && (
|
|
209
|
+
<Section title={`Results (${results.length})`} icon={ListChecks} defaultExpanded>
|
|
210
|
+
<div className="bg-theme-elevated/20 rounded-lg overflow-hidden max-h-[500px] overflow-y-auto">
|
|
211
|
+
{results.map((result: any, i: number) => (
|
|
212
|
+
<ResultRow key={i} result={result} />
|
|
213
|
+
))}
|
|
214
|
+
</div>
|
|
215
|
+
</Section>
|
|
216
|
+
)}
|
|
217
|
+
</>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ============================================================================
|
|
222
|
+
// Kyverno Policy / ClusterPolicy Renderer
|
|
223
|
+
// ============================================================================
|
|
224
|
+
|
|
225
|
+
interface KyvernoPolicyRendererProps {
|
|
226
|
+
data: any
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const ruleTypeColorMap: Record<string, string> = {
|
|
230
|
+
validate: 'bg-blue-500/20 text-blue-400',
|
|
231
|
+
mutate: 'bg-purple-500/20 text-purple-400',
|
|
232
|
+
generate: 'bg-green-500/20 text-green-400',
|
|
233
|
+
verifyImages: 'bg-orange-500/20 text-orange-400',
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function KyvernoPolicyRenderer({ data }: KyvernoPolicyRendererProps) {
|
|
237
|
+
const spec = data.spec || {}
|
|
238
|
+
const status = data.status || {}
|
|
239
|
+
const conditions = status.conditions || []
|
|
240
|
+
const action = getKyvernoPolicyAction(data)
|
|
241
|
+
const ruleCount = getKyvernoPolicyRuleCount(data)
|
|
242
|
+
const background = getKyvernoPolicyBackground(data)
|
|
243
|
+
const rules = getKyvernoPolicyRules(data)
|
|
244
|
+
const ruleCountByType = getKyvernoPolicyRuleCountByType(data)
|
|
245
|
+
const autogenRules = getKyvernoPolicyAutogenRules(data)
|
|
246
|
+
|
|
247
|
+
const isEnforce = action === 'Enforce'
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<>
|
|
251
|
+
{/* Configuration */}
|
|
252
|
+
<Section title="Configuration" icon={Shield}>
|
|
253
|
+
<PropertyList>
|
|
254
|
+
<Property label="Failure Action" value={
|
|
255
|
+
<span className={clsx(
|
|
256
|
+
'px-2 py-0.5 rounded text-xs font-medium',
|
|
257
|
+
isEnforce ? 'bg-red-500/20 text-red-400' : 'bg-yellow-500/20 text-yellow-400',
|
|
258
|
+
)}>
|
|
259
|
+
{action}
|
|
260
|
+
</span>
|
|
261
|
+
} />
|
|
262
|
+
<Property label="Background" value={background ? 'Enabled' : 'Disabled'} />
|
|
263
|
+
{spec.webhookTimeoutSeconds && (
|
|
264
|
+
<Property label="Webhook Timeout" value={`${spec.webhookTimeoutSeconds}s`} />
|
|
265
|
+
)}
|
|
266
|
+
{spec.failurePolicy && (
|
|
267
|
+
<Property label="Failure Policy" value={spec.failurePolicy} />
|
|
268
|
+
)}
|
|
269
|
+
{spec.schemaValidation !== undefined && (
|
|
270
|
+
<Property label="Schema Validation" value={spec.schemaValidation ? 'Enabled' : 'Disabled'} />
|
|
271
|
+
)}
|
|
272
|
+
</PropertyList>
|
|
273
|
+
|
|
274
|
+
{/* Rule count summary */}
|
|
275
|
+
<div className="mt-3 flex flex-wrap gap-2">
|
|
276
|
+
{ruleCountByType.validate > 0 && (
|
|
277
|
+
<span className="px-2 py-0.5 rounded text-[10px] font-medium bg-blue-500/20 text-blue-400">
|
|
278
|
+
{ruleCountByType.validate} validate
|
|
279
|
+
</span>
|
|
280
|
+
)}
|
|
281
|
+
{ruleCountByType.mutate > 0 && (
|
|
282
|
+
<span className="px-2 py-0.5 rounded text-[10px] font-medium bg-purple-500/20 text-purple-400">
|
|
283
|
+
{ruleCountByType.mutate} mutate
|
|
284
|
+
</span>
|
|
285
|
+
)}
|
|
286
|
+
{ruleCountByType.generate > 0 && (
|
|
287
|
+
<span className="px-2 py-0.5 rounded text-[10px] font-medium bg-green-500/20 text-green-400">
|
|
288
|
+
{ruleCountByType.generate} generate
|
|
289
|
+
</span>
|
|
290
|
+
)}
|
|
291
|
+
{ruleCountByType.verifyImages > 0 && (
|
|
292
|
+
<span className="px-2 py-0.5 rounded text-[10px] font-medium bg-orange-500/20 text-orange-400">
|
|
293
|
+
{ruleCountByType.verifyImages} verifyImages
|
|
294
|
+
</span>
|
|
295
|
+
)}
|
|
296
|
+
</div>
|
|
297
|
+
</Section>
|
|
298
|
+
|
|
299
|
+
{/* Rules */}
|
|
300
|
+
{ruleCount > 0 && (
|
|
301
|
+
<Section title={`Rules (${ruleCount})`} icon={FileWarning} defaultExpanded>
|
|
302
|
+
<div className="space-y-2">
|
|
303
|
+
{rules.map((rule, i) => (
|
|
304
|
+
<div key={i} className="bg-theme-elevated/30 rounded-lg p-3">
|
|
305
|
+
<div className="flex items-center gap-2 mb-1">
|
|
306
|
+
<span className="text-sm font-medium text-theme-text-primary">{rule.name}</span>
|
|
307
|
+
<span className={clsx('px-1.5 py-0.5 rounded text-[10px] font-medium', ruleTypeColorMap[rule.type] || 'bg-theme-hover text-theme-text-tertiary')}>
|
|
308
|
+
{rule.type}
|
|
309
|
+
</span>
|
|
310
|
+
</div>
|
|
311
|
+
<div className="flex gap-3 text-[10px] text-theme-text-tertiary">
|
|
312
|
+
{rule.hasMatch && <span>match</span>}
|
|
313
|
+
{rule.hasExclude && <span>exclude</span>}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
))}
|
|
317
|
+
</div>
|
|
318
|
+
</Section>
|
|
319
|
+
)}
|
|
320
|
+
|
|
321
|
+
{/* Autogenerated Rules */}
|
|
322
|
+
{autogenRules.length > 0 && (
|
|
323
|
+
<Section title={`Autogenerated Rules (${autogenRules.length})`} defaultExpanded={false}>
|
|
324
|
+
<div className="flex flex-wrap gap-1">
|
|
325
|
+
{autogenRules.map((name: string) => (
|
|
326
|
+
<span
|
|
327
|
+
key={name}
|
|
328
|
+
className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
|
|
329
|
+
>
|
|
330
|
+
{name}
|
|
331
|
+
</span>
|
|
332
|
+
))}
|
|
333
|
+
</div>
|
|
334
|
+
</Section>
|
|
335
|
+
)}
|
|
336
|
+
|
|
337
|
+
{/* Status rulecount from Kyverno controller */}
|
|
338
|
+
{status.rulecount && (
|
|
339
|
+
<Section title="Status Rule Count" icon={ShieldAlert} defaultExpanded={false}>
|
|
340
|
+
<PropertyList>
|
|
341
|
+
{status.rulecount.validate !== undefined && <Property label="Validate" value={status.rulecount.validate} />}
|
|
342
|
+
{status.rulecount.mutate !== undefined && <Property label="Mutate" value={status.rulecount.mutate} />}
|
|
343
|
+
{status.rulecount.generate !== undefined && <Property label="Generate" value={status.rulecount.generate} />}
|
|
344
|
+
{status.rulecount.verifyImages !== undefined && <Property label="Verify Images" value={status.rulecount.verifyImages} />}
|
|
345
|
+
</PropertyList>
|
|
346
|
+
</Section>
|
|
347
|
+
)}
|
|
348
|
+
|
|
349
|
+
<ConditionsSection conditions={conditions} />
|
|
350
|
+
</>
|
|
351
|
+
)
|
|
352
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Timer } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
|
|
5
|
+
interface LeaseRendererProps {
|
|
6
|
+
data: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatRelativeTime(timestamp: string): string {
|
|
10
|
+
const diff = Date.now() - new Date(timestamp).getTime()
|
|
11
|
+
if (diff < 0) return 'just now'
|
|
12
|
+
const seconds = Math.floor(diff / 1000)
|
|
13
|
+
if (seconds < 60) return `${seconds}s ago`
|
|
14
|
+
const minutes = Math.floor(seconds / 60)
|
|
15
|
+
if (minutes < 60) return `${minutes}m ago`
|
|
16
|
+
const hours = Math.floor(minutes / 60)
|
|
17
|
+
if (hours < 24) return `${hours}h ago`
|
|
18
|
+
const days = Math.floor(hours / 24)
|
|
19
|
+
return `${days}d ago`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function LeaseRenderer({ data }: LeaseRendererProps) {
|
|
23
|
+
const spec = data.spec || {}
|
|
24
|
+
|
|
25
|
+
const renewTime: string | undefined = spec.renewTime
|
|
26
|
+
const leaseDurationSeconds: number | undefined = spec.leaseDurationSeconds
|
|
27
|
+
|
|
28
|
+
// Staleness calculation
|
|
29
|
+
let isStale = false
|
|
30
|
+
let ageRatio = 0
|
|
31
|
+
if (renewTime && leaseDurationSeconds) {
|
|
32
|
+
const elapsedSeconds = (Date.now() - new Date(renewTime).getTime()) / 1000
|
|
33
|
+
ageRatio = elapsedSeconds / leaseDurationSeconds
|
|
34
|
+
isStale = elapsedSeconds > leaseDurationSeconds
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const renewColor = !renewTime
|
|
38
|
+
? ''
|
|
39
|
+
: isStale
|
|
40
|
+
? 'text-red-400'
|
|
41
|
+
: ageRatio > 0.75
|
|
42
|
+
? 'text-amber-400'
|
|
43
|
+
: 'text-green-400'
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
{isStale && (
|
|
48
|
+
<AlertBanner
|
|
49
|
+
variant="warning"
|
|
50
|
+
title="Lease May Be Expired"
|
|
51
|
+
message="The lease has not been renewed within its duration period. The holder may be unhealthy."
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
<Section title="Lease" icon={Timer} defaultExpanded>
|
|
56
|
+
<PropertyList>
|
|
57
|
+
<Property label="Holder" value={spec.holderIdentity || '-'} />
|
|
58
|
+
<Property
|
|
59
|
+
label="Duration"
|
|
60
|
+
value={leaseDurationSeconds != null ? `${leaseDurationSeconds}s` : '-'}
|
|
61
|
+
/>
|
|
62
|
+
<Property
|
|
63
|
+
label="Transitions"
|
|
64
|
+
value={spec.leaseTransitions != null ? String(spec.leaseTransitions) : '0'}
|
|
65
|
+
/>
|
|
66
|
+
<Property
|
|
67
|
+
label="Acquired"
|
|
68
|
+
value={
|
|
69
|
+
spec.acquireTime ? (
|
|
70
|
+
<span title={new Date(spec.acquireTime).toLocaleString()}>
|
|
71
|
+
{formatRelativeTime(spec.acquireTime)}
|
|
72
|
+
</span>
|
|
73
|
+
) : undefined
|
|
74
|
+
}
|
|
75
|
+
/>
|
|
76
|
+
<Property
|
|
77
|
+
label="Last Renewed"
|
|
78
|
+
value={
|
|
79
|
+
renewTime ? (
|
|
80
|
+
<span
|
|
81
|
+
className={clsx(renewColor)}
|
|
82
|
+
title={new Date(renewTime).toLocaleString()}
|
|
83
|
+
>
|
|
84
|
+
{formatRelativeTime(renewTime)}
|
|
85
|
+
</span>
|
|
86
|
+
) : undefined
|
|
87
|
+
}
|
|
88
|
+
/>
|
|
89
|
+
</PropertyList>
|
|
90
|
+
</Section>
|
|
91
|
+
</>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { Shield, ArrowDownToLine, ArrowUpFromLine } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
4
|
+
|
|
5
|
+
interface NetworkPolicyRendererProps {
|
|
6
|
+
data: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function NetworkPolicyRenderer({ data }: NetworkPolicyRendererProps) {
|
|
10
|
+
const spec = data.spec || {}
|
|
11
|
+
const podSelector = spec.podSelector || {}
|
|
12
|
+
const policyTypes: string[] = spec.policyTypes || []
|
|
13
|
+
const ingress: any[] | undefined = spec.ingress
|
|
14
|
+
const egress: any[] | undefined = spec.egress
|
|
15
|
+
|
|
16
|
+
const matchLabels = podSelector.matchLabels || {}
|
|
17
|
+
const hasMatchLabels = Object.keys(matchLabels).length > 0
|
|
18
|
+
const hasIngress = policyTypes.includes('Ingress')
|
|
19
|
+
const hasEgress = policyTypes.includes('Egress')
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<Section title="Target" icon={Shield}>
|
|
24
|
+
<PropertyList>
|
|
25
|
+
<Property
|
|
26
|
+
label="Pod Selector"
|
|
27
|
+
value={hasMatchLabels ? undefined : 'All pods in namespace'}
|
|
28
|
+
/>
|
|
29
|
+
</PropertyList>
|
|
30
|
+
{hasMatchLabels && (
|
|
31
|
+
<div className="mt-2">
|
|
32
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Pod Selector</div>
|
|
33
|
+
<div className="flex flex-wrap gap-1">
|
|
34
|
+
{Object.entries(matchLabels).map(([k, v]) => (
|
|
35
|
+
<span
|
|
36
|
+
key={k}
|
|
37
|
+
className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
|
|
38
|
+
>
|
|
39
|
+
{k}={String(v)}
|
|
40
|
+
</span>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
{policyTypes.length > 0 && (
|
|
46
|
+
<div className="mt-2">
|
|
47
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Policy Types</div>
|
|
48
|
+
<div className="flex flex-wrap gap-1">
|
|
49
|
+
{policyTypes.map((type) => (
|
|
50
|
+
<span
|
|
51
|
+
key={type}
|
|
52
|
+
className={clsx(
|
|
53
|
+
'px-2 py-0.5 rounded text-xs border',
|
|
54
|
+
type === 'Ingress'
|
|
55
|
+
? 'bg-blue-500/20 text-blue-400 border-blue-500/30'
|
|
56
|
+
: 'bg-purple-500/20 text-purple-400 border-purple-500/30'
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{type}
|
|
60
|
+
</span>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</Section>
|
|
66
|
+
|
|
67
|
+
{hasIngress && (
|
|
68
|
+
<Section title="Ingress Rules" icon={ArrowDownToLine} defaultExpanded>
|
|
69
|
+
{ingress && ingress.length > 0 ? (
|
|
70
|
+
<div className="space-y-3">
|
|
71
|
+
{ingress.map((rule: any, i: number) => (
|
|
72
|
+
<IngressEgressRuleCard key={i} rule={rule} direction="from" />
|
|
73
|
+
))}
|
|
74
|
+
</div>
|
|
75
|
+
) : (
|
|
76
|
+
<div className="text-sm text-red-400">Deny all ingress</div>
|
|
77
|
+
)}
|
|
78
|
+
</Section>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
{hasEgress && (
|
|
82
|
+
<Section title="Egress Rules" icon={ArrowUpFromLine} defaultExpanded>
|
|
83
|
+
{egress && egress.length > 0 ? (
|
|
84
|
+
<div className="space-y-3">
|
|
85
|
+
{egress.map((rule: any, i: number) => (
|
|
86
|
+
<IngressEgressRuleCard key={i} rule={rule} direction="to" />
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
) : (
|
|
90
|
+
<div className="text-sm text-red-400">Deny all egress</div>
|
|
91
|
+
)}
|
|
92
|
+
</Section>
|
|
93
|
+
)}
|
|
94
|
+
</>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function IngressEgressRuleCard({
|
|
99
|
+
rule,
|
|
100
|
+
direction,
|
|
101
|
+
}: {
|
|
102
|
+
rule: any
|
|
103
|
+
direction: 'from' | 'to'
|
|
104
|
+
}) {
|
|
105
|
+
const peers: any[] = rule[direction] || []
|
|
106
|
+
const ports: any[] = rule.ports || []
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div className="bg-theme-elevated/30 rounded p-3">
|
|
110
|
+
{peers.length > 0 && (
|
|
111
|
+
<div className="mb-2">
|
|
112
|
+
<div className="text-xs text-theme-text-tertiary mb-1 capitalize">{direction}</div>
|
|
113
|
+
<div className="space-y-1.5">
|
|
114
|
+
{peers.map((peer: any, j: number) => (
|
|
115
|
+
<PeerEntry key={j} peer={peer} />
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{ports.length > 0 && (
|
|
122
|
+
<div>
|
|
123
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Ports</div>
|
|
124
|
+
<div className="flex flex-wrap gap-1">
|
|
125
|
+
{ports.map((port: any, j: number) => (
|
|
126
|
+
<span
|
|
127
|
+
key={j}
|
|
128
|
+
className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
|
|
129
|
+
>
|
|
130
|
+
{port.protocol || 'TCP'}/{port.port}
|
|
131
|
+
</span>
|
|
132
|
+
))}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{peers.length === 0 && ports.length === 0 && (
|
|
138
|
+
<div className="text-xs text-theme-text-tertiary">Allow all</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function PeerEntry({ peer }: { peer: any }) {
|
|
145
|
+
if (peer.podSelector) {
|
|
146
|
+
const labels = peer.podSelector.matchLabels || {}
|
|
147
|
+
const hasLabels = Object.keys(labels).length > 0
|
|
148
|
+
return (
|
|
149
|
+
<div className="text-sm">
|
|
150
|
+
<span className="text-theme-text-secondary text-xs">podSelector: </span>
|
|
151
|
+
{hasLabels ? (
|
|
152
|
+
<span className="inline-flex flex-wrap gap-1 align-middle">
|
|
153
|
+
{Object.entries(labels).map(([k, v]) => (
|
|
154
|
+
<span
|
|
155
|
+
key={k}
|
|
156
|
+
className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
|
|
157
|
+
>
|
|
158
|
+
{k}={String(v)}
|
|
159
|
+
</span>
|
|
160
|
+
))}
|
|
161
|
+
</span>
|
|
162
|
+
) : (
|
|
163
|
+
<span className="text-xs text-theme-text-tertiary">all pods</span>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (peer.namespaceSelector) {
|
|
170
|
+
const labels = peer.namespaceSelector.matchLabels || {}
|
|
171
|
+
const hasLabels = Object.keys(labels).length > 0
|
|
172
|
+
return (
|
|
173
|
+
<div className="text-sm">
|
|
174
|
+
<span className="text-theme-text-secondary text-xs">namespaceSelector: </span>
|
|
175
|
+
{hasLabels ? (
|
|
176
|
+
<span className="inline-flex flex-wrap gap-1 align-middle">
|
|
177
|
+
{Object.entries(labels).map(([k, v]) => (
|
|
178
|
+
<span
|
|
179
|
+
key={k}
|
|
180
|
+
className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
|
|
181
|
+
>
|
|
182
|
+
{k}={String(v)}
|
|
183
|
+
</span>
|
|
184
|
+
))}
|
|
185
|
+
</span>
|
|
186
|
+
) : (
|
|
187
|
+
<span className="text-xs text-theme-text-tertiary">all namespaces</span>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (peer.ipBlock) {
|
|
194
|
+
return (
|
|
195
|
+
<div className="text-sm">
|
|
196
|
+
<span className="text-theme-text-secondary text-xs">ipBlock: </span>
|
|
197
|
+
<span className="text-xs text-theme-text-primary">{peer.ipBlock.cidr}</span>
|
|
198
|
+
{peer.ipBlock.except && peer.ipBlock.except.length > 0 && (
|
|
199
|
+
<span className="text-xs text-theme-text-tertiary">
|
|
200
|
+
{' '}except {peer.ipBlock.except.join(', ')}
|
|
201
|
+
</span>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return null
|
|
208
|
+
}
|