@wakastellar/ui 2.1.2 → 2.3.2
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/dist/blocks/apm-overview/index.d.ts +58 -0
- package/dist/blocks/cicd-builder/index.d.ts +47 -0
- package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
- package/dist/blocks/container-orchestrator/index.d.ts +63 -0
- package/dist/blocks/database-admin/index.d.ts +84 -0
- package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
- package/dist/blocks/incident-manager/index.d.ts +44 -0
- package/dist/blocks/index.d.ts +10 -0
- package/dist/blocks/infrastructure-map/index.d.ts +32 -0
- package/dist/blocks/on-call-schedule/index.d.ts +43 -0
- package/dist/blocks/release-notes/index.d.ts +49 -0
- package/dist/components/index.d.ts +34 -0
- package/dist/components/waka-ad-banner/index.d.ts +36 -0
- package/dist/components/waka-ad-fallback/index.d.ts +33 -0
- package/dist/components/waka-ad-inline/index.d.ts +15 -0
- package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
- package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
- package/dist/components/waka-ad-provider/index.d.ts +103 -0
- package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
- package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
- package/dist/components/waka-alert-panel/index.d.ts +45 -0
- package/dist/components/waka-artifact-list/index.d.ts +32 -0
- package/dist/components/waka-build-matrix/index.d.ts +36 -0
- package/dist/components/waka-config-comparator/index.d.ts +37 -0
- package/dist/components/waka-container-list/index.d.ts +51 -0
- package/dist/components/waka-content-recommendation/index.d.ts +23 -0
- package/dist/components/waka-database-card/index.d.ts +46 -0
- package/dist/components/waka-dependency-tree/index.d.ts +38 -0
- package/dist/components/waka-env-var-editor/index.d.ts +30 -0
- package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
- package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
- package/dist/components/waka-log-viewer/index.d.ts +38 -0
- package/dist/components/waka-migration-list/index.d.ts +36 -0
- package/dist/components/waka-outstream-video/index.d.ts +24 -0
- package/dist/components/waka-pod-card/index.d.ts +73 -0
- package/dist/components/waka-query-explain/index.d.ts +48 -0
- package/dist/components/waka-secret-card/index.d.ts +43 -0
- package/dist/components/waka-security-scan-result/index.d.ts +45 -0
- package/dist/components/waka-service-graph/index.d.ts +44 -0
- package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
- package/dist/components/waka-sponsored-card/index.d.ts +25 -0
- package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
- package/dist/components/waka-test-report/index.d.ts +60 -0
- package/dist/components/waka-trace-viewer/index.d.ts +36 -0
- package/dist/components/waka-video-ad/index.d.ts +32 -0
- package/dist/components/waka-video-overlay/index.d.ts +26 -0
- package/dist/index.cjs.js +251 -200
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +47315 -35823
- package/dist/utils/security.d.ts +96 -0
- package/package.json +4 -4
- package/src/blocks/apm-overview/index.tsx +672 -0
- package/src/blocks/cicd-builder/index.tsx +738 -0
- package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
- package/src/blocks/container-orchestrator/index.tsx +729 -0
- package/src/blocks/database-admin/index.tsx +679 -0
- package/src/blocks/gitops-sync-status/index.tsx +557 -0
- package/src/blocks/incident-manager/index.tsx +586 -0
- package/src/blocks/index.ts +119 -0
- package/src/blocks/infrastructure-map/index.tsx +638 -0
- package/src/blocks/on-call-schedule/index.tsx +615 -0
- package/src/blocks/release-notes/index.tsx +643 -0
- package/src/blocks/sidebar/index.tsx +6 -6
- package/src/components/DataTable/templates/index.tsx +3 -2
- package/src/components/index.ts +283 -0
- package/src/components/waka-3d-pie-chart/index.tsx +11 -11
- package/src/components/waka-achievement-unlock/index.tsx +16 -16
- package/src/components/waka-ad-banner/index.tsx +275 -0
- package/src/components/waka-ad-fallback/index.tsx +181 -0
- package/src/components/waka-ad-inline/index.tsx +103 -0
- package/src/components/waka-ad-interstitial/index.tsx +278 -0
- package/src/components/waka-ad-placeholder/index.tsx +84 -0
- package/src/components/waka-ad-provider/index.tsx +329 -0
- package/src/components/waka-ad-sidebar/index.tsx +113 -0
- package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
- package/src/components/waka-alert-panel/index.tsx +493 -0
- package/src/components/waka-artifact-list/index.tsx +416 -0
- package/src/components/waka-badge-showcase/index.tsx +12 -11
- package/src/components/waka-build-matrix/index.tsx +396 -0
- package/src/components/waka-command-bar/index.tsx +2 -1
- package/src/components/waka-config-comparator/index.tsx +416 -0
- package/src/components/waka-container-list/index.tsx +475 -0
- package/src/components/waka-content-recommendation/index.tsx +294 -0
- package/src/components/waka-cost-breakdown/index.tsx +10 -10
- package/src/components/waka-database-card/index.tsx +473 -0
- package/src/components/waka-dependency-tree/index.tsx +542 -0
- package/src/components/waka-env-var-editor/index.tsx +417 -0
- package/src/components/waka-feature-flag-row/index.tsx +386 -0
- package/src/components/waka-funnel-chart/index.tsx +8 -8
- package/src/components/waka-health-pulse/index.tsx +6 -6
- package/src/components/waka-kubernetes-overview/index.tsx +536 -0
- package/src/components/waka-leaderboard/index.tsx +9 -9
- package/src/components/waka-log-viewer/index.tsx +386 -0
- package/src/components/waka-loot-box/index.tsx +20 -20
- package/src/components/waka-migration-list/index.tsx +487 -0
- package/src/components/waka-outstream-video/index.tsx +240 -0
- package/src/components/waka-player-card/index.tsx +5 -5
- package/src/components/waka-pod-card/index.tsx +528 -0
- package/src/components/waka-query-explain/index.tsx +657 -0
- package/src/components/waka-quota-bar/index.tsx +4 -4
- package/src/components/waka-radar-score/index.tsx +10 -10
- package/src/components/waka-scratch-card/index.tsx +5 -4
- package/src/components/waka-secret-card/index.tsx +371 -0
- package/src/components/waka-security-scan-result/index.tsx +473 -0
- package/src/components/waka-server-rack/index.tsx +28 -27
- package/src/components/waka-service-graph/index.tsx +445 -0
- package/src/components/waka-sponsored-badge/index.tsx +97 -0
- package/src/components/waka-sponsored-card/index.tsx +275 -0
- package/src/components/waka-sponsored-feed/index.tsx +127 -0
- package/src/components/waka-spotlight/index.tsx +2 -1
- package/src/components/waka-success-explosion/index.tsx +4 -4
- package/src/components/waka-test-report/index.tsx +469 -0
- package/src/components/waka-trace-viewer/index.tsx +490 -0
- package/src/components/waka-video-ad/index.tsx +406 -0
- package/src/components/waka-video-overlay/index.tsx +257 -0
- package/src/components/waka-xp-bar/index.tsx +13 -13
- package/src/styles/base.css +16 -0
- package/src/styles/tailwind.preset.js +12 -0
- package/src/styles/themes/forest.css +16 -0
- package/src/styles/themes/monochrome.css +16 -0
- package/src/styles/themes/perpetuity.css +16 -0
- package/src/styles/themes/sunset.css +16 -0
- package/src/styles/themes/twilight.css +16 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../utils/cn"
|
|
5
|
+
import { Badge } from "../badge"
|
|
6
|
+
import { Button } from "../button"
|
|
7
|
+
import { Progress } from "../progress"
|
|
8
|
+
import { ScrollArea } from "../scroll-area"
|
|
9
|
+
import { Input } from "../input"
|
|
10
|
+
import {
|
|
11
|
+
Shield,
|
|
12
|
+
ShieldAlert,
|
|
13
|
+
ShieldCheck,
|
|
14
|
+
AlertTriangle,
|
|
15
|
+
AlertCircle,
|
|
16
|
+
Info,
|
|
17
|
+
Search,
|
|
18
|
+
ExternalLink,
|
|
19
|
+
ChevronDown,
|
|
20
|
+
ChevronRight,
|
|
21
|
+
Package,
|
|
22
|
+
FileCode,
|
|
23
|
+
Link,
|
|
24
|
+
Clock,
|
|
25
|
+
} from "lucide-react"
|
|
26
|
+
|
|
27
|
+
export type VulnerabilitySeverity = "critical" | "high" | "medium" | "low" | "info"
|
|
28
|
+
export type ScanType = "sast" | "dast" | "sca" | "secret" | "container"
|
|
29
|
+
|
|
30
|
+
export interface Vulnerability {
|
|
31
|
+
id: string
|
|
32
|
+
title: string
|
|
33
|
+
description: string
|
|
34
|
+
severity: VulnerabilitySeverity
|
|
35
|
+
type: ScanType
|
|
36
|
+
cveId?: string
|
|
37
|
+
cweId?: string
|
|
38
|
+
cvssScore?: number
|
|
39
|
+
package?: string
|
|
40
|
+
version?: string
|
|
41
|
+
fixedVersion?: string
|
|
42
|
+
file?: string
|
|
43
|
+
line?: number
|
|
44
|
+
url?: string
|
|
45
|
+
remediation?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ScanSummary {
|
|
49
|
+
critical: number
|
|
50
|
+
high: number
|
|
51
|
+
medium: number
|
|
52
|
+
low: number
|
|
53
|
+
info: number
|
|
54
|
+
total: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface WakaSecurityScanResultProps {
|
|
58
|
+
/** List of vulnerabilities */
|
|
59
|
+
vulnerabilities: Vulnerability[]
|
|
60
|
+
/** Scan type */
|
|
61
|
+
scanType?: ScanType | "all"
|
|
62
|
+
/** Scan timestamp */
|
|
63
|
+
scannedAt?: Date
|
|
64
|
+
/** Callback when clicking on a vulnerability */
|
|
65
|
+
onVulnerabilityClick?: (vuln: Vulnerability) => void
|
|
66
|
+
/** Callback when viewing CVE details */
|
|
67
|
+
onViewCVE?: (cveId: string) => void
|
|
68
|
+
/** Title */
|
|
69
|
+
title?: string
|
|
70
|
+
/** Custom class name */
|
|
71
|
+
className?: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const severityConfig: Record<VulnerabilitySeverity, { icon: React.ElementType; color: string; bgColor: string; label: string; score: string }> = {
|
|
75
|
+
critical: { icon: ShieldAlert, color: "text-red-600", bgColor: "bg-red-500", label: "Critical", score: "9.0-10.0" },
|
|
76
|
+
high: { icon: AlertCircle, color: "text-orange-500", bgColor: "bg-orange-500", label: "High", score: "7.0-8.9" },
|
|
77
|
+
medium: { icon: AlertTriangle, color: "text-yellow-500", bgColor: "bg-yellow-500", label: "Medium", score: "4.0-6.9" },
|
|
78
|
+
low: { icon: Info, color: "text-blue-500", bgColor: "bg-blue-500", label: "Low", score: "0.1-3.9" },
|
|
79
|
+
info: { icon: Info, color: "text-gray-500", bgColor: "bg-gray-500", label: "Info", score: "0" },
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const scanTypeConfig: Record<ScanType, { icon: React.ElementType; label: string; description: string }> = {
|
|
83
|
+
sast: { icon: FileCode, label: "SAST", description: "Static Application Security Testing" },
|
|
84
|
+
dast: { icon: Link, label: "DAST", description: "Dynamic Application Security Testing" },
|
|
85
|
+
sca: { icon: Package, label: "SCA", description: "Software Composition Analysis" },
|
|
86
|
+
secret: { icon: Shield, label: "Secrets", description: "Secret Detection" },
|
|
87
|
+
container: { icon: Package, label: "Container", description: "Container Image Scanning" },
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function SeverityBadge({ severity }: { severity: VulnerabilitySeverity }) {
|
|
91
|
+
const config = severityConfig[severity]
|
|
92
|
+
return (
|
|
93
|
+
<Badge className={cn("text-white", config.bgColor)}>
|
|
94
|
+
{config.label}
|
|
95
|
+
</Badge>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function VulnerabilityCard({
|
|
100
|
+
vulnerability,
|
|
101
|
+
isExpanded,
|
|
102
|
+
onToggle,
|
|
103
|
+
onViewCVE,
|
|
104
|
+
}: {
|
|
105
|
+
vulnerability: Vulnerability
|
|
106
|
+
isExpanded: boolean
|
|
107
|
+
onToggle: () => void
|
|
108
|
+
onViewCVE?: (cveId: string) => void
|
|
109
|
+
}) {
|
|
110
|
+
const sevConfig = severityConfig[vulnerability.severity]
|
|
111
|
+
const typeConfig = scanTypeConfig[vulnerability.type]
|
|
112
|
+
const SevIcon = sevConfig.icon
|
|
113
|
+
const TypeIcon = typeConfig.icon
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div
|
|
117
|
+
className={cn(
|
|
118
|
+
"border rounded-lg overflow-hidden",
|
|
119
|
+
vulnerability.severity === "critical" && "border-red-500/50",
|
|
120
|
+
vulnerability.severity === "high" && "border-orange-500/50"
|
|
121
|
+
)}
|
|
122
|
+
>
|
|
123
|
+
<button
|
|
124
|
+
className={cn(
|
|
125
|
+
"w-full flex items-start gap-3 p-4 text-left hover:bg-muted/50 transition-colors",
|
|
126
|
+
vulnerability.severity === "critical" && "bg-red-500/5",
|
|
127
|
+
vulnerability.severity === "high" && "bg-orange-500/5"
|
|
128
|
+
)}
|
|
129
|
+
onClick={onToggle}
|
|
130
|
+
>
|
|
131
|
+
{isExpanded ? (
|
|
132
|
+
<ChevronDown className="h-4 w-4 mt-1 text-muted-foreground shrink-0" />
|
|
133
|
+
) : (
|
|
134
|
+
<ChevronRight className="h-4 w-4 mt-1 text-muted-foreground shrink-0" />
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
<SevIcon className={cn("h-5 w-5 mt-0.5 shrink-0", sevConfig.color)} />
|
|
138
|
+
|
|
139
|
+
<div className="flex-1 min-w-0">
|
|
140
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
141
|
+
<span className="font-medium">{vulnerability.title}</span>
|
|
142
|
+
<SeverityBadge severity={vulnerability.severity} />
|
|
143
|
+
<Badge variant="outline" className="text-xs">
|
|
144
|
+
<TypeIcon className="h-3 w-3 mr-1" />
|
|
145
|
+
{typeConfig.label}
|
|
146
|
+
</Badge>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">
|
|
150
|
+
{vulnerability.description}
|
|
151
|
+
</p>
|
|
152
|
+
|
|
153
|
+
<div className="flex items-center gap-3 mt-2 text-xs text-muted-foreground">
|
|
154
|
+
{vulnerability.cveId && (
|
|
155
|
+
<button
|
|
156
|
+
className="text-primary hover:underline flex items-center gap-1"
|
|
157
|
+
onClick={(e) => {
|
|
158
|
+
e.stopPropagation()
|
|
159
|
+
onViewCVE?.(vulnerability.cveId!)
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
{vulnerability.cveId}
|
|
163
|
+
<ExternalLink className="h-3 w-3" />
|
|
164
|
+
</button>
|
|
165
|
+
)}
|
|
166
|
+
{vulnerability.cvssScore && (
|
|
167
|
+
<span>CVSS: {vulnerability.cvssScore.toFixed(1)}</span>
|
|
168
|
+
)}
|
|
169
|
+
{vulnerability.package && (
|
|
170
|
+
<span className="flex items-center gap-1">
|
|
171
|
+
<Package className="h-3 w-3" />
|
|
172
|
+
{vulnerability.package}@{vulnerability.version}
|
|
173
|
+
</span>
|
|
174
|
+
)}
|
|
175
|
+
{vulnerability.file && (
|
|
176
|
+
<span className="truncate max-w-[200px]">{vulnerability.file}:{vulnerability.line}</span>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</button>
|
|
181
|
+
|
|
182
|
+
{isExpanded && (
|
|
183
|
+
<div className="border-t bg-muted/20 p-4 space-y-4">
|
|
184
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
|
185
|
+
{vulnerability.cveId && (
|
|
186
|
+
<div>
|
|
187
|
+
<span className="text-muted-foreground">CVE ID:</span>{" "}
|
|
188
|
+
<span className="font-mono">{vulnerability.cveId}</span>
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
{vulnerability.cweId && (
|
|
192
|
+
<div>
|
|
193
|
+
<span className="text-muted-foreground">CWE ID:</span>{" "}
|
|
194
|
+
<span className="font-mono">{vulnerability.cweId}</span>
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
{vulnerability.cvssScore && (
|
|
198
|
+
<div>
|
|
199
|
+
<span className="text-muted-foreground">CVSS Score:</span>{" "}
|
|
200
|
+
<span className="font-medium">{vulnerability.cvssScore.toFixed(1)}</span>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
{vulnerability.package && (
|
|
204
|
+
<div>
|
|
205
|
+
<span className="text-muted-foreground">Package:</span>{" "}
|
|
206
|
+
<span className="font-mono">{vulnerability.package}@{vulnerability.version}</span>
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
{vulnerability.fixedVersion && (
|
|
210
|
+
<div>
|
|
211
|
+
<span className="text-muted-foreground">Fixed in:</span>{" "}
|
|
212
|
+
<span className="font-mono text-green-500">{vulnerability.fixedVersion}</span>
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
{vulnerability.file && (
|
|
216
|
+
<div>
|
|
217
|
+
<span className="text-muted-foreground">Location:</span>{" "}
|
|
218
|
+
<span className="font-mono">{vulnerability.file}:{vulnerability.line}</span>
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
{vulnerability.remediation && (
|
|
224
|
+
<div>
|
|
225
|
+
<div className="text-sm font-medium mb-1">Remediation</div>
|
|
226
|
+
<p className="text-sm text-muted-foreground">{vulnerability.remediation}</p>
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{vulnerability.url && (
|
|
231
|
+
<Button
|
|
232
|
+
variant="outline"
|
|
233
|
+
size="sm"
|
|
234
|
+
onClick={() => window.open(vulnerability.url, "_blank")}
|
|
235
|
+
>
|
|
236
|
+
<ExternalLink className="h-4 w-4 mr-2" />
|
|
237
|
+
Learn More
|
|
238
|
+
</Button>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function WakaSecurityScanResult({
|
|
247
|
+
vulnerabilities,
|
|
248
|
+
scanType = "all",
|
|
249
|
+
scannedAt,
|
|
250
|
+
onVulnerabilityClick,
|
|
251
|
+
onViewCVE,
|
|
252
|
+
title = "Security Scan Results",
|
|
253
|
+
className,
|
|
254
|
+
}: WakaSecurityScanResultProps) {
|
|
255
|
+
const [searchQuery, setSearchQuery] = React.useState("")
|
|
256
|
+
const [severityFilter, setSeverityFilter] = React.useState<VulnerabilitySeverity | "all">("all")
|
|
257
|
+
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(new Set())
|
|
258
|
+
|
|
259
|
+
const toggleExpand = (id: string) => {
|
|
260
|
+
setExpandedIds((prev) => {
|
|
261
|
+
const next = new Set(prev)
|
|
262
|
+
if (next.has(id)) {
|
|
263
|
+
next.delete(id)
|
|
264
|
+
} else {
|
|
265
|
+
next.add(id)
|
|
266
|
+
}
|
|
267
|
+
return next
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Calculate summary
|
|
272
|
+
const summary: ScanSummary = React.useMemo(() => {
|
|
273
|
+
return vulnerabilities.reduce(
|
|
274
|
+
(acc, vuln) => {
|
|
275
|
+
acc[vuln.severity]++
|
|
276
|
+
acc.total++
|
|
277
|
+
return acc
|
|
278
|
+
},
|
|
279
|
+
{ critical: 0, high: 0, medium: 0, low: 0, info: 0, total: 0 }
|
|
280
|
+
)
|
|
281
|
+
}, [vulnerabilities])
|
|
282
|
+
|
|
283
|
+
// Filter vulnerabilities
|
|
284
|
+
const filteredVulns = React.useMemo(() => {
|
|
285
|
+
return vulnerabilities.filter((vuln) => {
|
|
286
|
+
if (severityFilter !== "all" && vuln.severity !== severityFilter) return false
|
|
287
|
+
if (searchQuery) {
|
|
288
|
+
const query = searchQuery.toLowerCase()
|
|
289
|
+
return (
|
|
290
|
+
vuln.title.toLowerCase().includes(query) ||
|
|
291
|
+
vuln.description.toLowerCase().includes(query) ||
|
|
292
|
+
vuln.cveId?.toLowerCase().includes(query) ||
|
|
293
|
+
vuln.package?.toLowerCase().includes(query)
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
return true
|
|
297
|
+
})
|
|
298
|
+
}, [vulnerabilities, severityFilter, searchQuery])
|
|
299
|
+
|
|
300
|
+
// Security score (simple calculation)
|
|
301
|
+
const securityScore = React.useMemo(() => {
|
|
302
|
+
const weights = { critical: 40, high: 20, medium: 5, low: 1, info: 0 }
|
|
303
|
+
const penalty = Object.entries(summary).reduce((acc, [key, count]) => {
|
|
304
|
+
if (key === "total") return acc
|
|
305
|
+
return acc + (weights[key as VulnerabilitySeverity] || 0) * count
|
|
306
|
+
}, 0)
|
|
307
|
+
return Math.max(0, 100 - penalty)
|
|
308
|
+
}, [summary])
|
|
309
|
+
|
|
310
|
+
const scoreColor = securityScore >= 80 ? "text-green-500" : securityScore >= 50 ? "text-yellow-500" : "text-red-500"
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<div className={cn("flex flex-col border rounded-lg bg-background", className)}>
|
|
314
|
+
{/* Header */}
|
|
315
|
+
<div className="flex items-center justify-between gap-4 p-3 border-b">
|
|
316
|
+
<div className="flex items-center gap-3">
|
|
317
|
+
{summary.critical > 0 || summary.high > 0 ? (
|
|
318
|
+
<ShieldAlert className="h-5 w-5 text-red-500" />
|
|
319
|
+
) : (
|
|
320
|
+
<ShieldCheck className="h-5 w-5 text-green-500" />
|
|
321
|
+
)}
|
|
322
|
+
<h3 className="font-semibold">{title}</h3>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<div className="flex items-center gap-3">
|
|
326
|
+
<div className="text-right">
|
|
327
|
+
<div className={cn("text-2xl font-bold", scoreColor)}>{securityScore}</div>
|
|
328
|
+
<div className="text-xs text-muted-foreground">Security Score</div>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
{/* Summary */}
|
|
334
|
+
<div className="grid grid-cols-5 gap-2 p-3 border-b bg-muted/30">
|
|
335
|
+
{(["critical", "high", "medium", "low", "info"] as VulnerabilitySeverity[]).map((severity) => {
|
|
336
|
+
const config = severityConfig[severity]
|
|
337
|
+
const count = summary[severity]
|
|
338
|
+
return (
|
|
339
|
+
<button
|
|
340
|
+
key={severity}
|
|
341
|
+
className={cn(
|
|
342
|
+
"flex flex-col items-center p-2 rounded-lg transition-colors",
|
|
343
|
+
severityFilter === severity ? "bg-background ring-1 ring-primary" : "hover:bg-background/50",
|
|
344
|
+
count > 0 && severity === "critical" && "bg-red-500/10",
|
|
345
|
+
count > 0 && severity === "high" && "bg-orange-500/10"
|
|
346
|
+
)}
|
|
347
|
+
onClick={() => setSeverityFilter(severityFilter === severity ? "all" : severity)}
|
|
348
|
+
>
|
|
349
|
+
<span className={cn("text-2xl font-bold", config.color)}>{count}</span>
|
|
350
|
+
<span className="text-xs text-muted-foreground capitalize">{severity}</span>
|
|
351
|
+
</button>
|
|
352
|
+
)
|
|
353
|
+
})}
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
{/* Toolbar */}
|
|
357
|
+
<div className="flex items-center gap-2 p-2 border-b">
|
|
358
|
+
<div className="relative flex-1 max-w-sm">
|
|
359
|
+
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
360
|
+
<Input
|
|
361
|
+
placeholder="Search vulnerabilities..."
|
|
362
|
+
value={searchQuery}
|
|
363
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
364
|
+
className="pl-8 h-8"
|
|
365
|
+
/>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
{scannedAt && (
|
|
369
|
+
<span className="text-xs text-muted-foreground flex items-center gap-1">
|
|
370
|
+
<Clock className="h-3 w-3" />
|
|
371
|
+
Scanned {scannedAt.toLocaleString()}
|
|
372
|
+
</span>
|
|
373
|
+
)}
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
{/* Vulnerabilities list */}
|
|
377
|
+
<ScrollArea className="flex-1 max-h-[500px]">
|
|
378
|
+
<div className="p-3 space-y-2">
|
|
379
|
+
{filteredVulns.length === 0 ? (
|
|
380
|
+
<div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
|
|
381
|
+
<ShieldCheck className="h-8 w-8 mb-2 text-green-500" />
|
|
382
|
+
<span>No vulnerabilities found</span>
|
|
383
|
+
</div>
|
|
384
|
+
) : (
|
|
385
|
+
filteredVulns.map((vuln) => (
|
|
386
|
+
<VulnerabilityCard
|
|
387
|
+
key={vuln.id}
|
|
388
|
+
vulnerability={vuln}
|
|
389
|
+
isExpanded={expandedIds.has(vuln.id)}
|
|
390
|
+
onToggle={() => toggleExpand(vuln.id)}
|
|
391
|
+
onViewCVE={onViewCVE}
|
|
392
|
+
/>
|
|
393
|
+
))
|
|
394
|
+
)}
|
|
395
|
+
</div>
|
|
396
|
+
</ScrollArea>
|
|
397
|
+
|
|
398
|
+
{/* Footer */}
|
|
399
|
+
<div className="flex items-center justify-between px-3 py-2 border-t bg-muted/30 text-sm">
|
|
400
|
+
<span className="text-muted-foreground">
|
|
401
|
+
{filteredVulns.length} of {vulnerabilities.length} vulnerabilities
|
|
402
|
+
</span>
|
|
403
|
+
{summary.critical > 0 && (
|
|
404
|
+
<span className="text-red-500 font-medium">
|
|
405
|
+
{summary.critical} critical issues require immediate attention
|
|
406
|
+
</span>
|
|
407
|
+
)}
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Default sample vulnerabilities for demo
|
|
414
|
+
export const defaultVulnerabilities: Vulnerability[] = [
|
|
415
|
+
{
|
|
416
|
+
id: "1",
|
|
417
|
+
title: "SQL Injection in login endpoint",
|
|
418
|
+
description: "User input is directly concatenated into SQL query without proper sanitization, allowing attackers to execute arbitrary SQL commands.",
|
|
419
|
+
severity: "critical",
|
|
420
|
+
type: "sast",
|
|
421
|
+
cweId: "CWE-89",
|
|
422
|
+
cvssScore: 9.8,
|
|
423
|
+
file: "src/auth/login.ts",
|
|
424
|
+
line: 45,
|
|
425
|
+
remediation: "Use parameterized queries or prepared statements to prevent SQL injection attacks.",
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
id: "2",
|
|
429
|
+
title: "Prototype Pollution in lodash",
|
|
430
|
+
description: "Versions of lodash prior to 4.17.21 are vulnerable to prototype pollution via the template function.",
|
|
431
|
+
severity: "high",
|
|
432
|
+
type: "sca",
|
|
433
|
+
cveId: "CVE-2021-23337",
|
|
434
|
+
cvssScore: 7.2,
|
|
435
|
+
package: "lodash",
|
|
436
|
+
version: "4.17.19",
|
|
437
|
+
fixedVersion: "4.17.21",
|
|
438
|
+
url: "https://nvd.nist.gov/vuln/detail/CVE-2021-23337",
|
|
439
|
+
remediation: "Upgrade lodash to version 4.17.21 or later.",
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: "3",
|
|
443
|
+
title: "Hardcoded API Key",
|
|
444
|
+
description: "AWS API key found hardcoded in source code. This exposes the key to anyone with access to the repository.",
|
|
445
|
+
severity: "high",
|
|
446
|
+
type: "secret",
|
|
447
|
+
file: "config/aws.ts",
|
|
448
|
+
line: 12,
|
|
449
|
+
remediation: "Remove the hardcoded key and use environment variables or a secrets manager.",
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
id: "4",
|
|
453
|
+
title: "Cross-Site Scripting (XSS)",
|
|
454
|
+
description: "User-provided content is rendered without proper escaping, allowing script injection.",
|
|
455
|
+
severity: "medium",
|
|
456
|
+
type: "dast",
|
|
457
|
+
cweId: "CWE-79",
|
|
458
|
+
cvssScore: 6.1,
|
|
459
|
+
url: "https://owasp.org/www-community/attacks/xss/",
|
|
460
|
+
remediation: "Sanitize and escape all user-provided content before rendering.",
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
id: "5",
|
|
464
|
+
title: "Outdated base image",
|
|
465
|
+
description: "Container uses node:14 base image which has known vulnerabilities.",
|
|
466
|
+
severity: "low",
|
|
467
|
+
type: "container",
|
|
468
|
+
package: "node",
|
|
469
|
+
version: "14.0.0",
|
|
470
|
+
fixedVersion: "18.0.0",
|
|
471
|
+
remediation: "Update to a more recent Node.js LTS version.",
|
|
472
|
+
},
|
|
473
|
+
]
|
|
@@ -49,26 +49,27 @@ export interface WakaServerRackProps {
|
|
|
49
49
|
// Constants
|
|
50
50
|
// ============================================================================
|
|
51
51
|
|
|
52
|
+
// Status colors using CSS variables for theme support
|
|
52
53
|
const STATUS_COLORS = {
|
|
53
54
|
online: {
|
|
54
|
-
led: "
|
|
55
|
-
glow: "
|
|
56
|
-
bg: "
|
|
55
|
+
led: "hsl(var(--success))",
|
|
56
|
+
glow: "hsl(var(--success) / 0.6)",
|
|
57
|
+
bg: "hsl(var(--success) / 0.1)",
|
|
57
58
|
},
|
|
58
59
|
warning: {
|
|
59
|
-
led: "
|
|
60
|
-
glow: "
|
|
61
|
-
bg: "
|
|
60
|
+
led: "hsl(var(--warning))",
|
|
61
|
+
glow: "hsl(var(--warning) / 0.6)",
|
|
62
|
+
bg: "hsl(var(--warning) / 0.1)",
|
|
62
63
|
},
|
|
63
64
|
offline: {
|
|
64
|
-
led: "
|
|
65
|
-
glow: "
|
|
66
|
-
bg: "
|
|
65
|
+
led: "hsl(var(--destructive))",
|
|
66
|
+
glow: "hsl(var(--destructive) / 0.6)",
|
|
67
|
+
bg: "hsl(var(--destructive) / 0.1)",
|
|
67
68
|
},
|
|
68
69
|
maintenance: {
|
|
69
|
-
led: "
|
|
70
|
-
glow: "
|
|
71
|
-
bg: "
|
|
70
|
+
led: "hsl(var(--info))",
|
|
71
|
+
glow: "hsl(var(--info) / 0.6)",
|
|
72
|
+
bg: "hsl(var(--info) / 0.1)",
|
|
72
73
|
},
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -87,8 +88,8 @@ interface MetricBarProps {
|
|
|
87
88
|
|
|
88
89
|
function MetricBar({ value, label, color, animated }: MetricBarProps) {
|
|
89
90
|
const getBarColor = () => {
|
|
90
|
-
if (value >= 90) return "
|
|
91
|
-
if (value >= 70) return "
|
|
91
|
+
if (value >= 90) return "hsl(var(--destructive))"
|
|
92
|
+
if (value >= 70) return "hsl(var(--warning))"
|
|
92
93
|
return color
|
|
93
94
|
}
|
|
94
95
|
|
|
@@ -245,20 +246,20 @@ function ServerUnitRow({
|
|
|
245
246
|
<MetricBar
|
|
246
247
|
value={server.cpu}
|
|
247
248
|
label="CPU"
|
|
248
|
-
color="
|
|
249
|
+
color="hsl(var(--chart-1))"
|
|
249
250
|
animated={animated}
|
|
250
251
|
/>
|
|
251
252
|
<MetricBar
|
|
252
253
|
value={server.ram}
|
|
253
254
|
label="RAM"
|
|
254
|
-
color="
|
|
255
|
+
color="hsl(var(--chart-3))"
|
|
255
256
|
animated={animated}
|
|
256
257
|
/>
|
|
257
258
|
{server.height >= 3 && (
|
|
258
259
|
<MetricBar
|
|
259
260
|
value={server.disk}
|
|
260
261
|
label="DSK"
|
|
261
|
-
color="
|
|
262
|
+
color="hsl(var(--info))"
|
|
262
263
|
animated={animated}
|
|
263
264
|
/>
|
|
264
265
|
)}
|
|
@@ -279,8 +280,8 @@ function ServerUnitRow({
|
|
|
279
280
|
<div className="flex items-center gap-0.5">
|
|
280
281
|
{server.network && (
|
|
281
282
|
<div className="flex flex-col items-end mr-2 text-[7px] font-mono text-zinc-500">
|
|
282
|
-
<span className="text-
|
|
283
|
-
<span className="text-
|
|
283
|
+
<span className="text-success">{"\u25B2"} {server.network.out}</span>
|
|
284
|
+
<span className="text-info">{"\u25BC"} {server.network.in}</span>
|
|
284
285
|
</div>
|
|
285
286
|
)}
|
|
286
287
|
{/* Drive activity indicators */}
|
|
@@ -344,21 +345,21 @@ function ServerUnitRow({
|
|
|
344
345
|
<span className="font-mono">{server.height}U</span>
|
|
345
346
|
</div>
|
|
346
347
|
<div className="border-t border-zinc-700 my-2" />
|
|
347
|
-
<MetricBar value={server.cpu} label="CPU" color="
|
|
348
|
-
<MetricBar value={server.ram} label="RAM" color="
|
|
349
|
-
<MetricBar value={server.disk} label="DSK" color="
|
|
348
|
+
<MetricBar value={server.cpu} label="CPU" color="hsl(var(--chart-1))" />
|
|
349
|
+
<MetricBar value={server.ram} label="RAM" color="hsl(var(--chart-3))" />
|
|
350
|
+
<MetricBar value={server.disk} label="DSK" color="hsl(var(--info))" />
|
|
350
351
|
{server.network && (
|
|
351
352
|
<>
|
|
352
353
|
<div className="border-t border-zinc-700 my-2" />
|
|
353
354
|
<div className="flex justify-between text-zinc-400">
|
|
354
355
|
<span>Network In</span>
|
|
355
|
-
<span className="font-mono text-
|
|
356
|
+
<span className="font-mono text-info">
|
|
356
357
|
{server.network.in} Mbps
|
|
357
358
|
</span>
|
|
358
359
|
</div>
|
|
359
360
|
<div className="flex justify-between text-zinc-400">
|
|
360
361
|
<span>Network Out</span>
|
|
361
|
-
<span className="font-mono text-
|
|
362
|
+
<span className="font-mono text-success">
|
|
362
363
|
{server.network.out} Mbps
|
|
363
364
|
</span>
|
|
364
365
|
</div>
|
|
@@ -626,10 +627,10 @@ export function WakaServerRack({
|
|
|
626
627
|
style={{
|
|
627
628
|
background: `radial-gradient(ellipse at center, ${
|
|
628
629
|
servers.some((s) => s.status === "offline")
|
|
629
|
-
? "
|
|
630
|
+
? "hsl(var(--destructive) / 0.05)"
|
|
630
631
|
: servers.some((s) => s.status === "warning")
|
|
631
|
-
? "
|
|
632
|
-
: "
|
|
632
|
+
? "hsl(var(--warning) / 0.05)"
|
|
633
|
+
: "hsl(var(--success) / 0.03)"
|
|
633
634
|
} 0%, transparent 70%)`,
|
|
634
635
|
filter: "blur(20px)",
|
|
635
636
|
}}
|