@wakastellar/ui 2.1.2 → 2.3.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/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 +19 -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-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-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-test-report/index.d.ts +60 -0
- package/dist/components/waka-trace-viewer/index.d.ts +36 -0
- package/dist/index.cjs.js +239 -194
- package/dist/index.es.js +45560 -35791
- package/package.json +1 -1
- 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/components/index.ts +189 -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-build-matrix/index.tsx +396 -0
- package/src/components/waka-config-comparator/index.tsx +416 -0
- package/src/components/waka-container-list/index.tsx +475 -0
- 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-kubernetes-overview/index.tsx +536 -0
- package/src/components/waka-log-viewer/index.tsx +386 -0
- package/src/components/waka-migration-list/index.tsx +487 -0
- package/src/components/waka-pod-card/index.tsx +528 -0
- package/src/components/waka-query-explain/index.tsx +657 -0
- 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-service-graph/index.tsx +445 -0
- package/src/components/waka-test-report/index.tsx +469 -0
- package/src/components/waka-trace-viewer/index.tsx +490 -0
|
@@ -0,0 +1,542 @@
|
|
|
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 { Input } from "../input"
|
|
8
|
+
import { ScrollArea } from "../scroll-area"
|
|
9
|
+
import {
|
|
10
|
+
Tooltip,
|
|
11
|
+
TooltipContent,
|
|
12
|
+
TooltipProvider,
|
|
13
|
+
TooltipTrigger,
|
|
14
|
+
} from "../tooltip"
|
|
15
|
+
import {
|
|
16
|
+
Package,
|
|
17
|
+
ChevronDown,
|
|
18
|
+
ChevronRight,
|
|
19
|
+
Search,
|
|
20
|
+
AlertTriangle,
|
|
21
|
+
AlertCircle,
|
|
22
|
+
Shield,
|
|
23
|
+
ShieldAlert,
|
|
24
|
+
ExternalLink,
|
|
25
|
+
GitBranch,
|
|
26
|
+
ArrowUpCircle,
|
|
27
|
+
} from "lucide-react"
|
|
28
|
+
|
|
29
|
+
export type DependencyType = "direct" | "transitive" | "dev" | "peer" | "optional"
|
|
30
|
+
export type VulnSeverity = "critical" | "high" | "medium" | "low" | "none"
|
|
31
|
+
|
|
32
|
+
export interface Dependency {
|
|
33
|
+
id: string
|
|
34
|
+
name: string
|
|
35
|
+
version: string
|
|
36
|
+
latestVersion?: string
|
|
37
|
+
type: DependencyType
|
|
38
|
+
license?: string
|
|
39
|
+
vulnerabilities?: {
|
|
40
|
+
critical: number
|
|
41
|
+
high: number
|
|
42
|
+
medium: number
|
|
43
|
+
low: number
|
|
44
|
+
}
|
|
45
|
+
outdated?: boolean
|
|
46
|
+
children?: Dependency[]
|
|
47
|
+
size?: number // in bytes
|
|
48
|
+
repository?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface WakaDependencyTreeProps {
|
|
52
|
+
/** Root dependencies */
|
|
53
|
+
dependencies: Dependency[]
|
|
54
|
+
/** Callback when clicking on a dependency */
|
|
55
|
+
onDependencyClick?: (dep: Dependency) => void
|
|
56
|
+
/** Callback when upgrading a dependency */
|
|
57
|
+
onUpgrade?: (dep: Dependency) => void
|
|
58
|
+
/** Title */
|
|
59
|
+
title?: string
|
|
60
|
+
/** Show dev dependencies */
|
|
61
|
+
showDevDeps?: boolean
|
|
62
|
+
/** Show only vulnerable */
|
|
63
|
+
showVulnerableOnly?: boolean
|
|
64
|
+
/** Custom class name */
|
|
65
|
+
className?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const typeConfig: Record<DependencyType, { label: string; color: string }> = {
|
|
69
|
+
direct: { label: "Direct", color: "bg-blue-500" },
|
|
70
|
+
transitive: { label: "Transitive", color: "bg-gray-500" },
|
|
71
|
+
dev: { label: "Dev", color: "bg-purple-500" },
|
|
72
|
+
peer: { label: "Peer", color: "bg-orange-500" },
|
|
73
|
+
optional: { label: "Optional", color: "bg-cyan-500" },
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const vulnColors: Record<VulnSeverity, string> = {
|
|
77
|
+
critical: "text-red-600",
|
|
78
|
+
high: "text-orange-500",
|
|
79
|
+
medium: "text-yellow-500",
|
|
80
|
+
low: "text-blue-500",
|
|
81
|
+
none: "text-green-500",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getHighestSeverity(vuln?: Dependency["vulnerabilities"]): VulnSeverity {
|
|
85
|
+
if (!vuln) return "none"
|
|
86
|
+
if (vuln.critical > 0) return "critical"
|
|
87
|
+
if (vuln.high > 0) return "high"
|
|
88
|
+
if (vuln.medium > 0) return "medium"
|
|
89
|
+
if (vuln.low > 0) return "low"
|
|
90
|
+
return "none"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getTotalVulns(vuln?: Dependency["vulnerabilities"]): number {
|
|
94
|
+
if (!vuln) return 0
|
|
95
|
+
return vuln.critical + vuln.high + vuln.medium + vuln.low
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatBytes(bytes?: number): string {
|
|
99
|
+
if (!bytes) return ""
|
|
100
|
+
if (bytes < 1024) return `${bytes} B`
|
|
101
|
+
const k = 1024
|
|
102
|
+
const sizes = ["B", "KB", "MB"]
|
|
103
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
104
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function DependencyNode({
|
|
108
|
+
dependency,
|
|
109
|
+
depth,
|
|
110
|
+
isExpanded,
|
|
111
|
+
onToggle,
|
|
112
|
+
onDependencyClick,
|
|
113
|
+
onUpgrade,
|
|
114
|
+
}: {
|
|
115
|
+
dependency: Dependency
|
|
116
|
+
depth: number
|
|
117
|
+
isExpanded: boolean
|
|
118
|
+
onToggle: () => void
|
|
119
|
+
onDependencyClick?: (dep: Dependency) => void
|
|
120
|
+
onUpgrade?: (dep: Dependency) => void
|
|
121
|
+
}) {
|
|
122
|
+
const hasChildren = dependency.children && dependency.children.length > 0
|
|
123
|
+
const severity = getHighestSeverity(dependency.vulnerabilities)
|
|
124
|
+
const totalVulns = getTotalVulns(dependency.vulnerabilities)
|
|
125
|
+
const typeConf = typeConfig[dependency.type]
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div
|
|
129
|
+
className={cn(
|
|
130
|
+
"flex items-center gap-2 px-2 py-1.5 hover:bg-muted/50 transition-colors rounded",
|
|
131
|
+
severity === "critical" && "bg-red-500/5",
|
|
132
|
+
severity === "high" && "bg-orange-500/5"
|
|
133
|
+
)}
|
|
134
|
+
style={{ paddingLeft: `${depth * 20 + 8}px` }}
|
|
135
|
+
>
|
|
136
|
+
{/* Expand button */}
|
|
137
|
+
<button
|
|
138
|
+
className="w-5 h-5 flex items-center justify-center text-muted-foreground"
|
|
139
|
+
onClick={onToggle}
|
|
140
|
+
disabled={!hasChildren}
|
|
141
|
+
>
|
|
142
|
+
{hasChildren ? (
|
|
143
|
+
isExpanded ? (
|
|
144
|
+
<ChevronDown className="h-4 w-4" />
|
|
145
|
+
) : (
|
|
146
|
+
<ChevronRight className="h-4 w-4" />
|
|
147
|
+
)
|
|
148
|
+
) : (
|
|
149
|
+
<span className="w-4" />
|
|
150
|
+
)}
|
|
151
|
+
</button>
|
|
152
|
+
|
|
153
|
+
{/* Package icon with severity indicator */}
|
|
154
|
+
<div className="relative">
|
|
155
|
+
<Package className="h-4 w-4 text-muted-foreground" />
|
|
156
|
+
{severity !== "none" && (
|
|
157
|
+
<div className={cn("absolute -top-1 -right-1 w-2 h-2 rounded-full",
|
|
158
|
+
severity === "critical" ? "bg-red-500" :
|
|
159
|
+
severity === "high" ? "bg-orange-500" :
|
|
160
|
+
severity === "medium" ? "bg-yellow-500" : "bg-blue-500"
|
|
161
|
+
)} />
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
{/* Package name */}
|
|
166
|
+
<button
|
|
167
|
+
className="font-medium text-sm hover:text-primary hover:underline"
|
|
168
|
+
onClick={() => onDependencyClick?.(dependency)}
|
|
169
|
+
>
|
|
170
|
+
{dependency.name}
|
|
171
|
+
</button>
|
|
172
|
+
|
|
173
|
+
{/* Version */}
|
|
174
|
+
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">
|
|
175
|
+
{dependency.version}
|
|
176
|
+
</code>
|
|
177
|
+
|
|
178
|
+
{/* Type badge */}
|
|
179
|
+
<Badge variant="outline" className="text-xs">
|
|
180
|
+
{typeConf.label}
|
|
181
|
+
</Badge>
|
|
182
|
+
|
|
183
|
+
{/* Vulnerabilities */}
|
|
184
|
+
{totalVulns > 0 && (
|
|
185
|
+
<TooltipProvider>
|
|
186
|
+
<Tooltip>
|
|
187
|
+
<TooltipTrigger asChild>
|
|
188
|
+
<Badge className={cn("text-xs text-white",
|
|
189
|
+
severity === "critical" ? "bg-red-500" :
|
|
190
|
+
severity === "high" ? "bg-orange-500" :
|
|
191
|
+
severity === "medium" ? "bg-yellow-500" : "bg-blue-500"
|
|
192
|
+
)}>
|
|
193
|
+
<ShieldAlert className="h-3 w-3 mr-1" />
|
|
194
|
+
{totalVulns}
|
|
195
|
+
</Badge>
|
|
196
|
+
</TooltipTrigger>
|
|
197
|
+
<TooltipContent>
|
|
198
|
+
<div className="space-y-1 text-xs">
|
|
199
|
+
{dependency.vulnerabilities?.critical! > 0 && (
|
|
200
|
+
<div className="text-red-500">{dependency.vulnerabilities?.critical} critical</div>
|
|
201
|
+
)}
|
|
202
|
+
{dependency.vulnerabilities?.high! > 0 && (
|
|
203
|
+
<div className="text-orange-500">{dependency.vulnerabilities?.high} high</div>
|
|
204
|
+
)}
|
|
205
|
+
{dependency.vulnerabilities?.medium! > 0 && (
|
|
206
|
+
<div className="text-yellow-500">{dependency.vulnerabilities?.medium} medium</div>
|
|
207
|
+
)}
|
|
208
|
+
{dependency.vulnerabilities?.low! > 0 && (
|
|
209
|
+
<div className="text-blue-500">{dependency.vulnerabilities?.low} low</div>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
</TooltipContent>
|
|
213
|
+
</Tooltip>
|
|
214
|
+
</TooltipProvider>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
{/* Outdated indicator */}
|
|
218
|
+
{dependency.outdated && dependency.latestVersion && (
|
|
219
|
+
<TooltipProvider>
|
|
220
|
+
<Tooltip>
|
|
221
|
+
<TooltipTrigger asChild>
|
|
222
|
+
<Button
|
|
223
|
+
variant="ghost"
|
|
224
|
+
size="sm"
|
|
225
|
+
className="h-6 px-2 text-xs text-yellow-500"
|
|
226
|
+
onClick={(e) => {
|
|
227
|
+
e.stopPropagation()
|
|
228
|
+
onUpgrade?.(dependency)
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
<ArrowUpCircle className="h-3 w-3 mr-1" />
|
|
232
|
+
{dependency.latestVersion}
|
|
233
|
+
</Button>
|
|
234
|
+
</TooltipTrigger>
|
|
235
|
+
<TooltipContent>Upgrade to {dependency.latestVersion}</TooltipContent>
|
|
236
|
+
</Tooltip>
|
|
237
|
+
</TooltipProvider>
|
|
238
|
+
)}
|
|
239
|
+
|
|
240
|
+
{/* License */}
|
|
241
|
+
{dependency.license && (
|
|
242
|
+
<span className="text-xs text-muted-foreground ml-auto">
|
|
243
|
+
{dependency.license}
|
|
244
|
+
</span>
|
|
245
|
+
)}
|
|
246
|
+
|
|
247
|
+
{/* Size */}
|
|
248
|
+
{dependency.size && (
|
|
249
|
+
<span className="text-xs text-muted-foreground">
|
|
250
|
+
{formatBytes(dependency.size)}
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function DependencyTree({
|
|
258
|
+
dependencies,
|
|
259
|
+
depth,
|
|
260
|
+
expandedIds,
|
|
261
|
+
onToggle,
|
|
262
|
+
onDependencyClick,
|
|
263
|
+
onUpgrade,
|
|
264
|
+
searchQuery,
|
|
265
|
+
}: {
|
|
266
|
+
dependencies: Dependency[]
|
|
267
|
+
depth: number
|
|
268
|
+
expandedIds: Set<string>
|
|
269
|
+
onToggle: (id: string) => void
|
|
270
|
+
onDependencyClick?: (dep: Dependency) => void
|
|
271
|
+
onUpgrade?: (dep: Dependency) => void
|
|
272
|
+
searchQuery: string
|
|
273
|
+
}) {
|
|
274
|
+
return (
|
|
275
|
+
<>
|
|
276
|
+
{dependencies.map((dep) => {
|
|
277
|
+
const isExpanded = expandedIds.has(dep.id)
|
|
278
|
+
const matchesSearch = !searchQuery || dep.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
279
|
+
|
|
280
|
+
if (!matchesSearch && !dep.children?.some(c => c.name.toLowerCase().includes(searchQuery.toLowerCase()))) {
|
|
281
|
+
return null
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<React.Fragment key={dep.id}>
|
|
286
|
+
<DependencyNode
|
|
287
|
+
dependency={dep}
|
|
288
|
+
depth={depth}
|
|
289
|
+
isExpanded={isExpanded}
|
|
290
|
+
onToggle={() => onToggle(dep.id)}
|
|
291
|
+
onDependencyClick={onDependencyClick}
|
|
292
|
+
onUpgrade={onUpgrade}
|
|
293
|
+
/>
|
|
294
|
+
{isExpanded && dep.children && (
|
|
295
|
+
<DependencyTree
|
|
296
|
+
dependencies={dep.children}
|
|
297
|
+
depth={depth + 1}
|
|
298
|
+
expandedIds={expandedIds}
|
|
299
|
+
onToggle={onToggle}
|
|
300
|
+
onDependencyClick={onDependencyClick}
|
|
301
|
+
onUpgrade={onUpgrade}
|
|
302
|
+
searchQuery={searchQuery}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
</React.Fragment>
|
|
306
|
+
)
|
|
307
|
+
})}
|
|
308
|
+
</>
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function WakaDependencyTree({
|
|
313
|
+
dependencies,
|
|
314
|
+
onDependencyClick,
|
|
315
|
+
onUpgrade,
|
|
316
|
+
title = "Dependencies",
|
|
317
|
+
showDevDeps = true,
|
|
318
|
+
showVulnerableOnly = false,
|
|
319
|
+
className,
|
|
320
|
+
}: WakaDependencyTreeProps) {
|
|
321
|
+
const [searchQuery, setSearchQuery] = React.useState("")
|
|
322
|
+
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(new Set())
|
|
323
|
+
const [filterVulnerable, setFilterVulnerable] = React.useState(showVulnerableOnly)
|
|
324
|
+
|
|
325
|
+
const toggleExpand = (id: string) => {
|
|
326
|
+
setExpandedIds((prev) => {
|
|
327
|
+
const next = new Set(prev)
|
|
328
|
+
if (next.has(id)) {
|
|
329
|
+
next.delete(id)
|
|
330
|
+
} else {
|
|
331
|
+
next.add(id)
|
|
332
|
+
}
|
|
333
|
+
return next
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const expandAll = () => {
|
|
338
|
+
const allIds = new Set<string>()
|
|
339
|
+
const collectIds = (deps: Dependency[]) => {
|
|
340
|
+
deps.forEach((dep) => {
|
|
341
|
+
allIds.add(dep.id)
|
|
342
|
+
if (dep.children) collectIds(dep.children)
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
collectIds(dependencies)
|
|
346
|
+
setExpandedIds(allIds)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const collapseAll = () => {
|
|
350
|
+
setExpandedIds(new Set())
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Filter dependencies
|
|
354
|
+
const filteredDeps = React.useMemo(() => {
|
|
355
|
+
let filtered = dependencies
|
|
356
|
+
if (!showDevDeps) {
|
|
357
|
+
filtered = filtered.filter((d) => d.type !== "dev")
|
|
358
|
+
}
|
|
359
|
+
if (filterVulnerable) {
|
|
360
|
+
const hasVulns = (dep: Dependency): boolean => {
|
|
361
|
+
if (getTotalVulns(dep.vulnerabilities) > 0) return true
|
|
362
|
+
return dep.children?.some(hasVulns) || false
|
|
363
|
+
}
|
|
364
|
+
filtered = filtered.filter(hasVulns)
|
|
365
|
+
}
|
|
366
|
+
return filtered
|
|
367
|
+
}, [dependencies, showDevDeps, filterVulnerable])
|
|
368
|
+
|
|
369
|
+
// Calculate stats
|
|
370
|
+
const stats = React.useMemo(() => {
|
|
371
|
+
let total = 0
|
|
372
|
+
let vulnerable = 0
|
|
373
|
+
let outdated = 0
|
|
374
|
+
let criticalVulns = 0
|
|
375
|
+
|
|
376
|
+
const count = (deps: Dependency[]) => {
|
|
377
|
+
deps.forEach((dep) => {
|
|
378
|
+
total++
|
|
379
|
+
if (getTotalVulns(dep.vulnerabilities) > 0) vulnerable++
|
|
380
|
+
if (dep.vulnerabilities?.critical) criticalVulns += dep.vulnerabilities.critical
|
|
381
|
+
if (dep.outdated) outdated++
|
|
382
|
+
if (dep.children) count(dep.children)
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
count(dependencies)
|
|
386
|
+
|
|
387
|
+
return { total, vulnerable, outdated, criticalVulns }
|
|
388
|
+
}, [dependencies])
|
|
389
|
+
|
|
390
|
+
return (
|
|
391
|
+
<div className={cn("flex flex-col border rounded-lg bg-background", className)}>
|
|
392
|
+
{/* Header */}
|
|
393
|
+
<div className="flex items-center justify-between gap-4 p-3 border-b">
|
|
394
|
+
<div className="flex items-center gap-3">
|
|
395
|
+
<GitBranch className="h-5 w-5" />
|
|
396
|
+
<h3 className="font-semibold">{title}</h3>
|
|
397
|
+
<Badge variant="secondary">{stats.total}</Badge>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<div className="flex items-center gap-2">
|
|
401
|
+
{stats.criticalVulns > 0 && (
|
|
402
|
+
<Badge className="bg-red-500">{stats.criticalVulns} critical</Badge>
|
|
403
|
+
)}
|
|
404
|
+
{stats.vulnerable > 0 && (
|
|
405
|
+
<Badge variant="secondary" className="text-orange-500">
|
|
406
|
+
{stats.vulnerable} vulnerable
|
|
407
|
+
</Badge>
|
|
408
|
+
)}
|
|
409
|
+
{stats.outdated > 0 && (
|
|
410
|
+
<Badge variant="outline">{stats.outdated} outdated</Badge>
|
|
411
|
+
)}
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
{/* Toolbar */}
|
|
416
|
+
<div className="flex items-center gap-2 p-2 border-b bg-muted/30">
|
|
417
|
+
<div className="relative flex-1 max-w-sm">
|
|
418
|
+
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
419
|
+
<Input
|
|
420
|
+
placeholder="Search packages..."
|
|
421
|
+
value={searchQuery}
|
|
422
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
423
|
+
className="pl-8 h-8"
|
|
424
|
+
/>
|
|
425
|
+
</div>
|
|
426
|
+
|
|
427
|
+
<Button
|
|
428
|
+
variant={filterVulnerable ? "default" : "outline"}
|
|
429
|
+
size="sm"
|
|
430
|
+
className="h-8"
|
|
431
|
+
onClick={() => setFilterVulnerable(!filterVulnerable)}
|
|
432
|
+
>
|
|
433
|
+
<ShieldAlert className="h-4 w-4 mr-1" />
|
|
434
|
+
Vulnerable only
|
|
435
|
+
</Button>
|
|
436
|
+
|
|
437
|
+
<div className="flex items-center gap-1">
|
|
438
|
+
<Button variant="ghost" size="sm" className="h-8" onClick={expandAll}>
|
|
439
|
+
Expand All
|
|
440
|
+
</Button>
|
|
441
|
+
<Button variant="ghost" size="sm" className="h-8" onClick={collapseAll}>
|
|
442
|
+
Collapse All
|
|
443
|
+
</Button>
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
|
|
447
|
+
{/* Tree */}
|
|
448
|
+
<ScrollArea className="flex-1 max-h-[500px]">
|
|
449
|
+
<div className="py-2">
|
|
450
|
+
{filteredDeps.length === 0 ? (
|
|
451
|
+
<div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
|
|
452
|
+
<Package className="h-8 w-8 mb-2" />
|
|
453
|
+
<span>No dependencies found</span>
|
|
454
|
+
</div>
|
|
455
|
+
) : (
|
|
456
|
+
<DependencyTree
|
|
457
|
+
dependencies={filteredDeps}
|
|
458
|
+
depth={0}
|
|
459
|
+
expandedIds={expandedIds}
|
|
460
|
+
onToggle={toggleExpand}
|
|
461
|
+
onDependencyClick={onDependencyClick}
|
|
462
|
+
onUpgrade={onUpgrade}
|
|
463
|
+
searchQuery={searchQuery}
|
|
464
|
+
/>
|
|
465
|
+
)}
|
|
466
|
+
</div>
|
|
467
|
+
</ScrollArea>
|
|
468
|
+
</div>
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Default sample dependencies for demo
|
|
473
|
+
export const defaultDependencies: Dependency[] = [
|
|
474
|
+
{
|
|
475
|
+
id: "1",
|
|
476
|
+
name: "react",
|
|
477
|
+
version: "18.2.0",
|
|
478
|
+
latestVersion: "18.2.0",
|
|
479
|
+
type: "direct",
|
|
480
|
+
license: "MIT",
|
|
481
|
+
size: 2.5 * 1024 * 1024,
|
|
482
|
+
children: [
|
|
483
|
+
{
|
|
484
|
+
id: "1-1",
|
|
485
|
+
name: "loose-envify",
|
|
486
|
+
version: "1.4.0",
|
|
487
|
+
type: "transitive",
|
|
488
|
+
license: "MIT",
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
id: "2",
|
|
494
|
+
name: "lodash",
|
|
495
|
+
version: "4.17.19",
|
|
496
|
+
latestVersion: "4.17.21",
|
|
497
|
+
type: "direct",
|
|
498
|
+
license: "MIT",
|
|
499
|
+
outdated: true,
|
|
500
|
+
vulnerabilities: { critical: 0, high: 1, medium: 1, low: 0 },
|
|
501
|
+
size: 530 * 1024,
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
id: "3",
|
|
505
|
+
name: "axios",
|
|
506
|
+
version: "0.21.1",
|
|
507
|
+
latestVersion: "1.6.0",
|
|
508
|
+
type: "direct",
|
|
509
|
+
license: "MIT",
|
|
510
|
+
outdated: true,
|
|
511
|
+
vulnerabilities: { critical: 1, high: 0, medium: 0, low: 0 },
|
|
512
|
+
size: 450 * 1024,
|
|
513
|
+
children: [
|
|
514
|
+
{
|
|
515
|
+
id: "3-1",
|
|
516
|
+
name: "follow-redirects",
|
|
517
|
+
version: "1.14.0",
|
|
518
|
+
type: "transitive",
|
|
519
|
+
license: "MIT",
|
|
520
|
+
vulnerabilities: { critical: 0, high: 1, medium: 0, low: 0 },
|
|
521
|
+
},
|
|
522
|
+
],
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
id: "4",
|
|
526
|
+
name: "typescript",
|
|
527
|
+
version: "5.0.0",
|
|
528
|
+
latestVersion: "5.3.0",
|
|
529
|
+
type: "dev",
|
|
530
|
+
license: "Apache-2.0",
|
|
531
|
+
outdated: true,
|
|
532
|
+
size: 15 * 1024 * 1024,
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
id: "5",
|
|
536
|
+
name: "eslint",
|
|
537
|
+
version: "8.50.0",
|
|
538
|
+
type: "dev",
|
|
539
|
+
license: "MIT",
|
|
540
|
+
size: 3 * 1024 * 1024,
|
|
541
|
+
},
|
|
542
|
+
]
|