@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,416 @@
|
|
|
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
|
+
Download,
|
|
18
|
+
Trash2,
|
|
19
|
+
Search,
|
|
20
|
+
FileArchive,
|
|
21
|
+
FileText,
|
|
22
|
+
Image,
|
|
23
|
+
File,
|
|
24
|
+
Folder,
|
|
25
|
+
Clock,
|
|
26
|
+
HardDrive,
|
|
27
|
+
ExternalLink,
|
|
28
|
+
Copy,
|
|
29
|
+
CheckCircle2,
|
|
30
|
+
} from "lucide-react"
|
|
31
|
+
|
|
32
|
+
export interface Artifact {
|
|
33
|
+
id: string
|
|
34
|
+
name: string
|
|
35
|
+
type: "archive" | "binary" | "log" | "report" | "image" | "other"
|
|
36
|
+
size: number // in bytes
|
|
37
|
+
createdAt: Date
|
|
38
|
+
expiresAt?: Date
|
|
39
|
+
downloadUrl?: string
|
|
40
|
+
sha256?: string
|
|
41
|
+
buildId?: string
|
|
42
|
+
metadata?: Record<string, string>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WakaArtifactListProps {
|
|
46
|
+
/** List of artifacts */
|
|
47
|
+
artifacts: Artifact[]
|
|
48
|
+
/** Callback when downloading an artifact */
|
|
49
|
+
onDownload?: (artifact: Artifact) => void
|
|
50
|
+
/** Callback when deleting an artifact */
|
|
51
|
+
onDelete?: (artifact: Artifact) => void
|
|
52
|
+
/** Callback when viewing artifact details */
|
|
53
|
+
onView?: (artifact: Artifact) => void
|
|
54
|
+
/** Title */
|
|
55
|
+
title?: string
|
|
56
|
+
/** Show expiration */
|
|
57
|
+
showExpiration?: boolean
|
|
58
|
+
/** Allow deletion */
|
|
59
|
+
allowDelete?: boolean
|
|
60
|
+
/** Custom class name */
|
|
61
|
+
className?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const typeConfig: Record<Artifact["type"], { icon: React.ElementType; color: string }> = {
|
|
65
|
+
archive: { icon: FileArchive, color: "text-orange-500" },
|
|
66
|
+
binary: { icon: Package, color: "text-blue-500" },
|
|
67
|
+
log: { icon: FileText, color: "text-gray-500" },
|
|
68
|
+
report: { icon: FileText, color: "text-green-500" },
|
|
69
|
+
image: { icon: Image, color: "text-purple-500" },
|
|
70
|
+
other: { icon: File, color: "text-muted-foreground" },
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function formatBytes(bytes: number): string {
|
|
74
|
+
if (bytes === 0) return "0 B"
|
|
75
|
+
const k = 1024
|
|
76
|
+
const sizes = ["B", "KB", "MB", "GB"]
|
|
77
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
78
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function formatDate(date: Date): string {
|
|
82
|
+
return date.toLocaleDateString("fr-FR", {
|
|
83
|
+
day: "2-digit",
|
|
84
|
+
month: "short",
|
|
85
|
+
hour: "2-digit",
|
|
86
|
+
minute: "2-digit",
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isExpiringSoon(date?: Date): boolean {
|
|
91
|
+
if (!date) return false
|
|
92
|
+
const diff = date.getTime() - Date.now()
|
|
93
|
+
return diff > 0 && diff < 24 * 60 * 60 * 1000 // Less than 24h
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function ArtifactRow({
|
|
97
|
+
artifact,
|
|
98
|
+
onDownload,
|
|
99
|
+
onDelete,
|
|
100
|
+
onView,
|
|
101
|
+
showExpiration,
|
|
102
|
+
allowDelete,
|
|
103
|
+
}: {
|
|
104
|
+
artifact: Artifact
|
|
105
|
+
onDownload?: (artifact: Artifact) => void
|
|
106
|
+
onDelete?: (artifact: Artifact) => void
|
|
107
|
+
onView?: (artifact: Artifact) => void
|
|
108
|
+
showExpiration: boolean
|
|
109
|
+
allowDelete: boolean
|
|
110
|
+
}) {
|
|
111
|
+
const [copied, setCopied] = React.useState(false)
|
|
112
|
+
const config = typeConfig[artifact.type]
|
|
113
|
+
const Icon = config.icon
|
|
114
|
+
const expiringSoon = isExpiringSoon(artifact.expiresAt)
|
|
115
|
+
|
|
116
|
+
const copyHash = () => {
|
|
117
|
+
if (artifact.sha256) {
|
|
118
|
+
navigator.clipboard.writeText(artifact.sha256)
|
|
119
|
+
setCopied(true)
|
|
120
|
+
setTimeout(() => setCopied(false), 2000)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className="flex items-center gap-4 p-3 border-b hover:bg-muted/30 transition-colors">
|
|
126
|
+
{/* Icon */}
|
|
127
|
+
<Icon className={cn("h-8 w-8 shrink-0", config.color)} />
|
|
128
|
+
|
|
129
|
+
{/* Info */}
|
|
130
|
+
<div className="flex-1 min-w-0">
|
|
131
|
+
<div className="flex items-center gap-2">
|
|
132
|
+
<span
|
|
133
|
+
className="font-medium truncate cursor-pointer hover:text-primary"
|
|
134
|
+
onClick={() => onView?.(artifact)}
|
|
135
|
+
>
|
|
136
|
+
{artifact.name}
|
|
137
|
+
</span>
|
|
138
|
+
<Badge variant="outline" className="text-xs capitalize">
|
|
139
|
+
{artifact.type}
|
|
140
|
+
</Badge>
|
|
141
|
+
</div>
|
|
142
|
+
<div className="flex items-center gap-3 text-xs text-muted-foreground mt-1">
|
|
143
|
+
<span className="flex items-center gap-1">
|
|
144
|
+
<HardDrive className="h-3 w-3" />
|
|
145
|
+
{formatBytes(artifact.size)}
|
|
146
|
+
</span>
|
|
147
|
+
<span className="flex items-center gap-1">
|
|
148
|
+
<Clock className="h-3 w-3" />
|
|
149
|
+
{formatDate(artifact.createdAt)}
|
|
150
|
+
</span>
|
|
151
|
+
{showExpiration && artifact.expiresAt && (
|
|
152
|
+
<span className={cn("flex items-center gap-1", expiringSoon && "text-yellow-500")}>
|
|
153
|
+
Expires: {formatDate(artifact.expiresAt)}
|
|
154
|
+
</span>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* SHA256 */}
|
|
160
|
+
{artifact.sha256 && (
|
|
161
|
+
<TooltipProvider>
|
|
162
|
+
<Tooltip>
|
|
163
|
+
<TooltipTrigger asChild>
|
|
164
|
+
<button
|
|
165
|
+
className="hidden md:flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
|
166
|
+
onClick={copyHash}
|
|
167
|
+
>
|
|
168
|
+
{copied ? (
|
|
169
|
+
<CheckCircle2 className="h-3 w-3 text-green-500" />
|
|
170
|
+
) : (
|
|
171
|
+
<Copy className="h-3 w-3" />
|
|
172
|
+
)}
|
|
173
|
+
<code className="truncate max-w-[100px]">{artifact.sha256.slice(0, 12)}...</code>
|
|
174
|
+
</button>
|
|
175
|
+
</TooltipTrigger>
|
|
176
|
+
<TooltipContent>
|
|
177
|
+
<div className="space-y-1">
|
|
178
|
+
<div className="text-xs font-medium">SHA256</div>
|
|
179
|
+
<code className="text-xs break-all">{artifact.sha256}</code>
|
|
180
|
+
</div>
|
|
181
|
+
</TooltipContent>
|
|
182
|
+
</Tooltip>
|
|
183
|
+
</TooltipProvider>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
{/* Actions */}
|
|
187
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
188
|
+
{artifact.downloadUrl && (
|
|
189
|
+
<TooltipProvider>
|
|
190
|
+
<Tooltip>
|
|
191
|
+
<TooltipTrigger asChild>
|
|
192
|
+
<Button
|
|
193
|
+
variant="ghost"
|
|
194
|
+
size="sm"
|
|
195
|
+
className="h-8 w-8 p-0"
|
|
196
|
+
onClick={() => onDownload?.(artifact)}
|
|
197
|
+
>
|
|
198
|
+
<Download className="h-4 w-4" />
|
|
199
|
+
</Button>
|
|
200
|
+
</TooltipTrigger>
|
|
201
|
+
<TooltipContent>Download</TooltipContent>
|
|
202
|
+
</Tooltip>
|
|
203
|
+
</TooltipProvider>
|
|
204
|
+
)}
|
|
205
|
+
|
|
206
|
+
<TooltipProvider>
|
|
207
|
+
<Tooltip>
|
|
208
|
+
<TooltipTrigger asChild>
|
|
209
|
+
<Button
|
|
210
|
+
variant="ghost"
|
|
211
|
+
size="sm"
|
|
212
|
+
className="h-8 w-8 p-0"
|
|
213
|
+
onClick={() => onView?.(artifact)}
|
|
214
|
+
>
|
|
215
|
+
<ExternalLink className="h-4 w-4" />
|
|
216
|
+
</Button>
|
|
217
|
+
</TooltipTrigger>
|
|
218
|
+
<TooltipContent>View Details</TooltipContent>
|
|
219
|
+
</Tooltip>
|
|
220
|
+
</TooltipProvider>
|
|
221
|
+
|
|
222
|
+
{allowDelete && (
|
|
223
|
+
<TooltipProvider>
|
|
224
|
+
<Tooltip>
|
|
225
|
+
<TooltipTrigger asChild>
|
|
226
|
+
<Button
|
|
227
|
+
variant="ghost"
|
|
228
|
+
size="sm"
|
|
229
|
+
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
|
230
|
+
onClick={() => onDelete?.(artifact)}
|
|
231
|
+
>
|
|
232
|
+
<Trash2 className="h-4 w-4" />
|
|
233
|
+
</Button>
|
|
234
|
+
</TooltipTrigger>
|
|
235
|
+
<TooltipContent>Delete</TooltipContent>
|
|
236
|
+
</Tooltip>
|
|
237
|
+
</TooltipProvider>
|
|
238
|
+
)}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function WakaArtifactList({
|
|
245
|
+
artifacts,
|
|
246
|
+
onDownload,
|
|
247
|
+
onDelete,
|
|
248
|
+
onView,
|
|
249
|
+
title = "Artifacts",
|
|
250
|
+
showExpiration = true,
|
|
251
|
+
allowDelete = false,
|
|
252
|
+
className,
|
|
253
|
+
}: WakaArtifactListProps) {
|
|
254
|
+
const [searchQuery, setSearchQuery] = React.useState("")
|
|
255
|
+
const [typeFilter, setTypeFilter] = React.useState<Artifact["type"] | "all">("all")
|
|
256
|
+
|
|
257
|
+
// Filter artifacts
|
|
258
|
+
const filteredArtifacts = React.useMemo(() => {
|
|
259
|
+
return artifacts.filter((artifact) => {
|
|
260
|
+
if (typeFilter !== "all" && artifact.type !== typeFilter) return false
|
|
261
|
+
if (searchQuery) {
|
|
262
|
+
return artifact.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
263
|
+
}
|
|
264
|
+
return true
|
|
265
|
+
})
|
|
266
|
+
}, [artifacts, typeFilter, searchQuery])
|
|
267
|
+
|
|
268
|
+
// Calculate total size
|
|
269
|
+
const totalSize = artifacts.reduce((acc, a) => acc + a.size, 0)
|
|
270
|
+
|
|
271
|
+
// Count by type
|
|
272
|
+
const typeCounts = React.useMemo(() => {
|
|
273
|
+
return artifacts.reduce((acc, a) => {
|
|
274
|
+
acc[a.type] = (acc[a.type] || 0) + 1
|
|
275
|
+
return acc
|
|
276
|
+
}, {} as Record<string, number>)
|
|
277
|
+
}, [artifacts])
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div className={cn("flex flex-col border rounded-lg bg-background", className)}>
|
|
281
|
+
{/* Header */}
|
|
282
|
+
<div className="flex items-center justify-between gap-4 p-3 border-b">
|
|
283
|
+
<div className="flex items-center gap-3">
|
|
284
|
+
<Package className="h-5 w-5" />
|
|
285
|
+
<h3 className="font-semibold">{title}</h3>
|
|
286
|
+
<Badge variant="secondary">{artifacts.length}</Badge>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
290
|
+
<HardDrive className="h-4 w-4" />
|
|
291
|
+
Total: {formatBytes(totalSize)}
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
{/* Toolbar */}
|
|
296
|
+
<div className="flex items-center gap-2 p-2 border-b bg-muted/30">
|
|
297
|
+
<div className="relative flex-1 max-w-sm">
|
|
298
|
+
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
299
|
+
<Input
|
|
300
|
+
placeholder="Search artifacts..."
|
|
301
|
+
value={searchQuery}
|
|
302
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
303
|
+
className="pl-8 h-8"
|
|
304
|
+
/>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<div className="flex items-center gap-1 flex-wrap">
|
|
308
|
+
<Button
|
|
309
|
+
variant={typeFilter === "all" ? "default" : "ghost"}
|
|
310
|
+
size="sm"
|
|
311
|
+
className="h-7 text-xs"
|
|
312
|
+
onClick={() => setTypeFilter("all")}
|
|
313
|
+
>
|
|
314
|
+
All
|
|
315
|
+
</Button>
|
|
316
|
+
{Object.entries(typeCounts).map(([type, count]) => {
|
|
317
|
+
const config = typeConfig[type as Artifact["type"]]
|
|
318
|
+
const Icon = config.icon
|
|
319
|
+
return (
|
|
320
|
+
<Button
|
|
321
|
+
key={type}
|
|
322
|
+
variant={typeFilter === type ? "default" : "ghost"}
|
|
323
|
+
size="sm"
|
|
324
|
+
className="h-7 text-xs"
|
|
325
|
+
onClick={() => setTypeFilter(type as Artifact["type"])}
|
|
326
|
+
>
|
|
327
|
+
<Icon className={cn("h-3 w-3 mr-1", config.color)} />
|
|
328
|
+
{type} ({count})
|
|
329
|
+
</Button>
|
|
330
|
+
)
|
|
331
|
+
})}
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
{/* Artifact list */}
|
|
336
|
+
<ScrollArea className="flex-1 max-h-[400px]">
|
|
337
|
+
{filteredArtifacts.length === 0 ? (
|
|
338
|
+
<div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
|
|
339
|
+
<Folder className="h-8 w-8 mb-2" />
|
|
340
|
+
<span>No artifacts found</span>
|
|
341
|
+
</div>
|
|
342
|
+
) : (
|
|
343
|
+
filteredArtifacts.map((artifact) => (
|
|
344
|
+
<ArtifactRow
|
|
345
|
+
key={artifact.id}
|
|
346
|
+
artifact={artifact}
|
|
347
|
+
onDownload={onDownload}
|
|
348
|
+
onDelete={onDelete}
|
|
349
|
+
onView={onView}
|
|
350
|
+
showExpiration={showExpiration}
|
|
351
|
+
allowDelete={allowDelete}
|
|
352
|
+
/>
|
|
353
|
+
))
|
|
354
|
+
)}
|
|
355
|
+
</ScrollArea>
|
|
356
|
+
</div>
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Default sample artifacts for demo
|
|
361
|
+
export const defaultArtifacts: Artifact[] = [
|
|
362
|
+
{
|
|
363
|
+
id: "1",
|
|
364
|
+
name: "build-linux-x64.tar.gz",
|
|
365
|
+
type: "archive",
|
|
366
|
+
size: 45 * 1024 * 1024,
|
|
367
|
+
createdAt: new Date(Date.now() - 2 * 3600000),
|
|
368
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 3600000),
|
|
369
|
+
downloadUrl: "#",
|
|
370
|
+
sha256: "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6",
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
id: "2",
|
|
374
|
+
name: "build-darwin-arm64.tar.gz",
|
|
375
|
+
type: "archive",
|
|
376
|
+
size: 42 * 1024 * 1024,
|
|
377
|
+
createdAt: new Date(Date.now() - 2 * 3600000),
|
|
378
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 3600000),
|
|
379
|
+
downloadUrl: "#",
|
|
380
|
+
sha256: "b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a1",
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: "3",
|
|
384
|
+
name: "test-results.xml",
|
|
385
|
+
type: "report",
|
|
386
|
+
size: 256 * 1024,
|
|
387
|
+
createdAt: new Date(Date.now() - 2 * 3600000),
|
|
388
|
+
downloadUrl: "#",
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: "4",
|
|
392
|
+
name: "coverage-report.html",
|
|
393
|
+
type: "report",
|
|
394
|
+
size: 1.2 * 1024 * 1024,
|
|
395
|
+
createdAt: new Date(Date.now() - 2 * 3600000),
|
|
396
|
+
downloadUrl: "#",
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: "5",
|
|
400
|
+
name: "build.log",
|
|
401
|
+
type: "log",
|
|
402
|
+
size: 512 * 1024,
|
|
403
|
+
createdAt: new Date(Date.now() - 2 * 3600000),
|
|
404
|
+
downloadUrl: "#",
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: "6",
|
|
408
|
+
name: "docker-image.tar",
|
|
409
|
+
type: "image",
|
|
410
|
+
size: 250 * 1024 * 1024,
|
|
411
|
+
createdAt: new Date(Date.now() - 2 * 3600000),
|
|
412
|
+
expiresAt: new Date(Date.now() + 12 * 3600000), // Expiring soon
|
|
413
|
+
downloadUrl: "#",
|
|
414
|
+
sha256: "c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a1b2",
|
|
415
|
+
},
|
|
416
|
+
]
|