apteva 0.4.16 → 0.4.18
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/ActivityPage.yv28a2vj.js +3 -0
- package/dist/ApiDocsPage.4ccwjjbk.js +4 -0
- package/dist/App.155wke5v.js +4 -0
- package/dist/App.2e19nvn4.js +13 -0
- package/dist/App.2ye1b5n0.js +4 -0
- package/dist/App.4da4ycbe.js +4 -0
- package/dist/App.b6wtzd1j.js +4 -0
- package/dist/App.fjrh28tf.js +4 -0
- package/dist/App.htc36cy8.js +4 -0
- package/dist/App.me6reaa6.js +4 -0
- package/dist/App.n5q6p960.js +4 -0
- package/dist/App.nft7h9jt.js +4 -0
- package/dist/App.np463xvy.js +4 -0
- package/dist/App.nps62kvt.js +4 -0
- package/dist/App.q8ws33cc.js +181 -0
- package/dist/App.tb0y0jmt.js +40 -0
- package/dist/ConnectionsPage.52evzrp7.js +3 -0
- package/dist/McpPage.bjqrp0n2.js +3 -0
- package/dist/SettingsPage.es76hnj2.js +3 -0
- package/dist/SkillsPage.06h8yf0h.js +3 -0
- package/dist/TasksPage.99df66mk.js +3 -0
- package/dist/TelemetryPage.bmdnxhq7.js +3 -0
- package/dist/TestsPage.denxrg8c.js +3 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/auth/middleware.ts +2 -0
- package/src/db.ts +162 -11
- package/src/mcp-platform.ts +41 -1
- package/src/routes/api/agent-utils.ts +38 -2
- package/src/routes/api/agents.ts +65 -2
- package/src/routes/api/projects.ts +19 -2
- package/src/routes/api/system.ts +26 -12
- package/src/routes/api/triggers.ts +458 -0
- package/src/routes/api/webhooks.ts +171 -0
- package/src/routes/api.ts +4 -0
- package/src/routes/static.ts +12 -3
- package/src/server.ts +6 -4
- package/src/triggers/agentdojo.ts +248 -0
- package/src/triggers/composio.ts +264 -0
- package/src/triggers/index.ts +71 -0
- package/src/web/App.tsx +20 -12
- package/src/web/components/agents/AgentCard.tsx +14 -7
- package/src/web/components/agents/AgentPanel.tsx +105 -115
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/connections/ConnectionsPage.tsx +54 -0
- package/src/web/components/connections/IntegrationsTab.tsx +144 -0
- package/src/web/components/connections/OverviewTab.tsx +183 -0
- package/src/web/components/connections/TriggersTab.tsx +690 -0
- package/src/web/components/index.ts +1 -0
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/IntegrationsPanel.tsx +19 -3
- package/src/web/components/mcp/McpPage.tsx +9 -3
- package/src/web/components/settings/SettingsPage.tsx +96 -2
- package/src/web/components/tasks/TasksPage.tsx +2 -2
- package/src/web/components/tests/TestsPage.tsx +1 -2
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/context/index.ts +1 -1
- package/src/web/hooks/useAgents.ts +15 -11
- package/src/web/types.ts +1 -1
- package/dist/App.2194efgj.js +0 -228
package/src/web/App.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useMemo } from "react";
|
|
1
|
+
import React, { useState, useEffect, useMemo, lazy, Suspense } from "react";
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
3
|
import "@apteva/apteva-kit/styles.css";
|
|
4
4
|
|
|
@@ -7,38 +7,42 @@ import type { Agent, Provider, Route, NewAgentForm } from "./types";
|
|
|
7
7
|
import { DEFAULT_FEATURES } from "./types";
|
|
8
8
|
|
|
9
9
|
// Context
|
|
10
|
-
import { TelemetryProvider, AuthProvider, ProjectProvider, useAuth, useProjects, useAgentStatusChange } from "./context";
|
|
10
|
+
import { TelemetryProvider, AuthProvider, ProjectProvider, useAuth, useProjects, useAgentStatusChange, useTaskChange } from "./context";
|
|
11
11
|
|
|
12
12
|
// Hooks
|
|
13
13
|
import { useAgents, useProviders, useOnboarding } from "./hooks";
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// Core components (always needed)
|
|
16
16
|
import {
|
|
17
17
|
LoadingSpinner,
|
|
18
18
|
Header,
|
|
19
19
|
Sidebar,
|
|
20
20
|
ErrorBanner,
|
|
21
21
|
OnboardingWizard,
|
|
22
|
-
SettingsPage,
|
|
23
22
|
CreateAgentModal,
|
|
24
23
|
AgentsView,
|
|
25
24
|
Dashboard,
|
|
26
|
-
ActivityPage,
|
|
27
|
-
TasksPage,
|
|
28
|
-
McpPage,
|
|
29
|
-
SkillsPage,
|
|
30
|
-
TestsPage,
|
|
31
|
-
TelemetryPage,
|
|
32
25
|
LoginPage,
|
|
33
26
|
} from "./components";
|
|
34
|
-
import { ApiDocsPage } from "./components/api/ApiDocsPage";
|
|
35
27
|
import { MetaAgentProvider, MetaAgentPanel } from "./components/meta-agent/MetaAgent";
|
|
36
28
|
|
|
29
|
+
// Lazy-loaded page components (only loaded when navigated to)
|
|
30
|
+
const SettingsPage = lazy(() => import("./components/settings/SettingsPage").then(m => ({ default: m.SettingsPage })));
|
|
31
|
+
const ActivityPage = lazy(() => import("./components/activity/ActivityPage").then(m => ({ default: m.ActivityPage })));
|
|
32
|
+
const TasksPage = lazy(() => import("./components/tasks/TasksPage").then(m => ({ default: m.TasksPage })));
|
|
33
|
+
const McpPage = lazy(() => import("./components/mcp/McpPage").then(m => ({ default: m.McpPage })));
|
|
34
|
+
const SkillsPage = lazy(() => import("./components/skills/SkillsPage").then(m => ({ default: m.SkillsPage })));
|
|
35
|
+
const TestsPage = lazy(() => import("./components/tests/TestsPage").then(m => ({ default: m.TestsPage })));
|
|
36
|
+
const TelemetryPage = lazy(() => import("./components/telemetry/TelemetryPage").then(m => ({ default: m.TelemetryPage })));
|
|
37
|
+
const ConnectionsPage = lazy(() => import("./components/connections/ConnectionsPage").then(m => ({ default: m.ConnectionsPage })));
|
|
38
|
+
const ApiDocsPage = lazy(() => import("./components/api/ApiDocsPage").then(m => ({ default: m.ApiDocsPage })));
|
|
39
|
+
|
|
37
40
|
function AppContent() {
|
|
38
41
|
// Auth state
|
|
39
42
|
const { isAuthenticated, isLoading: authLoading, hasUsers, accessToken, checkAuth } = useAuth();
|
|
40
43
|
const { currentProjectId, refreshProjects } = useProjects();
|
|
41
44
|
const statusChangeCounter = useAgentStatusChange();
|
|
45
|
+
const taskChangeCounter = useTaskChange();
|
|
42
46
|
|
|
43
47
|
// Onboarding state
|
|
44
48
|
const { isComplete: onboardingComplete, setIsComplete: setOnboardingComplete } = useOnboarding();
|
|
@@ -107,7 +111,7 @@ function AppContent() {
|
|
|
107
111
|
};
|
|
108
112
|
|
|
109
113
|
fetchTaskCount();
|
|
110
|
-
}, [shouldFetchData, accessToken, currentProjectId, agents, statusChangeCounter]);
|
|
114
|
+
}, [shouldFetchData, accessToken, currentProjectId, agents, statusChangeCounter, taskChangeCounter]);
|
|
111
115
|
|
|
112
116
|
// Form state
|
|
113
117
|
const [newAgent, setNewAgent] = useState<NewAgentForm>({
|
|
@@ -253,6 +257,7 @@ function AppContent() {
|
|
|
253
257
|
/>
|
|
254
258
|
|
|
255
259
|
<main className="flex-1 overflow-hidden flex">
|
|
260
|
+
<Suspense fallback={<LoadingSpinner />}>
|
|
256
261
|
{route === "settings" && <SettingsPage />}
|
|
257
262
|
|
|
258
263
|
{route === "activity" && (
|
|
@@ -291,6 +296,8 @@ function AppContent() {
|
|
|
291
296
|
|
|
292
297
|
{route === "tasks" && <TasksPage />}
|
|
293
298
|
|
|
299
|
+
{route === "connections" && <ConnectionsPage />}
|
|
300
|
+
|
|
294
301
|
{route === "mcp" && <McpPage />}
|
|
295
302
|
|
|
296
303
|
{route === "skills" && <SkillsPage />}
|
|
@@ -300,6 +307,7 @@ function AppContent() {
|
|
|
300
307
|
{route === "telemetry" && <TelemetryPage />}
|
|
301
308
|
|
|
302
309
|
{route === "api" && <ApiDocsPage />}
|
|
310
|
+
</Suspense>
|
|
303
311
|
</main>
|
|
304
312
|
</div>
|
|
305
313
|
|
|
@@ -121,13 +121,16 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
121
121
|
|
|
122
122
|
<button
|
|
123
123
|
onClick={onToggle}
|
|
124
|
+
disabled={agent.status === "starting" || agent.status === "stopping"}
|
|
124
125
|
className={`w-full px-3 py-1.5 rounded text-sm font-medium transition mt-auto ${
|
|
125
|
-
agent.status === "
|
|
126
|
-
? "bg-[#
|
|
127
|
-
:
|
|
126
|
+
agent.status === "starting" || agent.status === "stopping"
|
|
127
|
+
? "bg-[#333] text-[#666] cursor-wait"
|
|
128
|
+
: agent.status === "running"
|
|
129
|
+
? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
|
|
130
|
+
: "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
|
|
128
131
|
}`}
|
|
129
132
|
>
|
|
130
|
-
{agent.status === "running" ? "Stop" : "Start"}
|
|
133
|
+
{agent.status === "starting" ? "Starting..." : agent.status === "stopping" ? "Stopping..." : agent.status === "running" ? "Stop" : "Start"}
|
|
131
134
|
</button>
|
|
132
135
|
</div>
|
|
133
136
|
);
|
|
@@ -142,12 +145,16 @@ function StatusBadge({ status, isActive, activityType }: { status: Agent["status
|
|
|
142
145
|
);
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
const isTransitioning = status === "starting" || status === "stopping";
|
|
149
|
+
|
|
145
150
|
return (
|
|
146
151
|
<span
|
|
147
152
|
className={`px-2 py-1 rounded text-xs font-medium ${
|
|
148
|
-
|
|
149
|
-
? "bg-
|
|
150
|
-
:
|
|
153
|
+
isTransitioning
|
|
154
|
+
? "bg-yellow-500/20 text-yellow-400 animate-pulse"
|
|
155
|
+
: status === "running"
|
|
156
|
+
? "bg-[#3b82f6]/20 text-[#3b82f6]"
|
|
157
|
+
: "bg-[#333] text-[#666]"
|
|
151
158
|
}`}
|
|
152
159
|
>
|
|
153
160
|
{status}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Chat } from "@apteva/apteva-kit";
|
|
3
|
-
import { CloseIcon, MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon } from "../common/Icons";
|
|
3
|
+
import { CloseIcon, MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon, RecurringIcon, ScheduledIcon, TaskOnceIcon } from "../common/Icons";
|
|
4
|
+
import { formatCron, formatRelativeTime } from "../tasks/TasksPage";
|
|
4
5
|
import { Select } from "../common/Select";
|
|
5
6
|
import { useConfirm } from "../common/Modal";
|
|
6
7
|
import { useTelemetry } from "../../context";
|
|
@@ -152,7 +153,7 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
152
153
|
const [loading, setLoading] = useState(true);
|
|
153
154
|
const [error, setError] = useState<string | null>(null);
|
|
154
155
|
const [selectedThread, setSelectedThread] = useState<string | null>(null);
|
|
155
|
-
const [
|
|
156
|
+
const [initialMessages, setInitialMessages] = useState<any[]>([]);
|
|
156
157
|
const [loadingMessages, setLoadingMessages] = useState(false);
|
|
157
158
|
const { confirm, ConfirmDialog } = useConfirm();
|
|
158
159
|
|
|
@@ -160,7 +161,6 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
160
161
|
useEffect(() => {
|
|
161
162
|
setThreads([]);
|
|
162
163
|
setSelectedThread(null);
|
|
163
|
-
setMessages([]);
|
|
164
164
|
setError(null);
|
|
165
165
|
setLoading(true);
|
|
166
166
|
}, [agent.id]);
|
|
@@ -188,19 +188,29 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
188
188
|
fetchThreads();
|
|
189
189
|
}, [agent.id, agent.status]);
|
|
190
190
|
|
|
191
|
-
const
|
|
192
|
-
setSelectedThread(threadId);
|
|
191
|
+
const openThread = async (threadId: string) => {
|
|
193
192
|
setLoadingMessages(true);
|
|
193
|
+
setSelectedThread(threadId);
|
|
194
194
|
try {
|
|
195
195
|
const res = await fetch(`/api/agents/${agent.id}/threads/${threadId}/messages`);
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
if (res.ok) {
|
|
197
|
+
const data = await res.json();
|
|
198
|
+
const msgs = (data.messages || [])
|
|
199
|
+
.filter((m: any) => typeof m.content === "string")
|
|
200
|
+
.map((m: any) => ({
|
|
201
|
+
id: m.id,
|
|
202
|
+
role: m.role,
|
|
203
|
+
content: m.content,
|
|
204
|
+
timestamp: new Date(m.created_at),
|
|
205
|
+
}));
|
|
206
|
+
setInitialMessages(msgs);
|
|
207
|
+
} else {
|
|
208
|
+
setInitialMessages([]);
|
|
209
|
+
}
|
|
199
210
|
} catch {
|
|
200
|
-
|
|
201
|
-
} finally {
|
|
202
|
-
setLoadingMessages(false);
|
|
211
|
+
setInitialMessages([]);
|
|
203
212
|
}
|
|
213
|
+
setLoadingMessages(false);
|
|
204
214
|
};
|
|
205
215
|
|
|
206
216
|
const deleteThread = async (threadId: string, e: React.MouseEvent) => {
|
|
@@ -213,7 +223,6 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
213
223
|
setThreads(prev => prev.filter(t => t.id !== threadId));
|
|
214
224
|
if (selectedThread === threadId) {
|
|
215
225
|
setSelectedThread(null);
|
|
216
|
-
setMessages([]);
|
|
217
226
|
}
|
|
218
227
|
} catch {
|
|
219
228
|
// Ignore errors
|
|
@@ -244,7 +253,7 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
244
253
|
);
|
|
245
254
|
}
|
|
246
255
|
|
|
247
|
-
// Show
|
|
256
|
+
// Show live chat for selected thread
|
|
248
257
|
if (selectedThread) {
|
|
249
258
|
const selectedThreadData = threads.find(t => t.id === selectedThread);
|
|
250
259
|
return (
|
|
@@ -252,15 +261,15 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
252
261
|
{ConfirmDialog}
|
|
253
262
|
<div className="flex-1 flex flex-col overflow-hidden">
|
|
254
263
|
{/* Header with back button */}
|
|
255
|
-
<div className="flex items-center gap-3 px-4 py-
|
|
264
|
+
<div className="flex items-center gap-3 px-4 py-2 border-b border-[#1a1a1a] flex-shrink-0">
|
|
256
265
|
<button
|
|
257
|
-
onClick={() => { setSelectedThread(null);
|
|
266
|
+
onClick={() => { setSelectedThread(null); setInitialMessages([]); }}
|
|
258
267
|
className="text-[#666] hover:text-[#e0e0e0] transition text-lg"
|
|
259
268
|
>
|
|
260
269
|
←
|
|
261
270
|
</button>
|
|
262
|
-
<div className="flex-1">
|
|
263
|
-
<p className="text-sm font-medium">
|
|
271
|
+
<div className="flex-1 min-w-0">
|
|
272
|
+
<p className="text-sm font-medium truncate">
|
|
264
273
|
{selectedThreadData?.title || `Thread ${selectedThread.slice(0, 8)}`}
|
|
265
274
|
</p>
|
|
266
275
|
<p className="text-xs text-[#666]">
|
|
@@ -275,54 +284,22 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
275
284
|
</button>
|
|
276
285
|
</div>
|
|
277
286
|
|
|
278
|
-
{/*
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
>
|
|
295
|
-
<div className="text-sm whitespace-pre-wrap">
|
|
296
|
-
{typeof msg.content === "string"
|
|
297
|
-
? msg.content
|
|
298
|
-
: Array.isArray(msg.content)
|
|
299
|
-
? msg.content.map((block: any, j: number) => (
|
|
300
|
-
<div key={j}>
|
|
301
|
-
{block.type === "text" && block.text}
|
|
302
|
-
{block.type === "tool_use" && (
|
|
303
|
-
<div className="bg-[#222] p-2 rounded mt-1 text-xs text-[#888]">
|
|
304
|
-
🔧 Tool: {block.name}
|
|
305
|
-
</div>
|
|
306
|
-
)}
|
|
307
|
-
{block.type === "tool_result" && (
|
|
308
|
-
<div className="bg-[#222] p-2 rounded mt-1 text-xs text-[#888]">
|
|
309
|
-
📋 Result: {typeof block.content === "string" ? block.content.slice(0, 200) : "..."}
|
|
310
|
-
</div>
|
|
311
|
-
)}
|
|
312
|
-
</div>
|
|
313
|
-
))
|
|
314
|
-
: JSON.stringify(msg.content)
|
|
315
|
-
}
|
|
316
|
-
</div>
|
|
317
|
-
<p className="text-xs text-[#666] mt-1">
|
|
318
|
-
{new Date(msg.created_at).toLocaleTimeString()}
|
|
319
|
-
</p>
|
|
320
|
-
</div>
|
|
321
|
-
</div>
|
|
322
|
-
))}
|
|
323
|
-
</div>
|
|
324
|
-
)}
|
|
325
|
-
</div>
|
|
287
|
+
{/* Live chat in this thread */}
|
|
288
|
+
{loadingMessages ? (
|
|
289
|
+
<div className="flex-1 flex items-center justify-center text-[#666]">Loading messages...</div>
|
|
290
|
+
) : (
|
|
291
|
+
<Chat
|
|
292
|
+
key={selectedThread}
|
|
293
|
+
agentId="default"
|
|
294
|
+
apiUrl={`/api/agents/${agent.id}`}
|
|
295
|
+
threadId={selectedThread}
|
|
296
|
+
initialMessages={initialMessages}
|
|
297
|
+
placeholder="Continue this conversation..."
|
|
298
|
+
context={agent.systemPrompt}
|
|
299
|
+
variant="terminal"
|
|
300
|
+
showHeader={false}
|
|
301
|
+
/>
|
|
302
|
+
)}
|
|
326
303
|
</div>
|
|
327
304
|
</>
|
|
328
305
|
);
|
|
@@ -342,7 +319,7 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
342
319
|
{threads.map(thread => (
|
|
343
320
|
<div
|
|
344
321
|
key={thread.id}
|
|
345
|
-
onClick={() =>
|
|
322
|
+
onClick={() => openThread(thread.id)}
|
|
346
323
|
className="p-4 cursor-pointer hover:bg-[#111] transition flex items-center justify-between"
|
|
347
324
|
>
|
|
348
325
|
<div className="flex-1 min-w-0">
|
|
@@ -369,24 +346,11 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
369
346
|
);
|
|
370
347
|
}
|
|
371
348
|
|
|
372
|
-
interface Task {
|
|
373
|
-
id: string;
|
|
374
|
-
name: string;
|
|
375
|
-
description?: string;
|
|
376
|
-
status: "pending" | "running" | "completed" | "failed";
|
|
377
|
-
created_at: string;
|
|
378
|
-
updated_at?: string;
|
|
379
|
-
scheduled_at?: string;
|
|
380
|
-
completed_at?: string;
|
|
381
|
-
result?: string;
|
|
382
|
-
error?: string;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
349
|
function TasksTab({ agent }: { agent: Agent }) {
|
|
386
|
-
const [tasks, setTasks] = useState<
|
|
350
|
+
const [tasks, setTasks] = useState<any[]>([]);
|
|
387
351
|
const [loading, setLoading] = useState(true);
|
|
388
352
|
const [error, setError] = useState<string | null>(null);
|
|
389
|
-
const [filter, setFilter] = useState<
|
|
353
|
+
const [filter, setFilter] = useState<string>("all");
|
|
390
354
|
const { events } = useTelemetry({ agent_id: agent.id, category: "task" });
|
|
391
355
|
|
|
392
356
|
// Reset state when agent changes
|
|
@@ -421,14 +385,12 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
421
385
|
fetchTasks();
|
|
422
386
|
}, [agent.id, agent.status, filter, events.length]);
|
|
423
387
|
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
default: return "bg-[#222] text-[#666]";
|
|
431
|
-
}
|
|
388
|
+
const statusColors: Record<string, string> = {
|
|
389
|
+
pending: "bg-yellow-500/20 text-yellow-400",
|
|
390
|
+
running: "bg-blue-500/20 text-blue-400",
|
|
391
|
+
completed: "bg-green-500/20 text-green-400",
|
|
392
|
+
failed: "bg-red-500/20 text-red-400",
|
|
393
|
+
cancelled: "bg-gray-500/20 text-gray-400",
|
|
432
394
|
};
|
|
433
395
|
|
|
434
396
|
if (agent.status !== "running") {
|
|
@@ -466,63 +428,91 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
466
428
|
);
|
|
467
429
|
}
|
|
468
430
|
|
|
431
|
+
const filterOptions = [
|
|
432
|
+
{ value: "all", label: "All" },
|
|
433
|
+
{ value: "pending", label: "Pending" },
|
|
434
|
+
{ value: "running", label: "Running" },
|
|
435
|
+
{ value: "completed", label: "Completed" },
|
|
436
|
+
{ value: "failed", label: "Failed" },
|
|
437
|
+
];
|
|
438
|
+
|
|
469
439
|
return (
|
|
470
440
|
<div className="flex-1 overflow-auto p-4">
|
|
471
441
|
{/* Filter tabs */}
|
|
472
442
|
<div className="flex gap-2 mb-4">
|
|
473
|
-
{
|
|
443
|
+
{filterOptions.map(opt => (
|
|
474
444
|
<button
|
|
475
|
-
key={
|
|
476
|
-
onClick={() => setFilter(
|
|
477
|
-
className={`px-3 py-1 text-
|
|
478
|
-
filter ===
|
|
445
|
+
key={opt.value}
|
|
446
|
+
onClick={() => setFilter(opt.value)}
|
|
447
|
+
className={`px-3 py-1.5 rounded text-sm transition ${
|
|
448
|
+
filter === opt.value
|
|
479
449
|
? "bg-[#f97316] text-black"
|
|
480
|
-
: "bg-[#1a1a1a]
|
|
450
|
+
: "bg-[#1a1a1a] hover:bg-[#222]"
|
|
481
451
|
}`}
|
|
482
452
|
>
|
|
483
|
-
{
|
|
453
|
+
{opt.label}
|
|
484
454
|
</button>
|
|
485
455
|
))}
|
|
486
456
|
</div>
|
|
487
457
|
|
|
488
458
|
{tasks.length === 0 ? (
|
|
489
|
-
<div className="text-center py-10
|
|
490
|
-
<
|
|
459
|
+
<div className="text-center py-10">
|
|
460
|
+
<TasksIcon className="w-10 h-10 mx-auto mb-3 text-[#333]" />
|
|
461
|
+
<p className="text-[#666]">No {filter === "all" ? "" : filter + " "}tasks</p>
|
|
462
|
+
<p className="text-sm text-[#444] mt-1">Tasks will appear here when created</p>
|
|
491
463
|
</div>
|
|
492
464
|
) : (
|
|
493
465
|
<div className="space-y-3">
|
|
494
466
|
{tasks.map(task => (
|
|
495
|
-
<div key={task.id} className="bg-[#111] border border-[#1a1a1a] rounded p-
|
|
496
|
-
<div className="flex items-start justify-between">
|
|
467
|
+
<div key={task.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
|
|
468
|
+
<div className="flex items-start justify-between mb-2">
|
|
497
469
|
<div className="flex-1 min-w-0">
|
|
498
|
-
<
|
|
499
|
-
{task.description && (
|
|
500
|
-
<p className="text-xs text-[#666] mt-1 line-clamp-2">{task.description}</p>
|
|
501
|
-
)}
|
|
470
|
+
<h3 className="font-medium">{task.title || task.name}</h3>
|
|
502
471
|
</div>
|
|
503
|
-
<span className={`
|
|
472
|
+
<span className={`px-2 py-1 rounded text-xs font-medium ml-2 ${statusColors[task.status] || statusColors.pending}`}>
|
|
504
473
|
{task.status}
|
|
505
474
|
</span>
|
|
506
475
|
</div>
|
|
507
476
|
|
|
508
|
-
|
|
509
|
-
<
|
|
510
|
-
|
|
511
|
-
|
|
477
|
+
{task.description && (
|
|
478
|
+
<p className="text-sm text-[#888] mb-2 line-clamp-2">{task.description}</p>
|
|
479
|
+
)}
|
|
480
|
+
|
|
481
|
+
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-[#555]">
|
|
482
|
+
<span className="flex items-center gap-1">
|
|
483
|
+
{task.type === "recurring"
|
|
484
|
+
? <RecurringIcon className="w-3.5 h-3.5" />
|
|
485
|
+
: task.execute_at
|
|
486
|
+
? <ScheduledIcon className="w-3.5 h-3.5" />
|
|
487
|
+
: <TaskOnceIcon className="w-3.5 h-3.5" />
|
|
488
|
+
}
|
|
489
|
+
{task.type === "recurring" && task.recurrence ? formatCron(task.recurrence) : task.type || "once"}
|
|
490
|
+
</span>
|
|
491
|
+
{task.priority !== undefined && (
|
|
492
|
+
<span>Priority: {task.priority}</span>
|
|
493
|
+
)}
|
|
494
|
+
{task.next_run && (
|
|
495
|
+
<span className="text-[#f97316]">{formatRelativeTime(task.next_run)}</span>
|
|
496
|
+
)}
|
|
497
|
+
{!task.next_run && task.execute_at && (
|
|
498
|
+
<span className="text-[#f97316]">{formatRelativeTime(task.execute_at)}</span>
|
|
512
499
|
)}
|
|
500
|
+
<span>Created: {new Date(task.created_at).toLocaleDateString()}</span>
|
|
513
501
|
</div>
|
|
514
502
|
|
|
515
503
|
{task.status === "completed" && task.result && (
|
|
516
|
-
<div className="mt-
|
|
517
|
-
<
|
|
518
|
-
<
|
|
504
|
+
<div className="mt-3 bg-green-500/10 border border-green-500/20 rounded p-3">
|
|
505
|
+
<h4 className="text-xs text-green-400 uppercase tracking-wider mb-1">Result</h4>
|
|
506
|
+
<pre className="text-sm text-green-400 whitespace-pre-wrap break-words">
|
|
507
|
+
{typeof task.result === "string" ? task.result : JSON.stringify(task.result, null, 2)}
|
|
508
|
+
</pre>
|
|
519
509
|
</div>
|
|
520
510
|
)}
|
|
521
511
|
|
|
522
512
|
{task.status === "failed" && task.error && (
|
|
523
|
-
<div className="mt-
|
|
524
|
-
<
|
|
525
|
-
<
|
|
513
|
+
<div className="mt-3 bg-red-500/10 border border-red-500/20 rounded p-3">
|
|
514
|
+
<h4 className="text-xs text-red-400 uppercase tracking-wider mb-1">Error</h4>
|
|
515
|
+
<pre className="text-sm text-red-400 whitespace-pre-wrap break-words">{task.error}</pre>
|
|
526
516
|
</div>
|
|
527
517
|
)}
|
|
528
518
|
</div>
|
|
@@ -182,6 +182,14 @@ export function TestsIcon({ className = "w-4 h-4" }: IconProps) {
|
|
|
182
182
|
);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
export function ConnectionsIcon({ className = "w-5 h-5" }: IconProps) {
|
|
186
|
+
return (
|
|
187
|
+
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
188
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
|
189
|
+
</svg>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
185
193
|
export function ActivityIcon({ className = "w-5 h-5" }: IconProps) {
|
|
186
194
|
return (
|
|
187
195
|
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { OverviewTab } from "./OverviewTab";
|
|
3
|
+
import { TriggersTab } from "./TriggersTab";
|
|
4
|
+
import { IntegrationsTab } from "./IntegrationsTab";
|
|
5
|
+
|
|
6
|
+
type Tab = "overview" | "triggers" | "integrations";
|
|
7
|
+
|
|
8
|
+
export function ConnectionsPage() {
|
|
9
|
+
const [activeTab, setActiveTab] = useState<Tab>("overview");
|
|
10
|
+
|
|
11
|
+
const tabs: { id: Tab; label: string }[] = [
|
|
12
|
+
{ id: "overview", label: "Overview" },
|
|
13
|
+
{ id: "triggers", label: "Triggers" },
|
|
14
|
+
{ id: "integrations", label: "Integrations" },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex-1 overflow-auto p-6">
|
|
19
|
+
<div className="max-w-6xl">
|
|
20
|
+
{/* Header */}
|
|
21
|
+
<div className="flex items-center justify-between mb-6">
|
|
22
|
+
<div>
|
|
23
|
+
<h1 className="text-2xl font-semibold mb-1">Connections</h1>
|
|
24
|
+
<p className="text-[#666]">
|
|
25
|
+
Manage external app connections, triggers, and webhooks.
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
{/* Tabs */}
|
|
31
|
+
<div className="flex gap-1 mb-6 bg-[#111] border border-[#1a1a1a] rounded-lg p-1 w-fit">
|
|
32
|
+
{tabs.map(tab => (
|
|
33
|
+
<button
|
|
34
|
+
key={tab.id}
|
|
35
|
+
onClick={() => setActiveTab(tab.id)}
|
|
36
|
+
className={`px-4 py-2 rounded text-sm font-medium transition ${
|
|
37
|
+
activeTab === tab.id
|
|
38
|
+
? "bg-[#1a1a1a] text-white"
|
|
39
|
+
: "text-[#666] hover:text-[#888]"
|
|
40
|
+
}`}
|
|
41
|
+
>
|
|
42
|
+
{tab.label}
|
|
43
|
+
</button>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{/* Tab Content */}
|
|
48
|
+
{activeTab === "overview" && <OverviewTab />}
|
|
49
|
+
{activeTab === "triggers" && <TriggersTab />}
|
|
50
|
+
{activeTab === "integrations" && <IntegrationsTab />}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|