@friggframework/devtools 2.0.0--canary.398.dd443c7.0 → 2.0.0--canary.402.d2f4ae6.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/frigg-cli/.eslintrc.js +141 -0
- package/frigg-cli/__tests__/jest.config.js +102 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
- package/frigg-cli/__tests__/utils/command-tester.js +170 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +286 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/index.js +19 -1
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/package.json +51 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +25 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/serverless-template.js +177 -292
- package/management-ui/.eslintrc.js +22 -0
- package/management-ui/README.md +203 -0
- package/management-ui/components.json +21 -0
- package/management-ui/docs/phase2-integration-guide.md +320 -0
- package/management-ui/{dist/index.html → index.html} +1 -2
- package/management-ui/package-lock.json +16517 -0
- package/management-ui/package.json +76 -0
- package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
- package/management-ui/postcss.config.js +6 -0
- package/management-ui/server/api/backend.js +256 -0
- package/management-ui/server/api/cli.js +315 -0
- package/management-ui/server/api/codegen.js +663 -0
- package/management-ui/server/api/connections.js +857 -0
- package/management-ui/server/api/discovery.js +185 -0
- package/management-ui/server/api/environment/index.js +1 -0
- package/management-ui/server/api/environment/router.js +378 -0
- package/management-ui/server/api/environment.js +328 -0
- package/management-ui/server/api/integrations.js +876 -0
- package/management-ui/server/api/logs.js +248 -0
- package/management-ui/server/api/monitoring.js +282 -0
- package/management-ui/server/api/open-ide.js +31 -0
- package/management-ui/server/api/project.js +1029 -0
- package/management-ui/server/api/users/sessions.js +371 -0
- package/management-ui/server/api/users/simulation.js +254 -0
- package/management-ui/server/api/users.js +362 -0
- package/management-ui/server/api-contract.md +275 -0
- package/management-ui/server/index.js +873 -0
- package/management-ui/server/middleware/errorHandler.js +93 -0
- package/management-ui/server/middleware/security.js +32 -0
- package/management-ui/server/processManager.js +296 -0
- package/management-ui/server/server.js +346 -0
- package/management-ui/server/services/aws-monitor.js +413 -0
- package/management-ui/server/services/npm-registry.js +347 -0
- package/management-ui/server/services/template-engine.js +538 -0
- package/management-ui/server/utils/cliIntegration.js +220 -0
- package/management-ui/server/utils/environment/auditLogger.js +471 -0
- package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
- package/management-ui/server/utils/environment/encryption.js +278 -0
- package/management-ui/server/utils/environment/envFileManager.js +286 -0
- package/management-ui/server/utils/import-commonjs.js +28 -0
- package/management-ui/server/utils/response.js +83 -0
- package/management-ui/server/websocket/handler.js +325 -0
- package/management-ui/src/App.jsx +109 -0
- package/management-ui/src/components/AppRouter.jsx +65 -0
- package/management-ui/src/components/Button.jsx +70 -0
- package/management-ui/src/components/Card.jsx +97 -0
- package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
- package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
- package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
- package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
- package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
- package/management-ui/src/components/ErrorBoundary.jsx +73 -0
- package/management-ui/src/components/IntegrationCard.jsx +481 -0
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
- package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
- package/management-ui/src/components/IntegrationStatus.jsx +336 -0
- package/management-ui/src/components/Layout.jsx +716 -0
- package/management-ui/src/components/LoadingSpinner.jsx +113 -0
- package/management-ui/src/components/RepositoryPicker.jsx +248 -0
- package/management-ui/src/components/SessionMonitor.jsx +350 -0
- package/management-ui/src/components/StatusBadge.jsx +208 -0
- package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
- package/management-ui/src/components/UserSimulation.jsx +327 -0
- package/management-ui/src/components/Welcome.jsx +434 -0
- package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
- package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
- package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
- package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
- package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
- package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
- package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
- package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
- package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
- package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
- package/management-ui/src/components/codegen/index.js +10 -0
- package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
- package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
- package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
- package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
- package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
- package/management-ui/src/components/connections/index.js +5 -0
- package/management-ui/src/components/index.js +21 -0
- package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
- package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
- package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
- package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
- package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
- package/management-ui/src/components/monitoring/index.js +6 -0
- package/management-ui/src/components/monitoring/monitoring.css +218 -0
- package/management-ui/src/components/theme-provider.jsx +52 -0
- package/management-ui/src/components/theme-toggle.jsx +39 -0
- package/management-ui/src/components/ui/badge.tsx +36 -0
- package/management-ui/src/components/ui/button.test.jsx +56 -0
- package/management-ui/src/components/ui/button.tsx +57 -0
- package/management-ui/src/components/ui/card.tsx +76 -0
- package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
- package/management-ui/src/components/ui/select.tsx +157 -0
- package/management-ui/src/components/ui/skeleton.jsx +15 -0
- package/management-ui/src/hooks/useFrigg.jsx +601 -0
- package/management-ui/src/hooks/useSocket.jsx +58 -0
- package/management-ui/src/index.css +193 -0
- package/management-ui/src/lib/utils.ts +6 -0
- package/management-ui/src/main.jsx +10 -0
- package/management-ui/src/pages/CodeGeneration.jsx +14 -0
- package/management-ui/src/pages/Connections.jsx +252 -0
- package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
- package/management-ui/src/pages/Dashboard.jsx +311 -0
- package/management-ui/src/pages/Environment.jsx +314 -0
- package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
- package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
- package/management-ui/src/pages/IntegrationTest.jsx +742 -0
- package/management-ui/src/pages/Integrations.jsx +253 -0
- package/management-ui/src/pages/Monitoring.jsx +17 -0
- package/management-ui/src/pages/Simulation.jsx +155 -0
- package/management-ui/src/pages/Users.jsx +492 -0
- package/management-ui/src/services/api.js +41 -0
- package/management-ui/src/services/apiModuleService.js +193 -0
- package/management-ui/src/services/websocket-handlers.js +120 -0
- package/management-ui/src/test/api/project.test.js +273 -0
- package/management-ui/src/test/components/Welcome.test.jsx +378 -0
- package/management-ui/src/test/mocks/server.js +178 -0
- package/management-ui/src/test/setup.js +61 -0
- package/management-ui/src/test/utils/test-utils.jsx +134 -0
- package/management-ui/src/utils/repository.js +98 -0
- package/management-ui/src/utils/repository.test.js +118 -0
- package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
- package/management-ui/tailwind.config.js +63 -0
- package/management-ui/tsconfig.json +37 -0
- package/management-ui/tsconfig.node.json +10 -0
- package/management-ui/vite.config.js +26 -0
- package/management-ui/vitest.config.js +38 -0
- package/package.json +5 -5
- package/management-ui/dist/assets/index-BA21WgFa.js +0 -1221
- package/management-ui/dist/assets/index-CbM64Oba.js +0 -1221
- package/management-ui/dist/assets/index-CkvseXTC.css +0 -1
- /package/management-ui/{dist/assets/FriggLogo-B7Xx8ZW1.svg → src/assets/FriggLogo.svg} +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import api from '../services/api'
|
|
3
|
+
|
|
4
|
+
const EnvironmentCompare = ({ onSync }) => {
|
|
5
|
+
const [environments, setEnvironments] = useState({
|
|
6
|
+
local: [],
|
|
7
|
+
staging: [],
|
|
8
|
+
production: []
|
|
9
|
+
})
|
|
10
|
+
const [loading, setLoading] = useState(true)
|
|
11
|
+
const [compareMode, setCompareMode] = useState('side-by-side') // side-by-side, diff, missing
|
|
12
|
+
const [selectedEnvs, setSelectedEnvs] = useState(['local', 'staging'])
|
|
13
|
+
const [syncDirection, setSyncDirection] = useState(null)
|
|
14
|
+
const [selectedVariables, setSelectedVariables] = useState(new Set())
|
|
15
|
+
const [showOnlyDifferences, setShowOnlyDifferences] = useState(false)
|
|
16
|
+
|
|
17
|
+
// Load all environments
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
loadAllEnvironments()
|
|
20
|
+
}, [])
|
|
21
|
+
|
|
22
|
+
const loadAllEnvironments = async () => {
|
|
23
|
+
setLoading(true)
|
|
24
|
+
try {
|
|
25
|
+
const [localRes, stagingRes, prodRes] = await Promise.all([
|
|
26
|
+
api.get('/api/environment/variables/local'),
|
|
27
|
+
api.get('/api/environment/variables/staging'),
|
|
28
|
+
api.get('/api/environment/variables/production')
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
setEnvironments({
|
|
32
|
+
local: localRes.data.variables || [],
|
|
33
|
+
staging: stagingRes.data.variables || [],
|
|
34
|
+
production: prodRes.data.variables || []
|
|
35
|
+
})
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Error loading environments:', error)
|
|
38
|
+
} finally {
|
|
39
|
+
setLoading(false)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Get all unique variable keys across selected environments
|
|
44
|
+
const getAllKeys = () => {
|
|
45
|
+
const keys = new Set()
|
|
46
|
+
selectedEnvs.forEach(env => {
|
|
47
|
+
environments[env].forEach(variable => {
|
|
48
|
+
keys.add(variable.key)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
return Array.from(keys).sort()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if variable values differ across environments
|
|
55
|
+
const isDifferent = (key) => {
|
|
56
|
+
const values = selectedEnvs.map(env => {
|
|
57
|
+
const variable = environments[env].find(v => v.key === key)
|
|
58
|
+
return variable ? variable.value : undefined
|
|
59
|
+
})
|
|
60
|
+
return new Set(values).size > 1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Get value for a specific environment and key
|
|
64
|
+
const getValue = (env, key) => {
|
|
65
|
+
const variable = environments[env].find(v => v.key === key)
|
|
66
|
+
return variable ? variable.value : null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Handle variable selection for sync
|
|
70
|
+
const toggleVariableSelection = (key) => {
|
|
71
|
+
const newSelected = new Set(selectedVariables)
|
|
72
|
+
if (newSelected.has(key)) {
|
|
73
|
+
newSelected.delete(key)
|
|
74
|
+
} else {
|
|
75
|
+
newSelected.add(key)
|
|
76
|
+
}
|
|
77
|
+
setSelectedVariables(newSelected)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Sync selected variables
|
|
81
|
+
const handleSync = async () => {
|
|
82
|
+
if (!syncDirection || selectedVariables.size === 0) return
|
|
83
|
+
|
|
84
|
+
const [source, target] = syncDirection.split('->')
|
|
85
|
+
const variablesToSync = []
|
|
86
|
+
|
|
87
|
+
selectedVariables.forEach(key => {
|
|
88
|
+
const sourceVar = environments[source].find(v => v.key === key)
|
|
89
|
+
if (sourceVar) {
|
|
90
|
+
variablesToSync.push({
|
|
91
|
+
key: sourceVar.key,
|
|
92
|
+
value: sourceVar.value,
|
|
93
|
+
description: sourceVar.description || ''
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await api.put(`/api/environment/variables/${target}`, {
|
|
100
|
+
variables: variablesToSync
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if (onSync) {
|
|
104
|
+
onSync({
|
|
105
|
+
source,
|
|
106
|
+
target,
|
|
107
|
+
count: variablesToSync.length
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Reload environments
|
|
112
|
+
await loadAllEnvironments()
|
|
113
|
+
setSelectedVariables(new Set())
|
|
114
|
+
setSyncDirection(null)
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('Error syncing variables:', error)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Copy environment
|
|
121
|
+
const handleCopyEnvironment = async (source, target) => {
|
|
122
|
+
try {
|
|
123
|
+
const confirm = window.confirm(
|
|
124
|
+
`Are you sure you want to copy all variables from ${source} to ${target}? This will overwrite existing values.`
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if (!confirm) return
|
|
128
|
+
|
|
129
|
+
await api.post('/api/environment/copy', {
|
|
130
|
+
source,
|
|
131
|
+
target,
|
|
132
|
+
excludeSecrets: false
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
await loadAllEnvironments()
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Error copying environment:', error)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Export comparison report
|
|
142
|
+
const exportComparison = () => {
|
|
143
|
+
const allKeys = getAllKeys()
|
|
144
|
+
const report = {
|
|
145
|
+
timestamp: new Date().toISOString(),
|
|
146
|
+
environments: selectedEnvs,
|
|
147
|
+
variables: {}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
allKeys.forEach(key => {
|
|
151
|
+
report.variables[key] = {}
|
|
152
|
+
selectedEnvs.forEach(env => {
|
|
153
|
+
const variable = environments[env].find(v => v.key === key)
|
|
154
|
+
report.variables[key][env] = variable ? {
|
|
155
|
+
value: variable.value,
|
|
156
|
+
exists: true
|
|
157
|
+
} : {
|
|
158
|
+
value: null,
|
|
159
|
+
exists: false
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const blob = new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' })
|
|
165
|
+
const url = URL.createObjectURL(blob)
|
|
166
|
+
const a = document.createElement('a')
|
|
167
|
+
a.href = url
|
|
168
|
+
a.download = `environment-comparison-${Date.now()}.json`
|
|
169
|
+
a.click()
|
|
170
|
+
URL.revokeObjectURL(url)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (loading) {
|
|
174
|
+
return (
|
|
175
|
+
<div className="flex items-center justify-center h-64">
|
|
176
|
+
<div className="text-gray-500">Loading environments...</div>
|
|
177
|
+
</div>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const allKeys = getAllKeys()
|
|
182
|
+
const filteredKeys = showOnlyDifferences
|
|
183
|
+
? allKeys.filter(key => isDifferent(key))
|
|
184
|
+
: allKeys
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div className="environment-compare">
|
|
188
|
+
{/* Controls */}
|
|
189
|
+
<div className="bg-white rounded-lg shadow p-4 mb-4">
|
|
190
|
+
<div className="flex items-center justify-between">
|
|
191
|
+
<div className="flex items-center space-x-4">
|
|
192
|
+
{/* Environment Selection */}
|
|
193
|
+
<div className="flex items-center space-x-2">
|
|
194
|
+
<span className="text-sm font-medium text-gray-700">Compare:</span>
|
|
195
|
+
{['local', 'staging', 'production'].map(env => (
|
|
196
|
+
<label key={env} className="flex items-center">
|
|
197
|
+
<input
|
|
198
|
+
type="checkbox"
|
|
199
|
+
checked={selectedEnvs.includes(env)}
|
|
200
|
+
onChange={(e) => {
|
|
201
|
+
if (e.target.checked) {
|
|
202
|
+
setSelectedEnvs([...selectedEnvs, env])
|
|
203
|
+
} else {
|
|
204
|
+
setSelectedEnvs(selectedEnvs.filter(e => e !== env))
|
|
205
|
+
}
|
|
206
|
+
}}
|
|
207
|
+
disabled={selectedEnvs.length === 1 && selectedEnvs.includes(env)}
|
|
208
|
+
className="mr-1"
|
|
209
|
+
/>
|
|
210
|
+
<span className="text-sm capitalize">{env}</span>
|
|
211
|
+
</label>
|
|
212
|
+
))}
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* View Mode */}
|
|
216
|
+
<div className="flex items-center space-x-2">
|
|
217
|
+
<span className="text-sm font-medium text-gray-700">View:</span>
|
|
218
|
+
<select
|
|
219
|
+
value={compareMode}
|
|
220
|
+
onChange={(e) => setCompareMode(e.target.value)}
|
|
221
|
+
className="text-sm border border-gray-300 rounded px-2 py-1"
|
|
222
|
+
>
|
|
223
|
+
<option value="side-by-side">Side by Side</option>
|
|
224
|
+
<option value="diff">Differences Only</option>
|
|
225
|
+
<option value="missing">Missing Variables</option>
|
|
226
|
+
</select>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{/* Show Only Differences */}
|
|
230
|
+
<label className="flex items-center">
|
|
231
|
+
<input
|
|
232
|
+
type="checkbox"
|
|
233
|
+
checked={showOnlyDifferences}
|
|
234
|
+
onChange={(e) => setShowOnlyDifferences(e.target.checked)}
|
|
235
|
+
className="mr-2"
|
|
236
|
+
/>
|
|
237
|
+
<span className="text-sm">Show only differences</span>
|
|
238
|
+
</label>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<button
|
|
242
|
+
onClick={exportComparison}
|
|
243
|
+
className="px-3 py-1 text-sm bg-gray-200 text-gray-700 rounded hover:bg-gray-300"
|
|
244
|
+
>
|
|
245
|
+
Export Report
|
|
246
|
+
</button>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
{/* Sync Controls */}
|
|
250
|
+
{selectedEnvs.length === 2 && (
|
|
251
|
+
<div className="mt-4 pt-4 border-t border-gray-200">
|
|
252
|
+
<div className="flex items-center justify-between">
|
|
253
|
+
<div className="flex items-center space-x-4">
|
|
254
|
+
<span className="text-sm font-medium text-gray-700">Sync Direction:</span>
|
|
255
|
+
<select
|
|
256
|
+
value={syncDirection || ''}
|
|
257
|
+
onChange={(e) => setSyncDirection(e.target.value)}
|
|
258
|
+
className="text-sm border border-gray-300 rounded px-2 py-1"
|
|
259
|
+
>
|
|
260
|
+
<option value="">Select direction</option>
|
|
261
|
+
<option value={`${selectedEnvs[0]}->${selectedEnvs[1]}`}>
|
|
262
|
+
{selectedEnvs[0]} → {selectedEnvs[1]}
|
|
263
|
+
</option>
|
|
264
|
+
<option value={`${selectedEnvs[1]}->${selectedEnvs[0]}`}>
|
|
265
|
+
{selectedEnvs[1]} → {selectedEnvs[0]}
|
|
266
|
+
</option>
|
|
267
|
+
</select>
|
|
268
|
+
|
|
269
|
+
{selectedVariables.size > 0 && (
|
|
270
|
+
<span className="text-sm text-gray-500">
|
|
271
|
+
{selectedVariables.size} variable{selectedVariables.size > 1 ? 's' : ''} selected
|
|
272
|
+
</span>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<div className="space-x-2">
|
|
277
|
+
<button
|
|
278
|
+
onClick={() => handleCopyEnvironment(...syncDirection.split('->'))}
|
|
279
|
+
disabled={!syncDirection}
|
|
280
|
+
className="px-3 py-1 text-sm bg-yellow-600 text-white rounded hover:bg-yellow-700 disabled:bg-gray-300 disabled:cursor-not-allowed"
|
|
281
|
+
>
|
|
282
|
+
Copy All
|
|
283
|
+
</button>
|
|
284
|
+
<button
|
|
285
|
+
onClick={handleSync}
|
|
286
|
+
disabled={!syncDirection || selectedVariables.size === 0}
|
|
287
|
+
className="px-3 py-1 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed"
|
|
288
|
+
>
|
|
289
|
+
Sync Selected
|
|
290
|
+
</button>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
)}
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
{/* Comparison Table */}
|
|
298
|
+
<div className="bg-white rounded-lg shadow overflow-hidden">
|
|
299
|
+
<table className="min-w-full divide-y divide-gray-200">
|
|
300
|
+
<thead className="bg-gray-50">
|
|
301
|
+
<tr>
|
|
302
|
+
{syncDirection && (
|
|
303
|
+
<th className="px-3 py-3 text-left">
|
|
304
|
+
<input
|
|
305
|
+
type="checkbox"
|
|
306
|
+
checked={selectedVariables.size === filteredKeys.length}
|
|
307
|
+
onChange={(e) => {
|
|
308
|
+
if (e.target.checked) {
|
|
309
|
+
setSelectedVariables(new Set(filteredKeys))
|
|
310
|
+
} else {
|
|
311
|
+
setSelectedVariables(new Set())
|
|
312
|
+
}
|
|
313
|
+
}}
|
|
314
|
+
/>
|
|
315
|
+
</th>
|
|
316
|
+
)}
|
|
317
|
+
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
318
|
+
Variable
|
|
319
|
+
</th>
|
|
320
|
+
{selectedEnvs.map(env => (
|
|
321
|
+
<th key={env} className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
322
|
+
{env}
|
|
323
|
+
</th>
|
|
324
|
+
))}
|
|
325
|
+
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
326
|
+
Status
|
|
327
|
+
</th>
|
|
328
|
+
</tr>
|
|
329
|
+
</thead>
|
|
330
|
+
<tbody className="bg-white divide-y divide-gray-200">
|
|
331
|
+
{filteredKeys.map(key => {
|
|
332
|
+
const isDiff = isDifferent(key)
|
|
333
|
+
const values = selectedEnvs.map(env => getValue(env, key))
|
|
334
|
+
const missingCount = values.filter(v => v === null).length
|
|
335
|
+
|
|
336
|
+
// Filter based on compare mode
|
|
337
|
+
if (compareMode === 'diff' && !isDiff) return null
|
|
338
|
+
if (compareMode === 'missing' && missingCount === 0) return null
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<tr key={key} className={isDiff ? 'bg-yellow-50' : ''}>
|
|
342
|
+
{syncDirection && (
|
|
343
|
+
<td className="px-3 py-4">
|
|
344
|
+
<input
|
|
345
|
+
type="checkbox"
|
|
346
|
+
checked={selectedVariables.has(key)}
|
|
347
|
+
onChange={() => toggleVariableSelection(key)}
|
|
348
|
+
/>
|
|
349
|
+
</td>
|
|
350
|
+
)}
|
|
351
|
+
<td className="px-6 py-4 whitespace-nowrap">
|
|
352
|
+
<code className="text-sm font-mono text-gray-900">{key}</code>
|
|
353
|
+
</td>
|
|
354
|
+
{selectedEnvs.map((env, index) => {
|
|
355
|
+
const value = values[index]
|
|
356
|
+
const variable = environments[env].find(v => v.key === key)
|
|
357
|
+
const isSecret = variable?.isSecret
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<td key={env} className="px-6 py-4">
|
|
361
|
+
{value === null ? (
|
|
362
|
+
<span className="text-gray-400 italic">Not set</span>
|
|
363
|
+
) : (
|
|
364
|
+
<code className={`text-sm font-mono ${
|
|
365
|
+
isSecret ? 'text-gray-400' : 'text-gray-600'
|
|
366
|
+
} break-all`}>
|
|
367
|
+
{isSecret ? '•••••••' : value}
|
|
368
|
+
</code>
|
|
369
|
+
)}
|
|
370
|
+
</td>
|
|
371
|
+
)
|
|
372
|
+
})}
|
|
373
|
+
<td className="px-6 py-4 whitespace-nowrap">
|
|
374
|
+
{isDiff && (
|
|
375
|
+
<span className="px-2 py-1 text-xs bg-yellow-100 text-yellow-800 rounded">
|
|
376
|
+
Different
|
|
377
|
+
</span>
|
|
378
|
+
)}
|
|
379
|
+
{missingCount > 0 && (
|
|
380
|
+
<span className="px-2 py-1 text-xs bg-red-100 text-red-800 rounded ml-1">
|
|
381
|
+
Missing in {missingCount}
|
|
382
|
+
</span>
|
|
383
|
+
)}
|
|
384
|
+
{!isDiff && missingCount === 0 && (
|
|
385
|
+
<span className="px-2 py-1 text-xs bg-green-100 text-green-800 rounded">
|
|
386
|
+
Synchronized
|
|
387
|
+
</span>
|
|
388
|
+
)}
|
|
389
|
+
</td>
|
|
390
|
+
</tr>
|
|
391
|
+
)
|
|
392
|
+
})}
|
|
393
|
+
</tbody>
|
|
394
|
+
</table>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export default EnvironmentCompare
|