@orthacms/webhooks-admin 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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/lib/application/useContentTypeOptions/index.d.ts +18 -0
- package/dist/lib/application/useContentTypeOptions/index.d.ts.map +1 -0
- package/dist/lib/application/useContentTypeOptions/index.js +40 -0
- package/dist/lib/application/useWebhookDeliveries/index.d.ts +19 -0
- package/dist/lib/application/useWebhookDeliveries/index.d.ts.map +1 -0
- package/dist/lib/application/useWebhookDeliveries/index.js +48 -0
- package/dist/lib/application/useWebhookEndpoints/index.d.ts +12 -0
- package/dist/lib/application/useWebhookEndpoints/index.d.ts.map +1 -0
- package/dist/lib/application/useWebhookEndpoints/index.js +26 -0
- package/dist/lib/application/useWebhookEvents/index.d.ts +9 -0
- package/dist/lib/application/useWebhookEvents/index.d.ts.map +1 -0
- package/dist/lib/application/useWebhookEvents/index.js +20 -0
- package/dist/lib/application/useWebhookMutations/index.d.ts +25 -0
- package/dist/lib/application/useWebhookMutations/index.d.ts.map +1 -0
- package/dist/lib/application/useWebhookMutations/index.js +79 -0
- package/dist/lib/application/useWorkspaceOptions/index.d.ts +9 -0
- package/dist/lib/application/useWorkspaceOptions/index.d.ts.map +1 -0
- package/dist/lib/application/useWorkspaceOptions/index.js +19 -0
- package/dist/lib/domain/contentTypeChoices/index.d.ts +65 -0
- package/dist/lib/domain/contentTypeChoices/index.d.ts.map +1 -0
- package/dist/lib/domain/contentTypeChoices/index.js +70 -0
- package/dist/lib/domain/headerRules/index.d.ts +37 -0
- package/dist/lib/domain/headerRules/index.d.ts.map +1 -0
- package/dist/lib/domain/headerRules/index.js +66 -0
- package/dist/lib/domain/types/contentTypeOption/index.d.ts +14 -0
- package/dist/lib/domain/types/contentTypeOption/index.d.ts.map +1 -0
- package/dist/lib/domain/types/contentTypeOption/index.js +1 -0
- package/dist/lib/domain/types/webhook/index.d.ts +102 -0
- package/dist/lib/domain/types/webhook/index.d.ts.map +1 -0
- package/dist/lib/domain/types/webhook/index.js +18 -0
- package/dist/lib/domain/types/workspaceOption/index.d.ts +16 -0
- package/dist/lib/domain/types/workspaceOption/index.d.ts.map +1 -0
- package/dist/lib/domain/types/workspaceOption/index.js +1 -0
- package/dist/lib/infrastructure/httpWebhookGateway/index.d.ts +11 -0
- package/dist/lib/infrastructure/httpWebhookGateway/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/httpWebhookGateway/index.js +138 -0
- package/dist/lib/infrastructure/serverMessageOf/index.d.ts +16 -0
- package/dist/lib/infrastructure/serverMessageOf/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/serverMessageOf/index.js +24 -0
- package/dist/lib/infrastructure/webhookGateway/index.d.ts +62 -0
- package/dist/lib/infrastructure/webhookGateway/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhookGateway/index.js +1 -0
- package/dist/lib/infrastructure/webhookMapper/index.d.ts +104 -0
- package/dist/lib/infrastructure/webhookMapper/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhookMapper/index.js +49 -0
- package/dist/lib/infrastructure/webhooksKeys/index.d.ts +35 -0
- package/dist/lib/infrastructure/webhooksKeys/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhooksKeys/index.js +23 -0
- package/dist/lib/presentation/components/DeliveryStatusBadge/index.d.ts +6 -0
- package/dist/lib/presentation/components/DeliveryStatusBadge/index.d.ts.map +1 -0
- package/dist/lib/presentation/components/DeliveryStatusBadge/index.js +30 -0
- package/dist/lib/presentation/components/RevealWebhookSecretDialog/index.d.ts +25 -0
- package/dist/lib/presentation/components/RevealWebhookSecretDialog/index.d.ts.map +1 -0
- package/dist/lib/presentation/components/RevealWebhookSecretDialog/index.js +107 -0
- package/dist/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.d.ts +19 -0
- package/dist/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.d.ts.map +1 -0
- package/dist/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.js +72 -0
- package/dist/lib/presentation/components/WebhookDeliveriesPanel/index.d.ts +12 -0
- package/dist/lib/presentation/components/WebhookDeliveriesPanel/index.d.ts.map +1 -0
- package/dist/lib/presentation/components/WebhookDeliveriesPanel/index.js +132 -0
- package/dist/lib/presentation/components/WebhookForm/index.d.ts +46 -0
- package/dist/lib/presentation/components/WebhookForm/index.d.ts.map +1 -0
- package/dist/lib/presentation/components/WebhookForm/index.js +412 -0
- package/dist/lib/presentation/components/WebhooksEmpty/index.d.ts +5 -0
- package/dist/lib/presentation/components/WebhooksEmpty/index.d.ts.map +1 -0
- package/dist/lib/presentation/components/WebhooksEmpty/index.js +23 -0
- package/dist/lib/presentation/components/WebhooksNoAccess/index.d.ts +3 -0
- package/dist/lib/presentation/components/WebhooksNoAccess/index.d.ts.map +1 -0
- package/dist/lib/presentation/components/WebhooksNoAccess/index.js +18 -0
- package/dist/lib/presentation/components/WebhooksSkeleton/index.d.ts +20 -0
- package/dist/lib/presentation/components/WebhooksSkeleton/index.d.ts.map +1 -0
- package/dist/lib/presentation/components/WebhooksSkeleton/index.js +45 -0
- package/dist/lib/presentation/components/WebhooksTable/index.d.ts +12 -0
- package/dist/lib/presentation/components/WebhooksTable/index.d.ts.map +1 -0
- package/dist/lib/presentation/components/WebhooksTable/index.js +79 -0
- package/dist/lib/presentation/pages/WebhookDetailPage/index.d.ts +10 -0
- package/dist/lib/presentation/pages/WebhookDetailPage/index.d.ts.map +1 -0
- package/dist/lib/presentation/pages/WebhookDetailPage/index.js +191 -0
- package/dist/lib/presentation/pages/WebhookEditorPage/index.d.ts +28 -0
- package/dist/lib/presentation/pages/WebhookEditorPage/index.d.ts.map +1 -0
- package/dist/lib/presentation/pages/WebhookEditorPage/index.js +149 -0
- package/dist/lib/presentation/pages/WebhooksPage/index.d.ts +11 -0
- package/dist/lib/presentation/pages/WebhooksPage/index.d.ts.map +1 -0
- package/dist/lib/presentation/pages/WebhooksPage/index.js +65 -0
- package/dist/lib/utils/webhooksPlugin/index.d.ts +35 -0
- package/dist/lib/utils/webhooksPlugin/index.d.ts.map +1 -0
- package/dist/lib/utils/webhooksPlugin/index.js +88 -0
- package/package.json +46 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
4
|
+
import { Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@orthacms/design-system';
|
|
5
|
+
import { DeliveryStatusBadge } from '../DeliveryStatusBadge';
|
|
6
|
+
const messages = defineMessages({
|
|
7
|
+
caption: {
|
|
8
|
+
id: 'webhooks.table.caption',
|
|
9
|
+
defaultMessage: 'Configured webhook endpoints'
|
|
10
|
+
},
|
|
11
|
+
name: { id: 'webhooks.table.name', defaultMessage: 'Name' },
|
|
12
|
+
url: { id: 'webhooks.table.url', defaultMessage: 'URL' },
|
|
13
|
+
events: { id: 'webhooks.table.events', defaultMessage: 'Sends' },
|
|
14
|
+
state: { id: 'webhooks.table.state', defaultMessage: 'State' },
|
|
15
|
+
lastDelivery: {
|
|
16
|
+
id: 'webhooks.table.lastDelivery',
|
|
17
|
+
defaultMessage: 'Last delivery'
|
|
18
|
+
},
|
|
19
|
+
active: { id: 'webhooks.table.active', defaultMessage: 'Active' },
|
|
20
|
+
paused: { id: 'webhooks.table.paused', defaultMessage: 'Paused' },
|
|
21
|
+
autoDisabled: {
|
|
22
|
+
id: 'webhooks.table.autoDisabled',
|
|
23
|
+
defaultMessage: 'Stopped after failures'
|
|
24
|
+
},
|
|
25
|
+
allEvents: { id: 'webhooks.table.allEvents', defaultMessage: 'All events' },
|
|
26
|
+
someEvents: {
|
|
27
|
+
id: 'webhooks.table.someEvents',
|
|
28
|
+
defaultMessage: '{count, plural, one {# event} other {# events}}'
|
|
29
|
+
},
|
|
30
|
+
allWorkspaces: {
|
|
31
|
+
id: 'webhooks.table.allWorkspaces',
|
|
32
|
+
defaultMessage: 'All workspaces'
|
|
33
|
+
},
|
|
34
|
+
someWorkspaces: {
|
|
35
|
+
id: 'webhooks.table.someWorkspaces',
|
|
36
|
+
defaultMessage: '{count, plural, one {# workspace} other {# workspaces}}'
|
|
37
|
+
},
|
|
38
|
+
someTypes: {
|
|
39
|
+
id: 'webhooks.table.someTypes',
|
|
40
|
+
defaultMessage: '{count, plural, one {# type} other {# types}}'
|
|
41
|
+
},
|
|
42
|
+
never: { id: 'webhooks.table.never', defaultMessage: 'Nothing sent yet' }
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* The endpoint list.
|
|
46
|
+
*
|
|
47
|
+
* The filter columns read as chips rather than as lists, because the question
|
|
48
|
+
* from the list is "roughly what does this one take?" — the exact set belongs on
|
|
49
|
+
* the detail page, where it can be edited.
|
|
50
|
+
*/
|
|
51
|
+
export function WebhooksTable({ endpoints }) {
|
|
52
|
+
const intl = useIntl();
|
|
53
|
+
return (_jsx("div", { className: "mt-4 overflow-hidden rounded-xl border bg-card shadow-xs", children: _jsxs(Table, { children: [_jsx("caption", { className: "sr-only", children: intl.formatMessage(messages.caption) }), _jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: intl.formatMessage(messages.name) }), _jsx(TableHead, { children: intl.formatMessage(messages.url) }), _jsx(TableHead, { children: intl.formatMessage(messages.events) }), _jsx(TableHead, { children: intl.formatMessage(messages.state) }), _jsx(TableHead, { children: intl.formatMessage(messages.lastDelivery) })] }) }), _jsx(TableBody, { children: endpoints.map((endpoint) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: _jsx(Link, { to: `/webhooks/${endpoint.id}`, className: "underline-offset-4 hover:underline focus-visible:underline", children: endpoint.name }) }), _jsx(TableCell, { className: "max-w-[24rem] truncate font-mono text-xs text-muted-foreground", children: endpoint.url }), _jsx(TableCell, { children: _jsxs("div", { className: "flex flex-wrap gap-1", children: [_jsx(Badge, { variant: "outline", children: endpoint.eventKinds.length === 0
|
|
54
|
+
? intl.formatMessage(messages.allEvents)
|
|
55
|
+
: intl.formatMessage(messages.someEvents, {
|
|
56
|
+
count: endpoint.eventKinds
|
|
57
|
+
.length
|
|
58
|
+
}) }), _jsx(Badge, { variant: "outline", children: endpoint.allWorkspaces
|
|
59
|
+
? intl.formatMessage(messages.allWorkspaces)
|
|
60
|
+
: intl.formatMessage(messages.someWorkspaces, {
|
|
61
|
+
count: endpoint
|
|
62
|
+
.workspaceIds.length
|
|
63
|
+
}) }), endpoint.contentTypes.length > 0 ? (_jsx(Badge, { variant: "outline", children: intl.formatMessage(messages.someTypes, {
|
|
64
|
+
count: endpoint.contentTypes
|
|
65
|
+
.length
|
|
66
|
+
}) })) : null] }) }), _jsx(TableCell, { children: endpoint.enabled ? (_jsx(Badge, { variant: "secondary", children: intl.formatMessage(messages.active) })) : (
|
|
67
|
+
// An endpoint the server switched off is a
|
|
68
|
+
// different situation from one someone
|
|
69
|
+
// paused, and the operator has to be able
|
|
70
|
+
// to tell them apart at a glance.
|
|
71
|
+
_jsx(Badge, { variant: endpoint.disabledReason
|
|
72
|
+
? 'destructive'
|
|
73
|
+
: 'outline', children: intl.formatMessage(endpoint.disabledReason
|
|
74
|
+
? messages.autoDisabled
|
|
75
|
+
: messages.paused) })) }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: endpoint.lastDelivery ? (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(DeliveryStatusBadge, { status: endpoint.lastDelivery.status }), _jsx("span", { children: intl.formatDate(endpoint.lastDelivery.createdAt, {
|
|
76
|
+
dateStyle: 'medium',
|
|
77
|
+
timeStyle: 'short'
|
|
78
|
+
}) })] })) : (intl.formatMessage(messages.never)) })] }, endpoint.id))) })] }) }));
|
|
79
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One endpoint: its configuration, and its delivery log.
|
|
3
|
+
*
|
|
4
|
+
* The two tabs answer the two questions someone comes here with — "what does
|
|
5
|
+
* this send?" and "did it arrive?" — and the second is where almost all the
|
|
6
|
+
* time is spent, which is why the log polls itself rather than waiting to be
|
|
7
|
+
* refreshed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function WebhookDetailPage(): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/presentation/pages/WebhookDetailPage/index.tsx"],"names":[],"mappings":"AAgJA;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,gCAsRhC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useNavigate, useParams } from 'react-router-dom';
|
|
4
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
5
|
+
import { Pencil, Send, Trash2, TriangleAlert, Webhook } from 'lucide-react';
|
|
6
|
+
import { PageTopBar } from '@orthacms/shell-admin';
|
|
7
|
+
import { useHasPermission } from '@orthacms/identity-admin';
|
|
8
|
+
import { useDocumentTitle } from '@orthacms/utils-admin';
|
|
9
|
+
import { Alert, AlertDescription, Button, ConfirmDialog, Container, ContainerHeader, Spinner, Tabs, TabsContent, TabsList, TabsTrigger, toast } from '@orthacms/design-system';
|
|
10
|
+
import { useWebhookEndpoint } from '../../../application/useWebhookEndpoints';
|
|
11
|
+
import { useDeleteWebhook, useRotateWebhookSecret, useTestWebhook } from '../../../application/useWebhookMutations';
|
|
12
|
+
import { RevealWebhookSecretDialog } from '../../components/RevealWebhookSecretDialog';
|
|
13
|
+
import { WebhookDeliveriesPanel } from '../../components/WebhookDeliveriesPanel';
|
|
14
|
+
import { WebhooksNoAccess } from '../../components/WebhooksNoAccess';
|
|
15
|
+
const messages = defineMessages({
|
|
16
|
+
listTitle: { id: 'webhooks.page.title', defaultMessage: 'Webhooks' },
|
|
17
|
+
settings: {
|
|
18
|
+
id: 'webhooks.detail.settings',
|
|
19
|
+
defaultMessage: 'Settings'
|
|
20
|
+
},
|
|
21
|
+
deliveries: {
|
|
22
|
+
id: 'webhooks.detail.deliveries',
|
|
23
|
+
defaultMessage: 'Deliveries'
|
|
24
|
+
},
|
|
25
|
+
edit: { id: 'webhooks.detail.edit', defaultMessage: 'Edit' },
|
|
26
|
+
test: { id: 'webhooks.detail.test', defaultMessage: 'Send test' },
|
|
27
|
+
rotate: {
|
|
28
|
+
id: 'webhooks.detail.rotate',
|
|
29
|
+
defaultMessage: 'Rotate secret'
|
|
30
|
+
},
|
|
31
|
+
remove: { id: 'webhooks.detail.remove', defaultMessage: 'Delete' },
|
|
32
|
+
url: { id: 'webhooks.detail.url', defaultMessage: 'URL' },
|
|
33
|
+
secret: { id: 'webhooks.detail.secret', defaultMessage: 'Signing secret' },
|
|
34
|
+
headers: {
|
|
35
|
+
id: 'webhooks.detail.headers',
|
|
36
|
+
defaultMessage: 'Custom headers'
|
|
37
|
+
},
|
|
38
|
+
noHeaders: {
|
|
39
|
+
id: 'webhooks.detail.noHeaders',
|
|
40
|
+
defaultMessage: 'None'
|
|
41
|
+
},
|
|
42
|
+
secretValue: {
|
|
43
|
+
id: 'webhooks.detail.secretValue',
|
|
44
|
+
defaultMessage: 'Ends in {hint}'
|
|
45
|
+
},
|
|
46
|
+
events: { id: 'webhooks.detail.events', defaultMessage: 'Events' },
|
|
47
|
+
allEvents: {
|
|
48
|
+
id: 'webhooks.detail.allEvents',
|
|
49
|
+
defaultMessage: 'All events'
|
|
50
|
+
},
|
|
51
|
+
workspaces: {
|
|
52
|
+
id: 'webhooks.detail.workspaces',
|
|
53
|
+
defaultMessage: 'Workspaces'
|
|
54
|
+
},
|
|
55
|
+
allWorkspaces: {
|
|
56
|
+
id: 'webhooks.detail.allWorkspaces',
|
|
57
|
+
defaultMessage: 'All workspaces'
|
|
58
|
+
},
|
|
59
|
+
contentTypes: {
|
|
60
|
+
id: 'webhooks.detail.contentTypes',
|
|
61
|
+
defaultMessage: 'Content types'
|
|
62
|
+
},
|
|
63
|
+
allContentTypes: {
|
|
64
|
+
id: 'webhooks.detail.allContentTypes',
|
|
65
|
+
defaultMessage: 'All content types'
|
|
66
|
+
},
|
|
67
|
+
state: { id: 'webhooks.detail.state', defaultMessage: 'State' },
|
|
68
|
+
active: { id: 'webhooks.detail.active', defaultMessage: 'Active' },
|
|
69
|
+
paused: { id: 'webhooks.detail.paused', defaultMessage: 'Paused' },
|
|
70
|
+
notFound: {
|
|
71
|
+
id: 'webhooks.detail.notFound',
|
|
72
|
+
defaultMessage: 'That webhook no longer exists.'
|
|
73
|
+
},
|
|
74
|
+
back: { id: 'webhooks.detail.back', defaultMessage: 'Back to webhooks' },
|
|
75
|
+
deleted: {
|
|
76
|
+
id: 'webhooks.detail.deleted',
|
|
77
|
+
defaultMessage: 'Webhook deleted'
|
|
78
|
+
},
|
|
79
|
+
deleteFailed: {
|
|
80
|
+
id: 'webhooks.detail.deleteFailed',
|
|
81
|
+
defaultMessage: 'Couldn’t delete the webhook. Please try again.'
|
|
82
|
+
},
|
|
83
|
+
confirmDeleteTitle: {
|
|
84
|
+
id: 'webhooks.detail.confirmDeleteTitle',
|
|
85
|
+
defaultMessage: 'Delete this webhook?'
|
|
86
|
+
},
|
|
87
|
+
confirmDeleteBody: {
|
|
88
|
+
id: 'webhooks.detail.confirmDeleteBody',
|
|
89
|
+
defaultMessage: 'Deliveries stop immediately and the delivery log goes with it. This cannot be undone.'
|
|
90
|
+
},
|
|
91
|
+
confirmDelete: {
|
|
92
|
+
id: 'webhooks.detail.confirmDelete',
|
|
93
|
+
defaultMessage: 'Delete webhook'
|
|
94
|
+
},
|
|
95
|
+
cancel: { id: 'webhooks.detail.cancel', defaultMessage: 'Cancel' },
|
|
96
|
+
testOk: {
|
|
97
|
+
id: 'webhooks.detail.testOk',
|
|
98
|
+
defaultMessage: 'Receiver answered {status} in {ms} ms'
|
|
99
|
+
},
|
|
100
|
+
testFailed: {
|
|
101
|
+
id: 'webhooks.detail.testFailed',
|
|
102
|
+
defaultMessage: 'Test delivery failed: {reason}'
|
|
103
|
+
},
|
|
104
|
+
autoDisabled: {
|
|
105
|
+
id: 'webhooks.detail.autoDisabled',
|
|
106
|
+
defaultMessage: '{reason} Fix the receiver, then switch the webhook back on from Edit.'
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
/** One labelled fact in the settings summary. */
|
|
110
|
+
function Fact({ label, children }) {
|
|
111
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: label }), _jsx("dd", { className: "text-sm", children: children })] }));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* One endpoint: its configuration, and its delivery log.
|
|
115
|
+
*
|
|
116
|
+
* The two tabs answer the two questions someone comes here with — "what does
|
|
117
|
+
* this send?" and "did it arrive?" — and the second is where almost all the
|
|
118
|
+
* time is spent, which is why the log polls itself rather than waiting to be
|
|
119
|
+
* refreshed.
|
|
120
|
+
*/
|
|
121
|
+
export function WebhookDetailPage() {
|
|
122
|
+
const intl = useIntl();
|
|
123
|
+
const navigate = useNavigate();
|
|
124
|
+
const { id = '' } = useParams();
|
|
125
|
+
const canRead = useHasPermission('webhooks:read');
|
|
126
|
+
const canManage = useHasPermission('webhooks:manage');
|
|
127
|
+
const [secret, setSecret] = useState(null);
|
|
128
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
129
|
+
const { data: endpoint, isPending, isError } = useWebhookEndpoint(id, canRead && id.length > 0);
|
|
130
|
+
const remove = useDeleteWebhook();
|
|
131
|
+
const rotate = useRotateWebhookSecret();
|
|
132
|
+
const test = useTestWebhook();
|
|
133
|
+
useDocumentTitle(endpoint?.name ?? intl.formatMessage(messages.listTitle));
|
|
134
|
+
if (!canRead) {
|
|
135
|
+
return (_jsx(Container, { children: _jsx(WebhooksNoAccess, {}) }));
|
|
136
|
+
}
|
|
137
|
+
if (isPending) {
|
|
138
|
+
return (_jsx(Container, { children: _jsx("div", { className: "flex justify-center py-16", children: _jsx(Spinner, {}) }) }));
|
|
139
|
+
}
|
|
140
|
+
if (isError || !endpoint) {
|
|
141
|
+
return (_jsx(Container, { children: _jsx(Alert, { variant: "destructive", role: "alert", className: "mt-8", children: _jsxs(AlertDescription, { className: "flex items-center justify-between gap-4", children: [intl.formatMessage(messages.notFound), _jsx(Button, { size: "sm", variant: "outline", onClick: () => navigate('/webhooks'), children: intl.formatMessage(messages.back) })] }) }) }));
|
|
142
|
+
}
|
|
143
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageTopBar, { icon: Webhook, crumbs: [
|
|
144
|
+
{
|
|
145
|
+
key: 'webhooks',
|
|
146
|
+
label: intl.formatMessage(messages.listTitle),
|
|
147
|
+
to: '/webhooks'
|
|
148
|
+
},
|
|
149
|
+
{ key: endpoint.id, label: endpoint.name }
|
|
150
|
+
] }), _jsxs(Container, { children: [_jsx(ContainerHeader, { title: endpoint.name, subtitle: endpoint.url, actions: canManage ? (_jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", disabled: test.isPending, onClick: () => test.mutate(endpoint.id, {
|
|
151
|
+
onSuccess: (result) => {
|
|
152
|
+
if (result.ok) {
|
|
153
|
+
toast.success(intl.formatMessage(messages.testOk, {
|
|
154
|
+
status: result.statusCode,
|
|
155
|
+
ms: result.durationMs
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
toast.error(intl.formatMessage(messages.testFailed, {
|
|
160
|
+
reason: result.error ??
|
|
161
|
+
`HTTP ${result.statusCode}`
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}), children: [_jsx(Send, { "aria-hidden": true }), intl.formatMessage(messages.test)] }), _jsxs(Button, { variant: "outline", onClick: () => navigate(`/webhooks/${endpoint.id}/edit`), children: [_jsx(Pencil, { "aria-hidden": true }), intl.formatMessage(messages.edit)] })] })) : undefined }), endpoint.disabledReason ? (_jsxs(Alert, { variant: "destructive", role: "alert", className: "mt-4", children: [_jsx(TriangleAlert, { "aria-hidden": true }), _jsx(AlertDescription, { children: intl.formatMessage(messages.autoDisabled, {
|
|
166
|
+
reason: endpoint.disabledReason
|
|
167
|
+
}) })] })) : null, _jsxs(Tabs, { defaultValue: "deliveries", className: "mt-6", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "deliveries", children: intl.formatMessage(messages.deliveries) }), _jsx(TabsTrigger, { value: "settings", children: intl.formatMessage(messages.settings) })] }), _jsx(TabsContent, { value: "deliveries", className: "mt-4", children: _jsx(WebhookDeliveriesPanel, { endpointId: endpoint.id, canRedeliver: canManage }) }), _jsxs(TabsContent, { value: "settings", className: "mt-4", children: [_jsxs("dl", { className: "grid gap-4 sm:grid-cols-2", children: [_jsx(Fact, { label: intl.formatMessage(messages.url), children: _jsx("span", { className: "font-mono text-xs break-all", children: endpoint.url }) }), _jsx(Fact, { label: intl.formatMessage(messages.state), children: intl.formatMessage(endpoint.enabled
|
|
168
|
+
? messages.active
|
|
169
|
+
: messages.paused) }), _jsx(Fact, { label: intl.formatMessage(messages.events), children: endpoint.eventKinds.length === 0
|
|
170
|
+
? intl.formatMessage(messages.allEvents)
|
|
171
|
+
: endpoint.eventKinds.join(', ') }), _jsx(Fact, { label: intl.formatMessage(messages.contentTypes), children: endpoint.contentTypes.length === 0
|
|
172
|
+
? intl.formatMessage(messages.allContentTypes)
|
|
173
|
+
: endpoint.contentTypes.join(', ') }), _jsx(Fact, { label: intl.formatMessage(messages.workspaces), children: endpoint.allWorkspaces
|
|
174
|
+
? intl.formatMessage(messages.allWorkspaces)
|
|
175
|
+
: endpoint.workspaceIds.length }), _jsx(Fact, { label: intl.formatMessage(messages.headers), children: _jsx("span", { className: "font-mono text-xs break-all", children: Object.keys(endpoint.headers).length === 0
|
|
176
|
+
? intl.formatMessage(messages.noHeaders)
|
|
177
|
+
: Object.keys(endpoint.headers).join(', ') }) }), _jsx(Fact, { label: intl.formatMessage(messages.secret), children: _jsx("span", { className: "font-mono text-xs", children: intl.formatMessage(messages.secretValue, {
|
|
178
|
+
hint: endpoint.secretHint
|
|
179
|
+
}) }) })] }), canManage ? (_jsxs("div", { className: "mt-8 flex flex-wrap gap-2 border-t pt-4", children: [_jsx(Button, { variant: "outline", disabled: rotate.isPending, onClick: () => rotate.mutate(endpoint.id, {
|
|
180
|
+
onSuccess: ({ secret: minted }) => setSecret(minted)
|
|
181
|
+
}), children: intl.formatMessage(messages.rotate) }), _jsxs(Button, { variant: "destructive", onClick: () => setConfirmDelete(true), children: [_jsx(Trash2, { "aria-hidden": true }), intl.formatMessage(messages.remove)] })] })) : null] })] })] }), _jsx(RevealWebhookSecretDialog, { secret: secret, rotated: true, open: secret !== null, onOpenChange: (next) => {
|
|
182
|
+
if (!next)
|
|
183
|
+
setSecret(null);
|
|
184
|
+
} }), _jsx(ConfirmDialog, { open: confirmDelete, onOpenChange: setConfirmDelete, title: intl.formatMessage(messages.confirmDeleteTitle), description: intl.formatMessage(messages.confirmDeleteBody), confirmLabel: intl.formatMessage(messages.confirmDelete), cancelLabel: intl.formatMessage(messages.cancel), confirmVariant: "destructive", busy: remove.isPending, onConfirm: () => remove.mutate(endpoint.id, {
|
|
185
|
+
onSuccess: () => {
|
|
186
|
+
toast.success(intl.formatMessage(messages.deleted));
|
|
187
|
+
navigate('/webhooks');
|
|
188
|
+
},
|
|
189
|
+
onError: () => toast.error(intl.formatMessage(messages.deleteFailed))
|
|
190
|
+
}) })] }));
|
|
191
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Props for {@link WebhookEditorPage}. */
|
|
2
|
+
export type WebhookEditorPageProps = {
|
|
3
|
+
/**
|
|
4
|
+
* `'create'` mounts a blank form; `'edit'` (the default) loads the endpoint
|
|
5
|
+
* named by the route.
|
|
6
|
+
*
|
|
7
|
+
* One component for both, as in the alarms rule editor: the two forms are
|
|
8
|
+
* the same form. The only differences are what seeds it and where saving
|
|
9
|
+
* goes afterwards, and a second page would duplicate the whole subscription
|
|
10
|
+
* surface for that.
|
|
11
|
+
*/
|
|
12
|
+
mode?: 'create' | 'edit';
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Creates or edits one endpoint, as a page rather than a dialog.
|
|
16
|
+
*
|
|
17
|
+
* The form asks eight questions across three filters, two of which are pickers
|
|
18
|
+
* that open their own popovers; inside a modal that meant a scrolling box with
|
|
19
|
+
* popovers portalled into it to survive the scroll lock. On a page the two
|
|
20
|
+
* halves — where deliveries go, and what is worth sending — sit side by side,
|
|
21
|
+
* and the browser's own back button is the way out.
|
|
22
|
+
*
|
|
23
|
+
* Creating ends on the one-time secret: it exists nowhere else from that moment
|
|
24
|
+
* on, so the reveal opens here and only then does the page move to the new
|
|
25
|
+
* endpoint.
|
|
26
|
+
*/
|
|
27
|
+
export declare function WebhookEditorPage({ mode }?: WebhookEditorPageProps): import("react").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/presentation/pages/WebhookEditorPage/index.tsx"],"names":[],"mappings":"AA6DA,2CAA2C;AAC3C,MAAM,MAAM,sBAAsB,GAAG;IACjC;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,IAAa,EAChB,GAAE,sBAA2B,+BAsL7B"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
4
|
+
import { useNavigate, useParams } from 'react-router-dom';
|
|
5
|
+
import { Webhook } from 'lucide-react';
|
|
6
|
+
import { PageTopBar } from '@orthacms/shell-admin';
|
|
7
|
+
import { useHasPermission } from '@orthacms/identity-admin';
|
|
8
|
+
import { useDocumentTitle } from '@orthacms/utils-admin';
|
|
9
|
+
import { Alert, AlertDescription, Button, Container, ContainerHeader, Spinner, toast } from '@orthacms/design-system';
|
|
10
|
+
import { useWebhookEndpoint } from '../../../application/useWebhookEndpoints';
|
|
11
|
+
import { useWebhookEvents } from '../../../application/useWebhookEvents';
|
|
12
|
+
import { useWorkspaceOptions } from '../../../application/useWorkspaceOptions';
|
|
13
|
+
import { useContentTypeOptions } from '../../../application/useContentTypeOptions';
|
|
14
|
+
import { useCreateWebhook, useUpdateWebhook } from '../../../application/useWebhookMutations';
|
|
15
|
+
import { serverMessageOf } from '../../../infrastructure/serverMessageOf';
|
|
16
|
+
import { WebhookForm } from '../../components/WebhookForm';
|
|
17
|
+
import { RevealWebhookSecretDialog } from '../../components/RevealWebhookSecretDialog';
|
|
18
|
+
import { WebhooksNoAccess } from '../../components/WebhooksNoAccess';
|
|
19
|
+
const messages = defineMessages({
|
|
20
|
+
listTitle: { id: 'webhooks.page.title', defaultMessage: 'Webhooks' },
|
|
21
|
+
createTitle: {
|
|
22
|
+
id: 'webhooks.editor.createTitle',
|
|
23
|
+
defaultMessage: 'New webhook'
|
|
24
|
+
},
|
|
25
|
+
editTitle: {
|
|
26
|
+
id: 'webhooks.editor.editTitle',
|
|
27
|
+
defaultMessage: 'Edit webhook'
|
|
28
|
+
},
|
|
29
|
+
subtitle: {
|
|
30
|
+
id: 'webhooks.editor.subtitle',
|
|
31
|
+
defaultMessage: 'Choose where deliveries go and which changes are worth sending.'
|
|
32
|
+
},
|
|
33
|
+
created: { id: 'webhooks.page.created', defaultMessage: 'Webhook created' },
|
|
34
|
+
saved: { id: 'webhooks.detail.saved', defaultMessage: 'Webhook updated' },
|
|
35
|
+
createFailed: {
|
|
36
|
+
id: 'webhooks.page.createFailed',
|
|
37
|
+
defaultMessage: 'Couldn’t create the webhook. Please try again.'
|
|
38
|
+
},
|
|
39
|
+
saveFailed: {
|
|
40
|
+
id: 'webhooks.detail.saveFailed',
|
|
41
|
+
defaultMessage: 'Couldn’t save the webhook. Please try again.'
|
|
42
|
+
},
|
|
43
|
+
notFound: {
|
|
44
|
+
id: 'webhooks.detail.notFound',
|
|
45
|
+
defaultMessage: 'That webhook no longer exists.'
|
|
46
|
+
},
|
|
47
|
+
back: { id: 'webhooks.detail.back', defaultMessage: 'Back to webhooks' }
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Creates or edits one endpoint, as a page rather than a dialog.
|
|
51
|
+
*
|
|
52
|
+
* The form asks eight questions across three filters, two of which are pickers
|
|
53
|
+
* that open their own popovers; inside a modal that meant a scrolling box with
|
|
54
|
+
* popovers portalled into it to survive the scroll lock. On a page the two
|
|
55
|
+
* halves — where deliveries go, and what is worth sending — sit side by side,
|
|
56
|
+
* and the browser's own back button is the way out.
|
|
57
|
+
*
|
|
58
|
+
* Creating ends on the one-time secret: it exists nowhere else from that moment
|
|
59
|
+
* on, so the reveal opens here and only then does the page move to the new
|
|
60
|
+
* endpoint.
|
|
61
|
+
*/
|
|
62
|
+
export function WebhookEditorPage({ mode = 'edit' } = {}) {
|
|
63
|
+
const intl = useIntl();
|
|
64
|
+
const navigate = useNavigate();
|
|
65
|
+
const { id = '' } = useParams();
|
|
66
|
+
const isCreate = mode === 'create';
|
|
67
|
+
const canRead = useHasPermission('webhooks:read');
|
|
68
|
+
const canManage = useHasPermission('webhooks:manage');
|
|
69
|
+
const [formError, setFormError] = useState(null);
|
|
70
|
+
const [created, setCreated] = useState(null);
|
|
71
|
+
const { data: endpoint, isPending, isError } = useWebhookEndpoint(id, canRead && !isCreate && id.length > 0);
|
|
72
|
+
// The pickers are the page now, so unlike the dialog they load with it.
|
|
73
|
+
const { data: events } = useWebhookEvents(canManage);
|
|
74
|
+
const { data: workspaces } = useWorkspaceOptions(canManage);
|
|
75
|
+
const { data: contentTypes } = useContentTypeOptions(canManage);
|
|
76
|
+
const create = useCreateWebhook();
|
|
77
|
+
const update = useUpdateWebhook();
|
|
78
|
+
const title = intl.formatMessage(isCreate ? messages.createTitle : messages.editTitle);
|
|
79
|
+
useDocumentTitle(title);
|
|
80
|
+
const crumbs = [
|
|
81
|
+
{
|
|
82
|
+
key: 'webhooks',
|
|
83
|
+
label: intl.formatMessage(messages.listTitle),
|
|
84
|
+
to: '/webhooks'
|
|
85
|
+
},
|
|
86
|
+
...(isCreate
|
|
87
|
+
? []
|
|
88
|
+
: [
|
|
89
|
+
{
|
|
90
|
+
key: 'endpoint',
|
|
91
|
+
label: endpoint?.name ?? title,
|
|
92
|
+
to: `/webhooks/${id}`
|
|
93
|
+
}
|
|
94
|
+
]),
|
|
95
|
+
{ key: 'editor', label: title }
|
|
96
|
+
];
|
|
97
|
+
// `webhooks:manage` and not merely `webhooks:read`: this page only exists
|
|
98
|
+
// to write. Reaching it by URL without the key gets the same answer the nav
|
|
99
|
+
// gives, rather than a form whose Save would be refused.
|
|
100
|
+
if (!canRead || !canManage) {
|
|
101
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageTopBar, { icon: Webhook, crumbs: crumbs }), _jsxs(Container, { children: [_jsx(ContainerHeader, { title: title }), _jsx(WebhooksNoAccess, {})] })] }));
|
|
102
|
+
}
|
|
103
|
+
if (!isCreate && isPending) {
|
|
104
|
+
return (_jsx(Container, { children: _jsx("div", { className: "flex justify-center py-16", children: _jsx(Spinner, {}) }) }));
|
|
105
|
+
}
|
|
106
|
+
if (!isCreate && (isError || !endpoint)) {
|
|
107
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageTopBar, { icon: Webhook, crumbs: crumbs }), _jsx(Container, { children: _jsx(Alert, { variant: "destructive", role: "alert", className: "mt-8", children: _jsxs(AlertDescription, { className: "flex items-center justify-between gap-4", children: [intl.formatMessage(messages.notFound), _jsx(Button, { size: "sm", variant: "outline", onClick: () => navigate('/webhooks'), children: intl.formatMessage(messages.back) })] }) }) })] }));
|
|
108
|
+
}
|
|
109
|
+
const leave = () => navigate(isCreate ? '/webhooks' : `/webhooks/${id}`, { replace: true });
|
|
110
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageTopBar, { icon: Webhook, crumbs: crumbs }), _jsxs(Container, { children: [_jsx(ContainerHeader, { title: title, subtitle: intl.formatMessage(messages.subtitle) }), _jsx("div", { className: "mt-6", children: _jsx(WebhookForm, { endpoint: isCreate ? undefined : endpoint, events: events ?? [], workspaces: workspaces ?? [], contentTypes: contentTypes ?? [], pending: create.isPending || update.isPending, error: formError, onCancel: leave, onSubmit: (input) => {
|
|
111
|
+
setFormError(null);
|
|
112
|
+
// A refused URL comes back with a message written
|
|
113
|
+
// for whoever typed it; show that rather than a
|
|
114
|
+
// generic failure, and stay on the form so it can
|
|
115
|
+
// be corrected in place.
|
|
116
|
+
const onError = (error) => setFormError(serverMessageOf(error) ??
|
|
117
|
+
intl.formatMessage(isCreate
|
|
118
|
+
? messages.createFailed
|
|
119
|
+
: messages.saveFailed));
|
|
120
|
+
if (isCreate) {
|
|
121
|
+
create.mutate(input, {
|
|
122
|
+
onSuccess: ({ endpoint: made, secret }) => {
|
|
123
|
+
toast.success(intl.formatMessage(messages.created));
|
|
124
|
+
// The secret exists nowhere else from
|
|
125
|
+
// here on, so the page waits on the
|
|
126
|
+
// reveal rather than navigating past
|
|
127
|
+
// it.
|
|
128
|
+
setCreated({ id: made.id, secret });
|
|
129
|
+
},
|
|
130
|
+
onError
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
update.mutate({ id, input }, {
|
|
135
|
+
onSuccess: () => {
|
|
136
|
+
toast.success(intl.formatMessage(messages.saved));
|
|
137
|
+
navigate(`/webhooks/${id}`, {
|
|
138
|
+
replace: true
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
onError
|
|
142
|
+
});
|
|
143
|
+
} }) })] }), _jsx(RevealWebhookSecretDialog, { secret: created?.secret ?? null, open: created !== null, onOpenChange: (next) => {
|
|
144
|
+
if (next || !created)
|
|
145
|
+
return;
|
|
146
|
+
navigate(`/webhooks/${created.id}`, { replace: true });
|
|
147
|
+
setCreated(null);
|
|
148
|
+
} })] }));
|
|
149
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The global webhooks page (`/webhooks`, in the sidebar's directory group — no
|
|
3
|
+
* workspace context).
|
|
4
|
+
*
|
|
5
|
+
* Everything here is gated on `webhooks:read`, and both keys are
|
|
6
|
+
* administrator-only: an endpoint spans every workspace it names and holds a
|
|
7
|
+
* signing secret, so this is deployment configuration rather than an editorial
|
|
8
|
+
* surface.
|
|
9
|
+
*/
|
|
10
|
+
export declare function WebhooksPage(): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/presentation/pages/WebhooksPage/index.tsx"],"names":[],"mappings":"AAqCA;;;;;;;;GAQG;AACH,wBAAgB,YAAY,gCAuG3B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import { useNavigate } from 'react-router-dom';
|
|
4
|
+
import { Plus, Webhook } from 'lucide-react';
|
|
5
|
+
import { PageTopBar } from '@orthacms/shell-admin';
|
|
6
|
+
import { useHasPermission } from '@orthacms/identity-admin';
|
|
7
|
+
import { useDocumentTitle } from '@orthacms/utils-admin';
|
|
8
|
+
import { Alert, AlertDescription, Button, Container, ContainerHeader } from '@orthacms/design-system';
|
|
9
|
+
import { useWebhookEndpoints } from '../../../application/useWebhookEndpoints';
|
|
10
|
+
import { WebhooksTable } from '../../components/WebhooksTable';
|
|
11
|
+
import { WebhooksEmpty } from '../../components/WebhooksEmpty';
|
|
12
|
+
import { WebhooksNoAccess } from '../../components/WebhooksNoAccess';
|
|
13
|
+
import { WebhooksSkeleton } from '../../components/WebhooksSkeleton';
|
|
14
|
+
const messages = defineMessages({
|
|
15
|
+
title: { id: 'webhooks.page.title', defaultMessage: 'Webhooks' },
|
|
16
|
+
subtitle: {
|
|
17
|
+
id: 'webhooks.page.subtitle',
|
|
18
|
+
defaultMessage: 'Endpoints this CMS notifies when content changes.'
|
|
19
|
+
},
|
|
20
|
+
create: { id: 'webhooks.page.create', defaultMessage: 'New webhook' },
|
|
21
|
+
error: {
|
|
22
|
+
id: 'webhooks.page.error',
|
|
23
|
+
defaultMessage: 'Couldn’t load webhooks. Please try again.'
|
|
24
|
+
},
|
|
25
|
+
retry: { id: 'webhooks.page.retry', defaultMessage: 'Retry' },
|
|
26
|
+
count: {
|
|
27
|
+
id: 'webhooks.page.count',
|
|
28
|
+
defaultMessage: '{count, plural, one {# webhook} other {# webhooks}}'
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* The global webhooks page (`/webhooks`, in the sidebar's directory group — no
|
|
33
|
+
* workspace context).
|
|
34
|
+
*
|
|
35
|
+
* Everything here is gated on `webhooks:read`, and both keys are
|
|
36
|
+
* administrator-only: an endpoint spans every workspace it names and holds a
|
|
37
|
+
* signing secret, so this is deployment configuration rather than an editorial
|
|
38
|
+
* surface.
|
|
39
|
+
*/
|
|
40
|
+
export function WebhooksPage() {
|
|
41
|
+
const intl = useIntl();
|
|
42
|
+
useDocumentTitle(intl.formatMessage(messages.title));
|
|
43
|
+
const canRead = useHasPermission('webhooks:read');
|
|
44
|
+
const canManage = useHasPermission('webhooks:manage');
|
|
45
|
+
const navigate = useNavigate();
|
|
46
|
+
const { data, isPending, isError, refetch } = useWebhookEndpoints(canRead);
|
|
47
|
+
if (!canRead) {
|
|
48
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageTopBar, { icon: Webhook, crumbs: [
|
|
49
|
+
{
|
|
50
|
+
key: 'webhooks',
|
|
51
|
+
label: intl.formatMessage(messages.title)
|
|
52
|
+
}
|
|
53
|
+
] }), _jsxs(Container, { children: [_jsx(ContainerHeader, { title: intl.formatMessage(messages.title) }), _jsx(WebhooksNoAccess, {})] })] }));
|
|
54
|
+
}
|
|
55
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageTopBar, { icon: Webhook, crumbs: [
|
|
56
|
+
{
|
|
57
|
+
key: 'webhooks',
|
|
58
|
+
label: intl.formatMessage(messages.title)
|
|
59
|
+
}
|
|
60
|
+
] }), _jsxs(Container, { children: [_jsx(ContainerHeader, { title: intl.formatMessage(messages.title), subtitle: intl.formatMessage(messages.subtitle), actions: canManage && (data?.length ?? 0) > 0 ? (_jsxs(Button, { onClick: () => navigate('/webhooks/new'), children: [_jsx(Plus, {}), intl.formatMessage(messages.create)] })) : undefined }), isPending ? _jsx(WebhooksSkeleton, {}) : null, isError ? (_jsx(Alert, { variant: "destructive", role: "alert", className: "mt-4", children: _jsxs(AlertDescription, { className: "flex items-center justify-between gap-4", children: [intl.formatMessage(messages.error), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void refetch(), children: intl.formatMessage(messages.retry) })] }) })) : null, data && data.length === 0 ? (_jsx(WebhooksEmpty, { onCreate: canManage
|
|
61
|
+
? () => navigate('/webhooks/new')
|
|
62
|
+
: undefined })) : null, data && data.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(WebhooksTable, { endpoints: data }), _jsx("p", { className: "mt-3 text-sm text-muted-foreground", children: intl.formatMessage(messages.count, {
|
|
63
|
+
count: data.length
|
|
64
|
+
}) })] })) : null] })] }));
|
|
65
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AdminPlugin } from '@orthacms/bootstrap-admin';
|
|
2
|
+
/**
|
|
3
|
+
* Admin-side webhooks plugin shape. A thin alias of {@link AdminPlugin}, kept
|
|
4
|
+
* named so future config has a home.
|
|
5
|
+
*/
|
|
6
|
+
export type WebhooksAdminPlugin = AdminPlugin;
|
|
7
|
+
/**
|
|
8
|
+
* Creates the admin-side webhooks plugin.
|
|
9
|
+
*
|
|
10
|
+
* It owns the global webhook-management feature — `/webhooks`,
|
|
11
|
+
* `/webhooks/new`, `/webhooks/:id` and `/webhooks/:id/edit` — rendered inside
|
|
12
|
+
* the shell's authenticated layout, plus an
|
|
13
|
+
* entry in the **global** sidebar's `directory` group alongside Workspaces,
|
|
14
|
+
* Members and API tokens — reachable without selecting a workspace, because an
|
|
15
|
+
* endpoint is not scoped to one.
|
|
16
|
+
*
|
|
17
|
+
* The nav row and both pages gate on `webhooks:read`, which is
|
|
18
|
+
* administrator-only: the rows hold a signing secret and reach across every
|
|
19
|
+
* workspace they name.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* createAdmin({
|
|
24
|
+
* plugins: [
|
|
25
|
+
* IdentityPlugin(),
|
|
26
|
+
* ShellPlugin(),
|
|
27
|
+
* WorkspacesPlugin(),
|
|
28
|
+
* ApiTokensPlugin(),
|
|
29
|
+
* WebhooksPlugin(),
|
|
30
|
+
* ],
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function WebhooksPlugin(): WebhooksAdminPlugin;
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/utils/webhooksPlugin/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AA0B7D;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,IAAI,mBAAmB,CA2DpD"}
|