@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,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
|
+
]
|