@agentforge-ai/cli 0.4.0 → 0.4.1
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/default/.env.example +11 -0
- package/dist/default/dashboard/app/components/DashboardLayout.tsx +245 -0
- package/dist/default/dashboard/app/components/ui/badge.tsx +26 -0
- package/dist/default/dashboard/app/components/ui/button.tsx +41 -0
- package/dist/default/dashboard/app/components/ui/card.tsx +44 -0
- package/dist/default/dashboard/app/components/ui/dialog.tsx +66 -0
- package/dist/default/dashboard/app/components/ui/input.tsx +21 -0
- package/dist/default/dashboard/app/components/ui/label.tsx +18 -0
- package/dist/default/dashboard/app/components/ui/select.tsx +75 -0
- package/dist/default/dashboard/app/components/ui/sheet.tsx +73 -0
- package/dist/default/dashboard/app/components/ui/switch.tsx +34 -0
- package/dist/default/dashboard/app/components/ui/table.tsx +60 -0
- package/dist/default/dashboard/app/components/ui/tabs.tsx +50 -0
- package/dist/default/dashboard/app/components/ui/tooltip.tsx +23 -0
- package/dist/default/dashboard/app/lib/utils.ts +6 -0
- package/dist/default/dashboard/app/main.tsx +35 -0
- package/dist/default/dashboard/app/routeTree.gen.ts +352 -0
- package/dist/default/dashboard/app/routes/__root.tsx +10 -0
- package/dist/default/dashboard/app/routes/agents.tsx +255 -0
- package/dist/default/dashboard/app/routes/chat.tsx +427 -0
- package/dist/default/dashboard/app/routes/connections.tsx +413 -0
- package/dist/default/dashboard/app/routes/cron.tsx +322 -0
- package/dist/default/dashboard/app/routes/files.tsx +203 -0
- package/dist/default/dashboard/app/routes/index.tsx +141 -0
- package/dist/default/dashboard/app/routes/projects.tsx +254 -0
- package/dist/default/dashboard/app/routes/sessions.tsx +272 -0
- package/dist/default/dashboard/app/routes/settings.tsx +583 -0
- package/dist/default/dashboard/app/routes/skills.tsx +252 -0
- package/dist/default/dashboard/app/routes/usage.tsx +181 -0
- package/dist/default/dashboard/app/styles/globals.css +93 -0
- package/dist/default/dashboard/index.html +13 -0
- package/dist/default/dashboard/package.json +36 -0
- package/dist/default/dashboard/postcss.config.js +6 -0
- package/dist/default/dashboard/tailwind.config.js +50 -0
- package/dist/default/dashboard/tsconfig.json +24 -0
- package/dist/default/dashboard/vite.config.ts +16 -0
- package/dist/default/package.json +5 -2
- package/dist/default/src/agent.ts +42 -2
- package/dist/index.js +135 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/.env.example +11 -0
- package/templates/default/dashboard/app/components/DashboardLayout.tsx +245 -0
- package/templates/default/dashboard/app/components/ui/badge.tsx +26 -0
- package/templates/default/dashboard/app/components/ui/button.tsx +41 -0
- package/templates/default/dashboard/app/components/ui/card.tsx +44 -0
- package/templates/default/dashboard/app/components/ui/dialog.tsx +66 -0
- package/templates/default/dashboard/app/components/ui/input.tsx +21 -0
- package/templates/default/dashboard/app/components/ui/label.tsx +18 -0
- package/templates/default/dashboard/app/components/ui/select.tsx +75 -0
- package/templates/default/dashboard/app/components/ui/sheet.tsx +73 -0
- package/templates/default/dashboard/app/components/ui/switch.tsx +34 -0
- package/templates/default/dashboard/app/components/ui/table.tsx +60 -0
- package/templates/default/dashboard/app/components/ui/tabs.tsx +50 -0
- package/templates/default/dashboard/app/components/ui/tooltip.tsx +23 -0
- package/templates/default/dashboard/app/lib/utils.ts +6 -0
- package/templates/default/dashboard/app/main.tsx +35 -0
- package/templates/default/dashboard/app/routeTree.gen.ts +352 -0
- package/templates/default/dashboard/app/routes/__root.tsx +10 -0
- package/templates/default/dashboard/app/routes/agents.tsx +255 -0
- package/templates/default/dashboard/app/routes/chat.tsx +427 -0
- package/templates/default/dashboard/app/routes/connections.tsx +413 -0
- package/templates/default/dashboard/app/routes/cron.tsx +322 -0
- package/templates/default/dashboard/app/routes/files.tsx +203 -0
- package/templates/default/dashboard/app/routes/index.tsx +141 -0
- package/templates/default/dashboard/app/routes/projects.tsx +254 -0
- package/templates/default/dashboard/app/routes/sessions.tsx +272 -0
- package/templates/default/dashboard/app/routes/settings.tsx +583 -0
- package/templates/default/dashboard/app/routes/skills.tsx +252 -0
- package/templates/default/dashboard/app/routes/usage.tsx +181 -0
- package/templates/default/dashboard/app/styles/globals.css +93 -0
- package/templates/default/dashboard/index.html +13 -0
- package/templates/default/dashboard/package.json +36 -0
- package/templates/default/dashboard/postcss.config.js +6 -0
- package/templates/default/dashboard/tailwind.config.js +50 -0
- package/templates/default/dashboard/tsconfig.json +24 -0
- package/templates/default/dashboard/vite.config.ts +16 -0
- package/templates/default/package.json +5 -2
- package/templates/default/src/agent.ts +42 -2
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
|
|
2
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
3
|
+
import { DashboardLayout } from '../components/DashboardLayout';
|
|
4
|
+
import React, { useState, useMemo } from 'react';
|
|
5
|
+
// import { useQuery, useMutation } from 'convex/react';
|
|
6
|
+
// import { api } from '../../convex/_generated/api';
|
|
7
|
+
import { Plug, Plus, RefreshCw, CheckCircle, XCircle, Trash2, MoreVertical, Edit, Search } from 'lucide-react';
|
|
8
|
+
|
|
9
|
+
// --- Mock Data and Types ---
|
|
10
|
+
type ConnectionStatus = 'connected' | 'disconnected' | 'testing';
|
|
11
|
+
type ConnectionType = 'MCP' | 'API' | 'Webhook';
|
|
12
|
+
|
|
13
|
+
interface Connection {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
type: ConnectionType;
|
|
17
|
+
status: ConnectionStatus;
|
|
18
|
+
lastConnected: string | null;
|
|
19
|
+
serverUrl: string;
|
|
20
|
+
protocol: string;
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const mockConnections: Connection[] = [
|
|
25
|
+
{
|
|
26
|
+
id: '1',
|
|
27
|
+
name: 'Cloudflare MCP',
|
|
28
|
+
type: 'MCP',
|
|
29
|
+
status: 'connected',
|
|
30
|
+
lastConnected: new Date(Date.now() - 86400000).toISOString(),
|
|
31
|
+
serverUrl: 'https://mcp.cloudflare.com',
|
|
32
|
+
protocol: 'mcp',
|
|
33
|
+
enabled: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: '2',
|
|
37
|
+
name: 'Stripe API',
|
|
38
|
+
type: 'API',
|
|
39
|
+
status: 'disconnected',
|
|
40
|
+
lastConnected: new Date(Date.now() - 604800000).toISOString(),
|
|
41
|
+
serverUrl: 'https://api.stripe.com',
|
|
42
|
+
protocol: 'https',
|
|
43
|
+
enabled: true,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: '3',
|
|
47
|
+
name: 'GitHub Webhook',
|
|
48
|
+
type: 'Webhook',
|
|
49
|
+
status: 'connected',
|
|
50
|
+
lastConnected: new Date().toISOString(),
|
|
51
|
+
serverUrl: 'https://api.github.com/webhooks',
|
|
52
|
+
protocol: 'https',
|
|
53
|
+
enabled: false,
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// --- Reusable UI Components (assuming these are in a components/ui folder) ---
|
|
58
|
+
const Button = ({ children, className, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
|
|
59
|
+
<button className={`px-4 py-2 rounded-md font-semibold transition-colors ${className}`} {...props}>
|
|
60
|
+
{children}
|
|
61
|
+
</button>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const Input = (props: React.InputHTMLAttributes<HTMLInputElement>) => (
|
|
65
|
+
<input {...props} className={`w-full px-3 py-2 bg-background border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-primary ${props.className}`} />
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const Select = ({ children, ...props }: React.SelectHTMLAttributes<HTMLSelectElement>) => (
|
|
69
|
+
<select {...props} className={`w-full px-3 py-2 bg-background border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-primary ${props.className}`}>
|
|
70
|
+
{children}
|
|
71
|
+
</select>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const Card = ({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
75
|
+
<div className={`bg-card border border-border rounded-lg shadow-sm ${className}`} {...props}>
|
|
76
|
+
{children}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const Dialog = ({ open, onClose, children }: { open: boolean; onClose: () => void; children: React.ReactNode }) => {
|
|
81
|
+
if (!open) return null;
|
|
82
|
+
return (
|
|
83
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
|
84
|
+
<div className="bg-card border border-border rounded-lg shadow-xl w-full max-w-md m-4">
|
|
85
|
+
{children}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const DialogHeader = ({ children }: { children: React.ReactNode }) => <div className="p-4 border-b border-border">{children}</div>;
|
|
92
|
+
const DialogTitle = ({ children }: { children: React.ReactNode }) => <h2 className="text-lg font-semibold text-foreground">{children}</h2>;
|
|
93
|
+
const DialogContent = ({ children }: { children: React.ReactNode }) => <div className="p-4 space-y-4">{children}</div>;
|
|
94
|
+
const DialogFooter = ({ children }: { children: React.ReactNode }) => <div className="p-4 border-t border-border flex justify-end space-x-2">{children}</div>;
|
|
95
|
+
|
|
96
|
+
const Switch = ({ checked, onCheckedChange }: { checked: boolean; onCheckedChange: (checked: boolean) => void }) => (
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
role="switch"
|
|
100
|
+
aria-checked={checked}
|
|
101
|
+
onClick={() => onCheckedChange(!checked)}
|
|
102
|
+
className={`${checked ? 'bg-primary' : 'bg-muted'} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-background`}
|
|
103
|
+
>
|
|
104
|
+
<span className={`${checked ? 'translate-x-5' : 'translate-x-0'} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out`} />
|
|
105
|
+
</button>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// --- Page Specific Components ---
|
|
109
|
+
|
|
110
|
+
function ConnectionCard({ connection, onTest, onEdit, onDelete, onToggle }: {
|
|
111
|
+
connection: Connection;
|
|
112
|
+
onTest: (id: string) => void;
|
|
113
|
+
onEdit: (connection: Connection) => void;
|
|
114
|
+
onDelete: (id: string) => void;
|
|
115
|
+
onToggle: (id: string, enabled: boolean) => void;
|
|
116
|
+
}) {
|
|
117
|
+
const StatusIndicator = () => {
|
|
118
|
+
switch (connection.status) {
|
|
119
|
+
case 'connected': return <CheckCircle className="h-5 w-5 text-green-500" />;
|
|
120
|
+
case 'disconnected': return <XCircle className="h-5 w-5 text-red-500" />;
|
|
121
|
+
case 'testing': return <RefreshCw className="h-5 w-5 text-yellow-500 animate-spin" />;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Card className="flex flex-col justify-between">
|
|
127
|
+
<div className="p-4">
|
|
128
|
+
<div className="flex justify-between items-start">
|
|
129
|
+
<div className="flex items-center space-x-3">
|
|
130
|
+
<Plug className="h-8 w-8 text-primary" />
|
|
131
|
+
<div>
|
|
132
|
+
<h3 className="font-bold text-foreground">{connection.name}</h3>
|
|
133
|
+
<p className="text-sm text-muted-foreground">{connection.type}</p>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
<Switch checked={connection.enabled} onCheckedChange={(checked) => onToggle(connection.id, checked)} />
|
|
137
|
+
</div>
|
|
138
|
+
<div className="mt-4 space-y-2 text-sm">
|
|
139
|
+
<div className="flex items-center space-x-2">
|
|
140
|
+
<StatusIndicator />
|
|
141
|
+
<span className="capitalize text-muted-foreground">{connection.status}</span>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="flex items-center space-x-2 text-muted-foreground">
|
|
144
|
+
<RefreshCw className="h-4 w-4" />
|
|
145
|
+
<span>Last connected: {connection.lastConnected ? new Date(connection.lastConnected).toLocaleDateString() : 'Never'}</span>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="p-4 bg-background/50 border-t border-border flex items-center justify-end space-x-2">
|
|
150
|
+
<Button onClick={() => onTest(connection.id)} className="bg-secondary text-secondary-foreground hover:bg-secondary/80 text-sm">Test</Button>
|
|
151
|
+
<Button onClick={() => onEdit(connection)} className="bg-secondary text-secondary-foreground hover:bg-secondary/80 text-sm"><Edit className="h-4 w-4" /></Button>
|
|
152
|
+
<Button onClick={() => onDelete(connection.id)} className="bg-destructive text-destructive-foreground hover:bg-destructive/80 text-sm"><Trash2 className="h-4 w-4" /></Button>
|
|
153
|
+
</div>
|
|
154
|
+
</Card>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function ConnectionFormModal({ open, onClose, onSave, connection: initialConnection }: {
|
|
159
|
+
open: boolean;
|
|
160
|
+
onClose: () => void;
|
|
161
|
+
onSave: (connection: Omit<Connection, 'id' | 'status' | 'lastConnected'> & { id?: string }) => void;
|
|
162
|
+
connection: Connection | null;
|
|
163
|
+
}) {
|
|
164
|
+
const [name, setName] = useState('');
|
|
165
|
+
const [type, setType] = useState<ConnectionType>('MCP');
|
|
166
|
+
const [serverUrl, setServerUrl] = useState('');
|
|
167
|
+
const [credentials, setCredentials] = useState('');
|
|
168
|
+
const [isTesting, setIsTesting] = useState(false);
|
|
169
|
+
const [testStatus, setTestStatus] = useState<'success' | 'error' | null>(null);
|
|
170
|
+
|
|
171
|
+
React.useEffect(() => {
|
|
172
|
+
if (initialConnection) {
|
|
173
|
+
setName(initialConnection.name);
|
|
174
|
+
setType(initialConnection.type);
|
|
175
|
+
setServerUrl(initialConnection.serverUrl);
|
|
176
|
+
setCredentials('********'); // Don't expose credentials
|
|
177
|
+
} else {
|
|
178
|
+
setName('');
|
|
179
|
+
setType('MCP');
|
|
180
|
+
setServerUrl('');
|
|
181
|
+
setCredentials('');
|
|
182
|
+
}
|
|
183
|
+
setTestStatus(null);
|
|
184
|
+
}, [initialConnection, open]);
|
|
185
|
+
|
|
186
|
+
const handleTest = async () => {
|
|
187
|
+
setIsTesting(true);
|
|
188
|
+
setTestStatus(null);
|
|
189
|
+
// const testConnection = useMutation(api.mcpConnections.test);
|
|
190
|
+
// In a real app, you'd call the mutation:
|
|
191
|
+
// try {
|
|
192
|
+
// await testConnection({ serverUrl, credentials });
|
|
193
|
+
// setTestStatus('success');
|
|
194
|
+
// } catch (error) {
|
|
195
|
+
// setTestStatus('error');
|
|
196
|
+
// }
|
|
197
|
+
await new Promise(resolve => setTimeout(resolve, 1500)); // Simulate network delay
|
|
198
|
+
if (serverUrl.includes('fail')) {
|
|
199
|
+
setTestStatus('error');
|
|
200
|
+
} else {
|
|
201
|
+
setTestStatus('success');
|
|
202
|
+
}
|
|
203
|
+
setIsTesting(false);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
onSave({
|
|
209
|
+
id: initialConnection?.id,
|
|
210
|
+
name,
|
|
211
|
+
type,
|
|
212
|
+
serverUrl,
|
|
213
|
+
protocol: type === 'MCP' ? 'mcp' : 'https',
|
|
214
|
+
enabled: initialConnection?.enabled ?? true,
|
|
215
|
+
// Credentials would be handled securely, not passed like this
|
|
216
|
+
});
|
|
217
|
+
onClose();
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<Dialog open={open} onClose={onClose}>
|
|
222
|
+
<form onSubmit={handleSubmit}>
|
|
223
|
+
<DialogHeader>
|
|
224
|
+
<DialogTitle>{initialConnection ? 'Edit Connection' : 'Add New Connection'}</DialogTitle>
|
|
225
|
+
</DialogHeader>
|
|
226
|
+
<DialogContent>
|
|
227
|
+
<div className="space-y-2">
|
|
228
|
+
<label htmlFor="name" className="text-sm font-medium text-muted-foreground">Name</label>
|
|
229
|
+
<Input id="name" value={name} onChange={e => setName(e.target.value)} placeholder="My Awesome API" required />
|
|
230
|
+
</div>
|
|
231
|
+
<div className="space-y-2">
|
|
232
|
+
<label htmlFor="type" className="text-sm font-medium text-muted-foreground">Type</label>
|
|
233
|
+
<Select id="type" value={type} onChange={e => setType(e.target.value as ConnectionType)} required>
|
|
234
|
+
<option value="MCP">MCP</option>
|
|
235
|
+
<option value="API">API</option>
|
|
236
|
+
<option value="Webhook">Webhook</option>
|
|
237
|
+
</Select>
|
|
238
|
+
</div>
|
|
239
|
+
<div className="space-y-2">
|
|
240
|
+
<label htmlFor="serverUrl" className="text-sm font-medium text-muted-foreground">Server URL</label>
|
|
241
|
+
<Input id="serverUrl" value={serverUrl} onChange={e => setServerUrl(e.target.value)} placeholder="https://example.com/api" required />
|
|
242
|
+
</div>
|
|
243
|
+
<div className="space-y-2">
|
|
244
|
+
<label htmlFor="credentials" className="text-sm font-medium text-muted-foreground">Credentials (e.g., API Key)</label>
|
|
245
|
+
<Input id="credentials" type="password" value={credentials} onChange={e => setCredentials(e.target.value)} placeholder={initialConnection ? 'Enter new key to update' : 'Your secret key'} />
|
|
246
|
+
</div>
|
|
247
|
+
{testStatus && (
|
|
248
|
+
<div className={`flex items-center space-x-2 text-sm p-2 rounded-md ${testStatus === 'success' ? 'bg-green-900/50 text-green-400' : 'bg-red-900/50 text-red-400'}`}>
|
|
249
|
+
{testStatus === 'success' ? <CheckCircle className="h-4 w-4" /> : <XCircle className="h-4 w-4" />}
|
|
250
|
+
<span>{testStatus === 'success' ? 'Connection successful!' : 'Connection failed.'}</span>
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
</DialogContent>
|
|
254
|
+
<DialogFooter>
|
|
255
|
+
<Button type="button" onClick={handleTest} className="bg-secondary text-secondary-foreground hover:bg-secondary/80" disabled={isTesting}>
|
|
256
|
+
{isTesting ? <><RefreshCw className="h-4 w-4 mr-2 animate-spin" /> Testing...</> : 'Test Connection'}
|
|
257
|
+
</Button>
|
|
258
|
+
<Button type="button" onClick={onClose} className="bg-muted text-muted-foreground hover:bg-muted/80">Cancel</Button>
|
|
259
|
+
<Button type="submit" className="bg-primary text-primary-foreground hover:bg-primary/80">{initialConnection ? 'Save Changes' : 'Add Connection'}</Button>
|
|
260
|
+
</DialogFooter>
|
|
261
|
+
</form>
|
|
262
|
+
</Dialog>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// --- Main Page Component ---
|
|
267
|
+
|
|
268
|
+
export const Route = createFileRoute('/connections')({ component: ConnectionsPage });
|
|
269
|
+
|
|
270
|
+
function ConnectionsPage() {
|
|
271
|
+
// --- Convex Hooks (Commented Out) ---
|
|
272
|
+
// const connections = useQuery(api.mcpConnections.list) ?? [];
|
|
273
|
+
// const createConnection = useMutation(api.mcpConnections.create);
|
|
274
|
+
// const updateConnection = useMutation(api.mcpConnections.update);
|
|
275
|
+
// const deleteConnection = useMutation(api.mcpConnections.delete);
|
|
276
|
+
// const testConnection = useMutation(api.mcpConnections.test);
|
|
277
|
+
// const toggleConnection = useMutation(api.mcpConnections.toggle);
|
|
278
|
+
|
|
279
|
+
// --- Local State Management ---
|
|
280
|
+
const [connections, setConnections] = useState<Connection[]>(mockConnections);
|
|
281
|
+
const [isLoading, setIsLoading] = useState(false); // For initial load
|
|
282
|
+
const [error, setError] = useState<string | null>(null);
|
|
283
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
284
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
285
|
+
const [editingConnection, setEditingConnection] = useState<Connection | null>(null);
|
|
286
|
+
|
|
287
|
+
const filteredConnections = useMemo(() =>
|
|
288
|
+
connections.filter(c => c.name.toLowerCase().includes(searchTerm.toLowerCase())),
|
|
289
|
+
[connections, searchTerm]
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const handleAdd = () => {
|
|
293
|
+
setEditingConnection(null);
|
|
294
|
+
setIsModalOpen(true);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const handleEdit = (connection: Connection) => {
|
|
298
|
+
setEditingConnection(connection);
|
|
299
|
+
setIsModalOpen(true);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const handleDelete = (id: string) => {
|
|
303
|
+
if (window.confirm('Are you sure you want to delete this connection?')) {
|
|
304
|
+
// await deleteConnection({ id });
|
|
305
|
+
setConnections(prev => prev.filter(c => c.id !== id));
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const handleSave = (data: Omit<Connection, 'id' | 'status' | 'lastConnected'> & { id?: string }) => {
|
|
310
|
+
if (data.id) { // Update
|
|
311
|
+
// await updateConnection({ id: data.id, ...data });
|
|
312
|
+
setConnections(prev => prev.map(c => c.id === data.id ? { ...c, ...data } : c));
|
|
313
|
+
} else { // Create
|
|
314
|
+
const newId = (Math.random() * 100000).toString();
|
|
315
|
+
// const newId = await createConnection(data);
|
|
316
|
+
const newConnection: Connection = {
|
|
317
|
+
...data,
|
|
318
|
+
id: newId,
|
|
319
|
+
status: 'disconnected',
|
|
320
|
+
lastConnected: null,
|
|
321
|
+
};
|
|
322
|
+
setConnections(prev => [newConnection, ...prev]);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const handleTest = async (id: string) => {
|
|
327
|
+
setConnections(prev => prev.map(c => c.id === id ? { ...c, status: 'testing' } : c));
|
|
328
|
+
// const result = await testConnection({ id });
|
|
329
|
+
await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate test
|
|
330
|
+
const result = { success: Math.random() > 0.3 }; // Simulate success/fail
|
|
331
|
+
|
|
332
|
+
setConnections(prev => prev.map(c => c.id === id ? {
|
|
333
|
+
...c,
|
|
334
|
+
status: result.success ? 'connected' : 'disconnected',
|
|
335
|
+
lastConnected: result.success ? new Date().toISOString() : c.lastConnected,
|
|
336
|
+
} : c));
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const handleToggle = (id: string, enabled: boolean) => {
|
|
340
|
+
// await toggleConnection({ id, enabled });
|
|
341
|
+
setConnections(prev => prev.map(c => c.id === id ? { ...c, enabled } : c));
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<DashboardLayout>
|
|
346
|
+
<div className="bg-background text-foreground p-4 sm:p-6 lg:p-8">
|
|
347
|
+
<header className="flex flex-col sm:flex-row justify-between sm:items-center mb-6 gap-4">
|
|
348
|
+
<div>
|
|
349
|
+
<h1 className="text-2xl font-bold">Connections</h1>
|
|
350
|
+
<p className="text-muted-foreground">Manage your MCP, API, and Webhook connections.</p>
|
|
351
|
+
</div>
|
|
352
|
+
<div className="flex items-center gap-2">
|
|
353
|
+
<div className="relative w-full sm:w-auto">
|
|
354
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
355
|
+
<Input
|
|
356
|
+
type="text"
|
|
357
|
+
placeholder="Search connections..."
|
|
358
|
+
value={searchTerm}
|
|
359
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
360
|
+
className="pl-10 w-full sm:w-64"
|
|
361
|
+
/>
|
|
362
|
+
</div>
|
|
363
|
+
<Button onClick={handleAdd} className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2">
|
|
364
|
+
<Plus className="h-4 w-4" />
|
|
365
|
+
<span>Add Connection</span>
|
|
366
|
+
</Button>
|
|
367
|
+
</div>
|
|
368
|
+
</header>
|
|
369
|
+
|
|
370
|
+
{isLoading ? (
|
|
371
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
372
|
+
{[...Array(3)].map((_, i) => <Card key={i} className="h-48 animate-pulse"></Card>)}
|
|
373
|
+
</div>
|
|
374
|
+
) : error ? (
|
|
375
|
+
<div className="flex flex-col items-center justify-center text-center h-64 bg-card rounded-lg">
|
|
376
|
+
<XCircle className="h-12 w-12 text-destructive mb-4" />
|
|
377
|
+
<h3 className="text-xl font-semibold">Failed to load connections</h3>
|
|
378
|
+
<p className="text-muted-foreground">{error}</p>
|
|
379
|
+
</div>
|
|
380
|
+
) : filteredConnections.length > 0 ? (
|
|
381
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
382
|
+
{filteredConnections.map(conn => (
|
|
383
|
+
<ConnectionCard
|
|
384
|
+
key={conn.id}
|
|
385
|
+
connection={conn}
|
|
386
|
+
onTest={handleTest}
|
|
387
|
+
onEdit={handleEdit}
|
|
388
|
+
onDelete={handleDelete}
|
|
389
|
+
onToggle={handleToggle}
|
|
390
|
+
/>
|
|
391
|
+
))}
|
|
392
|
+
</div>
|
|
393
|
+
) : (
|
|
394
|
+
<div className="flex flex-col items-center justify-center text-center h-64 bg-card rounded-lg border-2 border-dashed border-border">
|
|
395
|
+
<Plug className="h-12 w-12 text-muted-foreground mb-4" />
|
|
396
|
+
<h3 className="text-xl font-semibold">No Connections Found</h3>
|
|
397
|
+
<p className="text-muted-foreground mb-4">Get started by adding your first connection.</p>
|
|
398
|
+
<Button onClick={handleAdd} className="bg-primary text-primary-foreground hover:bg-primary/90">
|
|
399
|
+
Add Connection
|
|
400
|
+
</Button>
|
|
401
|
+
</div>
|
|
402
|
+
)}
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<ConnectionFormModal
|
|
406
|
+
open={isModalOpen}
|
|
407
|
+
onClose={() => setIsModalOpen(false)}
|
|
408
|
+
onSave={handleSave}
|
|
409
|
+
connection={editingConnection}
|
|
410
|
+
/>
|
|
411
|
+
</DashboardLayout>
|
|
412
|
+
);
|
|
413
|
+
}
|