@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,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 { ScrollArea } from "../scroll-area"
|
|
8
|
+
import {
|
|
9
|
+
Select,
|
|
10
|
+
SelectContent,
|
|
11
|
+
SelectItem,
|
|
12
|
+
SelectTrigger,
|
|
13
|
+
SelectValue,
|
|
14
|
+
} from "../select"
|
|
15
|
+
import {
|
|
16
|
+
Settings,
|
|
17
|
+
ArrowLeftRight,
|
|
18
|
+
Plus,
|
|
19
|
+
Minus,
|
|
20
|
+
Equal,
|
|
21
|
+
RefreshCw,
|
|
22
|
+
Copy,
|
|
23
|
+
CheckCircle2,
|
|
24
|
+
ChevronDown,
|
|
25
|
+
ChevronRight,
|
|
26
|
+
} from "lucide-react"
|
|
27
|
+
|
|
28
|
+
export interface ConfigValue {
|
|
29
|
+
key: string
|
|
30
|
+
value: string | number | boolean | null
|
|
31
|
+
type?: "string" | "number" | "boolean" | "null"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ConfigEnvironment {
|
|
35
|
+
id: string
|
|
36
|
+
name: string
|
|
37
|
+
values: ConfigValue[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type DiffType = "added" | "removed" | "modified" | "unchanged"
|
|
41
|
+
|
|
42
|
+
export interface ConfigDiff {
|
|
43
|
+
key: string
|
|
44
|
+
leftValue?: string | number | boolean | null
|
|
45
|
+
rightValue?: string | number | boolean | null
|
|
46
|
+
diffType: DiffType
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface WakaConfigComparatorProps {
|
|
50
|
+
/** List of environments to compare */
|
|
51
|
+
environments: ConfigEnvironment[]
|
|
52
|
+
/** Left environment ID */
|
|
53
|
+
leftEnvId?: string
|
|
54
|
+
/** Right environment ID */
|
|
55
|
+
rightEnvId?: string
|
|
56
|
+
/** Callback when environment selection changes */
|
|
57
|
+
onEnvironmentChange?: (left: string, right: string) => void
|
|
58
|
+
/** Callback when copying value */
|
|
59
|
+
onCopyValue?: (key: string, value: unknown, fromEnv: string, toEnv: string) => void
|
|
60
|
+
/** Show only differences */
|
|
61
|
+
showDiffsOnly?: boolean
|
|
62
|
+
/** Title */
|
|
63
|
+
title?: string
|
|
64
|
+
/** Custom class name */
|
|
65
|
+
className?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const diffTypeConfig: Record<DiffType, { icon: React.ElementType; color: string; bgColor: string; label: string }> = {
|
|
69
|
+
added: { icon: Plus, color: "text-green-500", bgColor: "bg-green-500/10", label: "Added" },
|
|
70
|
+
removed: { icon: Minus, color: "text-red-500", bgColor: "bg-red-500/10", label: "Removed" },
|
|
71
|
+
modified: { icon: ArrowLeftRight, color: "text-yellow-500", bgColor: "bg-yellow-500/10", label: "Modified" },
|
|
72
|
+
unchanged: { icon: Equal, color: "text-muted-foreground", bgColor: "", label: "Unchanged" },
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function formatValue(value: unknown): string {
|
|
76
|
+
if (value === null) return "null"
|
|
77
|
+
if (value === undefined) return "undefined"
|
|
78
|
+
if (typeof value === "boolean") return value ? "true" : "false"
|
|
79
|
+
if (typeof value === "string") return `"${value}"`
|
|
80
|
+
return String(value)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function DiffRow({
|
|
84
|
+
diff,
|
|
85
|
+
leftEnvName,
|
|
86
|
+
rightEnvName,
|
|
87
|
+
onCopyLeft,
|
|
88
|
+
onCopyRight,
|
|
89
|
+
}: {
|
|
90
|
+
diff: ConfigDiff
|
|
91
|
+
leftEnvName: string
|
|
92
|
+
rightEnvName: string
|
|
93
|
+
onCopyLeft?: () => void
|
|
94
|
+
onCopyRight?: () => void
|
|
95
|
+
}) {
|
|
96
|
+
const [copiedLeft, setCopiedLeft] = React.useState(false)
|
|
97
|
+
const [copiedRight, setCopiedRight] = React.useState(false)
|
|
98
|
+
const config = diffTypeConfig[diff.diffType]
|
|
99
|
+
const Icon = config.icon
|
|
100
|
+
|
|
101
|
+
const handleCopyLeft = () => {
|
|
102
|
+
onCopyLeft?.()
|
|
103
|
+
setCopiedLeft(true)
|
|
104
|
+
setTimeout(() => setCopiedLeft(false), 2000)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const handleCopyRight = () => {
|
|
108
|
+
onCopyRight?.()
|
|
109
|
+
setCopiedRight(true)
|
|
110
|
+
setTimeout(() => setCopiedRight(false), 2000)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div className={cn(
|
|
115
|
+
"grid grid-cols-[minmax(150px,1fr)_minmax(200px,2fr)_minmax(200px,2fr)] gap-2 p-2 border-b items-center",
|
|
116
|
+
config.bgColor
|
|
117
|
+
)}>
|
|
118
|
+
{/* Key */}
|
|
119
|
+
<div className="flex items-center gap-2">
|
|
120
|
+
<Icon className={cn("h-4 w-4 shrink-0", config.color)} />
|
|
121
|
+
<span className="font-mono text-sm truncate">{diff.key}</span>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{/* Left value */}
|
|
125
|
+
<div className="flex items-center gap-2">
|
|
126
|
+
{diff.leftValue !== undefined ? (
|
|
127
|
+
<>
|
|
128
|
+
<code className={cn(
|
|
129
|
+
"flex-1 text-xs bg-muted px-2 py-1 rounded truncate",
|
|
130
|
+
diff.diffType === "removed" && "line-through opacity-50"
|
|
131
|
+
)}>
|
|
132
|
+
{formatValue(diff.leftValue)}
|
|
133
|
+
</code>
|
|
134
|
+
{diff.diffType !== "unchanged" && onCopyRight && (
|
|
135
|
+
<Button
|
|
136
|
+
variant="ghost"
|
|
137
|
+
size="sm"
|
|
138
|
+
className="h-6 w-6 p-0 shrink-0"
|
|
139
|
+
onClick={handleCopyRight}
|
|
140
|
+
title={`Copy to ${rightEnvName}`}
|
|
141
|
+
>
|
|
142
|
+
{copiedRight ? (
|
|
143
|
+
<CheckCircle2 className="h-3 w-3 text-green-500" />
|
|
144
|
+
) : (
|
|
145
|
+
<Copy className="h-3 w-3" />
|
|
146
|
+
)}
|
|
147
|
+
</Button>
|
|
148
|
+
)}
|
|
149
|
+
</>
|
|
150
|
+
) : (
|
|
151
|
+
<span className="text-xs text-muted-foreground italic">Not set</span>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Right value */}
|
|
156
|
+
<div className="flex items-center gap-2">
|
|
157
|
+
{diff.rightValue !== undefined ? (
|
|
158
|
+
<>
|
|
159
|
+
<code className={cn(
|
|
160
|
+
"flex-1 text-xs bg-muted px-2 py-1 rounded truncate",
|
|
161
|
+
diff.diffType === "added" && "bg-green-500/20"
|
|
162
|
+
)}>
|
|
163
|
+
{formatValue(diff.rightValue)}
|
|
164
|
+
</code>
|
|
165
|
+
{diff.diffType !== "unchanged" && onCopyLeft && (
|
|
166
|
+
<Button
|
|
167
|
+
variant="ghost"
|
|
168
|
+
size="sm"
|
|
169
|
+
className="h-6 w-6 p-0 shrink-0"
|
|
170
|
+
onClick={handleCopyLeft}
|
|
171
|
+
title={`Copy to ${leftEnvName}`}
|
|
172
|
+
>
|
|
173
|
+
{copiedLeft ? (
|
|
174
|
+
<CheckCircle2 className="h-3 w-3 text-green-500" />
|
|
175
|
+
) : (
|
|
176
|
+
<Copy className="h-3 w-3" />
|
|
177
|
+
)}
|
|
178
|
+
</Button>
|
|
179
|
+
)}
|
|
180
|
+
</>
|
|
181
|
+
) : (
|
|
182
|
+
<span className="text-xs text-muted-foreground italic">Not set</span>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function WakaConfigComparator({
|
|
190
|
+
environments,
|
|
191
|
+
leftEnvId: initialLeftId,
|
|
192
|
+
rightEnvId: initialRightId,
|
|
193
|
+
onEnvironmentChange,
|
|
194
|
+
onCopyValue,
|
|
195
|
+
showDiffsOnly: initialShowDiffsOnly = false,
|
|
196
|
+
title = "Configuration Comparison",
|
|
197
|
+
className,
|
|
198
|
+
}: WakaConfigComparatorProps) {
|
|
199
|
+
const [leftEnvId, setLeftEnvId] = React.useState(initialLeftId || environments[0]?.id)
|
|
200
|
+
const [rightEnvId, setRightEnvId] = React.useState(initialRightId || environments[1]?.id)
|
|
201
|
+
const [showDiffsOnly, setShowDiffsOnly] = React.useState(initialShowDiffsOnly)
|
|
202
|
+
|
|
203
|
+
const leftEnv = environments.find((e) => e.id === leftEnvId)
|
|
204
|
+
const rightEnv = environments.find((e) => e.id === rightEnvId)
|
|
205
|
+
|
|
206
|
+
// Calculate diffs
|
|
207
|
+
const diffs = React.useMemo<ConfigDiff[]>(() => {
|
|
208
|
+
if (!leftEnv || !rightEnv) return []
|
|
209
|
+
|
|
210
|
+
const leftMap = new Map(leftEnv.values.map((v) => [v.key, v.value]))
|
|
211
|
+
const rightMap = new Map(rightEnv.values.map((v) => [v.key, v.value]))
|
|
212
|
+
const allKeys = new Set([...leftMap.keys(), ...rightMap.keys()])
|
|
213
|
+
|
|
214
|
+
return Array.from(allKeys)
|
|
215
|
+
.map((key) => {
|
|
216
|
+
const leftValue = leftMap.get(key)
|
|
217
|
+
const rightValue = rightMap.get(key)
|
|
218
|
+
|
|
219
|
+
let diffType: DiffType
|
|
220
|
+
if (leftValue === undefined) {
|
|
221
|
+
diffType = "added"
|
|
222
|
+
} else if (rightValue === undefined) {
|
|
223
|
+
diffType = "removed"
|
|
224
|
+
} else if (leftValue !== rightValue) {
|
|
225
|
+
diffType = "modified"
|
|
226
|
+
} else {
|
|
227
|
+
diffType = "unchanged"
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { key, leftValue, rightValue, diffType }
|
|
231
|
+
})
|
|
232
|
+
.sort((a, b) => {
|
|
233
|
+
// Sort by diff type priority, then by key
|
|
234
|
+
const priority: Record<DiffType, number> = { removed: 0, added: 1, modified: 2, unchanged: 3 }
|
|
235
|
+
const pDiff = priority[a.diffType] - priority[b.diffType]
|
|
236
|
+
if (pDiff !== 0) return pDiff
|
|
237
|
+
return a.key.localeCompare(b.key)
|
|
238
|
+
})
|
|
239
|
+
}, [leftEnv, rightEnv])
|
|
240
|
+
|
|
241
|
+
const filteredDiffs = showDiffsOnly
|
|
242
|
+
? diffs.filter((d) => d.diffType !== "unchanged")
|
|
243
|
+
: diffs
|
|
244
|
+
|
|
245
|
+
// Count by diff type
|
|
246
|
+
const diffCounts = React.useMemo(() => {
|
|
247
|
+
return diffs.reduce((acc, d) => {
|
|
248
|
+
acc[d.diffType] = (acc[d.diffType] || 0) + 1
|
|
249
|
+
return acc
|
|
250
|
+
}, {} as Record<DiffType, number>)
|
|
251
|
+
}, [diffs])
|
|
252
|
+
|
|
253
|
+
const handleLeftChange = (id: string) => {
|
|
254
|
+
setLeftEnvId(id)
|
|
255
|
+
onEnvironmentChange?.(id, rightEnvId!)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const handleRightChange = (id: string) => {
|
|
259
|
+
setRightEnvId(id)
|
|
260
|
+
onEnvironmentChange?.(leftEnvId!, id)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const swapEnvironments = () => {
|
|
264
|
+
const temp = leftEnvId
|
|
265
|
+
setLeftEnvId(rightEnvId)
|
|
266
|
+
setRightEnvId(temp)
|
|
267
|
+
onEnvironmentChange?.(rightEnvId!, temp!)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div className={cn("flex flex-col border rounded-lg bg-background", className)}>
|
|
272
|
+
{/* Header */}
|
|
273
|
+
<div className="flex items-center justify-between gap-4 p-3 border-b">
|
|
274
|
+
<div className="flex items-center gap-3">
|
|
275
|
+
<Settings className="h-5 w-5" />
|
|
276
|
+
<h3 className="font-semibold">{title}</h3>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div className="flex items-center gap-2">
|
|
280
|
+
{diffCounts.modified > 0 && (
|
|
281
|
+
<Badge className="bg-yellow-500">{diffCounts.modified} modified</Badge>
|
|
282
|
+
)}
|
|
283
|
+
{diffCounts.added > 0 && (
|
|
284
|
+
<Badge className="bg-green-500">{diffCounts.added} added</Badge>
|
|
285
|
+
)}
|
|
286
|
+
{diffCounts.removed > 0 && (
|
|
287
|
+
<Badge className="bg-red-500">{diffCounts.removed} removed</Badge>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Environment selectors */}
|
|
293
|
+
<div className="flex items-center gap-4 p-3 border-b bg-muted/30">
|
|
294
|
+
<div className="flex-1">
|
|
295
|
+
<div className="text-xs text-muted-foreground mb-1">Left Environment</div>
|
|
296
|
+
<Select value={leftEnvId} onValueChange={handleLeftChange}>
|
|
297
|
+
<SelectTrigger>
|
|
298
|
+
<SelectValue />
|
|
299
|
+
</SelectTrigger>
|
|
300
|
+
<SelectContent>
|
|
301
|
+
{environments.map((env) => (
|
|
302
|
+
<SelectItem key={env.id} value={env.id} disabled={env.id === rightEnvId}>
|
|
303
|
+
{env.name}
|
|
304
|
+
</SelectItem>
|
|
305
|
+
))}
|
|
306
|
+
</SelectContent>
|
|
307
|
+
</Select>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<Button variant="outline" size="sm" className="shrink-0" onClick={swapEnvironments}>
|
|
311
|
+
<ArrowLeftRight className="h-4 w-4" />
|
|
312
|
+
</Button>
|
|
313
|
+
|
|
314
|
+
<div className="flex-1">
|
|
315
|
+
<div className="text-xs text-muted-foreground mb-1">Right Environment</div>
|
|
316
|
+
<Select value={rightEnvId} onValueChange={handleRightChange}>
|
|
317
|
+
<SelectTrigger>
|
|
318
|
+
<SelectValue />
|
|
319
|
+
</SelectTrigger>
|
|
320
|
+
<SelectContent>
|
|
321
|
+
{environments.map((env) => (
|
|
322
|
+
<SelectItem key={env.id} value={env.id} disabled={env.id === leftEnvId}>
|
|
323
|
+
{env.name}
|
|
324
|
+
</SelectItem>
|
|
325
|
+
))}
|
|
326
|
+
</SelectContent>
|
|
327
|
+
</Select>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
{/* Toolbar */}
|
|
332
|
+
<div className="flex items-center gap-2 px-3 py-2 border-b">
|
|
333
|
+
<Button
|
|
334
|
+
variant={showDiffsOnly ? "default" : "outline"}
|
|
335
|
+
size="sm"
|
|
336
|
+
onClick={() => setShowDiffsOnly(!showDiffsOnly)}
|
|
337
|
+
>
|
|
338
|
+
Show differences only
|
|
339
|
+
</Button>
|
|
340
|
+
<span className="text-xs text-muted-foreground ml-auto">
|
|
341
|
+
{filteredDiffs.length} of {diffs.length} entries
|
|
342
|
+
</span>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
{/* Column headers */}
|
|
346
|
+
<div className="grid grid-cols-[minmax(150px,1fr)_minmax(200px,2fr)_minmax(200px,2fr)] gap-2 px-2 py-2 border-b bg-muted/50 text-xs font-medium text-muted-foreground">
|
|
347
|
+
<div>Key</div>
|
|
348
|
+
<div>{leftEnv?.name || "Left"}</div>
|
|
349
|
+
<div>{rightEnv?.name || "Right"}</div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
{/* Diff list */}
|
|
353
|
+
<ScrollArea className="flex-1 max-h-[400px]">
|
|
354
|
+
{filteredDiffs.length === 0 ? (
|
|
355
|
+
<div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
|
|
356
|
+
<CheckCircle2 className="h-8 w-8 mb-2 text-green-500" />
|
|
357
|
+
<span>No differences found</span>
|
|
358
|
+
</div>
|
|
359
|
+
) : (
|
|
360
|
+
filteredDiffs.map((diff) => (
|
|
361
|
+
<DiffRow
|
|
362
|
+
key={diff.key}
|
|
363
|
+
diff={diff}
|
|
364
|
+
leftEnvName={leftEnv?.name || "Left"}
|
|
365
|
+
rightEnvName={rightEnv?.name || "Right"}
|
|
366
|
+
onCopyLeft={() => onCopyValue?.(diff.key, diff.rightValue, rightEnvId!, leftEnvId!)}
|
|
367
|
+
onCopyRight={() => onCopyValue?.(diff.key, diff.leftValue, leftEnvId!, rightEnvId!)}
|
|
368
|
+
/>
|
|
369
|
+
))
|
|
370
|
+
)}
|
|
371
|
+
</ScrollArea>
|
|
372
|
+
</div>
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Default sample environments for demo
|
|
377
|
+
export const defaultConfigEnvironments: ConfigEnvironment[] = [
|
|
378
|
+
{
|
|
379
|
+
id: "production",
|
|
380
|
+
name: "Production",
|
|
381
|
+
values: [
|
|
382
|
+
{ key: "API_URL", value: "https://api.example.com" },
|
|
383
|
+
{ key: "DEBUG", value: false },
|
|
384
|
+
{ key: "LOG_LEVEL", value: "error" },
|
|
385
|
+
{ key: "MAX_CONNECTIONS", value: 100 },
|
|
386
|
+
{ key: "CACHE_TTL", value: 3600 },
|
|
387
|
+
{ key: "FEATURE_NEW_UI", value: false },
|
|
388
|
+
],
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: "staging",
|
|
392
|
+
name: "Staging",
|
|
393
|
+
values: [
|
|
394
|
+
{ key: "API_URL", value: "https://staging-api.example.com" },
|
|
395
|
+
{ key: "DEBUG", value: true },
|
|
396
|
+
{ key: "LOG_LEVEL", value: "debug" },
|
|
397
|
+
{ key: "MAX_CONNECTIONS", value: 50 },
|
|
398
|
+
{ key: "CACHE_TTL", value: 300 },
|
|
399
|
+
{ key: "FEATURE_NEW_UI", value: true },
|
|
400
|
+
{ key: "TEST_MODE", value: true },
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: "development",
|
|
405
|
+
name: "Development",
|
|
406
|
+
values: [
|
|
407
|
+
{ key: "API_URL", value: "http://localhost:3000" },
|
|
408
|
+
{ key: "DEBUG", value: true },
|
|
409
|
+
{ key: "LOG_LEVEL", value: "trace" },
|
|
410
|
+
{ key: "MAX_CONNECTIONS", value: 10 },
|
|
411
|
+
{ key: "FEATURE_NEW_UI", value: true },
|
|
412
|
+
{ key: "TEST_MODE", value: true },
|
|
413
|
+
{ key: "HOT_RELOAD", value: true },
|
|
414
|
+
],
|
|
415
|
+
},
|
|
416
|
+
]
|