@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.
Files changed (123) hide show
  1. package/dist/blocks/apm-overview/index.d.ts +58 -0
  2. package/dist/blocks/cicd-builder/index.d.ts +47 -0
  3. package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
  4. package/dist/blocks/container-orchestrator/index.d.ts +63 -0
  5. package/dist/blocks/database-admin/index.d.ts +84 -0
  6. package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
  7. package/dist/blocks/incident-manager/index.d.ts +44 -0
  8. package/dist/blocks/index.d.ts +10 -0
  9. package/dist/blocks/infrastructure-map/index.d.ts +32 -0
  10. package/dist/blocks/on-call-schedule/index.d.ts +43 -0
  11. package/dist/blocks/release-notes/index.d.ts +49 -0
  12. package/dist/components/index.d.ts +34 -0
  13. package/dist/components/waka-ad-banner/index.d.ts +36 -0
  14. package/dist/components/waka-ad-fallback/index.d.ts +33 -0
  15. package/dist/components/waka-ad-inline/index.d.ts +15 -0
  16. package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
  17. package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
  18. package/dist/components/waka-ad-provider/index.d.ts +103 -0
  19. package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
  20. package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
  21. package/dist/components/waka-alert-panel/index.d.ts +45 -0
  22. package/dist/components/waka-artifact-list/index.d.ts +32 -0
  23. package/dist/components/waka-build-matrix/index.d.ts +36 -0
  24. package/dist/components/waka-config-comparator/index.d.ts +37 -0
  25. package/dist/components/waka-container-list/index.d.ts +51 -0
  26. package/dist/components/waka-content-recommendation/index.d.ts +23 -0
  27. package/dist/components/waka-database-card/index.d.ts +46 -0
  28. package/dist/components/waka-dependency-tree/index.d.ts +38 -0
  29. package/dist/components/waka-env-var-editor/index.d.ts +30 -0
  30. package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
  31. package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
  32. package/dist/components/waka-log-viewer/index.d.ts +38 -0
  33. package/dist/components/waka-migration-list/index.d.ts +36 -0
  34. package/dist/components/waka-outstream-video/index.d.ts +24 -0
  35. package/dist/components/waka-pod-card/index.d.ts +73 -0
  36. package/dist/components/waka-query-explain/index.d.ts +48 -0
  37. package/dist/components/waka-secret-card/index.d.ts +43 -0
  38. package/dist/components/waka-security-scan-result/index.d.ts +45 -0
  39. package/dist/components/waka-service-graph/index.d.ts +44 -0
  40. package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
  41. package/dist/components/waka-sponsored-card/index.d.ts +25 -0
  42. package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
  43. package/dist/components/waka-test-report/index.d.ts +60 -0
  44. package/dist/components/waka-trace-viewer/index.d.ts +36 -0
  45. package/dist/components/waka-video-ad/index.d.ts +32 -0
  46. package/dist/components/waka-video-overlay/index.d.ts +26 -0
  47. package/dist/index.cjs.js +251 -200
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.es.js +47315 -35823
  50. package/dist/utils/security.d.ts +96 -0
  51. package/package.json +4 -4
  52. package/src/blocks/apm-overview/index.tsx +672 -0
  53. package/src/blocks/cicd-builder/index.tsx +738 -0
  54. package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
  55. package/src/blocks/container-orchestrator/index.tsx +729 -0
  56. package/src/blocks/database-admin/index.tsx +679 -0
  57. package/src/blocks/gitops-sync-status/index.tsx +557 -0
  58. package/src/blocks/incident-manager/index.tsx +586 -0
  59. package/src/blocks/index.ts +119 -0
  60. package/src/blocks/infrastructure-map/index.tsx +638 -0
  61. package/src/blocks/on-call-schedule/index.tsx +615 -0
  62. package/src/blocks/release-notes/index.tsx +643 -0
  63. package/src/blocks/sidebar/index.tsx +6 -6
  64. package/src/components/DataTable/templates/index.tsx +3 -2
  65. package/src/components/index.ts +283 -0
  66. package/src/components/waka-3d-pie-chart/index.tsx +11 -11
  67. package/src/components/waka-achievement-unlock/index.tsx +16 -16
  68. package/src/components/waka-ad-banner/index.tsx +275 -0
  69. package/src/components/waka-ad-fallback/index.tsx +181 -0
  70. package/src/components/waka-ad-inline/index.tsx +103 -0
  71. package/src/components/waka-ad-interstitial/index.tsx +278 -0
  72. package/src/components/waka-ad-placeholder/index.tsx +84 -0
  73. package/src/components/waka-ad-provider/index.tsx +329 -0
  74. package/src/components/waka-ad-sidebar/index.tsx +113 -0
  75. package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
  76. package/src/components/waka-alert-panel/index.tsx +493 -0
  77. package/src/components/waka-artifact-list/index.tsx +416 -0
  78. package/src/components/waka-badge-showcase/index.tsx +12 -11
  79. package/src/components/waka-build-matrix/index.tsx +396 -0
  80. package/src/components/waka-command-bar/index.tsx +2 -1
  81. package/src/components/waka-config-comparator/index.tsx +416 -0
  82. package/src/components/waka-container-list/index.tsx +475 -0
  83. package/src/components/waka-content-recommendation/index.tsx +294 -0
  84. package/src/components/waka-cost-breakdown/index.tsx +10 -10
  85. package/src/components/waka-database-card/index.tsx +473 -0
  86. package/src/components/waka-dependency-tree/index.tsx +542 -0
  87. package/src/components/waka-env-var-editor/index.tsx +417 -0
  88. package/src/components/waka-feature-flag-row/index.tsx +386 -0
  89. package/src/components/waka-funnel-chart/index.tsx +8 -8
  90. package/src/components/waka-health-pulse/index.tsx +6 -6
  91. package/src/components/waka-kubernetes-overview/index.tsx +536 -0
  92. package/src/components/waka-leaderboard/index.tsx +9 -9
  93. package/src/components/waka-log-viewer/index.tsx +386 -0
  94. package/src/components/waka-loot-box/index.tsx +20 -20
  95. package/src/components/waka-migration-list/index.tsx +487 -0
  96. package/src/components/waka-outstream-video/index.tsx +240 -0
  97. package/src/components/waka-player-card/index.tsx +5 -5
  98. package/src/components/waka-pod-card/index.tsx +528 -0
  99. package/src/components/waka-query-explain/index.tsx +657 -0
  100. package/src/components/waka-quota-bar/index.tsx +4 -4
  101. package/src/components/waka-radar-score/index.tsx +10 -10
  102. package/src/components/waka-scratch-card/index.tsx +5 -4
  103. package/src/components/waka-secret-card/index.tsx +371 -0
  104. package/src/components/waka-security-scan-result/index.tsx +473 -0
  105. package/src/components/waka-server-rack/index.tsx +28 -27
  106. package/src/components/waka-service-graph/index.tsx +445 -0
  107. package/src/components/waka-sponsored-badge/index.tsx +97 -0
  108. package/src/components/waka-sponsored-card/index.tsx +275 -0
  109. package/src/components/waka-sponsored-feed/index.tsx +127 -0
  110. package/src/components/waka-spotlight/index.tsx +2 -1
  111. package/src/components/waka-success-explosion/index.tsx +4 -4
  112. package/src/components/waka-test-report/index.tsx +469 -0
  113. package/src/components/waka-trace-viewer/index.tsx +490 -0
  114. package/src/components/waka-video-ad/index.tsx +406 -0
  115. package/src/components/waka-video-overlay/index.tsx +257 -0
  116. package/src/components/waka-xp-bar/index.tsx +13 -13
  117. package/src/styles/base.css +16 -0
  118. package/src/styles/tailwind.preset.js +12 -0
  119. package/src/styles/themes/forest.css +16 -0
  120. package/src/styles/themes/monochrome.css +16 -0
  121. package/src/styles/themes/perpetuity.css +16 -0
  122. package/src/styles/themes/sunset.css +16 -0
  123. package/src/styles/themes/twilight.css +16 -0
@@ -0,0 +1,557 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Badge } from "../../components/badge"
6
+ import { Button } from "../../components/button"
7
+ import { Card, CardContent, CardHeader, CardTitle } from "../../components/card"
8
+ import { Progress } from "../../components/progress"
9
+ import { ScrollArea } from "../../components/scroll-area"
10
+ import {
11
+ GitBranch,
12
+ GitCommit,
13
+ RefreshCw,
14
+ CheckCircle2,
15
+ XCircle,
16
+ Clock,
17
+ AlertTriangle,
18
+ Loader2,
19
+ ArrowRight,
20
+ ExternalLink,
21
+ Eye,
22
+ Play,
23
+ Pause,
24
+ RotateCcw,
25
+ Folder,
26
+ FileCode,
27
+ Server,
28
+ Cloud,
29
+ Settings,
30
+ Activity,
31
+ } from "lucide-react"
32
+
33
+ export type SyncStatus = "synced" | "out_of_sync" | "syncing" | "failed" | "unknown" | "suspended"
34
+ export type HealthStatus = "healthy" | "degraded" | "progressing" | "missing" | "unknown"
35
+ export type ResourceKind = "Deployment" | "Service" | "ConfigMap" | "Secret" | "Ingress" | "PersistentVolumeClaim" | "CronJob" | "Job" | "StatefulSet" | "DaemonSet"
36
+
37
+ export interface GitOpsResource {
38
+ kind: ResourceKind
39
+ name: string
40
+ namespace: string
41
+ syncStatus: SyncStatus
42
+ healthStatus: HealthStatus
43
+ message?: string
44
+ version?: string
45
+ }
46
+
47
+ export interface GitOpsApplication {
48
+ id: string
49
+ name: string
50
+ project: string
51
+ repo: {
52
+ url: string
53
+ branch: string
54
+ path: string
55
+ }
56
+ destination: {
57
+ server: string
58
+ namespace: string
59
+ }
60
+ syncStatus: SyncStatus
61
+ healthStatus: HealthStatus
62
+ lastSyncedAt?: Date
63
+ lastSyncedRevision?: string
64
+ targetRevision?: string
65
+ resources: GitOpsResource[]
66
+ message?: string
67
+ autoSync?: boolean
68
+ }
69
+
70
+ export interface GitOpsSyncStatusProps {
71
+ applications: GitOpsApplication[]
72
+ onSync?: (app: GitOpsApplication) => void
73
+ onRefresh?: () => void
74
+ onSuspend?: (app: GitOpsApplication) => void
75
+ onResume?: (app: GitOpsApplication) => void
76
+ onViewDetails?: (app: GitOpsApplication) => void
77
+ className?: string
78
+ }
79
+
80
+ const syncStatusConfig: Record<SyncStatus, { icon: React.ElementType; color: string; bgColor: string; label: string }> = {
81
+ synced: { icon: CheckCircle2, color: "text-green-500", bgColor: "bg-green-500", label: "Synced" },
82
+ out_of_sync: { icon: AlertTriangle, color: "text-yellow-500", bgColor: "bg-yellow-500", label: "Out of Sync" },
83
+ syncing: { icon: Loader2, color: "text-blue-500", bgColor: "bg-blue-500", label: "Syncing" },
84
+ failed: { icon: XCircle, color: "text-red-500", bgColor: "bg-red-500", label: "Failed" },
85
+ unknown: { icon: Clock, color: "text-gray-500", bgColor: "bg-gray-500", label: "Unknown" },
86
+ suspended: { icon: Pause, color: "text-orange-500", bgColor: "bg-orange-500", label: "Suspended" },
87
+ }
88
+
89
+ const healthStatusConfig: Record<HealthStatus, { color: string; bgColor: string; label: string }> = {
90
+ healthy: { color: "text-green-500", bgColor: "bg-green-500", label: "Healthy" },
91
+ degraded: { color: "text-red-500", bgColor: "bg-red-500", label: "Degraded" },
92
+ progressing: { color: "text-blue-500", bgColor: "bg-blue-500", label: "Progressing" },
93
+ missing: { color: "text-yellow-500", bgColor: "bg-yellow-500", label: "Missing" },
94
+ unknown: { color: "text-gray-500", bgColor: "bg-gray-500", label: "Unknown" },
95
+ }
96
+
97
+ function formatTimeAgo(date: Date): string {
98
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000)
99
+ if (seconds < 60) return `${seconds}s ago`
100
+ const minutes = Math.floor(seconds / 60)
101
+ if (minutes < 60) return `${minutes}m ago`
102
+ const hours = Math.floor(minutes / 60)
103
+ if (hours < 24) return `${hours}h ago`
104
+ const days = Math.floor(hours / 24)
105
+ return `${days}d ago`
106
+ }
107
+
108
+ function truncateCommit(sha: string): string {
109
+ return sha.slice(0, 7)
110
+ }
111
+
112
+ function SyncStatusBadge({ status }: { status: SyncStatus }) {
113
+ const config = syncStatusConfig[status]
114
+ const Icon = config.icon
115
+
116
+ return (
117
+ <Badge variant="outline" className={cn("gap-1", config.color)}>
118
+ <Icon className={cn("h-3 w-3", status === "syncing" && "animate-spin")} />
119
+ {config.label}
120
+ </Badge>
121
+ )
122
+ }
123
+
124
+ function HealthStatusIndicator({ status }: { status: HealthStatus }) {
125
+ const config = healthStatusConfig[status]
126
+
127
+ return (
128
+ <div className="flex items-center gap-1.5">
129
+ <div className={cn("w-2 h-2 rounded-full", config.bgColor)} />
130
+ <span className={cn("text-sm", config.color)}>{config.label}</span>
131
+ </div>
132
+ )
133
+ }
134
+
135
+ function ApplicationCard({
136
+ app,
137
+ onSync,
138
+ onSuspend,
139
+ onResume,
140
+ onViewDetails,
141
+ }: {
142
+ app: GitOpsApplication
143
+ onSync?: () => void
144
+ onSuspend?: () => void
145
+ onResume?: () => void
146
+ onViewDetails?: () => void
147
+ }) {
148
+ const syncConf = syncStatusConfig[app.syncStatus]
149
+ const healthConf = healthStatusConfig[app.healthStatus]
150
+ const SyncIcon = syncConf.icon
151
+
152
+ const resourceStats = React.useMemo(() => {
153
+ const synced = app.resources.filter((r) => r.syncStatus === "synced").length
154
+ const healthy = app.resources.filter((r) => r.healthStatus === "healthy").length
155
+ return { synced, healthy, total: app.resources.length }
156
+ }, [app.resources])
157
+
158
+ return (
159
+ <Card className={cn(
160
+ app.syncStatus === "failed" && "border-red-500/50",
161
+ app.syncStatus === "out_of_sync" && "border-yellow-500/30",
162
+ app.healthStatus === "degraded" && "border-red-500/30"
163
+ )}>
164
+ <CardContent className="p-4">
165
+ <div className="flex items-start gap-4">
166
+ {/* Status indicator */}
167
+ <div className={cn(
168
+ "w-12 h-12 rounded-lg flex items-center justify-center shrink-0",
169
+ `${syncConf.bgColor}/10`
170
+ )}>
171
+ <SyncIcon className={cn(
172
+ "h-6 w-6",
173
+ syncConf.color,
174
+ app.syncStatus === "syncing" && "animate-spin"
175
+ )} />
176
+ </div>
177
+
178
+ {/* Main info */}
179
+ <div className="flex-1 min-w-0 space-y-3">
180
+ {/* Header */}
181
+ <div className="flex items-start justify-between gap-2">
182
+ <div>
183
+ <div className="flex items-center gap-2">
184
+ <h3 className="font-semibold">{app.name}</h3>
185
+ <Badge variant="secondary" className="text-xs">
186
+ {app.project}
187
+ </Badge>
188
+ </div>
189
+ <div className="flex items-center gap-2 mt-1 text-sm text-muted-foreground">
190
+ <Server className="h-3 w-3" />
191
+ <span>{app.destination.namespace}</span>
192
+ <span>@</span>
193
+ <span className="truncate max-w-[200px]">{app.destination.server}</span>
194
+ </div>
195
+ </div>
196
+
197
+ <div className="flex items-center gap-2 shrink-0">
198
+ <SyncStatusBadge status={app.syncStatus} />
199
+ <HealthStatusIndicator status={app.healthStatus} />
200
+ </div>
201
+ </div>
202
+
203
+ {/* Git info */}
204
+ <div className="flex items-center gap-4 text-sm">
205
+ <div className="flex items-center gap-1.5 text-muted-foreground">
206
+ <GitBranch className="h-4 w-4" />
207
+ <span>{app.repo.branch}</span>
208
+ </div>
209
+ <div className="flex items-center gap-1.5 text-muted-foreground">
210
+ <Folder className="h-4 w-4" />
211
+ <span className="truncate max-w-[150px]">{app.repo.path}</span>
212
+ </div>
213
+ {app.lastSyncedRevision && (
214
+ <div className="flex items-center gap-1.5">
215
+ <GitCommit className="h-4 w-4 text-muted-foreground" />
216
+ <code className="text-xs bg-muted px-1.5 py-0.5 rounded">
217
+ {truncateCommit(app.lastSyncedRevision)}
218
+ </code>
219
+ {app.targetRevision && app.lastSyncedRevision !== app.targetRevision && (
220
+ <>
221
+ <ArrowRight className="h-3 w-3 text-muted-foreground" />
222
+ <code className="text-xs bg-yellow-500/20 text-yellow-600 px-1.5 py-0.5 rounded">
223
+ {truncateCommit(app.targetRevision)}
224
+ </code>
225
+ </>
226
+ )}
227
+ </div>
228
+ )}
229
+ </div>
230
+
231
+ {/* Resources summary */}
232
+ <div className="flex items-center gap-4">
233
+ <div className="flex-1">
234
+ <div className="flex items-center justify-between text-xs mb-1">
235
+ <span className="text-muted-foreground">Resources Synced</span>
236
+ <span>{resourceStats.synced}/{resourceStats.total}</span>
237
+ </div>
238
+ <Progress
239
+ value={(resourceStats.synced / resourceStats.total) * 100}
240
+ className="h-1.5"
241
+ />
242
+ </div>
243
+ <div className="flex-1">
244
+ <div className="flex items-center justify-between text-xs mb-1">
245
+ <span className="text-muted-foreground">Resources Healthy</span>
246
+ <span>{resourceStats.healthy}/{resourceStats.total}</span>
247
+ </div>
248
+ <Progress
249
+ value={(resourceStats.healthy / resourceStats.total) * 100}
250
+ className={cn(
251
+ "h-1.5",
252
+ resourceStats.healthy < resourceStats.total && "[&>div]:bg-yellow-500"
253
+ )}
254
+ />
255
+ </div>
256
+ </div>
257
+
258
+ {/* Error message */}
259
+ {app.message && (app.syncStatus === "failed" || app.healthStatus === "degraded") && (
260
+ <div className="text-sm text-red-500 bg-red-500/10 p-2 rounded">
261
+ {app.message}
262
+ </div>
263
+ )}
264
+
265
+ {/* Footer */}
266
+ <div className="flex items-center justify-between pt-2 border-t">
267
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
268
+ {app.lastSyncedAt && (
269
+ <span className="flex items-center gap-1">
270
+ <Clock className="h-3 w-3" />
271
+ Last synced {formatTimeAgo(app.lastSyncedAt)}
272
+ </span>
273
+ )}
274
+ {app.autoSync && (
275
+ <Badge variant="outline" className="text-xs">
276
+ <RefreshCw className="h-3 w-3 mr-1" />
277
+ Auto-sync
278
+ </Badge>
279
+ )}
280
+ </div>
281
+
282
+ <div className="flex items-center gap-1">
283
+ {onViewDetails && (
284
+ <Button variant="ghost" size="sm" onClick={onViewDetails}>
285
+ <Eye className="h-4 w-4 mr-1" />
286
+ Details
287
+ </Button>
288
+ )}
289
+ {app.syncStatus === "suspended" && onResume ? (
290
+ <Button variant="outline" size="sm" onClick={onResume}>
291
+ <Play className="h-4 w-4 mr-1" />
292
+ Resume
293
+ </Button>
294
+ ) : (
295
+ <>
296
+ {onSuspend && app.syncStatus !== "suspended" && (
297
+ <Button variant="ghost" size="sm" onClick={onSuspend}>
298
+ <Pause className="h-4 w-4" />
299
+ </Button>
300
+ )}
301
+ {onSync && app.syncStatus !== "syncing" && (
302
+ <Button variant="outline" size="sm" onClick={onSync}>
303
+ <RefreshCw className="h-4 w-4 mr-1" />
304
+ Sync
305
+ </Button>
306
+ )}
307
+ </>
308
+ )}
309
+ </div>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ </CardContent>
314
+ </Card>
315
+ )
316
+ }
317
+
318
+ function SyncStats({ applications }: { applications: GitOpsApplication[] }) {
319
+ const stats = React.useMemo(() => {
320
+ return {
321
+ total: applications.length,
322
+ synced: applications.filter((a) => a.syncStatus === "synced").length,
323
+ outOfSync: applications.filter((a) => a.syncStatus === "out_of_sync").length,
324
+ failed: applications.filter((a) => a.syncStatus === "failed").length,
325
+ healthy: applications.filter((a) => a.healthStatus === "healthy").length,
326
+ degraded: applications.filter((a) => a.healthStatus === "degraded").length,
327
+ }
328
+ }, [applications])
329
+
330
+ return (
331
+ <div className="grid grid-cols-6 gap-4">
332
+ <Card>
333
+ <CardContent className="p-4 text-center">
334
+ <div className="text-2xl font-bold">{stats.total}</div>
335
+ <div className="text-sm text-muted-foreground">Total Apps</div>
336
+ </CardContent>
337
+ </Card>
338
+ <Card>
339
+ <CardContent className="p-4 text-center">
340
+ <div className="text-2xl font-bold text-green-500">{stats.synced}</div>
341
+ <div className="text-sm text-muted-foreground">Synced</div>
342
+ </CardContent>
343
+ </Card>
344
+ <Card>
345
+ <CardContent className="p-4 text-center">
346
+ <div className="text-2xl font-bold text-yellow-500">{stats.outOfSync}</div>
347
+ <div className="text-sm text-muted-foreground">Out of Sync</div>
348
+ </CardContent>
349
+ </Card>
350
+ <Card>
351
+ <CardContent className="p-4 text-center">
352
+ <div className="text-2xl font-bold text-red-500">{stats.failed}</div>
353
+ <div className="text-sm text-muted-foreground">Failed</div>
354
+ </CardContent>
355
+ </Card>
356
+ <Card>
357
+ <CardContent className="p-4 text-center">
358
+ <div className="text-2xl font-bold text-green-500">{stats.healthy}</div>
359
+ <div className="text-sm text-muted-foreground">Healthy</div>
360
+ </CardContent>
361
+ </Card>
362
+ <Card>
363
+ <CardContent className="p-4 text-center">
364
+ <div className="text-2xl font-bold text-red-500">{stats.degraded}</div>
365
+ <div className="text-sm text-muted-foreground">Degraded</div>
366
+ </CardContent>
367
+ </Card>
368
+ </div>
369
+ )
370
+ }
371
+
372
+ export function GitOpsSyncStatus({
373
+ applications,
374
+ onSync,
375
+ onRefresh,
376
+ onSuspend,
377
+ onResume,
378
+ onViewDetails,
379
+ className,
380
+ }: GitOpsSyncStatusProps) {
381
+ const [filter, setFilter] = React.useState<SyncStatus | "all">("all")
382
+
383
+ const filteredApps = React.useMemo(() => {
384
+ if (filter === "all") return applications
385
+ return applications.filter((app) => app.syncStatus === filter)
386
+ }, [applications, filter])
387
+
388
+ return (
389
+ <div className={cn("space-y-6", className)}>
390
+ {/* Header */}
391
+ <Card>
392
+ <CardHeader>
393
+ <div className="flex items-center justify-between">
394
+ <div className="flex items-center gap-3">
395
+ <GitBranch className="h-6 w-6" />
396
+ <div>
397
+ <CardTitle>GitOps Sync Status</CardTitle>
398
+ <p className="text-sm text-muted-foreground">
399
+ Monitor application synchronization with Git
400
+ </p>
401
+ </div>
402
+ </div>
403
+
404
+ <div className="flex items-center gap-2">
405
+ {(["all", "synced", "out_of_sync", "failed", "syncing"] as const).map((status) => (
406
+ <Button
407
+ key={status}
408
+ variant={filter === status ? "default" : "ghost"}
409
+ size="sm"
410
+ onClick={() => setFilter(status)}
411
+ >
412
+ {status === "all" ? "All" : syncStatusConfig[status].label}
413
+ </Button>
414
+ ))}
415
+
416
+ {onRefresh && (
417
+ <Button variant="outline" onClick={onRefresh}>
418
+ <RefreshCw className="h-4 w-4 mr-1" />
419
+ Refresh All
420
+ </Button>
421
+ )}
422
+ </div>
423
+ </div>
424
+ </CardHeader>
425
+ </Card>
426
+
427
+ {/* Stats */}
428
+ <SyncStats applications={applications} />
429
+
430
+ {/* Applications */}
431
+ <ScrollArea className="h-[500px]">
432
+ <div className="space-y-4 pr-4">
433
+ {filteredApps.length === 0 ? (
434
+ <div className="flex flex-col items-center justify-center h-48 text-muted-foreground">
435
+ <Cloud className="h-12 w-12 mb-4" />
436
+ <p className="text-lg font-medium">No applications found</p>
437
+ <p className="text-sm">Adjust your filters or add new applications</p>
438
+ </div>
439
+ ) : (
440
+ filteredApps.map((app) => (
441
+ <ApplicationCard
442
+ key={app.id}
443
+ app={app}
444
+ onSync={onSync ? () => onSync(app) : undefined}
445
+ onSuspend={onSuspend ? () => onSuspend(app) : undefined}
446
+ onResume={onResume ? () => onResume(app) : undefined}
447
+ onViewDetails={onViewDetails ? () => onViewDetails(app) : undefined}
448
+ />
449
+ ))
450
+ )}
451
+ </div>
452
+ </ScrollArea>
453
+ </div>
454
+ )
455
+ }
456
+
457
+ // Default sample data
458
+ export const defaultGitOpsApplications: GitOpsApplication[] = [
459
+ {
460
+ id: "1",
461
+ name: "api-gateway",
462
+ project: "production",
463
+ repo: {
464
+ url: "https://github.com/example/infra",
465
+ branch: "main",
466
+ path: "apps/api-gateway",
467
+ },
468
+ destination: {
469
+ server: "https://kubernetes.default.svc",
470
+ namespace: "production",
471
+ },
472
+ syncStatus: "synced",
473
+ healthStatus: "healthy",
474
+ lastSyncedAt: new Date(Date.now() - 5 * 60000),
475
+ lastSyncedRevision: "a1b2c3d4e5f6789",
476
+ autoSync: true,
477
+ resources: [
478
+ { kind: "Deployment", name: "api-gateway", namespace: "production", syncStatus: "synced", healthStatus: "healthy" },
479
+ { kind: "Service", name: "api-gateway", namespace: "production", syncStatus: "synced", healthStatus: "healthy" },
480
+ { kind: "ConfigMap", name: "api-gateway-config", namespace: "production", syncStatus: "synced", healthStatus: "healthy" },
481
+ { kind: "Ingress", name: "api-gateway", namespace: "production", syncStatus: "synced", healthStatus: "healthy" },
482
+ ],
483
+ },
484
+ {
485
+ id: "2",
486
+ name: "user-service",
487
+ project: "production",
488
+ repo: {
489
+ url: "https://github.com/example/infra",
490
+ branch: "main",
491
+ path: "apps/user-service",
492
+ },
493
+ destination: {
494
+ server: "https://kubernetes.default.svc",
495
+ namespace: "production",
496
+ },
497
+ syncStatus: "out_of_sync",
498
+ healthStatus: "healthy",
499
+ lastSyncedAt: new Date(Date.now() - 2 * 3600000),
500
+ lastSyncedRevision: "old123456789",
501
+ targetRevision: "new987654321",
502
+ autoSync: false,
503
+ resources: [
504
+ { kind: "Deployment", name: "user-service", namespace: "production", syncStatus: "out_of_sync", healthStatus: "healthy" },
505
+ { kind: "Service", name: "user-service", namespace: "production", syncStatus: "synced", healthStatus: "healthy" },
506
+ { kind: "Secret", name: "user-service-secrets", namespace: "production", syncStatus: "out_of_sync", healthStatus: "healthy" },
507
+ ],
508
+ },
509
+ {
510
+ id: "3",
511
+ name: "payment-service",
512
+ project: "production",
513
+ repo: {
514
+ url: "https://github.com/example/infra",
515
+ branch: "main",
516
+ path: "apps/payment-service",
517
+ },
518
+ destination: {
519
+ server: "https://kubernetes.default.svc",
520
+ namespace: "production",
521
+ },
522
+ syncStatus: "failed",
523
+ healthStatus: "degraded",
524
+ lastSyncedAt: new Date(Date.now() - 30 * 60000),
525
+ lastSyncedRevision: "abc123def456",
526
+ message: "Failed to apply resource: Deployment/payment-service: ImagePullBackOff",
527
+ autoSync: true,
528
+ resources: [
529
+ { kind: "Deployment", name: "payment-service", namespace: "production", syncStatus: "failed", healthStatus: "degraded", message: "ImagePullBackOff" },
530
+ { kind: "Service", name: "payment-service", namespace: "production", syncStatus: "synced", healthStatus: "healthy" },
531
+ { kind: "Secret", name: "payment-service-secrets", namespace: "production", syncStatus: "synced", healthStatus: "healthy" },
532
+ ],
533
+ },
534
+ {
535
+ id: "4",
536
+ name: "notification-service",
537
+ project: "staging",
538
+ repo: {
539
+ url: "https://github.com/example/infra",
540
+ branch: "develop",
541
+ path: "apps/notification-service",
542
+ },
543
+ destination: {
544
+ server: "https://staging.k8s.example.com",
545
+ namespace: "staging",
546
+ },
547
+ syncStatus: "syncing",
548
+ healthStatus: "progressing",
549
+ lastSyncedAt: new Date(Date.now() - 60000),
550
+ lastSyncedRevision: "syncing123",
551
+ autoSync: true,
552
+ resources: [
553
+ { kind: "Deployment", name: "notification-service", namespace: "staging", syncStatus: "syncing", healthStatus: "progressing" },
554
+ { kind: "Service", name: "notification-service", namespace: "staging", syncStatus: "synced", healthStatus: "healthy" },
555
+ ],
556
+ },
557
+ ]