@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,50 @@
|
|
|
1
|
+
import { ArrowUpDown } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
3
|
+
|
|
4
|
+
interface PriorityClassRendererProps {
|
|
5
|
+
data: any
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function PriorityClassRenderer({ data }: PriorityClassRendererProps) {
|
|
9
|
+
const preemptionPolicy = data.preemptionPolicy || 'PreemptLowerPriority'
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<Section title="Priority Class" icon={ArrowUpDown}>
|
|
14
|
+
<PropertyList>
|
|
15
|
+
<Property label="Value" value={data.value != null ? String(data.value) : undefined} />
|
|
16
|
+
<Property
|
|
17
|
+
label="Global Default"
|
|
18
|
+
value={
|
|
19
|
+
data.globalDefault != null ? (
|
|
20
|
+
<span className={data.globalDefault ? 'text-green-400' : 'text-theme-text-tertiary'}>
|
|
21
|
+
{data.globalDefault ? 'Yes' : 'No'}
|
|
22
|
+
</span>
|
|
23
|
+
) : (
|
|
24
|
+
<span className="text-theme-text-tertiary">No</span>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
/>
|
|
28
|
+
<Property
|
|
29
|
+
label="Preemption Policy"
|
|
30
|
+
value={
|
|
31
|
+
<span
|
|
32
|
+
className={preemptionPolicy === 'Never' ? 'text-green-400' : 'text-amber-400'}
|
|
33
|
+
title={
|
|
34
|
+
preemptionPolicy === 'Never'
|
|
35
|
+
? 'Will wait in queue instead of preempting'
|
|
36
|
+
: 'Will preempt running pods with lower priority'
|
|
37
|
+
}
|
|
38
|
+
>
|
|
39
|
+
{preemptionPolicy}
|
|
40
|
+
</span>
|
|
41
|
+
}
|
|
42
|
+
/>
|
|
43
|
+
</PropertyList>
|
|
44
|
+
{data.description && (
|
|
45
|
+
<p className="mt-3 text-sm text-theme-text-tertiary leading-relaxed">{data.description}</p>
|
|
46
|
+
)}
|
|
47
|
+
</Section>
|
|
48
|
+
</>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Bell } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
|
|
3
|
+
import {
|
|
4
|
+
getPrometheusRuleGroups,
|
|
5
|
+
getPrometheusRuleTotalRules,
|
|
6
|
+
getPrometheusRuleGroupCount,
|
|
7
|
+
} from '../resource-utils-prometheus'
|
|
8
|
+
|
|
9
|
+
interface PrometheusRuleRendererProps {
|
|
10
|
+
data: any
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function PrometheusRuleRenderer({ data }: PrometheusRuleRendererProps) {
|
|
14
|
+
const groups = getPrometheusRuleGroups(data)
|
|
15
|
+
const totalRules = getPrometheusRuleTotalRules(data)
|
|
16
|
+
const totalAlerts = groups.reduce((sum, g) => sum + g.alertCount, 0)
|
|
17
|
+
const totalRecords = groups.reduce((sum, g) => sum + g.recordCount, 0)
|
|
18
|
+
const conditions = data.status?.conditions
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<Section title="PrometheusRule" icon={Bell}>
|
|
23
|
+
<PropertyList>
|
|
24
|
+
<Property label="Groups" value={String(getPrometheusRuleGroupCount(data))} />
|
|
25
|
+
<Property label="Total Rules" value={String(totalRules)} />
|
|
26
|
+
<Property label="Alert Rules" value={String(totalAlerts)} />
|
|
27
|
+
<Property label="Recording Rules" value={String(totalRecords)} />
|
|
28
|
+
</PropertyList>
|
|
29
|
+
</Section>
|
|
30
|
+
|
|
31
|
+
{groups.length > 0 && (
|
|
32
|
+
<Section title={`Rule Groups (${groups.length})`} defaultExpanded>
|
|
33
|
+
<div className="space-y-2">
|
|
34
|
+
{groups.map((group, i) => (
|
|
35
|
+
<div key={i} className="bg-theme-elevated/30 rounded p-2 text-sm">
|
|
36
|
+
<div className="flex items-center justify-between">
|
|
37
|
+
<span className="text-theme-text-primary font-medium">{group.name}</span>
|
|
38
|
+
<div className="flex items-center gap-2">
|
|
39
|
+
{group.interval && (
|
|
40
|
+
<span className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">
|
|
41
|
+
{group.interval}
|
|
42
|
+
</span>
|
|
43
|
+
)}
|
|
44
|
+
<span className="text-xs text-theme-text-tertiary">
|
|
45
|
+
{group.ruleCount} rule{group.ruleCount !== 1 ? 's' : ''}
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div className="text-xs text-theme-text-secondary mt-1 flex gap-3">
|
|
50
|
+
{group.alertCount > 0 && <span>{group.alertCount} alert{group.alertCount !== 1 ? 's' : ''}</span>}
|
|
51
|
+
{group.recordCount > 0 && <span>{group.recordCount} recording</span>}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
</Section>
|
|
57
|
+
)}
|
|
58
|
+
|
|
59
|
+
<ConditionsSection conditions={conditions} />
|
|
60
|
+
</>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Server } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, KeyValueBadgeList, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
3
|
+
|
|
4
|
+
interface ReplicaSetRendererProps {
|
|
5
|
+
data: any
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Extract problems from ReplicaSet status
|
|
9
|
+
function getReplicaSetProblems(data: any): string[] {
|
|
10
|
+
const problems: string[] = []
|
|
11
|
+
const status = data.status || {}
|
|
12
|
+
const spec = data.spec || {}
|
|
13
|
+
const conditions = status.conditions || []
|
|
14
|
+
|
|
15
|
+
const ready = status.readyReplicas || 0
|
|
16
|
+
const desired = spec.replicas || 0
|
|
17
|
+
const available = status.availableReplicas || 0
|
|
18
|
+
|
|
19
|
+
// Check replica counts
|
|
20
|
+
if (desired > 0 && ready < desired) {
|
|
21
|
+
problems.push(`${desired - ready} of ${desired} pods are not ready`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (desired > 0 && available < desired) {
|
|
25
|
+
problems.push(`${desired - available} pods are not available`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check conditions
|
|
29
|
+
for (const cond of conditions) {
|
|
30
|
+
if (cond.status === 'False' && cond.message) {
|
|
31
|
+
problems.push(`${cond.type}: ${cond.message}`)
|
|
32
|
+
}
|
|
33
|
+
if (cond.status === 'True' && cond.type === 'ReplicaFailure' && cond.message) {
|
|
34
|
+
problems.push(cond.message)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return problems
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function ReplicaSetRenderer({ data }: ReplicaSetRendererProps) {
|
|
42
|
+
const ownerRef = data.metadata?.ownerReferences?.[0]
|
|
43
|
+
const problems = getReplicaSetProblems(data)
|
|
44
|
+
const hasProblems = problems.length > 0
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
{/* Problems alert */}
|
|
49
|
+
{hasProblems && (
|
|
50
|
+
<AlertBanner variant="error" title="Issues Detected" items={problems} />
|
|
51
|
+
)}
|
|
52
|
+
|
|
53
|
+
<Section title="Status" icon={Server}>
|
|
54
|
+
<PropertyList>
|
|
55
|
+
<Property label="Replicas" value={`${data.status?.readyReplicas || 0}/${data.spec?.replicas || 0}`} />
|
|
56
|
+
<Property label="Available" value={data.status?.availableReplicas} />
|
|
57
|
+
{ownerRef && (
|
|
58
|
+
<Property label="Owner" value={`${ownerRef.kind}/${ownerRef.name}`} />
|
|
59
|
+
)}
|
|
60
|
+
<Property label="Revision" value={data.metadata?.annotations?.['deployment.kubernetes.io/revision']} />
|
|
61
|
+
</PropertyList>
|
|
62
|
+
</Section>
|
|
63
|
+
|
|
64
|
+
<Section title="Selector">
|
|
65
|
+
<KeyValueBadgeList items={data.spec?.selector?.matchLabels} />
|
|
66
|
+
</Section>
|
|
67
|
+
|
|
68
|
+
<ConditionsSection conditions={data.status?.conditions} />
|
|
69
|
+
</>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Shield, Users } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ResourceLink, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
|
|
5
|
+
interface RoleBindingRendererProps {
|
|
6
|
+
data: any
|
|
7
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getSubjectKindBadgeClass(kind: string): string {
|
|
11
|
+
switch (kind) {
|
|
12
|
+
case 'ServiceAccount':
|
|
13
|
+
return 'bg-green-500/20 text-green-400'
|
|
14
|
+
case 'User':
|
|
15
|
+
return 'bg-blue-500/20 text-blue-400'
|
|
16
|
+
case 'Group':
|
|
17
|
+
return 'bg-purple-500/20 text-purple-400'
|
|
18
|
+
default:
|
|
19
|
+
return 'bg-gray-500/20 text-gray-400'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getRoleRefKindBadgeClass(kind: string): string {
|
|
24
|
+
switch (kind) {
|
|
25
|
+
case 'Role':
|
|
26
|
+
return 'bg-blue-500/20 text-blue-400'
|
|
27
|
+
case 'ClusterRole':
|
|
28
|
+
return 'bg-purple-500/20 text-purple-400'
|
|
29
|
+
default:
|
|
30
|
+
return 'bg-gray-500/20 text-gray-400'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function RoleBindingRenderer({ data, onNavigate }: RoleBindingRendererProps) {
|
|
35
|
+
const roleRef = data.roleRef || {}
|
|
36
|
+
const subjects: any[] = data.subjects || []
|
|
37
|
+
const isClusterRoleBinding = data.kind === 'ClusterRoleBinding'
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
{subjects.length === 0 && (
|
|
42
|
+
<AlertBanner
|
|
43
|
+
variant="warning"
|
|
44
|
+
title="No Subjects"
|
|
45
|
+
message="This binding has no subjects — it has no effect until subjects are added."
|
|
46
|
+
/>
|
|
47
|
+
)}
|
|
48
|
+
|
|
49
|
+
{isClusterRoleBinding && (
|
|
50
|
+
<div className="p-2 bg-blue-500/10 border border-blue-500/30 rounded text-xs text-blue-300/80 flex items-start gap-2">
|
|
51
|
+
<span>ClusterRoleBinding grants permissions across all namespaces.</span>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
<Section title="Role Reference" icon={Shield}>
|
|
56
|
+
<PropertyList>
|
|
57
|
+
<Property
|
|
58
|
+
label="Kind"
|
|
59
|
+
value={
|
|
60
|
+
roleRef.kind ? (
|
|
61
|
+
<span className={clsx('px-2 py-0.5 rounded text-xs', getRoleRefKindBadgeClass(roleRef.kind))}>
|
|
62
|
+
{roleRef.kind}
|
|
63
|
+
</span>
|
|
64
|
+
) : undefined
|
|
65
|
+
}
|
|
66
|
+
/>
|
|
67
|
+
<Property label="Name" value={
|
|
68
|
+
roleRef.name ? <ResourceLink name={roleRef.name} kind={roleRef.kind === 'ClusterRole' ? 'clusterroles' : 'roles'} namespace={roleRef.kind === 'ClusterRole' ? '' : (data.metadata?.namespace || '')} onNavigate={onNavigate} /> : undefined
|
|
69
|
+
} />
|
|
70
|
+
<Property label="API Group" value={roleRef.apiGroup} />
|
|
71
|
+
</PropertyList>
|
|
72
|
+
</Section>
|
|
73
|
+
|
|
74
|
+
<Section title={`Subjects (${subjects.length})`} icon={Users} defaultExpanded>
|
|
75
|
+
<div className="space-y-2">
|
|
76
|
+
{subjects.map((subject: any, i: number) => (
|
|
77
|
+
<div key={`${subject.kind}-${subject.name}-${i}`} className="bg-theme-elevated/30 rounded p-2 text-sm">
|
|
78
|
+
<div className="flex items-center gap-2">
|
|
79
|
+
<span className={clsx('px-2 py-0.5 rounded text-xs', getSubjectKindBadgeClass(subject.kind))}>
|
|
80
|
+
{subject.kind}
|
|
81
|
+
</span>
|
|
82
|
+
{subject.kind === 'ServiceAccount' ? (
|
|
83
|
+
<ResourceLink name={subject.name} kind="serviceaccounts" namespace={subject.namespace || 'default'} onNavigate={onNavigate} />
|
|
84
|
+
) : (
|
|
85
|
+
<span className="text-theme-text-primary font-medium">{subject.name}</span>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
<div className="text-xs text-theme-text-tertiary mt-1">
|
|
89
|
+
Namespace: {subject.kind === 'ServiceAccount' ? (subject.namespace || 'default') : '-'}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
</Section>
|
|
95
|
+
</>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { Shield, Info } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
|
|
5
|
+
interface RoleRendererProps {
|
|
6
|
+
data: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const READ_VERBS = new Set(['get', 'list', 'watch'])
|
|
10
|
+
const WRITE_VERBS = new Set(['create', 'update', 'patch'])
|
|
11
|
+
const DELETE_VERBS = new Set(['delete', 'deletecollection'])
|
|
12
|
+
|
|
13
|
+
function getVerbColor(verb: string): string {
|
|
14
|
+
if (verb === '*') return 'bg-red-500/20 text-red-400'
|
|
15
|
+
if (READ_VERBS.has(verb)) return 'bg-green-500/20 text-green-400'
|
|
16
|
+
if (WRITE_VERBS.has(verb)) return 'bg-yellow-500/20 text-yellow-400'
|
|
17
|
+
if (DELETE_VERBS.has(verb)) return 'bg-red-500/20 text-red-400'
|
|
18
|
+
return 'bg-blue-500/20 text-blue-400'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function RoleRenderer({ data }: RoleRendererProps) {
|
|
22
|
+
const rules = data.rules || []
|
|
23
|
+
const aggregationRule = data.aggregationRule
|
|
24
|
+
|
|
25
|
+
const hasWildcard = rules.some((r: any) =>
|
|
26
|
+
(r.verbs || []).includes('*') || (r.apiGroups || []).includes('*') || (r.resources || []).includes('*')
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
{hasWildcard && (
|
|
32
|
+
<AlertBanner
|
|
33
|
+
variant="warning"
|
|
34
|
+
title="Wildcard Permissions"
|
|
35
|
+
message="This role grants wildcard (*) permissions. Review the rules to ensure this level of access is intended."
|
|
36
|
+
/>
|
|
37
|
+
)}
|
|
38
|
+
|
|
39
|
+
{/* Overview */}
|
|
40
|
+
<Section title="Overview" icon={Shield}>
|
|
41
|
+
<PropertyList>
|
|
42
|
+
<Property label="Rules" value={rules.length} />
|
|
43
|
+
{aggregationRule && (
|
|
44
|
+
<Property
|
|
45
|
+
label="Type"
|
|
46
|
+
value={
|
|
47
|
+
<span className="text-blue-400">Aggregated ClusterRole</span>
|
|
48
|
+
}
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
</PropertyList>
|
|
52
|
+
{aggregationRule && (
|
|
53
|
+
<div className="mt-2 p-2 bg-blue-500/10 border border-blue-500/30 rounded text-xs text-blue-300/80 flex items-start gap-2">
|
|
54
|
+
<Info className="w-3.5 h-3.5 mt-0.5 shrink-0 text-blue-400" />
|
|
55
|
+
<span>
|
|
56
|
+
This ClusterRole is automatically aggregated from other ClusterRoles
|
|
57
|
+
matching the specified label selectors.
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</Section>
|
|
62
|
+
|
|
63
|
+
{/* Rules */}
|
|
64
|
+
<Section title={`Rules (${rules.length})`} icon={Shield} defaultExpanded>
|
|
65
|
+
<div className="space-y-3">
|
|
66
|
+
{rules.map((rule: any, i: number) => (
|
|
67
|
+
<div key={i} className="bg-theme-elevated/30 rounded p-3">
|
|
68
|
+
{/* API Groups */}
|
|
69
|
+
{rule.apiGroups && rule.apiGroups.length > 0 && (
|
|
70
|
+
<div className="mb-2">
|
|
71
|
+
<div className="text-xs text-theme-text-tertiary mb-1">API Groups</div>
|
|
72
|
+
<div className="flex flex-wrap gap-1">
|
|
73
|
+
{rule.apiGroups.map((group: string, gi: number) => (
|
|
74
|
+
<span
|
|
75
|
+
key={gi}
|
|
76
|
+
className="px-2 py-0.5 rounded text-xs bg-theme-elevated text-theme-text-secondary"
|
|
77
|
+
>
|
|
78
|
+
{group === '' ? 'core' : group}
|
|
79
|
+
</span>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
{/* Resources */}
|
|
86
|
+
{rule.resources && rule.resources.length > 0 && (
|
|
87
|
+
<div className="mb-2">
|
|
88
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Resources</div>
|
|
89
|
+
<div className="flex flex-wrap gap-1">
|
|
90
|
+
{rule.resources.map((resource: string) => (
|
|
91
|
+
<span
|
|
92
|
+
key={resource}
|
|
93
|
+
className="px-2 py-0.5 rounded text-xs bg-purple-500/20 text-purple-400"
|
|
94
|
+
>
|
|
95
|
+
{resource}
|
|
96
|
+
</span>
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
{/* Verbs */}
|
|
103
|
+
{rule.verbs && rule.verbs.length > 0 && (
|
|
104
|
+
<div className="mb-2">
|
|
105
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Verbs</div>
|
|
106
|
+
<div className="flex flex-wrap gap-1">
|
|
107
|
+
{rule.verbs.map((verb: string) => (
|
|
108
|
+
<span
|
|
109
|
+
key={verb}
|
|
110
|
+
className={clsx(
|
|
111
|
+
'px-2 py-0.5 rounded text-xs',
|
|
112
|
+
getVerbColor(verb)
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
{verb}
|
|
116
|
+
</span>
|
|
117
|
+
))}
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{/* Resource Names */}
|
|
123
|
+
{rule.resourceNames && rule.resourceNames.length > 0 && (
|
|
124
|
+
<div className="mb-2">
|
|
125
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Resource Names</div>
|
|
126
|
+
<div className="flex flex-wrap gap-1">
|
|
127
|
+
{rule.resourceNames.map((name: string) => (
|
|
128
|
+
<span
|
|
129
|
+
key={name}
|
|
130
|
+
className="px-2 py-0.5 rounded text-xs bg-cyan-500/20 text-cyan-400"
|
|
131
|
+
>
|
|
132
|
+
{name}
|
|
133
|
+
</span>
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
{/* Non-Resource URLs */}
|
|
140
|
+
{rule.nonResourceURLs && rule.nonResourceURLs.length > 0 && (
|
|
141
|
+
<div>
|
|
142
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Non-Resource URLs</div>
|
|
143
|
+
<div className="flex flex-wrap gap-1">
|
|
144
|
+
{rule.nonResourceURLs.map((url: string) => (
|
|
145
|
+
<span
|
|
146
|
+
key={url}
|
|
147
|
+
className="px-2 py-0.5 rounded text-xs bg-orange-500/20 text-orange-400"
|
|
148
|
+
>
|
|
149
|
+
{url}
|
|
150
|
+
</span>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
))}
|
|
157
|
+
|
|
158
|
+
{rules.length === 0 && (
|
|
159
|
+
<div className="text-sm text-theme-text-tertiary">No rules defined</div>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
</Section>
|
|
163
|
+
|
|
164
|
+
{/* Aggregation Rule */}
|
|
165
|
+
{aggregationRule && aggregationRule.clusterRoleSelectors && (
|
|
166
|
+
<Section title="Aggregation Rule" defaultExpanded>
|
|
167
|
+
<div className="space-y-3">
|
|
168
|
+
{aggregationRule.clusterRoleSelectors.map((selector: any, i: number) => (
|
|
169
|
+
<div key={i} className="bg-theme-elevated/30 rounded p-3">
|
|
170
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Match Labels</div>
|
|
171
|
+
{selector.matchLabels && Object.keys(selector.matchLabels).length > 0 ? (
|
|
172
|
+
<div className="flex flex-wrap gap-1">
|
|
173
|
+
{Object.entries(selector.matchLabels).map(([k, v]) => (
|
|
174
|
+
<span
|
|
175
|
+
key={k}
|
|
176
|
+
className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
|
|
177
|
+
>
|
|
178
|
+
{k}={String(v)}
|
|
179
|
+
</span>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
) : (
|
|
183
|
+
<div className="text-xs text-theme-text-tertiary">No labels specified</div>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
))}
|
|
187
|
+
</div>
|
|
188
|
+
</Section>
|
|
189
|
+
)}
|
|
190
|
+
</>
|
|
191
|
+
)
|
|
192
|
+
}
|