@checkstack/integration-frontend 0.4.4 → 0.5.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/CHANGELOG.md +218 -0
- package/package.json +8 -8
- package/src/components/IntegrationMenuItem.tsx +14 -9
- package/src/index.tsx +9 -14
- package/src/pages/IntegrationsLandingPage.tsx +116 -0
- package/src/pages/ProviderConnectionsPage.tsx +6 -5
- package/src/components/CreateSubscriptionDialog.tsx +0 -651
- package/src/pages/DeliveryLogsPage.tsx +0 -239
- package/src/pages/IntegrationsPage.tsx +0 -378
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { useNavigate } from "react-router-dom";
|
|
3
|
-
import {
|
|
4
|
-
FileText,
|
|
5
|
-
RefreshCw,
|
|
6
|
-
CheckCircle,
|
|
7
|
-
XCircle,
|
|
8
|
-
Clock,
|
|
9
|
-
AlertCircle,
|
|
10
|
-
} from "lucide-react";
|
|
11
|
-
import {
|
|
12
|
-
PageLayout,
|
|
13
|
-
Card,
|
|
14
|
-
Button,
|
|
15
|
-
Badge,
|
|
16
|
-
SectionHeader,
|
|
17
|
-
Table,
|
|
18
|
-
TableBody,
|
|
19
|
-
TableCell,
|
|
20
|
-
TableHead,
|
|
21
|
-
TableHeader,
|
|
22
|
-
TableRow,
|
|
23
|
-
useToast,
|
|
24
|
-
usePagination,
|
|
25
|
-
usePaginationSync,
|
|
26
|
-
BackLink,
|
|
27
|
-
} from "@checkstack/ui";
|
|
28
|
-
import { usePluginClient } from "@checkstack/frontend-api";
|
|
29
|
-
import { resolveRoute } from "@checkstack/common";
|
|
30
|
-
import {
|
|
31
|
-
IntegrationApi,
|
|
32
|
-
integrationRoutes,
|
|
33
|
-
type DeliveryLog,
|
|
34
|
-
type DeliveryStatus,
|
|
35
|
-
} from "@checkstack/integration-common";
|
|
36
|
-
|
|
37
|
-
const statusConfig: Record<
|
|
38
|
-
DeliveryStatus,
|
|
39
|
-
{
|
|
40
|
-
icon: React.ReactNode;
|
|
41
|
-
variant: "success" | "destructive" | "warning" | "secondary";
|
|
42
|
-
}
|
|
43
|
-
> = {
|
|
44
|
-
success: {
|
|
45
|
-
icon: <CheckCircle className="h-4 w-4" />,
|
|
46
|
-
variant: "success",
|
|
47
|
-
},
|
|
48
|
-
failed: {
|
|
49
|
-
icon: <XCircle className="h-4 w-4" />,
|
|
50
|
-
variant: "destructive",
|
|
51
|
-
},
|
|
52
|
-
retrying: {
|
|
53
|
-
icon: <Clock className="h-4 w-4" />,
|
|
54
|
-
variant: "warning",
|
|
55
|
-
},
|
|
56
|
-
pending: {
|
|
57
|
-
icon: <AlertCircle className="h-4 w-4" />,
|
|
58
|
-
variant: "secondary",
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export const DeliveryLogsPage = () => {
|
|
63
|
-
const integrationClient = usePluginClient(IntegrationApi);
|
|
64
|
-
const toast = useToast();
|
|
65
|
-
const navigate = useNavigate();
|
|
66
|
-
|
|
67
|
-
const [retrying, setRetrying] = useState<string>();
|
|
68
|
-
|
|
69
|
-
// Pagination state
|
|
70
|
-
const pagination = usePagination({ defaultLimit: 20 });
|
|
71
|
-
|
|
72
|
-
// Fetch data with useQuery
|
|
73
|
-
const page = Math.floor(pagination.offset / pagination.limit) + 1;
|
|
74
|
-
const { data, isLoading, refetch } =
|
|
75
|
-
integrationClient.getDeliveryLogs.useQuery({
|
|
76
|
-
page,
|
|
77
|
-
pageSize: pagination.limit,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Sync total from response
|
|
81
|
-
usePaginationSync(pagination, data?.total);
|
|
82
|
-
|
|
83
|
-
const logs = data?.logs ?? [];
|
|
84
|
-
|
|
85
|
-
// Retry mutation
|
|
86
|
-
const retryMutation = integrationClient.retryDelivery.useMutation({
|
|
87
|
-
onSuccess: (result) => {
|
|
88
|
-
if (result.success) {
|
|
89
|
-
toast.success("Delivery re-queued");
|
|
90
|
-
void refetch();
|
|
91
|
-
} else {
|
|
92
|
-
toast.error(result.message ?? "Failed to retry delivery");
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
onError: () => {
|
|
96
|
-
toast.error("Failed to retry delivery");
|
|
97
|
-
},
|
|
98
|
-
onSettled: () => {
|
|
99
|
-
setRetrying(undefined);
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const handleRetry = (logId: string) => {
|
|
104
|
-
setRetrying(logId);
|
|
105
|
-
retryMutation.mutate({ logId });
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<PageLayout
|
|
110
|
-
title="Delivery Logs"
|
|
111
|
-
subtitle="View and manage webhook delivery attempts"
|
|
112
|
-
icon={FileText}
|
|
113
|
-
loading={isLoading}
|
|
114
|
-
actions={
|
|
115
|
-
<BackLink onClick={() => navigate(resolveRoute(integrationRoutes.routes.list))}>
|
|
116
|
-
Back to Subscriptions
|
|
117
|
-
</BackLink>
|
|
118
|
-
}
|
|
119
|
-
>
|
|
120
|
-
<div className="space-y-6">
|
|
121
|
-
<section>
|
|
122
|
-
<SectionHeader
|
|
123
|
-
title="Recent Deliveries"
|
|
124
|
-
description="All webhook delivery attempts across subscriptions"
|
|
125
|
-
icon={<FileText className="h-5 w-5" />}
|
|
126
|
-
/>
|
|
127
|
-
|
|
128
|
-
{logs.length === 0 && !isLoading ? (
|
|
129
|
-
<Card className="p-8">
|
|
130
|
-
<div className="text-center text-muted-foreground">
|
|
131
|
-
No delivery logs found
|
|
132
|
-
</div>
|
|
133
|
-
</Card>
|
|
134
|
-
) : (
|
|
135
|
-
<Card>
|
|
136
|
-
<Table>
|
|
137
|
-
<TableHeader>
|
|
138
|
-
<TableRow>
|
|
139
|
-
<TableHead>Status</TableHead>
|
|
140
|
-
<TableHead>Subscription</TableHead>
|
|
141
|
-
<TableHead>Event</TableHead>
|
|
142
|
-
<TableHead>Attempts</TableHead>
|
|
143
|
-
<TableHead>Created</TableHead>
|
|
144
|
-
<TableHead>Error</TableHead>
|
|
145
|
-
<TableHead></TableHead>
|
|
146
|
-
</TableRow>
|
|
147
|
-
</TableHeader>
|
|
148
|
-
<TableBody>
|
|
149
|
-
{logs.map((log: DeliveryLog) => {
|
|
150
|
-
const config = statusConfig[log.status];
|
|
151
|
-
return (
|
|
152
|
-
<TableRow key={log.id}>
|
|
153
|
-
<TableCell>
|
|
154
|
-
<Badge
|
|
155
|
-
variant={config.variant}
|
|
156
|
-
className="flex items-center gap-1 w-fit"
|
|
157
|
-
>
|
|
158
|
-
{config.icon}
|
|
159
|
-
{log.status}
|
|
160
|
-
</Badge>
|
|
161
|
-
</TableCell>
|
|
162
|
-
<TableCell>
|
|
163
|
-
<div className="font-medium">
|
|
164
|
-
{log.subscriptionName ?? "Unknown"}
|
|
165
|
-
</div>
|
|
166
|
-
</TableCell>
|
|
167
|
-
<TableCell>
|
|
168
|
-
<div className="text-sm font-mono">
|
|
169
|
-
{log.eventType}
|
|
170
|
-
</div>
|
|
171
|
-
</TableCell>
|
|
172
|
-
<TableCell>{log.attempts}</TableCell>
|
|
173
|
-
<TableCell>
|
|
174
|
-
<div className="text-sm text-muted-foreground">
|
|
175
|
-
{new Date(log.createdAt).toLocaleString()}
|
|
176
|
-
</div>
|
|
177
|
-
</TableCell>
|
|
178
|
-
<TableCell>
|
|
179
|
-
{log.errorMessage ? (
|
|
180
|
-
<div
|
|
181
|
-
className="text-sm text-destructive max-w-[200px] truncate"
|
|
182
|
-
title={log.errorMessage}
|
|
183
|
-
>
|
|
184
|
-
{log.errorMessage}
|
|
185
|
-
</div>
|
|
186
|
-
) : undefined}
|
|
187
|
-
</TableCell>
|
|
188
|
-
<TableCell>
|
|
189
|
-
{log.status === "failed" && (
|
|
190
|
-
<Button
|
|
191
|
-
variant="ghost"
|
|
192
|
-
size="sm"
|
|
193
|
-
onClick={() => handleRetry(log.id)}
|
|
194
|
-
disabled={retrying === log.id}
|
|
195
|
-
>
|
|
196
|
-
<RefreshCw
|
|
197
|
-
className={`h-4 w-4 mr-1 ${
|
|
198
|
-
retrying === log.id ? "animate-spin" : ""
|
|
199
|
-
}`}
|
|
200
|
-
/>
|
|
201
|
-
Retry
|
|
202
|
-
</Button>
|
|
203
|
-
)}
|
|
204
|
-
</TableCell>
|
|
205
|
-
</TableRow>
|
|
206
|
-
);
|
|
207
|
-
})}
|
|
208
|
-
</TableBody>
|
|
209
|
-
</Table>
|
|
210
|
-
{pagination.totalPages > 1 && (
|
|
211
|
-
<div className="p-4 border-t flex justify-center gap-2">
|
|
212
|
-
<Button
|
|
213
|
-
variant="outline"
|
|
214
|
-
size="sm"
|
|
215
|
-
disabled={!pagination.hasPrev}
|
|
216
|
-
onClick={pagination.prevPage}
|
|
217
|
-
>
|
|
218
|
-
Previous
|
|
219
|
-
</Button>
|
|
220
|
-
<span className="flex items-center text-sm text-muted-foreground">
|
|
221
|
-
Page {pagination.page} of {pagination.totalPages}
|
|
222
|
-
</span>
|
|
223
|
-
<Button
|
|
224
|
-
variant="outline"
|
|
225
|
-
size="sm"
|
|
226
|
-
disabled={!pagination.hasNext}
|
|
227
|
-
onClick={pagination.nextPage}
|
|
228
|
-
>
|
|
229
|
-
Next
|
|
230
|
-
</Button>
|
|
231
|
-
</div>
|
|
232
|
-
)}
|
|
233
|
-
</Card>
|
|
234
|
-
)}
|
|
235
|
-
</section>
|
|
236
|
-
</div>
|
|
237
|
-
</PageLayout>
|
|
238
|
-
);
|
|
239
|
-
};
|
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import { Link, useSearchParams } from "react-router-dom";
|
|
3
|
-
import {
|
|
4
|
-
Plus,
|
|
5
|
-
Webhook,
|
|
6
|
-
ArrowRight,
|
|
7
|
-
Activity,
|
|
8
|
-
Link as LinkIcon,
|
|
9
|
-
} from "lucide-react";
|
|
10
|
-
import {
|
|
11
|
-
PageLayout,
|
|
12
|
-
Card,
|
|
13
|
-
CardContent,
|
|
14
|
-
Button,
|
|
15
|
-
Badge,
|
|
16
|
-
SectionHeader,
|
|
17
|
-
DynamicIcon,
|
|
18
|
-
EmptyState,
|
|
19
|
-
Table,
|
|
20
|
-
TableBody,
|
|
21
|
-
TableCell,
|
|
22
|
-
TableHead,
|
|
23
|
-
TableHeader,
|
|
24
|
-
TableRow,
|
|
25
|
-
useToast,
|
|
26
|
-
type LucideIconName,
|
|
27
|
-
} from "@checkstack/ui";
|
|
28
|
-
import { usePluginClient } from "@checkstack/frontend-api";
|
|
29
|
-
import { resolveRoute, extractErrorMessage} from "@checkstack/common";
|
|
30
|
-
import {
|
|
31
|
-
IntegrationApi,
|
|
32
|
-
integrationRoutes,
|
|
33
|
-
pluginMetadata as integrationPluginMetadata,
|
|
34
|
-
type WebhookSubscription,
|
|
35
|
-
type IntegrationProviderInfo,
|
|
36
|
-
} from "@checkstack/integration-common";
|
|
37
|
-
import { Tip } from "@checkstack/tips-frontend";
|
|
38
|
-
import { SubscriptionDialog } from "../components/CreateSubscriptionDialog";
|
|
39
|
-
|
|
40
|
-
export const IntegrationsPage = () => {
|
|
41
|
-
const client = usePluginClient(IntegrationApi);
|
|
42
|
-
const toast = useToast();
|
|
43
|
-
const [searchParams, setSearchParams] = useSearchParams();
|
|
44
|
-
|
|
45
|
-
const [dialogOpen, setDialogOpen] = useState(false);
|
|
46
|
-
const [selectedSubscription, setSelectedSubscription] =
|
|
47
|
-
useState<WebhookSubscription>();
|
|
48
|
-
|
|
49
|
-
// Queries using hooks
|
|
50
|
-
const {
|
|
51
|
-
data: subscriptionsData,
|
|
52
|
-
isLoading: subsLoading,
|
|
53
|
-
refetch: refetchSubs,
|
|
54
|
-
} = client.listSubscriptions.useQuery({ page: 1, pageSize: 100 });
|
|
55
|
-
|
|
56
|
-
const { data: providers = [], isLoading: providersLoading } =
|
|
57
|
-
client.listProviders.useQuery({});
|
|
58
|
-
|
|
59
|
-
const { data: stats, isLoading: statsLoading } =
|
|
60
|
-
client.getDeliveryStats.useQuery({ hours: 24 });
|
|
61
|
-
|
|
62
|
-
// Mutation for toggling
|
|
63
|
-
const toggleMutation = client.toggleSubscription.useMutation({
|
|
64
|
-
onSuccess: (_result, variables) => {
|
|
65
|
-
toast.success(
|
|
66
|
-
variables.enabled ? "Subscription enabled" : "Subscription disabled",
|
|
67
|
-
);
|
|
68
|
-
void refetchSubs();
|
|
69
|
-
},
|
|
70
|
-
onError: (error) => {
|
|
71
|
-
toast.error(
|
|
72
|
-
extractErrorMessage(error, "Failed to toggle subscription"),
|
|
73
|
-
);
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const subscriptions = subscriptionsData?.subscriptions ?? [];
|
|
78
|
-
const loading = subsLoading || providersLoading || statsLoading;
|
|
79
|
-
|
|
80
|
-
// Handle ?action=create URL parameter (from command palette)
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
if (searchParams.get("action") === "create") {
|
|
83
|
-
setSelectedSubscription(undefined);
|
|
84
|
-
setDialogOpen(true);
|
|
85
|
-
// Clear the URL param after opening
|
|
86
|
-
searchParams.delete("action");
|
|
87
|
-
setSearchParams(searchParams, { replace: true });
|
|
88
|
-
}
|
|
89
|
-
}, [searchParams, setSearchParams]);
|
|
90
|
-
|
|
91
|
-
const getProviderInfo = (
|
|
92
|
-
providerId: string,
|
|
93
|
-
): IntegrationProviderInfo | undefined => {
|
|
94
|
-
return providers.find((p) => p.qualifiedId === providerId);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const handleToggle = (id: string, enabled: boolean) => {
|
|
98
|
-
toggleMutation.mutate({ id, enabled });
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const handleDialogClose = () => {
|
|
102
|
-
void refetchSubs();
|
|
103
|
-
setDialogOpen(false);
|
|
104
|
-
setSelectedSubscription(undefined);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const openEditDialog = (sub: WebhookSubscription) => {
|
|
108
|
-
setSelectedSubscription(sub);
|
|
109
|
-
setDialogOpen(true);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const openCreateDialog = () => {
|
|
113
|
-
setSelectedSubscription(undefined);
|
|
114
|
-
setDialogOpen(true);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<PageLayout
|
|
119
|
-
title="Integrations"
|
|
120
|
-
subtitle="Configure webhooks to send events to external systems"
|
|
121
|
-
icon={LinkIcon}
|
|
122
|
-
loading={loading}
|
|
123
|
-
actions={
|
|
124
|
-
<Tip
|
|
125
|
-
plugin={integrationPluginMetadata}
|
|
126
|
-
id="subscriptions.create"
|
|
127
|
-
title="Send Checkstack events anywhere"
|
|
128
|
-
description="A subscription forwards events (incidents, status changes, deployments) to an external system. Pick a connected provider — webhook, Jira, Teams, Slack — and choose which event types it cares about. Failed deliveries retry automatically and show up in the delivery logs."
|
|
129
|
-
side="bottom"
|
|
130
|
-
align="end"
|
|
131
|
-
>
|
|
132
|
-
<Button onClick={openCreateDialog}>
|
|
133
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
134
|
-
New Subscription
|
|
135
|
-
</Button>
|
|
136
|
-
</Tip>
|
|
137
|
-
}
|
|
138
|
-
>
|
|
139
|
-
<div className="space-y-8">
|
|
140
|
-
{/* Stats Overview */}
|
|
141
|
-
{stats && (
|
|
142
|
-
<section>
|
|
143
|
-
<SectionHeader
|
|
144
|
-
title="Delivery Activity (24h)"
|
|
145
|
-
icon={<Activity className="h-5 w-5" />}
|
|
146
|
-
/>
|
|
147
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
148
|
-
<Card>
|
|
149
|
-
<CardContent className="p-4">
|
|
150
|
-
<div className="text-2xl font-bold">{stats.total}</div>
|
|
151
|
-
<div className="text-sm text-muted-foreground">
|
|
152
|
-
Total Deliveries
|
|
153
|
-
</div>
|
|
154
|
-
</CardContent>
|
|
155
|
-
</Card>
|
|
156
|
-
<Card>
|
|
157
|
-
<CardContent className="p-4">
|
|
158
|
-
<div className="text-2xl font-bold text-green-600">
|
|
159
|
-
{stats.successful}
|
|
160
|
-
</div>
|
|
161
|
-
<div className="text-sm text-muted-foreground">
|
|
162
|
-
Successful
|
|
163
|
-
</div>
|
|
164
|
-
</CardContent>
|
|
165
|
-
</Card>
|
|
166
|
-
<Card>
|
|
167
|
-
<CardContent className="p-4">
|
|
168
|
-
<div className="text-2xl font-bold text-red-600">
|
|
169
|
-
{stats.failed}
|
|
170
|
-
</div>
|
|
171
|
-
<div className="text-sm text-muted-foreground">Failed</div>
|
|
172
|
-
</CardContent>
|
|
173
|
-
</Card>
|
|
174
|
-
<Card>
|
|
175
|
-
<CardContent className="p-4">
|
|
176
|
-
<div className="text-2xl font-bold text-yellow-600">
|
|
177
|
-
{stats.retrying + stats.pending}
|
|
178
|
-
</div>
|
|
179
|
-
<div className="text-sm text-muted-foreground">
|
|
180
|
-
In Progress
|
|
181
|
-
</div>
|
|
182
|
-
</CardContent>
|
|
183
|
-
</Card>
|
|
184
|
-
</div>
|
|
185
|
-
</section>
|
|
186
|
-
)}
|
|
187
|
-
|
|
188
|
-
{/* Subscriptions List */}
|
|
189
|
-
<section>
|
|
190
|
-
<div className="flex items-center justify-between mb-4">
|
|
191
|
-
<SectionHeader
|
|
192
|
-
title="Webhook Subscriptions"
|
|
193
|
-
description="Subscriptions route events to external systems via providers"
|
|
194
|
-
icon={<Webhook className="h-5 w-5" />}
|
|
195
|
-
/>
|
|
196
|
-
<Link
|
|
197
|
-
to={resolveRoute(integrationRoutes.routes.logs)}
|
|
198
|
-
className="text-sm text-primary hover:underline flex items-center gap-1"
|
|
199
|
-
>
|
|
200
|
-
View Delivery Logs
|
|
201
|
-
<ArrowRight className="h-4 w-4" />
|
|
202
|
-
</Link>
|
|
203
|
-
</div>
|
|
204
|
-
|
|
205
|
-
{subscriptions.length === 0 ? (
|
|
206
|
-
<EmptyState
|
|
207
|
-
icon={<Webhook className="h-12 w-12" />}
|
|
208
|
-
title="No webhook subscriptions yet"
|
|
209
|
-
description="A subscription forwards events from Checkstack (incidents, status changes, deployments) to external systems — Jira, ServiceNow, your own webhook receiver, anything that speaks HTTP."
|
|
210
|
-
steps={[
|
|
211
|
-
"Connect at least one provider on the Provider Connections page (Jira, Webhook, Teams, …).",
|
|
212
|
-
"Create a subscription that picks one or more event types to forward.",
|
|
213
|
-
"Watch the Delivery Logs to confirm payloads are reaching their destination.",
|
|
214
|
-
]}
|
|
215
|
-
actions={
|
|
216
|
-
<Button onClick={openCreateDialog}>
|
|
217
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
218
|
-
New subscription
|
|
219
|
-
</Button>
|
|
220
|
-
}
|
|
221
|
-
/>
|
|
222
|
-
) : (
|
|
223
|
-
<Card>
|
|
224
|
-
<Table>
|
|
225
|
-
<TableHeader>
|
|
226
|
-
<TableRow>
|
|
227
|
-
<TableHead>Subscription</TableHead>
|
|
228
|
-
<TableHead>Provider</TableHead>
|
|
229
|
-
<TableHead>Events</TableHead>
|
|
230
|
-
<TableHead>Status</TableHead>
|
|
231
|
-
<TableHead></TableHead>
|
|
232
|
-
</TableRow>
|
|
233
|
-
</TableHeader>
|
|
234
|
-
<TableBody>
|
|
235
|
-
{subscriptions.map((sub) => {
|
|
236
|
-
const provider = getProviderInfo(sub.providerId);
|
|
237
|
-
return (
|
|
238
|
-
<TableRow
|
|
239
|
-
key={sub.id}
|
|
240
|
-
className="cursor-pointer"
|
|
241
|
-
onClick={() => openEditDialog(sub)}
|
|
242
|
-
>
|
|
243
|
-
<TableCell>
|
|
244
|
-
<div className="flex items-center gap-3">
|
|
245
|
-
<div className="p-2 rounded-lg bg-muted">
|
|
246
|
-
<DynamicIcon
|
|
247
|
-
name={
|
|
248
|
-
(provider?.icon as LucideIconName | undefined) ??
|
|
249
|
-
"Webhook"
|
|
250
|
-
}
|
|
251
|
-
className="h-5 w-5 text-muted-foreground"
|
|
252
|
-
/>
|
|
253
|
-
</div>
|
|
254
|
-
<div>
|
|
255
|
-
<div className="font-medium">{sub.name}</div>
|
|
256
|
-
{sub.description && (
|
|
257
|
-
<div className="text-sm text-muted-foreground">
|
|
258
|
-
{sub.description}
|
|
259
|
-
</div>
|
|
260
|
-
)}
|
|
261
|
-
</div>
|
|
262
|
-
</div>
|
|
263
|
-
</TableCell>
|
|
264
|
-
<TableCell>
|
|
265
|
-
{provider?.displayName ?? sub.providerId}
|
|
266
|
-
</TableCell>
|
|
267
|
-
<TableCell>
|
|
268
|
-
<Badge variant="outline">{sub.eventId}</Badge>
|
|
269
|
-
</TableCell>
|
|
270
|
-
<TableCell>
|
|
271
|
-
<Badge
|
|
272
|
-
variant={sub.enabled ? "success" : "secondary"}
|
|
273
|
-
>
|
|
274
|
-
{sub.enabled ? "Active" : "Disabled"}
|
|
275
|
-
</Badge>
|
|
276
|
-
</TableCell>
|
|
277
|
-
<TableCell className="text-right">
|
|
278
|
-
<Button
|
|
279
|
-
variant="ghost"
|
|
280
|
-
size="sm"
|
|
281
|
-
onClick={(e) => {
|
|
282
|
-
e.stopPropagation();
|
|
283
|
-
handleToggle(sub.id, !sub.enabled);
|
|
284
|
-
}}
|
|
285
|
-
>
|
|
286
|
-
{sub.enabled ? "Disable" : "Enable"}
|
|
287
|
-
</Button>
|
|
288
|
-
</TableCell>
|
|
289
|
-
</TableRow>
|
|
290
|
-
);
|
|
291
|
-
})}
|
|
292
|
-
</TableBody>
|
|
293
|
-
</Table>
|
|
294
|
-
</Card>
|
|
295
|
-
)}
|
|
296
|
-
|
|
297
|
-
{subscriptions.length === 0 && (
|
|
298
|
-
<div className="mt-4 flex justify-center">
|
|
299
|
-
<Button onClick={openCreateDialog}>
|
|
300
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
301
|
-
Create Subscription
|
|
302
|
-
</Button>
|
|
303
|
-
</div>
|
|
304
|
-
)}
|
|
305
|
-
</section>
|
|
306
|
-
|
|
307
|
-
{/* Providers Overview */}
|
|
308
|
-
<section>
|
|
309
|
-
<SectionHeader
|
|
310
|
-
title="Available Providers"
|
|
311
|
-
description="Providers handle the delivery of events to external systems"
|
|
312
|
-
/>
|
|
313
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
314
|
-
{providers.map((provider) => (
|
|
315
|
-
<Card key={provider.qualifiedId}>
|
|
316
|
-
<CardContent className="p-4">
|
|
317
|
-
<div className="flex items-center justify-between">
|
|
318
|
-
<div className="flex items-center gap-3">
|
|
319
|
-
<div className="p-2 rounded-lg bg-muted">
|
|
320
|
-
<DynamicIcon
|
|
321
|
-
name={(provider.icon as LucideIconName | undefined) ?? "Webhook"}
|
|
322
|
-
className="h-6 w-6"
|
|
323
|
-
/>
|
|
324
|
-
</div>
|
|
325
|
-
<div>
|
|
326
|
-
<div className="font-medium">
|
|
327
|
-
{provider.displayName}
|
|
328
|
-
</div>
|
|
329
|
-
{provider.description && (
|
|
330
|
-
<div className="text-sm text-muted-foreground">
|
|
331
|
-
{provider.description}
|
|
332
|
-
</div>
|
|
333
|
-
)}
|
|
334
|
-
</div>
|
|
335
|
-
</div>
|
|
336
|
-
{provider.hasConnectionSchema && (
|
|
337
|
-
<Link
|
|
338
|
-
to={resolveRoute(integrationRoutes.routes.connections, {
|
|
339
|
-
providerId: provider.qualifiedId,
|
|
340
|
-
})}
|
|
341
|
-
className="text-sm text-primary hover:underline"
|
|
342
|
-
>
|
|
343
|
-
Connections
|
|
344
|
-
</Link>
|
|
345
|
-
)}
|
|
346
|
-
</div>
|
|
347
|
-
</CardContent>
|
|
348
|
-
</Card>
|
|
349
|
-
))}
|
|
350
|
-
{providers.length === 0 && (
|
|
351
|
-
<Card className="col-span-full">
|
|
352
|
-
<CardContent className="p-4">
|
|
353
|
-
<div className="text-center text-muted-foreground py-4">
|
|
354
|
-
No providers registered. Install provider plugins to enable
|
|
355
|
-
webhook delivery.
|
|
356
|
-
</div>
|
|
357
|
-
</CardContent>
|
|
358
|
-
</Card>
|
|
359
|
-
)}
|
|
360
|
-
</div>
|
|
361
|
-
</section>
|
|
362
|
-
</div>
|
|
363
|
-
|
|
364
|
-
<SubscriptionDialog
|
|
365
|
-
open={dialogOpen}
|
|
366
|
-
onOpenChange={(open) => {
|
|
367
|
-
setDialogOpen(open);
|
|
368
|
-
if (!open) setSelectedSubscription(undefined);
|
|
369
|
-
}}
|
|
370
|
-
providers={providers}
|
|
371
|
-
subscription={selectedSubscription}
|
|
372
|
-
onCreated={handleDialogClose}
|
|
373
|
-
onUpdated={handleDialogClose}
|
|
374
|
-
onDeleted={handleDialogClose}
|
|
375
|
-
/>
|
|
376
|
-
</PageLayout>
|
|
377
|
-
);
|
|
378
|
-
};
|