@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,134 @@
1
+ import { Clock, Archive } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
4
+ import {
5
+ getScheduleStatus,
6
+ getScheduleCron,
7
+ getScheduleLastBackup,
8
+ getSchedulePaused,
9
+ getScheduleTemplate,
10
+ getScheduleUseOwnerReferences,
11
+ } from '../resource-utils-velero'
12
+
13
+ interface VeleroScheduleRendererProps {
14
+ data: any
15
+ }
16
+
17
+ export function VeleroScheduleRenderer({ data }: VeleroScheduleRendererProps) {
18
+ const status = data.status || {}
19
+ const conditions = status.conditions || []
20
+
21
+ const scheduleStatus = getScheduleStatus(data)
22
+ const isPaused = getSchedulePaused(data)
23
+ const template = getScheduleTemplate(data)
24
+
25
+ const templateIncludedNs = template.includedNamespaces || []
26
+ const templateExcludedNs = template.excludedNamespaces || []
27
+ const templateIncludedResources = template.includedResources || []
28
+ const templateExcludedResources = template.excludedResources || []
29
+
30
+ return (
31
+ <>
32
+ {/* Problem alerts */}
33
+ {isPaused && (
34
+ <AlertBanner
35
+ variant="warning"
36
+ title="Schedule Paused"
37
+ message="This backup schedule is currently paused. No new backups will be created."
38
+ />
39
+ )}
40
+ {scheduleStatus.text === 'FailedValidation' && (
41
+ <AlertBanner
42
+ variant="error"
43
+ title="Validation Failed"
44
+ message="The schedule spec failed validation and is not active."
45
+ />
46
+ )}
47
+
48
+ {/* Schedule section */}
49
+ <Section title="Schedule" icon={Clock} defaultExpanded>
50
+ <PropertyList>
51
+ <Property label="Status" value={
52
+ <span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', scheduleStatus.color)}>
53
+ {scheduleStatus.text}
54
+ </span>
55
+ } />
56
+ <Property label="Cron Schedule" value={
57
+ <span className="font-mono text-sm">{getScheduleCron(data)}</span>
58
+ } />
59
+ <Property label="Last Backup" value={getScheduleLastBackup(data)} />
60
+ <Property label="Paused" value={isPaused ? 'Yes' : 'No'} />
61
+ <Property label="Use Owner References" value={getScheduleUseOwnerReferences(data) ? 'Yes' : 'No'} />
62
+ </PropertyList>
63
+ </Section>
64
+
65
+ {/* Backup Template section */}
66
+ <Section title="Backup Template" icon={Archive} defaultExpanded>
67
+ <PropertyList>
68
+ {template.storageLocation && (
69
+ <Property label="Storage Location" value={template.storageLocation} />
70
+ )}
71
+ {template.ttl && (
72
+ <Property label="TTL" value={template.ttl} />
73
+ )}
74
+ {templateIncludedNs.length > 0 && (
75
+ <Property label="Included Namespaces" value={
76
+ <div className="flex flex-wrap gap-1">
77
+ {templateIncludedNs.map((ns: string) => (
78
+ <span key={ns} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">{ns}</span>
79
+ ))}
80
+ </div>
81
+ } />
82
+ )}
83
+ {templateIncludedNs.length === 0 && (
84
+ <Property label="Included Namespaces" value="* (all)" />
85
+ )}
86
+ {templateExcludedNs.length > 0 && (
87
+ <Property label="Excluded Namespaces" value={
88
+ <div className="flex flex-wrap gap-1">
89
+ {templateExcludedNs.map((ns: string) => (
90
+ <span key={ns} className="px-1.5 py-0.5 bg-red-500/10 rounded text-xs text-red-400">{ns}</span>
91
+ ))}
92
+ </div>
93
+ } />
94
+ )}
95
+ {templateIncludedResources.length > 0 && (
96
+ <Property label="Included Resources" value={
97
+ <div className="flex flex-wrap gap-1">
98
+ {templateIncludedResources.map((r: string) => (
99
+ <span key={r} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">{r}</span>
100
+ ))}
101
+ </div>
102
+ } />
103
+ )}
104
+ {templateExcludedResources.length > 0 && (
105
+ <Property label="Excluded Resources" value={
106
+ <div className="flex flex-wrap gap-1">
107
+ {templateExcludedResources.map((r: string) => (
108
+ <span key={r} className="px-1.5 py-0.5 bg-red-500/10 rounded text-xs text-red-400">{r}</span>
109
+ ))}
110
+ </div>
111
+ } />
112
+ )}
113
+ {template.snapshotVolumes !== undefined && (
114
+ <Property label="Snapshot Volumes" value={template.snapshotVolumes ? 'Yes' : 'No'} />
115
+ )}
116
+ {template.defaultVolumesToFsBackup !== undefined && (
117
+ <Property label="Default FS Backup" value={template.defaultVolumesToFsBackup ? 'Yes' : 'No'} />
118
+ )}
119
+ {(template.volumeSnapshotLocations || []).length > 0 && (
120
+ <Property label="Volume Snapshot Locations" value={
121
+ <div className="flex flex-wrap gap-1">
122
+ {template.volumeSnapshotLocations.map((loc: string) => (
123
+ <span key={loc} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">{loc}</span>
124
+ ))}
125
+ </div>
126
+ } />
127
+ )}
128
+ </PropertyList>
129
+ </Section>
130
+
131
+ <ConditionsSection conditions={conditions} />
132
+ </>
133
+ )
134
+ }
@@ -0,0 +1,36 @@
1
+ import { HardDrive } from 'lucide-react'
2
+ import { Section, PropertyList, Property } from '../../ui/drawer-components'
3
+ import {
4
+ getVSLProvider,
5
+ getVSLConfig,
6
+ } from '../resource-utils-velero'
7
+
8
+ interface VeleroVSLRendererProps {
9
+ data: any
10
+ }
11
+
12
+ export function VeleroVSLRenderer({ data }: VeleroVSLRendererProps) {
13
+ const config = getVSLConfig(data)
14
+
15
+ return (
16
+ <>
17
+ <Section title="Provider" icon={HardDrive} defaultExpanded>
18
+ <PropertyList>
19
+ <Property label="Provider" value={getVSLProvider(data)} />
20
+ </PropertyList>
21
+ {Object.keys(config).length > 0 && (
22
+ <div className="mt-2 pt-2 border-t border-theme-border">
23
+ <div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1">Config</div>
24
+ <div className="flex flex-wrap gap-1">
25
+ {Object.entries(config).map(([k, v]) => (
26
+ <span key={k} className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">
27
+ {k}: {String(v)}
28
+ </span>
29
+ ))}
30
+ </div>
31
+ </div>
32
+ )}
33
+ </Section>
34
+ </>
35
+ )
36
+ }
@@ -0,0 +1,347 @@
1
+ import { useState, useMemo } from 'react'
2
+ import { Shield, Package, ChevronDown, ChevronRight, ChevronUp, ExternalLink, Search, ArrowUpDown, Wrench } from 'lucide-react'
3
+ import { clsx } from 'clsx'
4
+ import { Section, PropertyList, Property } from '../../ui/drawer-components'
5
+ import { formatAge } from '../resource-utils'
6
+ import { SEVERITY_BADGE_COLORS, SEVERITY_BAR_COLORS, SEVERITY_TEXT_COLORS, SEVERITY_ORDER, TrivyAlertBanner, formatTrivyImage } from './trivy-shared'
7
+
8
+ interface VulnerabilityReportRendererProps {
9
+ data: any
10
+ }
11
+
12
+ const INITIAL_SHOW_COUNT = 50
13
+
14
+ type SortColumn = 'vulnerabilityID' | 'severity' | 'score' | 'resource' | 'installedVersion' | 'fixedVersion'
15
+ type SortDirection = 'asc' | 'desc'
16
+
17
+ const COLUMNS: { key: SortColumn; label: string }[] = [
18
+ { key: 'vulnerabilityID', label: 'CVE' },
19
+ { key: 'severity', label: 'Severity' },
20
+ { key: 'score', label: 'Score' },
21
+ { key: 'resource', label: 'Package' },
22
+ { key: 'installedVersion', label: 'Installed' },
23
+ { key: 'fixedVersion', label: 'Fixed' },
24
+ ]
25
+
26
+ export function VulnerabilityReportRenderer({ data }: VulnerabilityReportRendererProps) {
27
+ const [showAll, setShowAll] = useState(false)
28
+ const [expanded, setExpanded] = useState(true)
29
+ const [searchTerm, setSearchTerm] = useState('')
30
+ const [severityFilters, setSeverityFilters] = useState<Set<string>>(new Set())
31
+ const [fixableOnly, setFixableOnly] = useState(false)
32
+ const [sortColumn, setSortColumn] = useState<SortColumn | null>(null)
33
+ const [sortDirection, setSortDirection] = useState<SortDirection>('desc')
34
+
35
+ const report = data.report || {}
36
+ const summary = report.summary || {}
37
+ const vulnerabilities = Array.isArray(report.vulnerabilities) ? report.vulnerabilities : []
38
+ const scanner = report.scanner || {}
39
+ const labels = data.metadata?.labels || {}
40
+
41
+ const critical = summary.criticalCount || 0
42
+ const high = summary.highCount || 0
43
+ const medium = summary.mediumCount || 0
44
+ const low = summary.lowCount || 0
45
+ const unknown = summary.unknownCount || 0
46
+ const total = critical + high + medium + low + unknown
47
+
48
+ const containerName = labels['trivy-operator.container.name'] || '-'
49
+ const image = formatTrivyImage(report)
50
+ const os = report.os || {}
51
+ const osDisplay = os.family && os.name
52
+ ? `${os.family} ${os.name}`
53
+ : os.family || null
54
+
55
+ const toggleSeverityFilter = (severity: string) => {
56
+ setSeverityFilters(prev => {
57
+ const next = new Set(prev)
58
+ if (next.has(severity)) {
59
+ next.delete(severity)
60
+ } else {
61
+ next.add(severity)
62
+ }
63
+ return next
64
+ })
65
+ setShowAll(false)
66
+ }
67
+
68
+ const handleSort = (column: SortColumn) => {
69
+ if (sortColumn === column) {
70
+ if (sortDirection === 'desc') {
71
+ setSortDirection('asc')
72
+ } else {
73
+ // Third click: clear sort
74
+ setSortColumn(null)
75
+ setSortDirection('desc')
76
+ }
77
+ } else {
78
+ setSortColumn(column)
79
+ setSortDirection('desc')
80
+ }
81
+ }
82
+
83
+ const hasActiveFilters = searchTerm !== '' || severityFilters.size > 0 || fixableOnly
84
+
85
+ const processedVulns = useMemo(() => {
86
+ let result = vulnerabilities
87
+
88
+ // Filter by severity
89
+ if (severityFilters.size > 0) {
90
+ result = result.filter((v: any) => severityFilters.has(v.severity || 'UNKNOWN'))
91
+ }
92
+
93
+ // Filter by search term
94
+ if (searchTerm) {
95
+ const term = searchTerm.toLowerCase()
96
+ result = result.filter((v: any) =>
97
+ (v.vulnerabilityID || '').toLowerCase().includes(term) ||
98
+ (v.resource || '').toLowerCase().includes(term)
99
+ )
100
+ }
101
+
102
+ // Filter fixable only
103
+ if (fixableOnly) {
104
+ result = result.filter((v: any) => v.fixedVersion)
105
+ }
106
+
107
+ // Sort
108
+ if (sortColumn) {
109
+ result = [...result].sort((a: any, b: any) => {
110
+ let cmp = 0
111
+ switch (sortColumn) {
112
+ case 'severity': {
113
+ const aOrder = SEVERITY_ORDER[a.severity] ?? 99
114
+ const bOrder = SEVERITY_ORDER[b.severity] ?? 99
115
+ cmp = aOrder - bOrder
116
+ break
117
+ }
118
+ case 'score': {
119
+ const aScore = a.score ?? 0
120
+ const bScore = b.score ?? 0
121
+ cmp = aScore - bScore
122
+ break
123
+ }
124
+ default: {
125
+ const aVal = (a[sortColumn] || '').toLowerCase()
126
+ const bVal = (b[sortColumn] || '').toLowerCase()
127
+ cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0
128
+ }
129
+ }
130
+ return sortDirection === 'asc' ? cmp : -cmp
131
+ })
132
+ }
133
+
134
+ return result
135
+ }, [vulnerabilities, severityFilters, searchTerm, fixableOnly, sortColumn, sortDirection])
136
+
137
+ const displayedVulns = showAll ? processedVulns : processedVulns.slice(0, INITIAL_SHOW_COUNT)
138
+
139
+ return (
140
+ <>
141
+ <TrivyAlertBanner critical={critical} high={high} noun="vulnerability" />
142
+
143
+ {/* Report Overview */}
144
+ <Section title="Report Overview" icon={Shield}>
145
+ <PropertyList>
146
+ <Property label="Container" value={containerName} />
147
+ <Property label="Image" value={image} />
148
+ {osDisplay && (
149
+ <Property label="OS" value={
150
+ <span className="flex items-center gap-1.5">
151
+ {osDisplay}
152
+ {os.eosl && (
153
+ <span className="px-1.5 py-0.5 rounded text-[10px] font-medium bg-amber-500/20 text-amber-400">
154
+ End of Service Life
155
+ </span>
156
+ )}
157
+ </span>
158
+ } />
159
+ )}
160
+ <Property label="Scanner" value={scanner.name ? `${scanner.name} ${scanner.version || ''}`.trim() : '-'} />
161
+ <Property label="Scanned" value={report.updateTimestamp ? formatAge(report.updateTimestamp) + ' ago' : '-'} />
162
+ </PropertyList>
163
+ </Section>
164
+
165
+ {/* Severity Summary */}
166
+ <Section title="Severity Summary">
167
+ {total > 0 && (
168
+ <div className="mb-3">
169
+ <div className="h-3 rounded overflow-hidden flex">
170
+ {critical > 0 && <div className={clsx('h-full', SEVERITY_BAR_COLORS.CRITICAL)} style={{ width: `${(critical / total) * 100}%` }} />}
171
+ {high > 0 && <div className={clsx('h-full', SEVERITY_BAR_COLORS.HIGH)} style={{ width: `${(high / total) * 100}%` }} />}
172
+ {medium > 0 && <div className={clsx('h-full', SEVERITY_BAR_COLORS.MEDIUM)} style={{ width: `${(medium / total) * 100}%` }} />}
173
+ {low > 0 && <div className={clsx('h-full', SEVERITY_BAR_COLORS.LOW)} style={{ width: `${(low / total) * 100}%` }} />}
174
+ {unknown > 0 && <div className={clsx('h-full', SEVERITY_BAR_COLORS.UNKNOWN)} style={{ width: `${(unknown / total) * 100}%` }} />}
175
+ </div>
176
+ </div>
177
+ )}
178
+ <div className="flex flex-wrap gap-3">
179
+ {[
180
+ { label: 'Critical', count: critical, key: 'CRITICAL' },
181
+ { label: 'High', count: high, key: 'HIGH' },
182
+ { label: 'Medium', count: medium, key: 'MEDIUM' },
183
+ { label: 'Low', count: low, key: 'LOW' },
184
+ { label: 'Unknown', count: unknown, key: 'UNKNOWN' },
185
+ ].map(({ label, count, key }) => {
186
+ const isActive = severityFilters.has(key)
187
+ return (
188
+ <button
189
+ key={key}
190
+ onClick={() => count > 0 && toggleSeverityFilter(key)}
191
+ className={clsx(
192
+ 'flex items-center gap-1.5 rounded px-1.5 py-0.5 transition-colors',
193
+ count > 0 ? 'cursor-pointer hover:bg-theme-hover' : 'cursor-default',
194
+ isActive && 'ring-1 ring-theme-text-secondary bg-theme-hover'
195
+ )}
196
+ >
197
+ <div className={clsx('w-2.5 h-2.5 rounded-sm', SEVERITY_BAR_COLORS[key])} />
198
+ <span className={clsx('text-sm font-medium', count > 0 ? SEVERITY_TEXT_COLORS[key] : 'text-theme-text-tertiary')}>
199
+ {count}
200
+ </span>
201
+ <span className="text-xs text-theme-text-tertiary">{label}</span>
202
+ </button>
203
+ )
204
+ })}
205
+ <div className="text-xs text-theme-text-tertiary self-center ml-auto">{total} total</div>
206
+ </div>
207
+ </Section>
208
+
209
+ {/* Vulnerabilities Table */}
210
+ {vulnerabilities.length > 0 && (
211
+ <Section title="Vulnerabilities" icon={Package}>
212
+ <button
213
+ onClick={() => setExpanded(!expanded)}
214
+ className="flex items-center gap-1 text-xs text-theme-text-secondary hover:text-theme-text-primary mb-2"
215
+ >
216
+ {expanded ? <ChevronDown className="w-3.5 h-3.5" /> : <ChevronRight className="w-3.5 h-3.5" />}
217
+ {vulnerabilities.length} vulnerabilities
218
+ </button>
219
+ {expanded && (
220
+ <div className="overflow-x-auto -mx-1">
221
+ {/* Search and filter toolbar */}
222
+ <div className="flex items-center gap-2 mb-2 px-1">
223
+ <div className="relative flex-1">
224
+ <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 text-theme-text-tertiary" />
225
+ <input
226
+ type="text"
227
+ placeholder="Filter by CVE or package..."
228
+ value={searchTerm}
229
+ onChange={(e) => { setSearchTerm(e.target.value); setShowAll(false) }}
230
+ className="w-full pl-6 pr-2 py-1 text-xs bg-theme-bg border border-theme-border rounded text-theme-text-primary placeholder:text-theme-text-tertiary focus:outline-none focus:ring-1 focus:ring-blue-500/50"
231
+ />
232
+ </div>
233
+ <button
234
+ onClick={() => { setFixableOnly(!fixableOnly); setShowAll(false) }}
235
+ className={clsx(
236
+ 'flex items-center gap-1 px-2 py-1 text-xs rounded border transition-colors whitespace-nowrap',
237
+ fixableOnly
238
+ ? 'bg-green-500/15 border-green-500/30 text-green-400'
239
+ : 'bg-theme-bg border-theme-border text-theme-text-tertiary hover:text-theme-text-secondary'
240
+ )}
241
+ title="Show only vulnerabilities with a fix available"
242
+ >
243
+ <Wrench className="w-3 h-3" />
244
+ Fixable
245
+ </button>
246
+ </div>
247
+
248
+ {/* Active filter summary */}
249
+ {hasActiveFilters && (
250
+ <div className="flex items-center gap-1 mb-2 px-1 text-xs text-theme-text-tertiary">
251
+ Showing {processedVulns.length} of {vulnerabilities.length}
252
+ <button
253
+ onClick={() => { setSearchTerm(''); setSeverityFilters(new Set()); setFixableOnly(false); setShowAll(false) }}
254
+ className="ml-1 text-blue-400 hover:text-blue-300 hover:underline"
255
+ >
256
+ Clear filters
257
+ </button>
258
+ </div>
259
+ )}
260
+
261
+ <table className="w-full text-xs">
262
+ <thead>
263
+ <tr className="border-b border-theme-border text-theme-text-tertiary">
264
+ {COLUMNS.map(({ key, label }) => {
265
+ const isSorted = sortColumn === key
266
+ return (
267
+ <th
268
+ key={key}
269
+ className="text-left py-1.5 px-1 font-medium cursor-pointer select-none hover:text-theme-text-secondary"
270
+ onClick={() => handleSort(key)}
271
+ >
272
+ <div className="flex items-center gap-0.5">
273
+ {label}
274
+ <span className="text-theme-text-tertiary">
275
+ {isSorted ? (
276
+ sortDirection === 'asc' ? (
277
+ <ChevronUp className="w-3 h-3" />
278
+ ) : (
279
+ <ChevronDown className="w-3 h-3" />
280
+ )
281
+ ) : (
282
+ <ArrowUpDown className="w-2.5 h-2.5 opacity-40" />
283
+ )}
284
+ </span>
285
+ </div>
286
+ </th>
287
+ )
288
+ })}
289
+ </tr>
290
+ </thead>
291
+ <tbody>
292
+ {displayedVulns.map((vuln: any, i: number) => (
293
+ <tr key={`${vuln.vulnerabilityID || i}-${i}`} className="border-b border-theme-border/50 hover:bg-theme-hover/50">
294
+ <td className="py-1.5 px-1">
295
+ {vuln.primaryLink ? (
296
+ <a
297
+ href={vuln.primaryLink}
298
+ target="_blank"
299
+ rel="noopener noreferrer"
300
+ className="text-blue-400 hover:text-blue-300 hover:underline inline-flex items-center gap-0.5"
301
+ >
302
+ {vuln.vulnerabilityID || '-'}
303
+ <ExternalLink className="w-2.5 h-2.5" />
304
+ </a>
305
+ ) : (
306
+ <span className="text-theme-text-secondary">{vuln.vulnerabilityID || '-'}</span>
307
+ )}
308
+ </td>
309
+ <td className="py-1.5 px-1">
310
+ <span className={clsx('px-1.5 py-0.5 rounded text-[10px] font-medium', SEVERITY_BADGE_COLORS[vuln.severity] || SEVERITY_BADGE_COLORS.UNKNOWN)}>
311
+ {vuln.severity || 'UNKNOWN'}
312
+ </span>
313
+ </td>
314
+ <td className="py-1.5 px-1 text-theme-text-secondary">{vuln.score || '-'}</td>
315
+ <td className="py-1.5 px-1 text-theme-text-secondary max-w-[120px] truncate" title={vuln.resource || ''}>{vuln.resource || '-'}</td>
316
+ <td className="py-1.5 px-1 text-theme-text-secondary max-w-[100px] truncate" title={vuln.installedVersion}>{vuln.installedVersion || '-'}</td>
317
+ <td className="py-1.5 px-1 max-w-[100px] truncate" title={vuln.fixedVersion}>
318
+ {vuln.fixedVersion ? (
319
+ <span className="text-green-400">{vuln.fixedVersion}</span>
320
+ ) : (
321
+ <span className="text-theme-text-tertiary">-</span>
322
+ )}
323
+ </td>
324
+ </tr>
325
+ ))}
326
+ </tbody>
327
+ </table>
328
+ {displayedVulns.length === 0 && (
329
+ <div className="py-4 text-center text-xs text-theme-text-tertiary">
330
+ No vulnerabilities match the current filters.
331
+ </div>
332
+ )}
333
+ {!showAll && processedVulns.length > INITIAL_SHOW_COUNT && (
334
+ <button
335
+ onClick={() => setShowAll(true)}
336
+ className="mt-2 text-xs text-blue-400 hover:text-blue-300 hover:underline"
337
+ >
338
+ Show all {processedVulns.length} vulnerabilities
339
+ </button>
340
+ )}
341
+ </div>
342
+ )}
343
+ </Section>
344
+ )}
345
+ </>
346
+ )
347
+ }
@@ -0,0 +1,156 @@
1
+ import { Shield } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, AlertBanner } from '../../ui/drawer-components'
4
+
5
+ interface WebhookConfigRendererProps {
6
+ data: any
7
+ isMutating?: boolean
8
+ }
9
+
10
+ function getOperationColor(op: string): string {
11
+ if (op === 'CREATE') return 'bg-green-500/20 text-green-400'
12
+ if (op === 'UPDATE') return 'bg-yellow-500/20 text-yellow-400'
13
+ if (op === 'DELETE') return 'bg-red-500/20 text-red-400'
14
+ if (op === '*') return 'bg-red-500/20 text-red-400'
15
+ if (op === 'CONNECT') return 'bg-blue-500/20 text-blue-400'
16
+ return 'bg-theme-elevated text-theme-text-secondary'
17
+ }
18
+
19
+ function getSideEffectsColor(se: string): string {
20
+ if (se === 'None' || se === 'NoneOnDryRun') return 'bg-green-500/20 text-green-400'
21
+ return 'bg-amber-500/20 text-amber-400'
22
+ }
23
+
24
+ export function WebhookConfigRenderer({ data, isMutating }: WebhookConfigRendererProps) {
25
+ const webhooks = data.webhooks || []
26
+ const hasFailPolicy = webhooks.some((w: any) => w.failurePolicy === 'Fail')
27
+ const allIgnore = webhooks.length > 0 && webhooks.every((w: any) => w.failurePolicy === 'Ignore')
28
+
29
+ return (
30
+ <>
31
+ {hasFailPolicy && (
32
+ <AlertBanner
33
+ variant="warning"
34
+ title="Blocking Webhook"
35
+ message="One or more webhooks use failurePolicy: Fail. API requests will be rejected if the webhook is unavailable."
36
+ />
37
+ )}
38
+
39
+ <Section title="Overview" icon={Shield}>
40
+ <PropertyList>
41
+ <Property label="Webhooks" value={webhooks.length} />
42
+ <Property
43
+ label="Failure Policy"
44
+ value={
45
+ hasFailPolicy ? (
46
+ <span className="px-1.5 py-0.5 rounded text-xs bg-red-500/20 text-red-400">Fail</span>
47
+ ) : allIgnore ? (
48
+ <span className="px-1.5 py-0.5 rounded text-xs bg-theme-elevated text-theme-text-secondary">Ignore</span>
49
+ ) : (
50
+ 'Mixed'
51
+ )
52
+ }
53
+ />
54
+ </PropertyList>
55
+ </Section>
56
+
57
+ <Section title={`Webhooks (${webhooks.length})`} icon={Shield} defaultExpanded>
58
+ <div className="space-y-3">
59
+ {webhooks.map((wh: any, i: number) => {
60
+ const svc = wh.clientConfig?.service
61
+ const url = wh.clientConfig?.url
62
+ const target = svc
63
+ ? `Service: ${svc.namespace}/${svc.name}${svc.port ? `:${svc.port}` : ''}${svc.path || ''}`
64
+ : url
65
+ ? `URL: ${url}`
66
+ : 'No target'
67
+
68
+ const nsLabels = wh.namespaceSelector?.matchLabels
69
+ const objLabels = wh.objectSelector?.matchLabels
70
+
71
+ return (
72
+ <div key={i} className="bg-theme-elevated/30 rounded p-3">
73
+ <div className="text-sm font-medium text-theme-text-primary">{wh.name}</div>
74
+ <div className="text-xs text-theme-text-secondary mt-0.5">{target}</div>
75
+
76
+ {/* Policy badges */}
77
+ <div className="flex flex-wrap gap-1.5 mt-2">
78
+ <span className={clsx('px-1.5 py-0.5 rounded text-xs', wh.failurePolicy === 'Fail' ? 'bg-red-500/20 text-red-400' : 'bg-theme-elevated text-theme-text-secondary')}>
79
+ {wh.failurePolicy || 'Fail'}
80
+ </span>
81
+ {wh.sideEffects && (
82
+ <span className={clsx('px-1.5 py-0.5 rounded text-xs', getSideEffectsColor(wh.sideEffects))}>
83
+ {wh.sideEffects}
84
+ </span>
85
+ )}
86
+ <span className="px-1.5 py-0.5 rounded text-xs bg-theme-elevated text-theme-text-secondary">
87
+ {wh.timeoutSeconds ?? 10}s
88
+ </span>
89
+ {wh.matchPolicy && (
90
+ <span className="px-1.5 py-0.5 rounded text-xs bg-theme-elevated text-theme-text-secondary">
91
+ {wh.matchPolicy}
92
+ </span>
93
+ )}
94
+ {isMutating && wh.reinvocationPolicy && (
95
+ <span className="px-1.5 py-0.5 rounded text-xs bg-theme-elevated text-theme-text-secondary">
96
+ {wh.reinvocationPolicy}
97
+ </span>
98
+ )}
99
+ </div>
100
+
101
+ {/* Rules */}
102
+ {wh.rules && wh.rules.length > 0 && (
103
+ <div className="mt-2 space-y-2">
104
+ {wh.rules.map((rule: any, ri: number) => (
105
+ <div key={ri} className="flex flex-wrap items-center gap-1 text-xs">
106
+ {(rule.operations || []).map((op: string) => (
107
+ <span key={op} className={clsx('px-1.5 py-0.5 rounded', getOperationColor(op))}>{op}</span>
108
+ ))}
109
+ <span className="text-theme-text-tertiary">on</span>
110
+ {(rule.resources || []).map((r: string) => (
111
+ <span key={r} className="px-1.5 py-0.5 rounded bg-purple-500/20 text-purple-400">{r}</span>
112
+ ))}
113
+ {(rule.apiGroups || []).map((g: string) => (
114
+ <span key={g} className="px-1.5 py-0.5 rounded bg-theme-elevated text-theme-text-secondary">{g === '' ? 'core' : g}</span>
115
+ ))}
116
+ </div>
117
+ ))}
118
+ </div>
119
+ )}
120
+
121
+ {/* Selectors */}
122
+ {(nsLabels || objLabels) && (
123
+ <div className="mt-2 space-y-1">
124
+ {nsLabels && Object.keys(nsLabels).length > 0 && (
125
+ <div>
126
+ <div className="text-xs text-theme-text-tertiary mb-1">Namespace Selector</div>
127
+ <div className="flex flex-wrap gap-1">
128
+ {Object.entries(nsLabels).map(([k, v]) => (
129
+ <span key={k} className="px-1.5 py-0.5 rounded text-xs bg-theme-elevated text-theme-text-secondary">{k}={String(v)}</span>
130
+ ))}
131
+ </div>
132
+ </div>
133
+ )}
134
+ {objLabels && Object.keys(objLabels).length > 0 && (
135
+ <div>
136
+ <div className="text-xs text-theme-text-tertiary mb-1">Object Selector</div>
137
+ <div className="flex flex-wrap gap-1">
138
+ {Object.entries(objLabels).map(([k, v]) => (
139
+ <span key={k} className="px-1.5 py-0.5 rounded text-xs bg-theme-elevated text-theme-text-secondary">{k}={String(v)}</span>
140
+ ))}
141
+ </div>
142
+ </div>
143
+ )}
144
+ </div>
145
+ )}
146
+ </div>
147
+ )
148
+ })}
149
+ {webhooks.length === 0 && (
150
+ <div className="text-sm text-theme-text-tertiary">No webhooks defined</div>
151
+ )}
152
+ </div>
153
+ </Section>
154
+ </>
155
+ )
156
+ }