@friggframework/devtools 2.0.0--canary.398.7664c46.0 → 2.0.0--canary.400.bed3308.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/build-command/index.js +15 -2
- package/frigg-cli/deploy-command/index.js +15 -2
- 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 +350 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/index.js +66 -4
- package/frigg-cli/install-command/index.js +15 -2
- package/frigg-cli/package.json +75 -0
- package/frigg-cli/start-command/index.js +17 -2
- package/frigg-cli/ui-command/index.js +167 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +38 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +43 -19
- package/infrastructure/IAM-POLICY-TEMPLATES.md +1 -1
- package/infrastructure/frigg-deployment-iam-stack.yaml +16 -2
- package/infrastructure/iam-generator.js +129 -6
- package/infrastructure/iam-policy-basic.json +29 -5
- package/infrastructure/iam-policy-full.json +28 -5
- package/infrastructure/serverless-template.js +209 -3
- package/infrastructure/serverless-template.test.js +12 -0
- package/management-ui/.eslintrc.js +22 -0
- package/management-ui/README.md +203 -0
- package/management-ui/components.json +21 -0
- package/management-ui/{dist/index.html → index.html} +1 -2
- package/management-ui/merge-conflict-cleaner.py +371 -0
- package/management-ui/package-lock.json +10997 -0
- package/management-ui/package.json +76 -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 +479 -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 +553 -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 +428 -0
- package/management-ui/server/middleware/errorHandler.js +70 -0
- package/management-ui/server/middleware/security.js +32 -0
- package/management-ui/server/processManager.js +296 -0
- package/management-ui/server/server.js +188 -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 +51 -0
- package/management-ui/src/components/AppRouter.jsx +65 -0
- package/management-ui/src/components/Button.jsx +2 -0
- package/management-ui/src/components/Card.jsx +9 -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 +199 -0
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +490 -0
- package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
- package/management-ui/src/components/IntegrationStatus.jsx +235 -0
- package/management-ui/src/components/Layout.jsx +250 -0
- package/management-ui/src/components/LoadingSpinner.jsx +45 -0
- package/management-ui/src/components/RepositoryPicker.jsx +248 -0
- package/management-ui/src/components/SessionMonitor.jsx +255 -0
- package/management-ui/src/components/StatusBadge.jsx +70 -0
- package/management-ui/src/components/UserContextSwitcher.jsx +154 -0
- package/management-ui/src/components/UserSimulation.jsx +299 -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 +387 -0
- package/management-ui/src/hooks/useSocket.jsx +58 -0
- package/management-ui/src/index.css +194 -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 +427 -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 +544 -0
- package/management-ui/src/pages/IntegrationDiscovery.jsx +479 -0
- package/management-ui/src/pages/IntegrationTest.jsx +494 -0
- package/management-ui/src/pages/Integrations.jsx +254 -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-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,199 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Download, CheckCircle, ExternalLink, Settings, AlertCircle } from 'lucide-react'
|
|
3
|
+
import { Card, CardContent } from './ui/card'
|
|
4
|
+
import { Button } from './ui/button'
|
|
5
|
+
import LoadingSpinner from './LoadingSpinner'
|
|
6
|
+
import { cn } from '../lib/utils'
|
|
7
|
+
|
|
8
|
+
const IntegrationCard = ({
|
|
9
|
+
integration,
|
|
10
|
+
onInstall,
|
|
11
|
+
onConfigure,
|
|
12
|
+
onUninstall,
|
|
13
|
+
installing = false,
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}) => {
|
|
17
|
+
const [showDetails, setShowDetails] = useState(false)
|
|
18
|
+
const isInstalled = integration.installed || integration.status === 'installed'
|
|
19
|
+
const isInstalling = installing || integration.status === 'installing'
|
|
20
|
+
const hasError = integration.status === 'error'
|
|
21
|
+
|
|
22
|
+
const handleInstall = async () => {
|
|
23
|
+
if (onInstall && !isInstalled && !isInstalling) {
|
|
24
|
+
await onInstall(integration.name)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const handleConfigure = () => {
|
|
29
|
+
if (onConfigure && isInstalled) {
|
|
30
|
+
onConfigure(integration.name)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handleUninstall = async () => {
|
|
35
|
+
if (onUninstall && isInstalled) {
|
|
36
|
+
await onUninstall(integration.name)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Filter out invalid DOM props
|
|
41
|
+
const { onUpdate, onTest, uninstalling, updating, error, ...cardProps } = props
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Card className={cn('hover:shadow-lg industrial-transition', className)} {...cardProps}>
|
|
45
|
+
<CardContent className="p-6">
|
|
46
|
+
<div className="flex items-start justify-between mb-4">
|
|
47
|
+
<div className="flex-1">
|
|
48
|
+
<div className="flex items-center mb-2">
|
|
49
|
+
<h4 className="font-semibold text-foreground text-lg">
|
|
50
|
+
{integration.displayName || integration.name}
|
|
51
|
+
</h4>
|
|
52
|
+
{integration.version && (
|
|
53
|
+
<span className="ml-2 text-xs bg-muted text-muted-foreground px-2 py-1 sharp-badge">
|
|
54
|
+
v{integration.version}
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
<p className="text-sm text-muted-foreground mb-3">
|
|
59
|
+
{integration.description || 'No description available'}
|
|
60
|
+
</p>
|
|
61
|
+
{integration.tags && integration.tags.length > 0 && (
|
|
62
|
+
<div className="flex flex-wrap gap-1 mb-3">
|
|
63
|
+
{integration.tags.map((tag, index) => (
|
|
64
|
+
<span
|
|
65
|
+
key={index}
|
|
66
|
+
className="text-xs bg-primary/10 text-primary px-2 py-1 sharp-badge"
|
|
67
|
+
>
|
|
68
|
+
{tag}
|
|
69
|
+
</span>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
<div className="flex items-center ml-4">
|
|
75
|
+
{hasError && (
|
|
76
|
+
<AlertCircle size={20} className="text-red-500 mr-2" />
|
|
77
|
+
)}
|
|
78
|
+
{isInstalled && !hasError && (
|
|
79
|
+
<CheckCircle size={20} className="text-green-500 mr-2" />
|
|
80
|
+
)}
|
|
81
|
+
{isInstalling && (
|
|
82
|
+
<LoadingSpinner size="sm" className="mr-2" />
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className="flex items-center justify-between">
|
|
88
|
+
<div className="flex items-center space-x-2">
|
|
89
|
+
{!isInstalled && !isInstalling && (
|
|
90
|
+
<Button
|
|
91
|
+
size="sm"
|
|
92
|
+
onClick={handleInstall}
|
|
93
|
+
disabled={isInstalling}
|
|
94
|
+
className="inline-flex items-center"
|
|
95
|
+
>
|
|
96
|
+
<Download size={16} className="mr-1" />
|
|
97
|
+
Install
|
|
98
|
+
</Button>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
{isInstalled && !hasError && (
|
|
102
|
+
<Button
|
|
103
|
+
size="sm"
|
|
104
|
+
variant="outline"
|
|
105
|
+
onClick={handleConfigure}
|
|
106
|
+
className="inline-flex items-center"
|
|
107
|
+
>
|
|
108
|
+
<Settings size={16} className="mr-1" />
|
|
109
|
+
Configure
|
|
110
|
+
</Button>
|
|
111
|
+
)}
|
|
112
|
+
|
|
113
|
+
{isInstalling && (
|
|
114
|
+
<Button size="sm" disabled className="inline-flex items-center">
|
|
115
|
+
<LoadingSpinner size="sm" className="mr-1" />
|
|
116
|
+
Installing...
|
|
117
|
+
</Button>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{hasError && (
|
|
121
|
+
<Button
|
|
122
|
+
size="sm"
|
|
123
|
+
variant="destructive"
|
|
124
|
+
onClick={handleInstall}
|
|
125
|
+
className="inline-flex items-center"
|
|
126
|
+
>
|
|
127
|
+
<AlertCircle size={16} className="mr-1" />
|
|
128
|
+
Retry
|
|
129
|
+
</Button>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
<div className="flex items-center space-x-2">
|
|
133
|
+
{integration.docsUrl && (
|
|
134
|
+
<Button
|
|
135
|
+
size="sm"
|
|
136
|
+
variant="ghost"
|
|
137
|
+
onClick={() => window.open(integration.docsUrl, '_blank')}
|
|
138
|
+
className="inline-flex items-center text-xs"
|
|
139
|
+
>
|
|
140
|
+
<ExternalLink size={14} className="mr-1" />
|
|
141
|
+
Docs
|
|
142
|
+
</Button>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
{isInstalled && (
|
|
146
|
+
<Button
|
|
147
|
+
size="sm"
|
|
148
|
+
variant="ghost"
|
|
149
|
+
onClick={() => setShowDetails(!showDetails)}
|
|
150
|
+
className="text-xs"
|
|
151
|
+
>
|
|
152
|
+
{showDetails ? 'Less' : 'More'}
|
|
153
|
+
</Button>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{showDetails && isInstalled && (
|
|
159
|
+
<div className="mt-4 pt-4 border-t border-border">
|
|
160
|
+
<div className="space-y-2 text-sm">
|
|
161
|
+
{integration.endpoints && (
|
|
162
|
+
<div>
|
|
163
|
+
<span className="font-medium text-foreground">Endpoints:</span>
|
|
164
|
+
<span className="ml-2 text-muted-foreground">{integration.endpoints.length}</span>
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
{integration.lastUpdated && (
|
|
168
|
+
<div>
|
|
169
|
+
<span className="font-medium text-foreground">Last Updated:</span>
|
|
170
|
+
<span className="ml-2 text-muted-foreground">
|
|
171
|
+
{new Date(integration.lastUpdated).toLocaleDateString()}
|
|
172
|
+
</span>
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
{integration.connections && (
|
|
176
|
+
<div>
|
|
177
|
+
<span className="font-medium text-foreground">Active Connections:</span>
|
|
178
|
+
<span className="ml-2 text-muted-foreground">{integration.connections}</span>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
<div className="mt-3 flex justify-end">
|
|
183
|
+
<Button
|
|
184
|
+
size="sm"
|
|
185
|
+
variant="destructive"
|
|
186
|
+
onClick={handleUninstall}
|
|
187
|
+
className="inline-flex items-center text-xs"
|
|
188
|
+
>
|
|
189
|
+
Uninstall
|
|
190
|
+
</Button>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
)}
|
|
194
|
+
</CardContent>
|
|
195
|
+
</Card>
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export default IntegrationCard
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Download, CheckCircle, ExternalLink, Settings, AlertCircle,
|
|
4
|
+
RefreshCw, TestTube, Info, Clock, TrendingUp, Shield,
|
|
5
|
+
ChevronDown, ChevronUp
|
|
6
|
+
} from 'lucide-react'
|
|
7
|
+
import { Card, CardContent } from './Card'
|
|
8
|
+
import { Button } from './Button'
|
|
9
|
+
import LoadingSpinner from './LoadingSpinner'
|
|
10
|
+
import { cn } from '../lib/utils'
|
|
11
|
+
|
|
12
|
+
const IntegrationCardEnhanced = ({
|
|
13
|
+
integration,
|
|
14
|
+
onInstall,
|
|
15
|
+
onUninstall,
|
|
16
|
+
onUpdate,
|
|
17
|
+
onConfigure,
|
|
18
|
+
onTest,
|
|
19
|
+
onViewDetails,
|
|
20
|
+
installing = false,
|
|
21
|
+
uninstalling = false,
|
|
22
|
+
updating = false,
|
|
23
|
+
error = false,
|
|
24
|
+
message = '',
|
|
25
|
+
progress = 0,
|
|
26
|
+
className,
|
|
27
|
+
viewMode = 'grid',
|
|
28
|
+
...props
|
|
29
|
+
}) => {
|
|
30
|
+
const [expanded, setExpanded] = useState(false)
|
|
31
|
+
const [showDetails, setShowDetails] = useState(false)
|
|
32
|
+
|
|
33
|
+
const isInstalled = integration.installed || integration.status === 'installed'
|
|
34
|
+
const isProcessing = installing || uninstalling || updating
|
|
35
|
+
const hasUpdate = integration.updateAvailable
|
|
36
|
+
const isOfficial = integration.isOfficial
|
|
37
|
+
|
|
38
|
+
const getStatusIcon = () => {
|
|
39
|
+
if (error) return <AlertCircle size={20} className="text-red-500" />
|
|
40
|
+
if (isProcessing) return <LoadingSpinner size="sm" />
|
|
41
|
+
if (isInstalled) return <CheckCircle size={20} className="text-green-500" />
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const getProgressBarColor = () => {
|
|
46
|
+
if (error) return 'bg-red-500'
|
|
47
|
+
if (uninstalling) return 'bg-orange-500'
|
|
48
|
+
if (updating) return 'bg-blue-500'
|
|
49
|
+
return 'bg-green-500'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const formatCategory = (category) => {
|
|
53
|
+
return category?.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) || 'Other'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handleAction = async (action, handler) => {
|
|
57
|
+
if (handler && !isProcessing) {
|
|
58
|
+
await handler(integration.name)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// List view layout
|
|
63
|
+
if (viewMode === 'list') {
|
|
64
|
+
return (
|
|
65
|
+
<Card className={cn('hover:shadow-md transition-shadow', className)} {...props}>
|
|
66
|
+
<CardContent className="p-4">
|
|
67
|
+
<div className="flex items-center justify-between">
|
|
68
|
+
{/* Left side - Integration info */}
|
|
69
|
+
<div className="flex items-center flex-1">
|
|
70
|
+
<div className="flex-shrink-0 mr-4">
|
|
71
|
+
{getStatusIcon()}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div className="flex-1">
|
|
75
|
+
<div className="flex items-center mb-1">
|
|
76
|
+
<h4 className="font-semibold text-gray-900">
|
|
77
|
+
{integration.displayName || integration.name}
|
|
78
|
+
</h4>
|
|
79
|
+
{integration.version && (
|
|
80
|
+
<span className="ml-2 text-xs bg-gray-100 text-gray-600 px-2 py-0.5 rounded">
|
|
81
|
+
v{integration.version}
|
|
82
|
+
</span>
|
|
83
|
+
)}
|
|
84
|
+
{hasUpdate && (
|
|
85
|
+
<span className="ml-2 text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded flex items-center">
|
|
86
|
+
<TrendingUp size={10} className="mr-1" />
|
|
87
|
+
Update available
|
|
88
|
+
</span>
|
|
89
|
+
)}
|
|
90
|
+
{isOfficial && (
|
|
91
|
+
<span className="ml-2 text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded flex items-center">
|
|
92
|
+
<Shield size={10} className="mr-1" />
|
|
93
|
+
Official
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<p className="text-sm text-gray-600 mb-2">
|
|
99
|
+
{integration.description || 'No description available'}
|
|
100
|
+
</p>
|
|
101
|
+
|
|
102
|
+
{/* Tags and category */}
|
|
103
|
+
<div className="flex items-center gap-2">
|
|
104
|
+
<span className="text-xs bg-gray-200 text-gray-700 px-2 py-0.5 rounded">
|
|
105
|
+
{formatCategory(integration.category)}
|
|
106
|
+
</span>
|
|
107
|
+
{integration.tags?.slice(0, 3).map((tag, index) => (
|
|
108
|
+
<span
|
|
109
|
+
key={index}
|
|
110
|
+
className="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded"
|
|
111
|
+
>
|
|
112
|
+
{tag}
|
|
113
|
+
</span>
|
|
114
|
+
))}
|
|
115
|
+
{integration.tags?.length > 3 && (
|
|
116
|
+
<span className="text-xs text-gray-500">
|
|
117
|
+
+{integration.tags.length - 3} more
|
|
118
|
+
</span>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{/* Right side - Actions */}
|
|
125
|
+
<div className="flex items-center gap-2 ml-4">
|
|
126
|
+
{!isInstalled && !isProcessing && (
|
|
127
|
+
<Button
|
|
128
|
+
size="sm"
|
|
129
|
+
onClick={() => handleAction('install', onInstall)}
|
|
130
|
+
className="inline-flex items-center"
|
|
131
|
+
>
|
|
132
|
+
<Download size={16} className="mr-1" />
|
|
133
|
+
Install
|
|
134
|
+
</Button>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{isInstalled && !isProcessing && (
|
|
138
|
+
<>
|
|
139
|
+
<Button
|
|
140
|
+
size="sm"
|
|
141
|
+
variant="outline"
|
|
142
|
+
onClick={() => handleAction('configure', onConfigure)}
|
|
143
|
+
className="inline-flex items-center"
|
|
144
|
+
>
|
|
145
|
+
<Settings size={16} className="mr-1" />
|
|
146
|
+
Configure
|
|
147
|
+
</Button>
|
|
148
|
+
{hasUpdate && (
|
|
149
|
+
<Button
|
|
150
|
+
size="sm"
|
|
151
|
+
variant="outline"
|
|
152
|
+
onClick={() => handleAction('update', onUpdate)}
|
|
153
|
+
className="inline-flex items-center text-blue-600 hover:text-blue-700"
|
|
154
|
+
>
|
|
155
|
+
<RefreshCw size={16} className="mr-1" />
|
|
156
|
+
Update
|
|
157
|
+
</Button>
|
|
158
|
+
)}
|
|
159
|
+
</>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{isProcessing && (
|
|
163
|
+
<Button size="sm" disabled className="inline-flex items-center min-w-[100px]">
|
|
164
|
+
<LoadingSpinner size="sm" className="mr-1" />
|
|
165
|
+
{installing && 'Installing'}
|
|
166
|
+
{uninstalling && 'Uninstalling'}
|
|
167
|
+
{updating && 'Updating'}
|
|
168
|
+
</Button>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
<Button
|
|
172
|
+
size="sm"
|
|
173
|
+
variant="ghost"
|
|
174
|
+
onClick={() => setExpanded(!expanded)}
|
|
175
|
+
className="ml-2"
|
|
176
|
+
>
|
|
177
|
+
{expanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
|
178
|
+
</Button>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* Progress bar */}
|
|
183
|
+
{isProcessing && progress > 0 && (
|
|
184
|
+
<div className="mt-3">
|
|
185
|
+
<div className="w-full bg-gray-200 rounded-full h-2">
|
|
186
|
+
<div
|
|
187
|
+
className={cn("h-2 rounded-full transition-all", getProgressBarColor())}
|
|
188
|
+
style={{ width: `${progress}%` }}
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
{message && (
|
|
192
|
+
<p className="text-xs text-gray-600 mt-1">{message}</p>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{/* Error message */}
|
|
198
|
+
{error && message && (
|
|
199
|
+
<div className="mt-3 text-sm text-red-600 flex items-start">
|
|
200
|
+
<AlertCircle size={16} className="mr-1 flex-shrink-0 mt-0.5" />
|
|
201
|
+
{message}
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
204
|
+
|
|
205
|
+
{/* Expanded details */}
|
|
206
|
+
{expanded && (
|
|
207
|
+
<div className="mt-4 pt-4 border-t border-gray-200">
|
|
208
|
+
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
209
|
+
<div>
|
|
210
|
+
<span className="font-medium text-gray-700">Package:</span>
|
|
211
|
+
<span className="ml-2 text-gray-600 font-mono text-xs">{integration.name}</span>
|
|
212
|
+
</div>
|
|
213
|
+
{integration.author && (
|
|
214
|
+
<div>
|
|
215
|
+
<span className="font-medium text-gray-700">Author:</span>
|
|
216
|
+
<span className="ml-2 text-gray-600">{integration.author}</span>
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
{integration.lastUpdated && (
|
|
220
|
+
<div>
|
|
221
|
+
<span className="font-medium text-gray-700">Last Updated:</span>
|
|
222
|
+
<span className="ml-2 text-gray-600">
|
|
223
|
+
{new Date(integration.lastUpdated).toLocaleDateString()}
|
|
224
|
+
</span>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
{integration.connections !== undefined && (
|
|
228
|
+
<div>
|
|
229
|
+
<span className="font-medium text-gray-700">Active Connections:</span>
|
|
230
|
+
<span className="ml-2 text-gray-600">{integration.connections}</span>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{isInstalled && (
|
|
236
|
+
<div className="mt-4 flex justify-end gap-2">
|
|
237
|
+
{onTest && (
|
|
238
|
+
<Button
|
|
239
|
+
size="sm"
|
|
240
|
+
variant="outline"
|
|
241
|
+
onClick={() => handleAction('test', onTest)}
|
|
242
|
+
className="inline-flex items-center text-xs"
|
|
243
|
+
>
|
|
244
|
+
<TestTube size={14} className="mr-1" />
|
|
245
|
+
Test
|
|
246
|
+
</Button>
|
|
247
|
+
)}
|
|
248
|
+
<Button
|
|
249
|
+
size="sm"
|
|
250
|
+
variant="destructive"
|
|
251
|
+
onClick={() => handleAction('uninstall', onUninstall)}
|
|
252
|
+
className="inline-flex items-center text-xs"
|
|
253
|
+
>
|
|
254
|
+
Uninstall
|
|
255
|
+
</Button>
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
)}
|
|
260
|
+
</CardContent>
|
|
261
|
+
</Card>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Grid view layout (default)
|
|
266
|
+
return (
|
|
267
|
+
<Card className={cn('hover:shadow-md transition-shadow', className)} {...props}>
|
|
268
|
+
<CardContent className="p-6">
|
|
269
|
+
<div className="flex items-start justify-between mb-4">
|
|
270
|
+
<div className="flex-1">
|
|
271
|
+
<div className="flex items-center mb-2">
|
|
272
|
+
<h4 className="font-semibold text-gray-900 text-lg">
|
|
273
|
+
{integration.displayName || integration.name}
|
|
274
|
+
</h4>
|
|
275
|
+
{integration.version && (
|
|
276
|
+
<span className="ml-2 text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded">
|
|
277
|
+
v{integration.version}
|
|
278
|
+
</span>
|
|
279
|
+
)}
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
{/* Badges */}
|
|
283
|
+
<div className="flex items-center gap-2 mb-3">
|
|
284
|
+
{hasUpdate && (
|
|
285
|
+
<span className="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded flex items-center">
|
|
286
|
+
<TrendingUp size={10} className="mr-1" />
|
|
287
|
+
Update available
|
|
288
|
+
</span>
|
|
289
|
+
)}
|
|
290
|
+
{isOfficial && (
|
|
291
|
+
<span className="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded flex items-center">
|
|
292
|
+
<Shield size={10} className="mr-1" />
|
|
293
|
+
Official
|
|
294
|
+
</span>
|
|
295
|
+
)}
|
|
296
|
+
<span className="text-xs bg-gray-200 text-gray-700 px-2 py-0.5 rounded">
|
|
297
|
+
{formatCategory(integration.category)}
|
|
298
|
+
</span>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<p className="text-sm text-gray-600 mb-3">
|
|
302
|
+
{integration.description || 'No description available'}
|
|
303
|
+
</p>
|
|
304
|
+
|
|
305
|
+
{integration.tags && integration.tags.length > 0 && (
|
|
306
|
+
<div className="flex flex-wrap gap-1 mb-3">
|
|
307
|
+
{integration.tags.map((tag, index) => (
|
|
308
|
+
<span
|
|
309
|
+
key={index}
|
|
310
|
+
className="text-xs bg-blue-100 text-blue-700 px-2 py-1 rounded-full"
|
|
311
|
+
>
|
|
312
|
+
{tag}
|
|
313
|
+
</span>
|
|
314
|
+
))}
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div className="flex items-center ml-4">
|
|
320
|
+
{getStatusIcon()}
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
{/* Progress bar */}
|
|
325
|
+
{isProcessing && progress > 0 && (
|
|
326
|
+
<div className="mb-4">
|
|
327
|
+
<div className="w-full bg-gray-200 rounded-full h-2">
|
|
328
|
+
<div
|
|
329
|
+
className={cn("h-2 rounded-full transition-all", getProgressBarColor())}
|
|
330
|
+
style={{ width: `${progress}%` }}
|
|
331
|
+
/>
|
|
332
|
+
</div>
|
|
333
|
+
{message && (
|
|
334
|
+
<p className="text-xs text-gray-600 mt-1">{message}</p>
|
|
335
|
+
)}
|
|
336
|
+
</div>
|
|
337
|
+
)}
|
|
338
|
+
|
|
339
|
+
{/* Error message */}
|
|
340
|
+
{error && message && (
|
|
341
|
+
<div className="mb-4 text-sm text-red-600 flex items-start">
|
|
342
|
+
<AlertCircle size={16} className="mr-1 flex-shrink-0 mt-0.5" />
|
|
343
|
+
{message}
|
|
344
|
+
</div>
|
|
345
|
+
)}
|
|
346
|
+
|
|
347
|
+
<div className="flex items-center justify-between">
|
|
348
|
+
<div className="flex items-center space-x-2">
|
|
349
|
+
{!isInstalled && !isProcessing && (
|
|
350
|
+
<Button
|
|
351
|
+
size="sm"
|
|
352
|
+
onClick={() => handleAction('install', onInstall)}
|
|
353
|
+
className="inline-flex items-center"
|
|
354
|
+
>
|
|
355
|
+
<Download size={16} className="mr-1" />
|
|
356
|
+
Install
|
|
357
|
+
</Button>
|
|
358
|
+
)}
|
|
359
|
+
|
|
360
|
+
{isInstalled && !isProcessing && (
|
|
361
|
+
<>
|
|
362
|
+
<Button
|
|
363
|
+
size="sm"
|
|
364
|
+
variant="outline"
|
|
365
|
+
onClick={() => handleAction('configure', onConfigure)}
|
|
366
|
+
className="inline-flex items-center"
|
|
367
|
+
>
|
|
368
|
+
<Settings size={16} className="mr-1" />
|
|
369
|
+
Configure
|
|
370
|
+
</Button>
|
|
371
|
+
{hasUpdate && (
|
|
372
|
+
<Button
|
|
373
|
+
size="sm"
|
|
374
|
+
variant="outline"
|
|
375
|
+
onClick={() => handleAction('update', onUpdate)}
|
|
376
|
+
className="inline-flex items-center text-blue-600 hover:text-blue-700"
|
|
377
|
+
>
|
|
378
|
+
<RefreshCw size={16} className="mr-1" />
|
|
379
|
+
Update
|
|
380
|
+
</Button>
|
|
381
|
+
)}
|
|
382
|
+
</>
|
|
383
|
+
)}
|
|
384
|
+
|
|
385
|
+
{isProcessing && (
|
|
386
|
+
<Button size="sm" disabled className="inline-flex items-center">
|
|
387
|
+
<LoadingSpinner size="sm" className="mr-1" />
|
|
388
|
+
{installing && 'Installing...'}
|
|
389
|
+
{uninstalling && 'Uninstalling...'}
|
|
390
|
+
{updating && 'Updating...'}
|
|
391
|
+
</Button>
|
|
392
|
+
)}
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<div className="flex items-center space-x-2">
|
|
396
|
+
{integration.docsUrl && (
|
|
397
|
+
<Button
|
|
398
|
+
size="sm"
|
|
399
|
+
variant="ghost"
|
|
400
|
+
onClick={() => window.open(integration.docsUrl, '_blank')}
|
|
401
|
+
className="inline-flex items-center text-xs"
|
|
402
|
+
>
|
|
403
|
+
<ExternalLink size={14} className="mr-1" />
|
|
404
|
+
Docs
|
|
405
|
+
</Button>
|
|
406
|
+
)}
|
|
407
|
+
|
|
408
|
+
{onViewDetails && (
|
|
409
|
+
<Button
|
|
410
|
+
size="sm"
|
|
411
|
+
variant="ghost"
|
|
412
|
+
onClick={() => onViewDetails(integration.name)}
|
|
413
|
+
className="inline-flex items-center text-xs"
|
|
414
|
+
>
|
|
415
|
+
<Info size={14} className="mr-1" />
|
|
416
|
+
Details
|
|
417
|
+
</Button>
|
|
418
|
+
)}
|
|
419
|
+
|
|
420
|
+
{isInstalled && (
|
|
421
|
+
<Button
|
|
422
|
+
size="sm"
|
|
423
|
+
variant="ghost"
|
|
424
|
+
onClick={() => setShowDetails(!showDetails)}
|
|
425
|
+
className="text-xs"
|
|
426
|
+
>
|
|
427
|
+
{showDetails ? 'Less' : 'More'}
|
|
428
|
+
</Button>
|
|
429
|
+
)}
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
{showDetails && isInstalled && (
|
|
434
|
+
<div className="mt-4 pt-4 border-t border-gray-200">
|
|
435
|
+
<div className="space-y-2 text-sm">
|
|
436
|
+
<div>
|
|
437
|
+
<span className="font-medium text-gray-700">Package:</span>
|
|
438
|
+
<span className="ml-2 text-gray-600 font-mono text-xs">{integration.name}</span>
|
|
439
|
+
</div>
|
|
440
|
+
{integration.endpoints && (
|
|
441
|
+
<div>
|
|
442
|
+
<span className="font-medium text-gray-700">Endpoints:</span>
|
|
443
|
+
<span className="ml-2 text-gray-600">{integration.endpoints.length}</span>
|
|
444
|
+
</div>
|
|
445
|
+
)}
|
|
446
|
+
{integration.lastUpdated && (
|
|
447
|
+
<div>
|
|
448
|
+
<span className="font-medium text-gray-700">Last Updated:</span>
|
|
449
|
+
<span className="ml-2 text-gray-600">
|
|
450
|
+
{new Date(integration.lastUpdated).toLocaleDateString()}
|
|
451
|
+
</span>
|
|
452
|
+
</div>
|
|
453
|
+
)}
|
|
454
|
+
{integration.connections !== undefined && (
|
|
455
|
+
<div>
|
|
456
|
+
<span className="font-medium text-gray-700">Active Connections:</span>
|
|
457
|
+
<span className="ml-2 text-gray-600">{integration.connections}</span>
|
|
458
|
+
</div>
|
|
459
|
+
)}
|
|
460
|
+
</div>
|
|
461
|
+
|
|
462
|
+
<div className="mt-3 flex justify-between">
|
|
463
|
+
{onTest && (
|
|
464
|
+
<Button
|
|
465
|
+
size="sm"
|
|
466
|
+
variant="outline"
|
|
467
|
+
onClick={() => handleAction('test', onTest)}
|
|
468
|
+
className="inline-flex items-center text-xs"
|
|
469
|
+
>
|
|
470
|
+
<TestTube size={14} className="mr-1" />
|
|
471
|
+
Test Integration
|
|
472
|
+
</Button>
|
|
473
|
+
)}
|
|
474
|
+
<Button
|
|
475
|
+
size="sm"
|
|
476
|
+
variant="destructive"
|
|
477
|
+
onClick={() => handleAction('uninstall', onUninstall)}
|
|
478
|
+
className="inline-flex items-center text-xs"
|
|
479
|
+
>
|
|
480
|
+
Uninstall
|
|
481
|
+
</Button>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
)}
|
|
485
|
+
</CardContent>
|
|
486
|
+
</Card>
|
|
487
|
+
)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export default IntegrationCardEnhanced
|