@skyhook-io/k8s-ui 1.3.2 → 1.4.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 +1 -1
- package/src/components/audit/AuditAlerts.tsx +88 -0
- package/src/components/audit/AuditCard.tsx +130 -0
- package/src/components/audit/AuditFindingsTable.tsx +526 -0
- package/src/components/audit/index.ts +3 -0
- package/src/components/dock/DockContext.tsx +5 -2
- package/src/components/dock/LocalTerminalTab.tsx +11 -0
- package/src/components/resources/ResourcesView.tsx +14 -0
- package/src/components/resources/renderers/CiliumNetworkPolicyRenderer.tsx +228 -0
- package/src/components/resources/renderers/ClusterNetworkPolicyRenderer.tsx +174 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +1 -1
- package/src/components/resources/renderers/NetworkPolicyDiagram.tsx +277 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +10 -1
- package/src/components/resources/renderers/ServiceRenderer.tsx +40 -28
- package/src/components/resources/renderers/index.ts +3 -0
- package/src/components/shared/CreateResourceDialog.tsx +236 -0
- package/src/components/shared/EditableYamlView.tsx +16 -1
- package/src/components/shared/ResourceActionsBar.tsx +9 -7
- package/src/components/shared/ResourceRendererDispatch.tsx +9 -2
- package/src/components/shared/index.ts +1 -0
- package/src/components/topology/K8sResourceNode.tsx +15 -0
- package/src/components/topology/TopologyControls.tsx +21 -1
- package/src/components/topology/TopologyGraph.tsx +1 -1
- package/src/components/ui/Badge.tsx +8 -0
- package/src/components/ui/drawer-components.tsx +20 -4
- package/src/components/workload/WorkloadView.tsx +55 -29
- package/src/index.ts +3 -0
- package/src/types/core.ts +2 -1
- package/src/utils/badge-colors.ts +7 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/k8s-errors.ts +142 -0
- package/src/utils/resource-icons.ts +3 -0
- package/src/utils/skeleton-yaml.ts +302 -0
package/package.json
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { ClipboardCheck, ChevronRight, ShieldAlert, AlertTriangle, ArrowRight } from 'lucide-react'
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import { SEVERITY_TEXT, BP_CATEGORY_BADGE, DEFAULT_BADGE_COLOR } from '../../utils/badge-colors'
|
|
5
|
+
|
|
6
|
+
export interface AuditFinding {
|
|
7
|
+
kind: string
|
|
8
|
+
namespace: string
|
|
9
|
+
name: string
|
|
10
|
+
checkID: string
|
|
11
|
+
category: string
|
|
12
|
+
severity: string
|
|
13
|
+
message: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface AuditAlertsProps {
|
|
17
|
+
findings: AuditFinding[]
|
|
18
|
+
onViewAll?: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Subtle collapsible section showing audit findings for a resource.
|
|
23
|
+
* Renders as a collapsed summary by default — not intrusive.
|
|
24
|
+
*/
|
|
25
|
+
export function AuditAlerts({ findings, onViewAll }: AuditAlertsProps) {
|
|
26
|
+
const [expanded, setExpanded] = useState(false)
|
|
27
|
+
|
|
28
|
+
if (findings.length === 0) return null
|
|
29
|
+
|
|
30
|
+
const dangers = findings.filter(f => f.severity === 'danger').length
|
|
31
|
+
const warnings = findings.filter(f => f.severity === 'warning').length
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="border-b-subtle pb-4 last:border-0">
|
|
35
|
+
<button
|
|
36
|
+
onClick={() => setExpanded(!expanded)}
|
|
37
|
+
className="flex items-center gap-2 w-full text-left mb-2 hover:text-theme-text-primary transition-colors"
|
|
38
|
+
>
|
|
39
|
+
<ChevronRight className={clsx('w-4 h-4 text-theme-text-tertiary transition-transform duration-200', expanded && 'rotate-90')} />
|
|
40
|
+
<ClipboardCheck className="w-4 h-4 text-theme-text-secondary" />
|
|
41
|
+
<span className="text-sm font-medium text-theme-text-secondary">Audit Findings</span>
|
|
42
|
+
<div className="flex items-center gap-2 ml-1">
|
|
43
|
+
{dangers > 0 && (
|
|
44
|
+
<span className={clsx('text-xs font-medium tabular-nums', SEVERITY_TEXT.error)}>{dangers} critical</span>
|
|
45
|
+
)}
|
|
46
|
+
{warnings > 0 && (
|
|
47
|
+
<span className={clsx('text-xs font-medium tabular-nums', SEVERITY_TEXT.warning)}>{warnings} warning</span>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
</button>
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
className="grid transition-[grid-template-rows] duration-200 ease-out"
|
|
54
|
+
style={{ gridTemplateRows: expanded ? '1fr' : '0fr' }}
|
|
55
|
+
>
|
|
56
|
+
<div className="overflow-hidden">
|
|
57
|
+
<div className="pl-6 pt-1 flex flex-col gap-0.5">
|
|
58
|
+
{findings.map((f, i) => {
|
|
59
|
+
const isDanger = f.severity === 'danger'
|
|
60
|
+
return (
|
|
61
|
+
<div key={`${f.checkID}-${i}`} className="flex items-start gap-2 py-1">
|
|
62
|
+
{isDanger ? (
|
|
63
|
+
<ShieldAlert className={clsx('w-3.5 h-3.5 shrink-0 mt-0.5', SEVERITY_TEXT.error)} />
|
|
64
|
+
) : (
|
|
65
|
+
<AlertTriangle className={clsx('w-3.5 h-3.5 shrink-0 mt-0.5', SEVERITY_TEXT.warning)} />
|
|
66
|
+
)}
|
|
67
|
+
<span className="text-xs text-theme-text-secondary flex-1">{f.message}</span>
|
|
68
|
+
<span className={clsx('badge-sm text-[10px] shrink-0', BP_CATEGORY_BADGE[f.category] || DEFAULT_BADGE_COLOR)}>
|
|
69
|
+
{f.category}
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
})}
|
|
74
|
+
{onViewAll && (
|
|
75
|
+
<button
|
|
76
|
+
onClick={(e) => { e.stopPropagation(); onViewAll() }}
|
|
77
|
+
className="flex items-center gap-1 text-xs text-skyhook-500 hover:text-skyhook-400 mt-1 py-1 transition-colors"
|
|
78
|
+
>
|
|
79
|
+
View all findings
|
|
80
|
+
<ArrowRight className="w-3 h-3" />
|
|
81
|
+
</button>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { ClipboardCheck, ArrowRight, Check } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { SEVERITY_TEXT, SEVERITY_DOT } from '../../utils/badge-colors'
|
|
4
|
+
|
|
5
|
+
export interface AuditCardData {
|
|
6
|
+
passing: number
|
|
7
|
+
warning: number
|
|
8
|
+
danger: number
|
|
9
|
+
categories: Record<string, { passing: number; warning: number; danger: number }>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface AuditCardProps {
|
|
13
|
+
data: AuditCardData
|
|
14
|
+
onNavigate: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type SeverityLevel = 'success' | 'warning' | 'error'
|
|
18
|
+
|
|
19
|
+
function getSeverityLevel(data: AuditCardData): SeverityLevel {
|
|
20
|
+
if (data.warning + data.danger === 0) return 'success'
|
|
21
|
+
const dangerRatio = data.danger / (data.warning + data.danger)
|
|
22
|
+
if (dangerRatio > 0.2) return 'error'
|
|
23
|
+
return 'warning'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Card-specific accent backgrounds (light opacity variants, work in both themes)
|
|
27
|
+
const ACCENT_BG: Record<SeverityLevel, string> = {
|
|
28
|
+
success: 'bg-green-500/10',
|
|
29
|
+
warning: 'bg-yellow-500/10',
|
|
30
|
+
error: 'bg-red-500/10',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function AuditCard({ data, onNavigate }: AuditCardProps) {
|
|
34
|
+
const total = data.passing + data.warning + data.danger
|
|
35
|
+
const issueCount = data.warning + data.danger
|
|
36
|
+
const allPassing = issueCount === 0
|
|
37
|
+
const level = getSeverityLevel(data)
|
|
38
|
+
const accentColor = SEVERITY_TEXT[level]
|
|
39
|
+
const accentBg = ACCENT_BG[level]
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
onClick={onNavigate}
|
|
44
|
+
className="group h-[260px] rounded-xl bg-theme-surface shadow-theme-sm hover:-translate-y-1 hover:shadow-theme-md transition-all duration-200 text-left animate-fade-in-up"
|
|
45
|
+
>
|
|
46
|
+
<div className="flex flex-col h-full w-full">
|
|
47
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50">
|
|
48
|
+
<div className="flex items-center gap-2">
|
|
49
|
+
<ClipboardCheck className={clsx('w-4 h-4', accentColor)} />
|
|
50
|
+
<span className={clsx('text-xs font-semibold uppercase tracking-wider', accentColor)}>Cluster Audit</span>
|
|
51
|
+
{issueCount > 0 ? (
|
|
52
|
+
<span className={clsx('badge-sm', accentBg, accentColor)}>
|
|
53
|
+
{issueCount}
|
|
54
|
+
</span>
|
|
55
|
+
) : (
|
|
56
|
+
<span className={clsx('badge-sm', accentBg, accentColor)}>
|
|
57
|
+
<Check className="w-3 h-3" />
|
|
58
|
+
</span>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div className="flex-1 min-h-0 flex flex-col items-center justify-center px-4 py-4">
|
|
64
|
+
{allPassing ? (
|
|
65
|
+
<div className="flex flex-col items-center gap-2">
|
|
66
|
+
<Check className={clsx('w-8 h-8', SEVERITY_TEXT.success)} />
|
|
67
|
+
<span className={clsx('text-sm font-medium', SEVERITY_TEXT.success)}>All checks passing</span>
|
|
68
|
+
{total > 0 && (
|
|
69
|
+
<span className="text-xs text-theme-text-tertiary">{total} checks across {Object.keys(data.categories).length} categories</span>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
) : (
|
|
73
|
+
<>
|
|
74
|
+
{/* Distribution bar — only show when we have passing data for context */}
|
|
75
|
+
{data.passing > 0 && total > 0 && (
|
|
76
|
+
<div className="flex items-center gap-3 w-full">
|
|
77
|
+
<div className="flex-1 h-3 rounded-full overflow-hidden bg-theme-hover flex">
|
|
78
|
+
<div className={clsx('h-full', SEVERITY_DOT.success)} style={{ width: `${(data.passing / total) * 100}%` }} />
|
|
79
|
+
{data.warning > 0 && (
|
|
80
|
+
<div className={clsx('h-full', SEVERITY_DOT.warning)} style={{ width: `${(data.warning / total) * 100}%` }} />
|
|
81
|
+
)}
|
|
82
|
+
{data.danger > 0 && (
|
|
83
|
+
<div className={clsx('h-full', SEVERITY_DOT.error)} style={{ width: `${(data.danger / total) * 100}%` }} />
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{/* Category breakdown */}
|
|
90
|
+
<div className="grid grid-cols-1 gap-y-2 mt-4 w-full">
|
|
91
|
+
{Object.entries(data.categories).map(([category, counts]) => {
|
|
92
|
+
const catIssues = counts.warning + counts.danger
|
|
93
|
+
const dotColor = counts.danger > 0 ? SEVERITY_DOT.error : counts.warning > 0 ? SEVERITY_DOT.warning : SEVERITY_DOT.success
|
|
94
|
+
return (
|
|
95
|
+
<div key={category} className="flex items-center gap-2">
|
|
96
|
+
<span className={clsx('w-2 h-2 rounded-full shrink-0', dotColor)} />
|
|
97
|
+
<span className="text-xs text-theme-text-secondary flex-1">{category}</span>
|
|
98
|
+
{catIssues > 0 ? (
|
|
99
|
+
<div className="flex items-center gap-2">
|
|
100
|
+
{counts.danger > 0 && (
|
|
101
|
+
<span className={clsx('text-xs font-semibold tabular-nums', SEVERITY_TEXT.error)}>{counts.danger} critical</span>
|
|
102
|
+
)}
|
|
103
|
+
{counts.warning > 0 && (
|
|
104
|
+
<span className={clsx('text-xs font-semibold tabular-nums', SEVERITY_TEXT.warning)}>{counts.warning} warning</span>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
) : (
|
|
108
|
+
<span className={clsx('text-xs font-semibold', SEVERITY_TEXT.success)}>All passing</span>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
})}
|
|
113
|
+
</div>
|
|
114
|
+
</>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div className="px-4 py-1.5 border-t border-theme-border/50 flex items-center justify-end">
|
|
119
|
+
<span className={clsx(
|
|
120
|
+
'flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider transition-colors',
|
|
121
|
+
accentColor,
|
|
122
|
+
)}>
|
|
123
|
+
View Details
|
|
124
|
+
<ArrowRight className="w-3.5 h-3.5 transition-transform group-hover:translate-x-0.5" />
|
|
125
|
+
</span>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</button>
|
|
129
|
+
)
|
|
130
|
+
}
|