@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.
Files changed (193) hide show
  1. package/package.json +63 -0
  2. package/src/components/dock/BottomDock.tsx +204 -0
  3. package/src/components/dock/DockContext.tsx +212 -0
  4. package/src/components/dock/TerminalTab.tsx +333 -0
  5. package/src/components/dock/index.ts +3 -0
  6. package/src/components/gitops/GitOpsActions.tsx +227 -0
  7. package/src/components/gitops/GitOpsStatusBadge.tsx +221 -0
  8. package/src/components/gitops/ManagedResourcesList.tsx +210 -0
  9. package/src/components/gitops/SyncCountdown.tsx +146 -0
  10. package/src/components/gitops/index.ts +4 -0
  11. package/src/components/logs/JsonLogLine.tsx +90 -0
  12. package/src/components/logs/LogCore.tsx +553 -0
  13. package/src/components/logs/LogToolbarSelects.tsx +74 -0
  14. package/src/components/logs/LogsViewer.tsx +146 -0
  15. package/src/components/logs/WorkloadLogsViewer.tsx +263 -0
  16. package/src/components/logs/index.ts +13 -0
  17. package/src/components/logs/useLogBuffer.ts +138 -0
  18. package/src/components/logs/useLogSearch.ts +151 -0
  19. package/src/components/logs/useLogStream.ts +61 -0
  20. package/src/components/resources/ResourcesView.tsx +5494 -0
  21. package/src/components/resources/index.ts +17 -0
  22. package/src/components/resources/renderers/AlertRenderer.tsx +166 -0
  23. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +233 -0
  24. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +89 -0
  25. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +194 -0
  26. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +79 -0
  27. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +68 -0
  28. package/src/components/resources/renderers/CertificateRenderer.tsx +209 -0
  29. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +123 -0
  30. package/src/components/resources/renderers/ChallengeRenderer.tsx +111 -0
  31. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +164 -0
  32. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +140 -0
  33. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +153 -0
  34. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +221 -0
  35. package/src/components/resources/renderers/ConfigMapRenderer.tsx +124 -0
  36. package/src/components/resources/renderers/CronJobRenderer.tsx +86 -0
  37. package/src/components/resources/renderers/EventRenderer.tsx +175 -0
  38. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +119 -0
  39. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +172 -0
  40. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +242 -0
  41. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +263 -0
  42. package/src/components/resources/renderers/GatewayClassRenderer.tsx +68 -0
  43. package/src/components/resources/renderers/GatewayRenderer.tsx +218 -0
  44. package/src/components/resources/renderers/GenericRenderer.tsx +476 -0
  45. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +154 -0
  46. package/src/components/resources/renderers/HPARenderer.tsx +164 -0
  47. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +303 -0
  48. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +167 -0
  49. package/src/components/resources/renderers/IngressClassRenderer.tsx +49 -0
  50. package/src/components/resources/renderers/IngressRenderer.tsx +104 -0
  51. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +175 -0
  52. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +176 -0
  53. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +125 -0
  54. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +84 -0
  55. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +112 -0
  56. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +288 -0
  57. package/src/components/resources/renderers/JobRenderer.tsx +103 -0
  58. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +157 -0
  59. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +188 -0
  60. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +148 -0
  61. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +90 -0
  62. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +136 -0
  63. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +151 -0
  64. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +70 -0
  65. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +309 -0
  66. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +142 -0
  67. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +232 -0
  68. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +97 -0
  69. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +74 -0
  70. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +138 -0
  71. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +220 -0
  72. package/src/components/resources/renderers/KustomizationRenderer.tsx +151 -0
  73. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +352 -0
  74. package/src/components/resources/renderers/LeaseRenderer.tsx +93 -0
  75. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +208 -0
  76. package/src/components/resources/renderers/NodeRenderer.tsx +282 -0
  77. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +153 -0
  78. package/src/components/resources/renderers/OrderRenderer.tsx +108 -0
  79. package/src/components/resources/renderers/PVCRenderer.tsx +98 -0
  80. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +138 -0
  81. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +126 -0
  82. package/src/components/resources/renderers/PodMonitorRenderer.tsx +63 -0
  83. package/src/components/resources/renderers/PodRenderer.tsx +677 -0
  84. package/src/components/resources/renderers/PriorityClassRenderer.tsx +50 -0
  85. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +62 -0
  86. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +71 -0
  87. package/src/components/resources/renderers/RoleBindingRenderer.tsx +97 -0
  88. package/src/components/resources/renderers/RoleRenderer.tsx +192 -0
  89. package/src/components/resources/renderers/RolloutRenderer.tsx +227 -0
  90. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +75 -0
  91. package/src/components/resources/renderers/SbomReportRenderer.tsx +98 -0
  92. package/src/components/resources/renderers/SealedSecretRenderer.tsx +121 -0
  93. package/src/components/resources/renderers/SecretRenderer.tsx +321 -0
  94. package/src/components/resources/renderers/SecretStoreRenderer.tsx +111 -0
  95. package/src/components/resources/renderers/ServiceAccountRenderer.tsx +81 -0
  96. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +65 -0
  97. package/src/components/resources/renderers/ServiceRenderer.tsx +98 -0
  98. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +195 -0
  99. package/src/components/resources/renderers/StorageClassRenderer.tsx +82 -0
  100. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +257 -0
  101. package/src/components/resources/renderers/VPARenderer.tsx +193 -0
  102. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +73 -0
  103. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +193 -0
  104. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +161 -0
  105. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +134 -0
  106. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +36 -0
  107. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +347 -0
  108. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +156 -0
  109. package/src/components/resources/renderers/WorkflowRenderer.tsx +202 -0
  110. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +91 -0
  111. package/src/components/resources/renderers/WorkloadRenderer.tsx +311 -0
  112. package/src/components/resources/renderers/argo-cells.tsx +118 -0
  113. package/src/components/resources/renderers/certmanager-cells.tsx +164 -0
  114. package/src/components/resources/renderers/cnpg-cells.tsx +161 -0
  115. package/src/components/resources/renderers/eso-cells.tsx +115 -0
  116. package/src/components/resources/renderers/flux-cells.tsx +253 -0
  117. package/src/components/resources/renderers/index.ts +105 -0
  118. package/src/components/resources/renderers/istio-cells.tsx +210 -0
  119. package/src/components/resources/renderers/karpenter-cells.tsx +107 -0
  120. package/src/components/resources/renderers/keda-cells.tsx +94 -0
  121. package/src/components/resources/renderers/knative-cells.tsx +396 -0
  122. package/src/components/resources/renderers/kyverno-cells.tsx +85 -0
  123. package/src/components/resources/renderers/prometheus-cells.tsx +74 -0
  124. package/src/components/resources/renderers/traefik-cells.tsx +120 -0
  125. package/src/components/resources/renderers/trivy-cells.tsx +153 -0
  126. package/src/components/resources/renderers/trivy-shared.tsx +67 -0
  127. package/src/components/resources/renderers/velero-cells.tsx +164 -0
  128. package/src/components/resources/resource-utils-argo.ts +155 -0
  129. package/src/components/resources/resource-utils-certmanager.ts +198 -0
  130. package/src/components/resources/resource-utils-cnpg.ts +344 -0
  131. package/src/components/resources/resource-utils-eso.ts +385 -0
  132. package/src/components/resources/resource-utils-flux.ts +186 -0
  133. package/src/components/resources/resource-utils-istio.ts +372 -0
  134. package/src/components/resources/resource-utils-karpenter.ts +150 -0
  135. package/src/components/resources/resource-utils-keda.ts +203 -0
  136. package/src/components/resources/resource-utils-knative.ts +218 -0
  137. package/src/components/resources/resource-utils-kyverno.ts +224 -0
  138. package/src/components/resources/resource-utils-prometheus.ts +159 -0
  139. package/src/components/resources/resource-utils-traefik.ts +180 -0
  140. package/src/components/resources/resource-utils-trivy.ts +146 -0
  141. package/src/components/resources/resource-utils-velero.ts +293 -0
  142. package/src/components/resources/resource-utils.ts +1558 -0
  143. package/src/components/shared/EditableYamlView.tsx +302 -0
  144. package/src/components/shared/ResourceActionsBar.tsx +924 -0
  145. package/src/components/shared/ResourceRendererDispatch.tsx +557 -0
  146. package/src/components/shared/index.ts +3 -0
  147. package/src/components/timeline/index.ts +1 -0
  148. package/src/components/timeline/shared.tsx +614 -0
  149. package/src/components/topology/GroupNode.tsx +198 -0
  150. package/src/components/topology/K8sResourceNode.tsx +440 -0
  151. package/src/components/topology/TopologyControls.tsx +62 -0
  152. package/src/components/topology/TopologyFilterSidebar.tsx +377 -0
  153. package/src/components/topology/TopologyGraph.tsx +748 -0
  154. package/src/components/topology/TopologySearch.tsx +304 -0
  155. package/src/components/topology/index.ts +7 -0
  156. package/src/components/topology/layout.ts +734 -0
  157. package/src/components/topology/layout.worker.ts +272 -0
  158. package/src/components/topology/topology.css +129 -0
  159. package/src/components/ui/CodeViewer.tsx +451 -0
  160. package/src/components/ui/ConfirmDialog.tsx +188 -0
  161. package/src/components/ui/ForceDeleteConfirmDialog.tsx +57 -0
  162. package/src/components/ui/HealthRing.tsx +72 -0
  163. package/src/components/ui/MetricsChart.tsx +259 -0
  164. package/src/components/ui/ResourceBar.tsx +68 -0
  165. package/src/components/ui/Toast.tsx +225 -0
  166. package/src/components/ui/Tooltip.tsx +165 -0
  167. package/src/components/ui/YamlEditor.tsx +280 -0
  168. package/src/components/ui/drawer-components.tsx +792 -0
  169. package/src/components/ui/index.ts +10 -0
  170. package/src/components/workload/ResourceDetailDrawer.tsx +158 -0
  171. package/src/components/workload/WorkloadView.tsx +1109 -0
  172. package/src/components/workload/index.ts +2 -0
  173. package/src/hooks/index.ts +9 -0
  174. package/src/hooks/useAnimatedUnmount.ts +30 -0
  175. package/src/hooks/useKeyboardShortcuts.tsx +336 -0
  176. package/src/hooks/useRefreshAnimation.ts +58 -0
  177. package/src/index.ts +35 -0
  178. package/src/types/core.ts +894 -0
  179. package/src/types/gitops.ts +458 -0
  180. package/src/types/index.ts +2 -0
  181. package/src/utils/animation.ts +40 -0
  182. package/src/utils/api-resources.ts +185 -0
  183. package/src/utils/badge-colors.ts +399 -0
  184. package/src/utils/context-name.ts +72 -0
  185. package/src/utils/download.ts +19 -0
  186. package/src/utils/format.ts +233 -0
  187. package/src/utils/index.ts +10 -0
  188. package/src/utils/log-format.ts +300 -0
  189. package/src/utils/navigation.ts +119 -0
  190. package/src/utils/resource-hierarchy.ts +630 -0
  191. package/src/utils/resource-icons.ts +206 -0
  192. package/src/vite-env.d.ts +1 -0
  193. 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
+ }