@open-mercato/webhooks 0.5.1-develop.2681.c559bb2bc3 → 0.5.1-develop.2683.4878a05b8e
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/modules/webhooks/backend/webhooks/[id]/page.js +4 -4
- package/dist/modules/webhooks/backend/webhooks/[id]/page.js.map +2 -2
- package/dist/modules/webhooks/backend/webhooks/page.js +5 -2
- package/dist/modules/webhooks/backend/webhooks/page.js.map +2 -2
- package/dist/modules/webhooks/components/WebhookSecretPanel.js +4 -4
- package/dist/modules/webhooks/components/WebhookSecretPanel.js.map +2 -2
- package/dist/modules/webhooks/components/webhook-form-config.js +13 -7
- package/dist/modules/webhooks/components/webhook-form-config.js.map +2 -2
- package/package.json +6 -6
- package/src/modules/webhooks/backend/webhooks/[id]/page.tsx +10 -4
- package/src/modules/webhooks/backend/webhooks/page.tsx +5 -2
- package/src/modules/webhooks/components/WebhookSecretPanel.tsx +10 -6
- package/src/modules/webhooks/components/webhook-form-config.tsx +27 -7
|
@@ -16,7 +16,7 @@ import { FormHeader } from "@open-mercato/ui/backend/forms";
|
|
|
16
16
|
import { RowActions } from "@open-mercato/ui/backend/RowActions";
|
|
17
17
|
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
18
18
|
import { deleteCrud, updateCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
19
|
-
import {
|
|
19
|
+
import { Alert, AlertDescription } from "@open-mercato/ui/primitives/alert";
|
|
20
20
|
import {
|
|
21
21
|
buildWebhookFormContentHeader,
|
|
22
22
|
buildWebhookFormFields,
|
|
@@ -414,10 +414,10 @@ function WebhookDetailPage() {
|
|
|
414
414
|
}
|
|
415
415
|
),
|
|
416
416
|
/* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-4", children: [
|
|
417
|
-
!access.isLoading && !access.canManage && !access.canSecrets && !access.canTest ? /* @__PURE__ */ jsx(
|
|
417
|
+
!access.isLoading && !access.canManage && !access.canSecrets && !access.canTest ? /* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.detail.readOnlyTip") }) }) : null,
|
|
418
418
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 lg:grid-cols-2", children: [
|
|
419
|
-
/* @__PURE__ */ jsx(
|
|
420
|
-
/* @__PURE__ */ jsx(
|
|
419
|
+
/* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.detail.deliveryTip") }) }),
|
|
420
|
+
/* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.detail.signatureTip") }) })
|
|
421
421
|
] }),
|
|
422
422
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4 text-sm", children: [
|
|
423
423
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/webhooks/backend/webhooks/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useParams, usePathname, useRouter } from 'next/navigation'\nimport { RotateCw } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { Notice } from '@open-mercato/ui/primitives/Notice'\nimport {\n buildWebhookFormContentHeader,\n buildWebhookFormFields,\n buildWebhookFormGroups,\n createWebhookInitialValues,\n normalizeWebhookFormPayload,\n type WebhookFormValues,\n} from '../../../components/webhook-form-config'\nimport { useWebhookFeatureAccess } from '../useWebhookFeatureAccess'\nimport { WebhookSecretPanel } from '../../../components/WebhookSecretPanel'\n\ntype Webhook = {\n id: string\n name: string\n description: string | null\n url: string\n subscribedEvents: string[]\n httpMethod: 'POST' | 'PUT' | 'PATCH'\n isActive: boolean\n maxRetries: number\n timeoutMs: number\n rateLimitPerMinute: number\n autoDisableThreshold: number\n consecutiveFailures: number\n lastSuccessAt: string | null\n lastFailureAt: string | null\n customHeaders: Record<string, string> | null\n createdAt: string\n updatedAt: string\n maskedSecret: string\n previousSecretSetAt: string | null\n}\n\ntype DeliveryRow = {\n id: string\n webhookId: string\n eventType: string\n messageId: string\n status: string\n responseStatus: number | null\n errorMessage: string | null\n attemptNumber: number\n maxAttempts: number\n durationMs: number | null\n targetUrl: string\n enqueuedAt: string\n lastAttemptAt: string | null\n deliveredAt: string | null\n createdAt: string\n}\n\ntype DeliveryResponse = {\n items: DeliveryRow[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\ntype DeliveryDetail = DeliveryRow & {\n payload: Record<string, unknown>\n responseBody: string | null\n responseHeaders: Record<string, string> | null\n nextRetryAt: string | null\n updatedAt: string\n}\n\nconst statusVariantMap: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {\n delivered: 'default',\n pending: 'secondary',\n sending: 'outline',\n failed: 'destructive',\n expired: 'destructive',\n}\nconst DELIVERY_AUTO_REFRESH_INTERVAL_MS = 30000\n\nexport default function WebhookDetailPage() {\n const params = useParams()\n const pathname = usePathname()\n const router = useRouter()\n const t = useT()\n const webhookId = React.useMemo(() => resolveWebhookId(params?.id, pathname), [params?.id, pathname])\n\n const [webhook, setWebhook] = React.useState<Webhook | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isEditing, setIsEditing] = React.useState(false)\n\n const [deliveries, setDeliveries] = React.useState<DeliveryRow[]>([])\n const [deliveryPage, setDeliveryPage] = React.useState(1)\n const [deliveryTotal, setDeliveryTotal] = React.useState(0)\n const [deliveryTotalPages, setDeliveryTotalPages] = React.useState(1)\n const [deliveriesLoading, setDeliveriesLoading] = React.useState(false)\n const [isRefreshingDeliveries, setIsRefreshingDeliveries] = React.useState(false)\n const [testDelivery, setTestDelivery] = React.useState<DeliveryDetail | null>(null)\n const [selectedDelivery, setSelectedDelivery] = React.useState<DeliveryDetail | null>(null)\n const [selectedDeliveryLoading, setSelectedDeliveryLoading] = React.useState(false)\n const [revealedSecret, setRevealedSecret] = React.useState<string | null>(null)\n const refreshInFlightRef = React.useRef(false)\n const access = useWebhookFeatureAccess()\n\n const fetchWebhook = React.useCallback(async (options?: { silent?: boolean }) => {\n if (!webhookId) {\n setError(t('webhooks.errors.notFound'))\n setIsLoading(false)\n return\n }\n\n const silent = options?.silent === true\n if (!silent) {\n setIsLoading(true)\n }\n\n setError(null)\n\n try {\n const call = await apiCall<Webhook>(\n `/api/webhooks/${encodeURIComponent(webhookId)}`,\n undefined,\n { fallback: null },\n )\n\n if (call.ok && call.result) {\n setWebhook(call.result)\n return\n }\n\n setError(t('webhooks.errors.notFound'))\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError.message : t('webhooks.detail.loadError'))\n } finally {\n if (!silent) {\n setIsLoading(false)\n }\n }\n }, [t, webhookId])\n\n const fetchDeliveries = React.useCallback(async (options?: { silent?: boolean }) => {\n if (!webhookId) return\n\n const silent = options?.silent === true\n if (!silent) {\n setDeliveriesLoading(true)\n }\n\n try {\n const params = new URLSearchParams()\n params.set('webhookId', webhookId)\n params.set('page', String(deliveryPage))\n params.set('pageSize', '20')\n\n const fallback: DeliveryResponse = { items: [], total: 0, page: deliveryPage, pageSize: 20, totalPages: 1 }\n const call = await apiCall<DeliveryResponse>(\n `/api/webhooks/deliveries?${params.toString()}`,\n undefined,\n { fallback },\n )\n\n if (call.ok && call.result) {\n setDeliveries(call.result.items)\n setDeliveryTotal(call.result.total)\n setDeliveryTotalPages(call.result.totalPages)\n }\n } finally {\n if (!silent) {\n setDeliveriesLoading(false)\n }\n }\n }, [deliveryPage, webhookId])\n\n const fetchDeliveryDetail = React.useCallback(async (deliveryId: string): Promise<DeliveryDetail | null> => {\n const call = await apiCall<DeliveryDetail>(\n `/api/webhooks/deliveries/${encodeURIComponent(deliveryId)}`,\n undefined,\n { fallback: null },\n )\n\n if (!call.ok || !call.result) {\n return null\n }\n\n return call.result\n }, [])\n\n const refreshDeliveryState = React.useCallback(async () => {\n if (!webhookId || refreshInFlightRef.current) return\n\n refreshInFlightRef.current = true\n setIsRefreshingDeliveries(true)\n\n try {\n await Promise.all([\n fetchWebhook({ silent: true }),\n fetchDeliveries({ silent: true }),\n ])\n\n const detailIds = [selectedDelivery?.id, testDelivery?.id].filter(\n (value): value is string => typeof value === 'string' && value.length > 0,\n )\n\n if (detailIds.length > 0) {\n const details = await Promise.all(detailIds.map((deliveryId) => fetchDeliveryDetail(deliveryId)))\n const detailMap = new Map<string, DeliveryDetail>()\n\n for (const detail of details) {\n if (detail) {\n detailMap.set(detail.id, detail)\n }\n }\n\n if (selectedDelivery?.id) {\n setSelectedDelivery((current) => current?.id ? (detailMap.get(current.id) ?? current) : current)\n }\n\n if (testDelivery?.id) {\n setTestDelivery((current) => current?.id ? (detailMap.get(current.id) ?? current) : current)\n }\n }\n } finally {\n refreshInFlightRef.current = false\n setIsRefreshingDeliveries(false)\n }\n }, [fetchDeliveries, fetchDeliveryDetail, fetchWebhook, selectedDelivery?.id, testDelivery?.id, webhookId])\n\n React.useEffect(() => {\n void fetchWebhook()\n }, [fetchWebhook])\n\n React.useEffect(() => {\n if (!webhookId) return\n void fetchDeliveries()\n }, [fetchDeliveries, webhookId])\n\n useAppEvent('webhooks.delivery.*', (event) => {\n const eventWebhookId = typeof event.payload?.webhookId === 'string' ? event.payload.webhookId : null\n if (eventWebhookId !== webhookId) return\n void refreshDeliveryState()\n }, [refreshDeliveryState, webhookId])\n\n React.useEffect(() => {\n if (!webhookId || isEditing) return\n\n const intervalId = window.setInterval(() => {\n if (document.visibilityState !== 'visible') return\n void refreshDeliveryState()\n }, DELIVERY_AUTO_REFRESH_INTERVAL_MS)\n\n return () => {\n window.clearInterval(intervalId)\n }\n }, [isEditing, refreshDeliveryState, webhookId])\n\n const handleToggleActive = React.useCallback(async () => {\n if (!webhook) return\n try {\n const call = await apiCall<Webhook>(\n `/api/webhooks/${encodeURIComponent(webhook.id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ isActive: !webhook.isActive }),\n },\n { fallback: null },\n )\n if (call.ok) {\n setWebhook((prev) => prev ? { ...prev, isActive: !prev.isActive } : prev)\n flash(t('webhooks.form.updateSuccess'), 'success')\n void refreshDeliveryState()\n }\n } catch {\n flash(t('webhooks.form.updateError'), 'error')\n }\n }, [refreshDeliveryState, webhook, t])\n\n const handleRotateSecret = React.useCallback(async () => {\n if (!webhook) return\n try {\n const call = await apiCall<{ secret: string }>(\n `/api/webhooks/${encodeURIComponent(webhook.id)}/rotate-secret`,\n { method: 'POST' },\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n flash(t('webhooks.detail.rotateError'), 'error')\n return\n }\n setRevealedSecret(call.result.secret)\n flash(t('webhooks.detail.rotateSuccess'), 'success')\n void refreshDeliveryState()\n } catch {\n flash(t('webhooks.detail.rotateError'), 'error')\n }\n }, [refreshDeliveryState, t, webhook])\n\n const handleTest = React.useCallback(async () => {\n if (!webhook) return\n try {\n const call = await apiCall<{ delivery: DeliveryDetail }>(\n `/api/webhooks/${encodeURIComponent(webhook.id)}/test`,\n { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({}) },\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n flash(t('webhooks.detail.testError'), 'error')\n return\n }\n const deliveryStatus = call.result.delivery.status\n setTestDelivery(call.result.delivery)\n flash(\n deliveryStatus === 'delivered' ? t('webhooks.detail.testSuccess') : t('webhooks.detail.testQueued'),\n deliveryStatus === 'delivered' ? 'success' : 'error',\n )\n void refreshDeliveryState()\n } catch {\n flash(t('webhooks.detail.testError'), 'error')\n }\n }, [refreshDeliveryState, t, webhook])\n\n const handleRetryDelivery = React.useCallback(async (deliveryId: string) => {\n try {\n const call = await apiCall(\n `/api/webhooks/deliveries/${encodeURIComponent(deliveryId)}/retry`,\n { method: 'POST' },\n { fallback: null },\n )\n if (!call.ok) {\n flash(t('webhooks.deliveries.retryError'), 'error')\n return\n }\n flash(t('webhooks.deliveries.retrySuccess'), 'success')\n void refreshDeliveryState()\n } catch {\n flash(t('webhooks.deliveries.retryError'), 'error')\n }\n }, [refreshDeliveryState, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!webhook) return\n try {\n await deleteCrud(`webhooks/${encodeURIComponent(webhook.id)}`, { fallbackResult: null })\n flash(t('webhooks.list.deleteSuccess'), 'success')\n router.push('/backend/webhooks')\n } catch {\n flash(t('webhooks.list.deleteError'), 'error')\n }\n }, [router, t, webhook])\n\n const handleDeliveryOpen = React.useCallback(async (deliveryId: string) => {\n setSelectedDeliveryLoading(true)\n try {\n const detail = await fetchDeliveryDetail(deliveryId)\n if (!detail) {\n flash(t('webhooks.deliveries.loadError'), 'error')\n return\n }\n setSelectedDelivery(detail)\n } catch {\n flash(t('webhooks.deliveries.loadError'), 'error')\n } finally {\n setSelectedDeliveryLoading(false)\n }\n }, [fetchDeliveryDetail, t])\n\n const deliveryColumns = React.useMemo<ColumnDef<DeliveryRow>[]>(() => [\n { accessorKey: 'eventType', header: t('webhooks.deliveries.columns.event') },\n {\n accessorKey: 'status',\n header: t('webhooks.deliveries.columns.status'),\n cell: ({ row }) => (\n <Badge variant={statusVariantMap[row.original.status] ?? 'secondary'}>\n {t(`webhooks.deliveries.status.${row.original.status}` as Parameters<typeof t>[0])}\n </Badge>\n ),\n },\n {\n accessorKey: 'responseStatus',\n header: t('webhooks.deliveries.columns.responseStatus'),\n cell: ({ row }) => row.original.responseStatus ?? '\u2014',\n },\n {\n accessorKey: 'attemptNumber',\n header: t('webhooks.deliveries.columns.attempts'),\n cell: ({ row }) => `${row.original.attemptNumber}/${row.original.maxAttempts}`,\n },\n {\n accessorKey: 'durationMs',\n header: t('webhooks.deliveries.columns.duration'),\n cell: ({ row }) => row.original.durationMs != null ? `${row.original.durationMs}ms` : '\u2014',\n },\n {\n accessorKey: 'createdAt',\n header: t('webhooks.deliveries.columns.enqueuedAt'),\n cell: ({ row }) => {\n try { return new Date(row.original.enqueuedAt).toLocaleString() }\n catch { return '\u2014' }\n },\n },\n ], [t])\n\n const fields = React.useMemo(() => buildWebhookFormFields(t), [t])\n const groups = React.useMemo(() => buildWebhookFormGroups(t), [t])\n const contentHeader = React.useMemo(() => buildWebhookFormContentHeader(t), [t])\n const menuActions = React.useMemo(() => {\n const items: Array<{ id: string; label: string; onSelect: () => void }> = []\n const isActive = webhook?.isActive ?? false\n\n if (access.canManage) {\n items.push(\n {\n id: 'edit',\n label: t('webhooks.list.actions.edit'),\n onSelect: () => setIsEditing(true),\n },\n {\n id: 'toggle-active',\n label: isActive ? t('webhooks.detail.actions.deactivate') : t('webhooks.detail.actions.activate'),\n onSelect: () => { void handleToggleActive() },\n },\n )\n }\n\n if (access.canSecrets) {\n items.push({\n id: 'rotate-secret',\n label: t('webhooks.detail.actions.rotateSecret'),\n onSelect: () => { void handleRotateSecret() },\n })\n }\n\n if (access.canTest) {\n items.push({\n id: 'test',\n label: t('webhooks.detail.actions.test'),\n onSelect: () => { void handleTest() },\n })\n }\n\n if (access.canManage) {\n items.push({\n id: 'delete',\n label: t('webhooks.list.actions.delete'),\n onSelect: () => { void handleDelete() },\n })\n }\n\n return items\n }, [access.canManage, access.canSecrets, access.canTest, handleDelete, handleRotateSecret, handleTest, handleToggleActive, t, webhook?.isActive])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('webhooks.detail.loading')} /></PageBody></Page>\n if (error || !webhook) return <Page><PageBody><ErrorMessage label={error ?? t('webhooks.errors.notFound')} /></PageBody></Page>\n\n if (isEditing) {\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('webhooks.form.title.edit')}\n backHref={`/backend/webhooks/${webhook.id}`}\n fields={fields}\n groups={groups}\n initialValues={createWebhookInitialValues(webhook)}\n submitLabel={t('common.save')}\n cancelHref={`/backend/webhooks/${webhook.id}`}\n contentHeader={contentHeader}\n onDelete={access.canManage ? handleDelete : undefined}\n onSubmit={async (values) => {\n const payload = normalizeWebhookFormPayload(values as WebhookFormValues, t)\n await updateCrud(`webhooks/${encodeURIComponent(webhook.id)}`, payload)\n flash(t('webhooks.form.updateSuccess'), 'success')\n setIsEditing(false)\n await refreshDeliveryState()\n }}\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n {revealedSecret ? (\n <WebhookSecretPanel secret={revealedSecret} onClose={() => setRevealedSecret(null)} />\n ) : null}\n <FormHeader\n mode=\"detail\"\n title={webhook.name}\n entityTypeLabel={t('webhooks.nav.title')}\n statusBadge={\n <Badge\n className={webhook.isActive\n ? 'border-transparent bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'\n : 'border-transparent bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'}\n >\n {webhook.isActive ? t('webhooks.list.status.active') : t('webhooks.list.status.inactive')}\n </Badge>\n }\n backHref=\"/backend/webhooks\"\n menuActions={menuActions}\n />\n\n <div className=\"mt-6 space-y-4\">\n {!access.isLoading && !access.canManage && !access.canSecrets && !access.canTest ? (\n <Notice compact>{t('webhooks.detail.readOnlyTip')}</Notice>\n ) : null}\n <div className=\"grid gap-3 lg:grid-cols-2\">\n <Notice compact>{t('webhooks.detail.deliveryTip')}</Notice>\n <Notice compact>{t('webhooks.detail.signatureTip')}</Notice>\n </div>\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.url')}:</span>\n <code className=\"ml-2 text-xs break-all\">{webhook.url}</code>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.httpMethod')}:</span>\n <span className=\"ml-2\">{webhook.httpMethod}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.subscribedEvents')}:</span>\n <span className=\"ml-2 text-xs\">{webhook.subscribedEvents.join(', ')}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.maxRetries')}:</span>\n <span className=\"ml-2\">{webhook.maxRetries}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.rateLimitPerMinute')}:</span>\n <span className=\"ml-2\">{webhook.rateLimitPerMinute}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.autoDisableThreshold')}:</span>\n <span className=\"ml-2\">{webhook.autoDisableThreshold}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.detail.consecutiveFailures')}:</span>\n <span className=\"ml-2\">{webhook.consecutiveFailures}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.secret')}:</span>\n <span className=\"ml-2 inline-flex items-center gap-2\">\n <span className=\"font-mono text-xs\">{webhook.maskedSecret}</span>\n {access.canSecrets ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7 px-2 text-xs\"\n onClick={() => { void handleRotateSecret() }}\n >\n <RotateCw className=\"mr-1.5 size-3.5\" />\n {t('webhooks.detail.actions.rotateSecret')}\n </Button>\n ) : null}\n </span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.list.columns.lastDelivery')}:</span>\n <span className=\"ml-2\">{webhook.lastSuccessAt ?? webhook.lastFailureAt ?? '\u2014'}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.detail.previousSecretSetAt')}:</span>\n <span className=\"ml-2\">{webhook.previousSecretSetAt ?? '\u2014'}</span>\n </div>\n <div className=\"col-span-2\">\n <span className=\"text-muted-foreground\">{t('webhooks.form.customHeaders')}:</span>\n <pre className=\"mt-2 rounded border bg-muted/40 p-3 text-xs\">\n {webhook.customHeaders ? JSON.stringify(webhook.customHeaders, null, 2) : '\u2014'}\n </pre>\n </div>\n </div>\n </div>\n\n {testDelivery ? (\n <div className=\"mt-8 rounded-lg border bg-card p-4\">\n <h2 className=\"text-sm font-semibold\">{t('webhooks.detail.testResult')}</h2>\n <div className=\"mt-3 grid gap-2 text-sm\">\n <div>{t('webhooks.deliveries.columns.status')}: {testDelivery.status}</div>\n <div>{t('webhooks.deliveries.columns.responseStatus')}: {testDelivery.responseStatus ?? '\u2014'}</div>\n <div>{t('webhooks.deliveries.columns.duration')}: {testDelivery.durationMs != null ? `${testDelivery.durationMs}ms` : '\u2014'}</div>\n <pre className=\"overflow-auto rounded border bg-muted/40 p-3 text-xs\">\n {JSON.stringify(testDelivery.payload, null, 2)}\n </pre>\n </div>\n </div>\n ) : null}\n\n <div className=\"mt-8\">\n <DataTable\n title={t('webhooks.deliveries.title')}\n actions={(\n <div className=\"flex items-center gap-2\">\n <span className=\"hidden text-xs text-muted-foreground md:inline\">\n {t('webhooks.deliveries.autoRefreshHint')}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => { void refreshDeliveryState() }}\n disabled={isRefreshingDeliveries}\n >\n {isRefreshingDeliveries\n ? t('webhooks.deliveries.refreshing')\n : t('webhooks.deliveries.refresh')}\n </Button>\n </div>\n )}\n columns={deliveryColumns}\n data={deliveries}\n onRowClick={(row) => { void handleDeliveryOpen(row.id) }}\n rowActions={(row) => {\n const items: Array<{ id: string; label: string; onSelect: () => void }> = [\n {\n id: 'view-details',\n label: t('webhooks.deliveries.actions.viewDetails'),\n onSelect: () => { void handleDeliveryOpen(row.id) },\n },\n ]\n\n if (access.canManage && (row.status === 'failed' || row.status === 'expired')) {\n items.push({\n id: 'retry',\n label: t('webhooks.deliveries.actions.retry'),\n onSelect: () => { void handleRetryDelivery(row.id) },\n })\n }\n\n return <RowActions items={items} />\n }}\n perspective={{ tableId: 'webhooks.deliveries' }}\n pagination={{\n page: deliveryPage,\n pageSize: 20,\n total: deliveryTotal,\n totalPages: deliveryTotalPages,\n onPageChange: setDeliveryPage,\n }}\n isLoading={deliveriesLoading || isRefreshingDeliveries}\n />\n </div>\n\n {selectedDelivery || selectedDeliveryLoading ? (\n <div className=\"mt-6 rounded-lg border bg-card p-4\">\n <h2 className=\"text-sm font-semibold\">{t('webhooks.deliveries.detailTitle')}</h2>\n {selectedDeliveryLoading || !selectedDelivery ? (\n <div className=\"mt-3 text-sm text-muted-foreground\">{t('common.loading')}</div>\n ) : (\n <div className=\"mt-3 space-y-4 text-sm\">\n <div>{t('webhooks.deliveries.columns.status')}: {selectedDelivery.status}</div>\n <div>{t('webhooks.deliveries.columns.responseStatus')}: {selectedDelivery.responseStatus ?? '\u2014'}</div>\n <div>{t('webhooks.deliveries.columns.duration')}: {selectedDelivery.durationMs != null ? `${selectedDelivery.durationMs}ms` : '\u2014'}</div>\n <div>\n <div className=\"mb-2 font-medium\">{t('webhooks.deliveries.requestBody')}</div>\n <pre className=\"overflow-auto rounded border bg-muted/40 p-3 text-xs\">\n {JSON.stringify(selectedDelivery.payload, null, 2)}\n </pre>\n </div>\n <div>\n <div className=\"mb-2 font-medium\">{t('webhooks.deliveries.responseBody')}</div>\n <pre className=\"overflow-auto rounded border bg-muted/40 p-3 text-xs\">\n {selectedDelivery.responseBody ?? '\u2014'}\n </pre>\n </div>\n <div>\n <div className=\"mb-2 font-medium\">{t('webhooks.deliveries.responseHeaders')}</div>\n <pre className=\"overflow-auto rounded border bg-muted/40 p-3 text-xs\">\n {selectedDelivery.responseHeaders ? JSON.stringify(selectedDelivery.responseHeaders, null, 2) : '\u2014'}\n </pre>\n </div>\n </div>\n )}\n </div>\n ) : null}\n </PageBody>\n </Page>\n )\n}\n\nfunction resolveWebhookId(paramValue: string | string[] | undefined, pathname: string | null): string | null {\n if (typeof paramValue === 'string' && paramValue.trim().length > 0) {\n return paramValue\n }\n\n if (Array.isArray(paramValue)) {\n const first = paramValue.find((value) => typeof value === 'string' && value.trim().length > 0)\n if (first) return first\n }\n\n if (typeof pathname === 'string') {\n const match = pathname.match(/\\/backend\\/webhooks\\/([^/?#]+)/)\n if (match?.[1]) {\n return decodeURIComponent(match[1])\n }\n }\n\n return null\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useParams, usePathname, useRouter } from 'next/navigation'\nimport { RotateCw } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport {\n buildWebhookFormContentHeader,\n buildWebhookFormFields,\n buildWebhookFormGroups,\n createWebhookInitialValues,\n normalizeWebhookFormPayload,\n type WebhookFormValues,\n} from '../../../components/webhook-form-config'\nimport { useWebhookFeatureAccess } from '../useWebhookFeatureAccess'\nimport { WebhookSecretPanel } from '../../../components/WebhookSecretPanel'\n\ntype Webhook = {\n id: string\n name: string\n description: string | null\n url: string\n subscribedEvents: string[]\n httpMethod: 'POST' | 'PUT' | 'PATCH'\n isActive: boolean\n maxRetries: number\n timeoutMs: number\n rateLimitPerMinute: number\n autoDisableThreshold: number\n consecutiveFailures: number\n lastSuccessAt: string | null\n lastFailureAt: string | null\n customHeaders: Record<string, string> | null\n createdAt: string\n updatedAt: string\n maskedSecret: string\n previousSecretSetAt: string | null\n}\n\ntype DeliveryRow = {\n id: string\n webhookId: string\n eventType: string\n messageId: string\n status: string\n responseStatus: number | null\n errorMessage: string | null\n attemptNumber: number\n maxAttempts: number\n durationMs: number | null\n targetUrl: string\n enqueuedAt: string\n lastAttemptAt: string | null\n deliveredAt: string | null\n createdAt: string\n}\n\ntype DeliveryResponse = {\n items: DeliveryRow[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\ntype DeliveryDetail = DeliveryRow & {\n payload: Record<string, unknown>\n responseBody: string | null\n responseHeaders: Record<string, string> | null\n nextRetryAt: string | null\n updatedAt: string\n}\n\nconst statusVariantMap: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {\n delivered: 'default',\n pending: 'secondary',\n sending: 'outline',\n failed: 'destructive',\n expired: 'destructive',\n}\nconst DELIVERY_AUTO_REFRESH_INTERVAL_MS = 30000\n\nexport default function WebhookDetailPage() {\n const params = useParams()\n const pathname = usePathname()\n const router = useRouter()\n const t = useT()\n const webhookId = React.useMemo(() => resolveWebhookId(params?.id, pathname), [params?.id, pathname])\n\n const [webhook, setWebhook] = React.useState<Webhook | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isEditing, setIsEditing] = React.useState(false)\n\n const [deliveries, setDeliveries] = React.useState<DeliveryRow[]>([])\n const [deliveryPage, setDeliveryPage] = React.useState(1)\n const [deliveryTotal, setDeliveryTotal] = React.useState(0)\n const [deliveryTotalPages, setDeliveryTotalPages] = React.useState(1)\n const [deliveriesLoading, setDeliveriesLoading] = React.useState(false)\n const [isRefreshingDeliveries, setIsRefreshingDeliveries] = React.useState(false)\n const [testDelivery, setTestDelivery] = React.useState<DeliveryDetail | null>(null)\n const [selectedDelivery, setSelectedDelivery] = React.useState<DeliveryDetail | null>(null)\n const [selectedDeliveryLoading, setSelectedDeliveryLoading] = React.useState(false)\n const [revealedSecret, setRevealedSecret] = React.useState<string | null>(null)\n const refreshInFlightRef = React.useRef(false)\n const access = useWebhookFeatureAccess()\n\n const fetchWebhook = React.useCallback(async (options?: { silent?: boolean }) => {\n if (!webhookId) {\n setError(t('webhooks.errors.notFound'))\n setIsLoading(false)\n return\n }\n\n const silent = options?.silent === true\n if (!silent) {\n setIsLoading(true)\n }\n\n setError(null)\n\n try {\n const call = await apiCall<Webhook>(\n `/api/webhooks/${encodeURIComponent(webhookId)}`,\n undefined,\n { fallback: null },\n )\n\n if (call.ok && call.result) {\n setWebhook(call.result)\n return\n }\n\n setError(t('webhooks.errors.notFound'))\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError.message : t('webhooks.detail.loadError'))\n } finally {\n if (!silent) {\n setIsLoading(false)\n }\n }\n }, [t, webhookId])\n\n const fetchDeliveries = React.useCallback(async (options?: { silent?: boolean }) => {\n if (!webhookId) return\n\n const silent = options?.silent === true\n if (!silent) {\n setDeliveriesLoading(true)\n }\n\n try {\n const params = new URLSearchParams()\n params.set('webhookId', webhookId)\n params.set('page', String(deliveryPage))\n params.set('pageSize', '20')\n\n const fallback: DeliveryResponse = { items: [], total: 0, page: deliveryPage, pageSize: 20, totalPages: 1 }\n const call = await apiCall<DeliveryResponse>(\n `/api/webhooks/deliveries?${params.toString()}`,\n undefined,\n { fallback },\n )\n\n if (call.ok && call.result) {\n setDeliveries(call.result.items)\n setDeliveryTotal(call.result.total)\n setDeliveryTotalPages(call.result.totalPages)\n }\n } finally {\n if (!silent) {\n setDeliveriesLoading(false)\n }\n }\n }, [deliveryPage, webhookId])\n\n const fetchDeliveryDetail = React.useCallback(async (deliveryId: string): Promise<DeliveryDetail | null> => {\n const call = await apiCall<DeliveryDetail>(\n `/api/webhooks/deliveries/${encodeURIComponent(deliveryId)}`,\n undefined,\n { fallback: null },\n )\n\n if (!call.ok || !call.result) {\n return null\n }\n\n return call.result\n }, [])\n\n const refreshDeliveryState = React.useCallback(async () => {\n if (!webhookId || refreshInFlightRef.current) return\n\n refreshInFlightRef.current = true\n setIsRefreshingDeliveries(true)\n\n try {\n await Promise.all([\n fetchWebhook({ silent: true }),\n fetchDeliveries({ silent: true }),\n ])\n\n const detailIds = [selectedDelivery?.id, testDelivery?.id].filter(\n (value): value is string => typeof value === 'string' && value.length > 0,\n )\n\n if (detailIds.length > 0) {\n const details = await Promise.all(detailIds.map((deliveryId) => fetchDeliveryDetail(deliveryId)))\n const detailMap = new Map<string, DeliveryDetail>()\n\n for (const detail of details) {\n if (detail) {\n detailMap.set(detail.id, detail)\n }\n }\n\n if (selectedDelivery?.id) {\n setSelectedDelivery((current) => current?.id ? (detailMap.get(current.id) ?? current) : current)\n }\n\n if (testDelivery?.id) {\n setTestDelivery((current) => current?.id ? (detailMap.get(current.id) ?? current) : current)\n }\n }\n } finally {\n refreshInFlightRef.current = false\n setIsRefreshingDeliveries(false)\n }\n }, [fetchDeliveries, fetchDeliveryDetail, fetchWebhook, selectedDelivery?.id, testDelivery?.id, webhookId])\n\n React.useEffect(() => {\n void fetchWebhook()\n }, [fetchWebhook])\n\n React.useEffect(() => {\n if (!webhookId) return\n void fetchDeliveries()\n }, [fetchDeliveries, webhookId])\n\n useAppEvent('webhooks.delivery.*', (event) => {\n const eventWebhookId = typeof event.payload?.webhookId === 'string' ? event.payload.webhookId : null\n if (eventWebhookId !== webhookId) return\n void refreshDeliveryState()\n }, [refreshDeliveryState, webhookId])\n\n React.useEffect(() => {\n if (!webhookId || isEditing) return\n\n const intervalId = window.setInterval(() => {\n if (document.visibilityState !== 'visible') return\n void refreshDeliveryState()\n }, DELIVERY_AUTO_REFRESH_INTERVAL_MS)\n\n return () => {\n window.clearInterval(intervalId)\n }\n }, [isEditing, refreshDeliveryState, webhookId])\n\n const handleToggleActive = React.useCallback(async () => {\n if (!webhook) return\n try {\n const call = await apiCall<Webhook>(\n `/api/webhooks/${encodeURIComponent(webhook.id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ isActive: !webhook.isActive }),\n },\n { fallback: null },\n )\n if (call.ok) {\n setWebhook((prev) => prev ? { ...prev, isActive: !prev.isActive } : prev)\n flash(t('webhooks.form.updateSuccess'), 'success')\n void refreshDeliveryState()\n }\n } catch {\n flash(t('webhooks.form.updateError'), 'error')\n }\n }, [refreshDeliveryState, webhook, t])\n\n const handleRotateSecret = React.useCallback(async () => {\n if (!webhook) return\n try {\n const call = await apiCall<{ secret: string }>(\n `/api/webhooks/${encodeURIComponent(webhook.id)}/rotate-secret`,\n { method: 'POST' },\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n flash(t('webhooks.detail.rotateError'), 'error')\n return\n }\n setRevealedSecret(call.result.secret)\n flash(t('webhooks.detail.rotateSuccess'), 'success')\n void refreshDeliveryState()\n } catch {\n flash(t('webhooks.detail.rotateError'), 'error')\n }\n }, [refreshDeliveryState, t, webhook])\n\n const handleTest = React.useCallback(async () => {\n if (!webhook) return\n try {\n const call = await apiCall<{ delivery: DeliveryDetail }>(\n `/api/webhooks/${encodeURIComponent(webhook.id)}/test`,\n { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({}) },\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n flash(t('webhooks.detail.testError'), 'error')\n return\n }\n const deliveryStatus = call.result.delivery.status\n setTestDelivery(call.result.delivery)\n flash(\n deliveryStatus === 'delivered' ? t('webhooks.detail.testSuccess') : t('webhooks.detail.testQueued'),\n deliveryStatus === 'delivered' ? 'success' : 'error',\n )\n void refreshDeliveryState()\n } catch {\n flash(t('webhooks.detail.testError'), 'error')\n }\n }, [refreshDeliveryState, t, webhook])\n\n const handleRetryDelivery = React.useCallback(async (deliveryId: string) => {\n try {\n const call = await apiCall(\n `/api/webhooks/deliveries/${encodeURIComponent(deliveryId)}/retry`,\n { method: 'POST' },\n { fallback: null },\n )\n if (!call.ok) {\n flash(t('webhooks.deliveries.retryError'), 'error')\n return\n }\n flash(t('webhooks.deliveries.retrySuccess'), 'success')\n void refreshDeliveryState()\n } catch {\n flash(t('webhooks.deliveries.retryError'), 'error')\n }\n }, [refreshDeliveryState, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!webhook) return\n try {\n await deleteCrud(`webhooks/${encodeURIComponent(webhook.id)}`, { fallbackResult: null })\n flash(t('webhooks.list.deleteSuccess'), 'success')\n router.push('/backend/webhooks')\n } catch {\n flash(t('webhooks.list.deleteError'), 'error')\n }\n }, [router, t, webhook])\n\n const handleDeliveryOpen = React.useCallback(async (deliveryId: string) => {\n setSelectedDeliveryLoading(true)\n try {\n const detail = await fetchDeliveryDetail(deliveryId)\n if (!detail) {\n flash(t('webhooks.deliveries.loadError'), 'error')\n return\n }\n setSelectedDelivery(detail)\n } catch {\n flash(t('webhooks.deliveries.loadError'), 'error')\n } finally {\n setSelectedDeliveryLoading(false)\n }\n }, [fetchDeliveryDetail, t])\n\n const deliveryColumns = React.useMemo<ColumnDef<DeliveryRow>[]>(() => [\n { accessorKey: 'eventType', header: t('webhooks.deliveries.columns.event') },\n {\n accessorKey: 'status',\n header: t('webhooks.deliveries.columns.status'),\n cell: ({ row }) => (\n <Badge variant={statusVariantMap[row.original.status] ?? 'secondary'}>\n {t(`webhooks.deliveries.status.${row.original.status}` as Parameters<typeof t>[0])}\n </Badge>\n ),\n },\n {\n accessorKey: 'responseStatus',\n header: t('webhooks.deliveries.columns.responseStatus'),\n cell: ({ row }) => row.original.responseStatus ?? '\u2014',\n },\n {\n accessorKey: 'attemptNumber',\n header: t('webhooks.deliveries.columns.attempts'),\n cell: ({ row }) => `${row.original.attemptNumber}/${row.original.maxAttempts}`,\n },\n {\n accessorKey: 'durationMs',\n header: t('webhooks.deliveries.columns.duration'),\n cell: ({ row }) => row.original.durationMs != null ? `${row.original.durationMs}ms` : '\u2014',\n },\n {\n accessorKey: 'createdAt',\n header: t('webhooks.deliveries.columns.enqueuedAt'),\n cell: ({ row }) => {\n try { return new Date(row.original.enqueuedAt).toLocaleString() }\n catch { return '\u2014' }\n },\n },\n ], [t])\n\n const fields = React.useMemo(() => buildWebhookFormFields(t), [t])\n const groups = React.useMemo(() => buildWebhookFormGroups(t), [t])\n const contentHeader = React.useMemo(() => buildWebhookFormContentHeader(t), [t])\n const menuActions = React.useMemo(() => {\n const items: Array<{ id: string; label: string; onSelect: () => void }> = []\n const isActive = webhook?.isActive ?? false\n\n if (access.canManage) {\n items.push(\n {\n id: 'edit',\n label: t('webhooks.list.actions.edit'),\n onSelect: () => setIsEditing(true),\n },\n {\n id: 'toggle-active',\n label: isActive ? t('webhooks.detail.actions.deactivate') : t('webhooks.detail.actions.activate'),\n onSelect: () => { void handleToggleActive() },\n },\n )\n }\n\n if (access.canSecrets) {\n items.push({\n id: 'rotate-secret',\n label: t('webhooks.detail.actions.rotateSecret'),\n onSelect: () => { void handleRotateSecret() },\n })\n }\n\n if (access.canTest) {\n items.push({\n id: 'test',\n label: t('webhooks.detail.actions.test'),\n onSelect: () => { void handleTest() },\n })\n }\n\n if (access.canManage) {\n items.push({\n id: 'delete',\n label: t('webhooks.list.actions.delete'),\n onSelect: () => { void handleDelete() },\n })\n }\n\n return items\n }, [access.canManage, access.canSecrets, access.canTest, handleDelete, handleRotateSecret, handleTest, handleToggleActive, t, webhook?.isActive])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('webhooks.detail.loading')} /></PageBody></Page>\n if (error || !webhook) return <Page><PageBody><ErrorMessage label={error ?? t('webhooks.errors.notFound')} /></PageBody></Page>\n\n if (isEditing) {\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('webhooks.form.title.edit')}\n backHref={`/backend/webhooks/${webhook.id}`}\n fields={fields}\n groups={groups}\n initialValues={createWebhookInitialValues(webhook)}\n submitLabel={t('common.save')}\n cancelHref={`/backend/webhooks/${webhook.id}`}\n contentHeader={contentHeader}\n onDelete={access.canManage ? handleDelete : undefined}\n onSubmit={async (values) => {\n const payload = normalizeWebhookFormPayload(values as WebhookFormValues, t)\n await updateCrud(`webhooks/${encodeURIComponent(webhook.id)}`, payload)\n flash(t('webhooks.form.updateSuccess'), 'success')\n setIsEditing(false)\n await refreshDeliveryState()\n }}\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n {revealedSecret ? (\n <WebhookSecretPanel secret={revealedSecret} onClose={() => setRevealedSecret(null)} />\n ) : null}\n <FormHeader\n mode=\"detail\"\n title={webhook.name}\n entityTypeLabel={t('webhooks.nav.title')}\n statusBadge={\n <Badge\n className={webhook.isActive\n ? 'border-transparent bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'\n : 'border-transparent bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'}\n >\n {webhook.isActive ? t('webhooks.list.status.active') : t('webhooks.list.status.inactive')}\n </Badge>\n }\n backHref=\"/backend/webhooks\"\n menuActions={menuActions}\n />\n\n <div className=\"mt-6 space-y-4\">\n {!access.isLoading && !access.canManage && !access.canSecrets && !access.canTest ? (\n <Alert variant=\"info\">\n <AlertDescription>{t('webhooks.detail.readOnlyTip')}</AlertDescription>\n </Alert>\n ) : null}\n <div className=\"grid gap-3 lg:grid-cols-2\">\n <Alert variant=\"info\">\n <AlertDescription>{t('webhooks.detail.deliveryTip')}</AlertDescription>\n </Alert>\n <Alert variant=\"info\">\n <AlertDescription>{t('webhooks.detail.signatureTip')}</AlertDescription>\n </Alert>\n </div>\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.url')}:</span>\n <code className=\"ml-2 text-xs break-all\">{webhook.url}</code>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.httpMethod')}:</span>\n <span className=\"ml-2\">{webhook.httpMethod}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.subscribedEvents')}:</span>\n <span className=\"ml-2 text-xs\">{webhook.subscribedEvents.join(', ')}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.maxRetries')}:</span>\n <span className=\"ml-2\">{webhook.maxRetries}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.rateLimitPerMinute')}:</span>\n <span className=\"ml-2\">{webhook.rateLimitPerMinute}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.autoDisableThreshold')}:</span>\n <span className=\"ml-2\">{webhook.autoDisableThreshold}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.detail.consecutiveFailures')}:</span>\n <span className=\"ml-2\">{webhook.consecutiveFailures}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.form.secret')}:</span>\n <span className=\"ml-2 inline-flex items-center gap-2\">\n <span className=\"font-mono text-xs\">{webhook.maskedSecret}</span>\n {access.canSecrets ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7 px-2 text-xs\"\n onClick={() => { void handleRotateSecret() }}\n >\n <RotateCw className=\"mr-1.5 size-3.5\" />\n {t('webhooks.detail.actions.rotateSecret')}\n </Button>\n ) : null}\n </span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.list.columns.lastDelivery')}:</span>\n <span className=\"ml-2\">{webhook.lastSuccessAt ?? webhook.lastFailureAt ?? '\u2014'}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground\">{t('webhooks.detail.previousSecretSetAt')}:</span>\n <span className=\"ml-2\">{webhook.previousSecretSetAt ?? '\u2014'}</span>\n </div>\n <div className=\"col-span-2\">\n <span className=\"text-muted-foreground\">{t('webhooks.form.customHeaders')}:</span>\n <pre className=\"mt-2 rounded border bg-muted/40 p-3 text-xs\">\n {webhook.customHeaders ? JSON.stringify(webhook.customHeaders, null, 2) : '\u2014'}\n </pre>\n </div>\n </div>\n </div>\n\n {testDelivery ? (\n <div className=\"mt-8 rounded-lg border bg-card p-4\">\n <h2 className=\"text-sm font-semibold\">{t('webhooks.detail.testResult')}</h2>\n <div className=\"mt-3 grid gap-2 text-sm\">\n <div>{t('webhooks.deliveries.columns.status')}: {testDelivery.status}</div>\n <div>{t('webhooks.deliveries.columns.responseStatus')}: {testDelivery.responseStatus ?? '\u2014'}</div>\n <div>{t('webhooks.deliveries.columns.duration')}: {testDelivery.durationMs != null ? `${testDelivery.durationMs}ms` : '\u2014'}</div>\n <pre className=\"overflow-auto rounded border bg-muted/40 p-3 text-xs\">\n {JSON.stringify(testDelivery.payload, null, 2)}\n </pre>\n </div>\n </div>\n ) : null}\n\n <div className=\"mt-8\">\n <DataTable\n title={t('webhooks.deliveries.title')}\n actions={(\n <div className=\"flex items-center gap-2\">\n <span className=\"hidden text-xs text-muted-foreground md:inline\">\n {t('webhooks.deliveries.autoRefreshHint')}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => { void refreshDeliveryState() }}\n disabled={isRefreshingDeliveries}\n >\n {isRefreshingDeliveries\n ? t('webhooks.deliveries.refreshing')\n : t('webhooks.deliveries.refresh')}\n </Button>\n </div>\n )}\n columns={deliveryColumns}\n data={deliveries}\n onRowClick={(row) => { void handleDeliveryOpen(row.id) }}\n rowActions={(row) => {\n const items: Array<{ id: string; label: string; onSelect: () => void }> = [\n {\n id: 'view-details',\n label: t('webhooks.deliveries.actions.viewDetails'),\n onSelect: () => { void handleDeliveryOpen(row.id) },\n },\n ]\n\n if (access.canManage && (row.status === 'failed' || row.status === 'expired')) {\n items.push({\n id: 'retry',\n label: t('webhooks.deliveries.actions.retry'),\n onSelect: () => { void handleRetryDelivery(row.id) },\n })\n }\n\n return <RowActions items={items} />\n }}\n perspective={{ tableId: 'webhooks.deliveries' }}\n pagination={{\n page: deliveryPage,\n pageSize: 20,\n total: deliveryTotal,\n totalPages: deliveryTotalPages,\n onPageChange: setDeliveryPage,\n }}\n isLoading={deliveriesLoading || isRefreshingDeliveries}\n />\n </div>\n\n {selectedDelivery || selectedDeliveryLoading ? (\n <div className=\"mt-6 rounded-lg border bg-card p-4\">\n <h2 className=\"text-sm font-semibold\">{t('webhooks.deliveries.detailTitle')}</h2>\n {selectedDeliveryLoading || !selectedDelivery ? (\n <div className=\"mt-3 text-sm text-muted-foreground\">{t('common.loading')}</div>\n ) : (\n <div className=\"mt-3 space-y-4 text-sm\">\n <div>{t('webhooks.deliveries.columns.status')}: {selectedDelivery.status}</div>\n <div>{t('webhooks.deliveries.columns.responseStatus')}: {selectedDelivery.responseStatus ?? '\u2014'}</div>\n <div>{t('webhooks.deliveries.columns.duration')}: {selectedDelivery.durationMs != null ? `${selectedDelivery.durationMs}ms` : '\u2014'}</div>\n <div>\n <div className=\"mb-2 font-medium\">{t('webhooks.deliveries.requestBody')}</div>\n <pre className=\"overflow-auto rounded border bg-muted/40 p-3 text-xs\">\n {JSON.stringify(selectedDelivery.payload, null, 2)}\n </pre>\n </div>\n <div>\n <div className=\"mb-2 font-medium\">{t('webhooks.deliveries.responseBody')}</div>\n <pre className=\"overflow-auto rounded border bg-muted/40 p-3 text-xs\">\n {selectedDelivery.responseBody ?? '\u2014'}\n </pre>\n </div>\n <div>\n <div className=\"mb-2 font-medium\">{t('webhooks.deliveries.responseHeaders')}</div>\n <pre className=\"overflow-auto rounded border bg-muted/40 p-3 text-xs\">\n {selectedDelivery.responseHeaders ? JSON.stringify(selectedDelivery.responseHeaders, null, 2) : '\u2014'}\n </pre>\n </div>\n </div>\n )}\n </div>\n ) : null}\n </PageBody>\n </Page>\n )\n}\n\nfunction resolveWebhookId(paramValue: string | string[] | undefined, pathname: string | null): string | null {\n if (typeof paramValue === 'string' && paramValue.trim().length > 0) {\n return paramValue\n }\n\n if (Array.isArray(paramValue)) {\n const first = paramValue.find((value) => typeof value === 'string' && value.trim().length > 0)\n if (first) return first\n }\n\n if (typeof pathname === 'string') {\n const match = pathname.match(/\\/backend\\/webhooks\\/([^/?#]+)/)\n if (match?.[1]) {\n return decodeURIComponent(match[1])\n }\n }\n\n return null\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["params"]
|
|
7
7
|
}
|
|
@@ -12,7 +12,7 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
|
12
12
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
13
13
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
14
14
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
15
|
-
import {
|
|
15
|
+
import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
|
|
16
16
|
import { useWebhookFeatureAccess } from "./useWebhookFeatureAccess.js";
|
|
17
17
|
function WebhooksListPage() {
|
|
18
18
|
const [rows, setRows] = React.useState([]);
|
|
@@ -186,7 +186,10 @@ function WebhooksListPage() {
|
|
|
186
186
|
], [t]);
|
|
187
187
|
return /* @__PURE__ */ jsxs(Page, { children: [
|
|
188
188
|
/* @__PURE__ */ jsxs(PageBody, { className: "space-y-4", children: [
|
|
189
|
-
/* @__PURE__ */
|
|
189
|
+
/* @__PURE__ */ jsxs(Alert, { variant: "info", children: [
|
|
190
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: t("webhooks.list.description") }),
|
|
191
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.list.operatorTip") })
|
|
192
|
+
] }),
|
|
190
193
|
/* @__PURE__ */ jsx(
|
|
191
194
|
DataTable,
|
|
192
195
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/webhooks/backend/webhooks/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Notice } from '@open-mercato/ui/primitives/Notice'\nimport { useWebhookFeatureAccess } from './useWebhookFeatureAccess'\n\ntype Row = {\n id: string\n name: string\n description: string | null\n url: string\n subscribedEvents: string[]\n httpMethod: string\n isActive: boolean\n deliveryStrategy: string\n maxRetries: number\n consecutiveFailures: number\n lastSuccessAt: string | null\n lastFailureAt: string | null\n createdAt: string\n updatedAt: string\n}\n\ntype ResponsePayload = {\n items: Row[]\n total: number\n page: number\n totalPages: number\n}\n\nexport default function WebhooksListPage() {\n const [rows, setRows] = React.useState<Row[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n const router = useRouter()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const access = useWebhookFeatureAccess()\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '20')\n if (search) params.set('search', search)\n if (typeof filterValues.status === 'string' && filterValues.status.length > 0) {\n params.set('isActive', filterValues.status)\n }\n const fallback: ResponsePayload = { items: [], total: 0, page, totalPages: 1 }\n const call = await apiCall<ResponsePayload>(\n `/api/webhooks?${params.toString()}`,\n undefined,\n { fallback },\n )\n if (!call.ok) {\n const errorPayload = call.result as { error?: string } | undefined\n const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('webhooks.list.loadError')\n flash(message, 'error')\n return\n }\n const payload = call.result ?? fallback\n if (!cancelled) {\n setRows(Array.isArray(payload.items) ? payload.items : [])\n setTotal(payload.total || 0)\n setTotalPages(payload.totalPages || 1)\n }\n } catch (error) {\n if (!cancelled) {\n const message = error instanceof Error ? error.message : t('webhooks.list.loadError')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [filterValues.status, page, search, reloadToken, scopeVersion, t])\n\n const handleDelete = React.useCallback(async (row: Row) => {\n const confirmed = await confirm({\n title: t('webhooks.list.confirmDelete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await apiCall<{ error?: string }>(\n `/api/webhooks/${encodeURIComponent(row.id)}`,\n { method: 'DELETE' },\n { fallback: null },\n )\n if (!call.ok) {\n const errorPayload = call.result as { error?: string } | undefined\n const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('webhooks.list.deleteError')\n flash(message, 'error')\n return\n }\n flash(t('webhooks.list.deleteSuccess'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n const message = error instanceof Error ? error.message : t('webhooks.list.deleteError')\n flash(message, 'error')\n }\n }, [confirm, t])\n\n const handleToggleActive = React.useCallback(async (row: Row) => {\n try {\n const call = await apiCall<Row>(\n `/api/webhooks/${encodeURIComponent(row.id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ isActive: !row.isActive }),\n },\n { fallback: null },\n )\n if (!call.ok) {\n flash(t('webhooks.form.updateError'), 'error')\n return\n }\n flash(t('webhooks.form.updateSuccess'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n flash(error instanceof Error ? error.message : t('webhooks.form.updateError'), 'error')\n }\n }, [t])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: 'status',\n label: t('webhooks.list.filters.status'),\n type: 'select',\n options: [\n { value: 'true', label: t('webhooks.list.status.active') },\n { value: 'false', label: t('webhooks.list.status.inactive') },\n ],\n },\n ], [t])\n\n const columns = React.useMemo<ColumnDef<Row>[]>(() => [\n {\n accessorKey: 'name',\n header: t('webhooks.list.columns.name'),\n cell: ({ row }) => (\n <Link href={`/backend/webhooks/${row.original.id}`} className=\"font-medium text-primary hover:underline\">\n {row.original.name}\n </Link>\n ),\n },\n {\n accessorKey: 'url',\n header: t('webhooks.list.columns.url'),\n cell: ({ row }) => (\n <code className=\"text-xs truncate max-w-[200px] block\" title={row.original.url}>{row.original.url}</code>\n ),\n meta: { truncate: true, maxWidth: 250 },\n },\n {\n accessorKey: 'subscribedEvents',\n header: t('webhooks.list.columns.events'),\n cell: ({ row }) => {\n const events = row.original.subscribedEvents\n if (events.length === 0) return <span className=\"text-muted-foreground text-xs\">\u2014</span>\n if (events.length <= 2) return <span className=\"text-xs\">{events.join(', ')}</span>\n return <span className=\"text-xs\">{events.slice(0, 2).join(', ')} +{events.length - 2}</span>\n },\n },\n {\n accessorKey: 'isActive',\n header: t('webhooks.list.columns.status'),\n cell: ({ row }) => (\n <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${row.original.isActive ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'}`}>\n {row.original.isActive ? t('webhooks.list.status.active') : t('webhooks.list.status.inactive')}\n </span>\n ),\n },\n {\n accessorKey: 'lastSuccessAt',\n header: t('webhooks.list.columns.lastDelivery'),\n cell: ({ row }) => {\n const lastSuccessAt = row.original.lastSuccessAt ? new Date(row.original.lastSuccessAt).getTime() : null\n const lastFailureAt = row.original.lastFailureAt ? new Date(row.original.lastFailureAt).getTime() : null\n const lastTimestamp = Math.max(lastSuccessAt ?? 0, lastFailureAt ?? 0)\n if (!lastTimestamp) return <span className=\"text-muted-foreground\">\u2014</span>\n const isFailure = lastFailureAt !== null && lastFailureAt >= (lastSuccessAt ?? 0)\n return (\n <span className={isFailure ? 'text-destructive' : 'text-muted-foreground'}>\n {new Date(lastTimestamp).toLocaleString()}\n </span>\n )\n },\n },\n {\n accessorKey: 'createdAt',\n header: t('webhooks.list.columns.createdAt'),\n cell: ({ row }) => {\n try {\n return new Date(row.original.createdAt).toLocaleDateString()\n } catch {\n return '\u2014'\n }\n },\n },\n ], [t])\n\n return (\n <Page>\n <PageBody className=\"space-y-4\">\n <Notice title={t('webhooks.list.description')} message={t('webhooks.list.operatorTip')} />\n <DataTable\n title={t('webhooks.list.title')}\n actions={access.canManage ? (\n <Button asChild>\n <Link href=\"/backend/webhooks/create\">{t('webhooks.nav.create')}</Link>\n </Button>\n ) : undefined}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(next) => {\n setFilterValues(next)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n perspective={{ tableId: 'webhooks.list' }}\n rowActions={(row) => {\n const items = [\n {\n id: 'view-deliveries',\n label: t('webhooks.list.actions.viewDeliveries'),\n onSelect: () => { router.push(`/backend/webhooks/${row.id}`) },\n },\n ]\n\n if (access.canManage) {\n items.unshift(\n { id: 'edit', label: t('webhooks.list.actions.edit'), onSelect: () => { router.push(`/backend/webhooks/${row.id}`) } },\n {\n id: 'toggle-active',\n label: row.isActive ? t('webhooks.detail.actions.deactivate') : t('webhooks.detail.actions.activate'),\n onSelect: () => { void handleToggleActive(row) },\n },\n )\n items.push({ id: 'delete', label: t('webhooks.list.actions.delete'), onSelect: () => { void handleDelete(row) } })\n }\n\n return <RowActions items={items} />\n }}\n pagination={{ page, pageSize: 20, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AAqKQ,cAoBO,YApBP;AApKR,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAC1B,SAAS,MAAM,gBAAgB;AAC/B,SAAS,iBAAiB;AAE1B,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,mCAAmC;AAC5C,SAAS,YAAY;AACrB,SAAS,wBAAwB;AAEjC,SAAS,
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { useWebhookFeatureAccess } from './useWebhookFeatureAccess'\n\ntype Row = {\n id: string\n name: string\n description: string | null\n url: string\n subscribedEvents: string[]\n httpMethod: string\n isActive: boolean\n deliveryStrategy: string\n maxRetries: number\n consecutiveFailures: number\n lastSuccessAt: string | null\n lastFailureAt: string | null\n createdAt: string\n updatedAt: string\n}\n\ntype ResponsePayload = {\n items: Row[]\n total: number\n page: number\n totalPages: number\n}\n\nexport default function WebhooksListPage() {\n const [rows, setRows] = React.useState<Row[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n const router = useRouter()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const access = useWebhookFeatureAccess()\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '20')\n if (search) params.set('search', search)\n if (typeof filterValues.status === 'string' && filterValues.status.length > 0) {\n params.set('isActive', filterValues.status)\n }\n const fallback: ResponsePayload = { items: [], total: 0, page, totalPages: 1 }\n const call = await apiCall<ResponsePayload>(\n `/api/webhooks?${params.toString()}`,\n undefined,\n { fallback },\n )\n if (!call.ok) {\n const errorPayload = call.result as { error?: string } | undefined\n const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('webhooks.list.loadError')\n flash(message, 'error')\n return\n }\n const payload = call.result ?? fallback\n if (!cancelled) {\n setRows(Array.isArray(payload.items) ? payload.items : [])\n setTotal(payload.total || 0)\n setTotalPages(payload.totalPages || 1)\n }\n } catch (error) {\n if (!cancelled) {\n const message = error instanceof Error ? error.message : t('webhooks.list.loadError')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [filterValues.status, page, search, reloadToken, scopeVersion, t])\n\n const handleDelete = React.useCallback(async (row: Row) => {\n const confirmed = await confirm({\n title: t('webhooks.list.confirmDelete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await apiCall<{ error?: string }>(\n `/api/webhooks/${encodeURIComponent(row.id)}`,\n { method: 'DELETE' },\n { fallback: null },\n )\n if (!call.ok) {\n const errorPayload = call.result as { error?: string } | undefined\n const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('webhooks.list.deleteError')\n flash(message, 'error')\n return\n }\n flash(t('webhooks.list.deleteSuccess'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n const message = error instanceof Error ? error.message : t('webhooks.list.deleteError')\n flash(message, 'error')\n }\n }, [confirm, t])\n\n const handleToggleActive = React.useCallback(async (row: Row) => {\n try {\n const call = await apiCall<Row>(\n `/api/webhooks/${encodeURIComponent(row.id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ isActive: !row.isActive }),\n },\n { fallback: null },\n )\n if (!call.ok) {\n flash(t('webhooks.form.updateError'), 'error')\n return\n }\n flash(t('webhooks.form.updateSuccess'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n flash(error instanceof Error ? error.message : t('webhooks.form.updateError'), 'error')\n }\n }, [t])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: 'status',\n label: t('webhooks.list.filters.status'),\n type: 'select',\n options: [\n { value: 'true', label: t('webhooks.list.status.active') },\n { value: 'false', label: t('webhooks.list.status.inactive') },\n ],\n },\n ], [t])\n\n const columns = React.useMemo<ColumnDef<Row>[]>(() => [\n {\n accessorKey: 'name',\n header: t('webhooks.list.columns.name'),\n cell: ({ row }) => (\n <Link href={`/backend/webhooks/${row.original.id}`} className=\"font-medium text-primary hover:underline\">\n {row.original.name}\n </Link>\n ),\n },\n {\n accessorKey: 'url',\n header: t('webhooks.list.columns.url'),\n cell: ({ row }) => (\n <code className=\"text-xs truncate max-w-[200px] block\" title={row.original.url}>{row.original.url}</code>\n ),\n meta: { truncate: true, maxWidth: 250 },\n },\n {\n accessorKey: 'subscribedEvents',\n header: t('webhooks.list.columns.events'),\n cell: ({ row }) => {\n const events = row.original.subscribedEvents\n if (events.length === 0) return <span className=\"text-muted-foreground text-xs\">\u2014</span>\n if (events.length <= 2) return <span className=\"text-xs\">{events.join(', ')}</span>\n return <span className=\"text-xs\">{events.slice(0, 2).join(', ')} +{events.length - 2}</span>\n },\n },\n {\n accessorKey: 'isActive',\n header: t('webhooks.list.columns.status'),\n cell: ({ row }) => (\n <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${row.original.isActive ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'}`}>\n {row.original.isActive ? t('webhooks.list.status.active') : t('webhooks.list.status.inactive')}\n </span>\n ),\n },\n {\n accessorKey: 'lastSuccessAt',\n header: t('webhooks.list.columns.lastDelivery'),\n cell: ({ row }) => {\n const lastSuccessAt = row.original.lastSuccessAt ? new Date(row.original.lastSuccessAt).getTime() : null\n const lastFailureAt = row.original.lastFailureAt ? new Date(row.original.lastFailureAt).getTime() : null\n const lastTimestamp = Math.max(lastSuccessAt ?? 0, lastFailureAt ?? 0)\n if (!lastTimestamp) return <span className=\"text-muted-foreground\">\u2014</span>\n const isFailure = lastFailureAt !== null && lastFailureAt >= (lastSuccessAt ?? 0)\n return (\n <span className={isFailure ? 'text-destructive' : 'text-muted-foreground'}>\n {new Date(lastTimestamp).toLocaleString()}\n </span>\n )\n },\n },\n {\n accessorKey: 'createdAt',\n header: t('webhooks.list.columns.createdAt'),\n cell: ({ row }) => {\n try {\n return new Date(row.original.createdAt).toLocaleDateString()\n } catch {\n return '\u2014'\n }\n },\n },\n ], [t])\n\n return (\n <Page>\n <PageBody className=\"space-y-4\">\n <Alert variant=\"info\">\n <AlertTitle>{t('webhooks.list.description')}</AlertTitle>\n <AlertDescription>{t('webhooks.list.operatorTip')}</AlertDescription>\n </Alert>\n <DataTable\n title={t('webhooks.list.title')}\n actions={access.canManage ? (\n <Button asChild>\n <Link href=\"/backend/webhooks/create\">{t('webhooks.nav.create')}</Link>\n </Button>\n ) : undefined}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(next) => {\n setFilterValues(next)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n perspective={{ tableId: 'webhooks.list' }}\n rowActions={(row) => {\n const items = [\n {\n id: 'view-deliveries',\n label: t('webhooks.list.actions.viewDeliveries'),\n onSelect: () => { router.push(`/backend/webhooks/${row.id}`) },\n },\n ]\n\n if (access.canManage) {\n items.unshift(\n { id: 'edit', label: t('webhooks.list.actions.edit'), onSelect: () => { router.push(`/backend/webhooks/${row.id}`) } },\n {\n id: 'toggle-active',\n label: row.isActive ? t('webhooks.detail.actions.deactivate') : t('webhooks.detail.actions.activate'),\n onSelect: () => { void handleToggleActive(row) },\n },\n )\n items.push({ id: 'delete', label: t('webhooks.list.actions.delete'), onSelect: () => { void handleDelete(row) } })\n }\n\n return <RowActions items={items} />\n }}\n pagination={{ page, pageSize: 20, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Check, Copy } from "lucide-react";
|
|
5
5
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
6
|
-
import {
|
|
6
|
+
import { Alert, AlertDescription } from "@open-mercato/ui/primitives/alert";
|
|
7
7
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
8
8
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
9
9
|
function WebhookSecretPanel({ secret, onClose }) {
|
|
@@ -26,7 +26,7 @@ function WebhookSecretPanel({ secret, onClose }) {
|
|
|
26
26
|
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: t("webhooks.form.secretVisibleOnce") })
|
|
27
27
|
] }),
|
|
28
28
|
/* @__PURE__ */ jsxs("div", { className: "space-y-4 p-6", children: [
|
|
29
|
-
/* @__PURE__ */ jsx(
|
|
29
|
+
/* @__PURE__ */ jsx(Alert, { variant: "warning", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.form.secretUsageTip") }) }),
|
|
30
30
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 rounded-md border bg-muted/40 p-4", children: [
|
|
31
31
|
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 font-mono text-sm break-all", children: secret }),
|
|
32
32
|
/* @__PURE__ */ jsx(
|
|
@@ -46,8 +46,8 @@ function WebhookSecretPanel({ secret, onClose }) {
|
|
|
46
46
|
)
|
|
47
47
|
] }),
|
|
48
48
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
49
|
-
/* @__PURE__ */ jsx(
|
|
50
|
-
/* @__PURE__ */ jsx(
|
|
49
|
+
/* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.form.secretVerificationTip") }) }),
|
|
50
|
+
/* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.form.secretRotationTip") }) })
|
|
51
51
|
] }),
|
|
52
52
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [
|
|
53
53
|
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", onClick: () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/webhooks/components/WebhookSecretPanel.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Check, Copy } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {
|
|
5
|
-
"mappings": ";AAkCM,SACE,KADF;AAhCN,YAAY,WAAW;AACvB,SAAS,OAAO,YAAY;AAC5B,SAAS,cAAc;AACvB,SAAS,
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Check, Copy } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype WebhookSecretPanelProps = {\n secret: string\n onClose?: () => void\n}\n\nexport function WebhookSecretPanel({ secret, onClose }: WebhookSecretPanelProps) {\n const t = useT()\n const [copied, setCopied] = React.useState(false)\n\n async function handleCopySecret() {\n await navigator.clipboard.writeText(secret)\n setCopied(true)\n flash(t('webhooks.form.secretCopied'), 'success')\n }\n\n React.useEffect(() => {\n if (!copied) return\n const timeout = window.setTimeout(() => setCopied(false), 2000)\n return () => window.clearTimeout(timeout)\n }, [copied])\n\n const CopyIcon = copied ? Check : Copy\n\n return (\n <div className=\"mx-auto w-full max-w-3xl rounded-xl border bg-card shadow-sm\">\n <div className=\"border-b p-6\">\n <h1 className=\"text-lg font-semibold leading-7\">{t('webhooks.form.secret')}</h1>\n <p className=\"mt-2 text-sm text-muted-foreground\">{t('webhooks.form.secretVisibleOnce')}</p>\n </div>\n <div className=\"space-y-4 p-6\">\n <Alert variant=\"warning\">\n <AlertDescription>{t('webhooks.form.secretUsageTip')}</AlertDescription>\n </Alert>\n <div className=\"flex items-start gap-3 rounded-md border bg-muted/40 p-4\">\n <div className=\"min-w-0 flex-1 font-mono text-sm break-all\">\n {secret}\n </div>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"shrink-0\"\n aria-label={t('webhooks.form.secretCopy')}\n title={t('webhooks.form.secretCopy')}\n onClick={() => { void handleCopySecret() }}\n >\n <CopyIcon className={`h-4 w-4 ${copied ? 'text-green-600' : ''}`} />\n </Button>\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Alert variant=\"info\">\n <AlertDescription>{t('webhooks.form.secretVerificationTip')}</AlertDescription>\n </Alert>\n <Alert variant=\"info\">\n <AlertDescription>{t('webhooks.form.secretRotationTip')}</AlertDescription>\n </Alert>\n </div>\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={() => { void handleCopySecret() }}>\n <CopyIcon className={`mr-2 h-4 w-4 ${copied ? 'text-green-600' : ''}`} />\n {t('webhooks.form.secretCopy')}\n </Button>\n {onClose ? (\n <Button type=\"button\" onClick={onClose}>\n {t('common.close')}\n </Button>\n ) : null}\n </div>\n </div>\n </div>\n )\n}\n\nexport default WebhookSecretPanel\n"],
|
|
5
|
+
"mappings": ";AAkCM,SACE,KADF;AAhCN,YAAY,WAAW;AACvB,SAAS,OAAO,YAAY;AAC5B,SAAS,cAAc;AACvB,SAAS,OAAO,wBAAwB;AACxC,SAAS,aAAa;AACtB,SAAS,YAAY;AAOd,SAAS,mBAAmB,EAAE,QAAQ,QAAQ,GAA4B;AAC/E,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAEhD,iBAAe,mBAAmB;AAChC,UAAM,UAAU,UAAU,UAAU,MAAM;AAC1C,cAAU,IAAI;AACd,UAAM,EAAE,4BAA4B,GAAG,SAAS;AAAA,EAClD;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,OAAQ;AACb,UAAM,UAAU,OAAO,WAAW,MAAM,UAAU,KAAK,GAAG,GAAI;AAC9D,WAAO,MAAM,OAAO,aAAa,OAAO;AAAA,EAC1C,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,WAAW,SAAS,QAAQ;AAElC,SACE,qBAAC,SAAI,WAAU,gEACb;AAAA,yBAAC,SAAI,WAAU,gBACb;AAAA,0BAAC,QAAG,WAAU,mCAAmC,YAAE,sBAAsB,GAAE;AAAA,MAC3E,oBAAC,OAAE,WAAU,sCAAsC,YAAE,iCAAiC,GAAE;AAAA,OAC1F;AAAA,IACA,qBAAC,SAAI,WAAU,iBACb;AAAA,0BAAC,SAAM,SAAQ,WACb,8BAAC,oBAAkB,YAAE,8BAA8B,GAAE,GACvD;AAAA,MACA,qBAAC,SAAI,WAAU,4DACb;AAAA,4BAAC,SAAI,WAAU,8CACZ,kBACH;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YACV,cAAY,EAAE,0BAA0B;AAAA,YACxC,OAAO,EAAE,0BAA0B;AAAA,YACnC,SAAS,MAAM;AAAE,mBAAK,iBAAiB;AAAA,YAAE;AAAA,YAEzC,8BAAC,YAAS,WAAW,WAAW,SAAS,mBAAmB,EAAE,IAAI;AAAA;AAAA,QACpE;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,6BACb;AAAA,4BAAC,SAAM,SAAQ,QACb,8BAAC,oBAAkB,YAAE,qCAAqC,GAAE,GAC9D;AAAA,QACA,oBAAC,SAAM,SAAQ,QACb,8BAAC,oBAAkB,YAAE,iCAAiC,GAAE,GAC1D;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,oCACb;AAAA,6BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM;AAAE,eAAK,iBAAiB;AAAA,QAAE,GAC/E;AAAA,8BAAC,YAAS,WAAW,gBAAgB,SAAS,mBAAmB,EAAE,IAAI;AAAA,UACtE,EAAE,0BAA0B;AAAA,WAC/B;AAAA,QACC,UACC,oBAAC,UAAO,MAAK,UAAS,SAAS,SAC5B,YAAE,cAAc,GACnB,IACE;AAAA,SACN;AAAA,OACF;AAAA,KACF;AAEJ;AAEA,IAAO,6BAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { JsonBuilder } from "@open-mercato/ui/backend/JsonBuilder";
|
|
4
4
|
import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
5
5
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
6
|
-
import {
|
|
6
|
+
import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
|
|
7
7
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
8
8
|
async function loadWebhookEventOptions(query) {
|
|
9
9
|
const result = await apiCall(
|
|
@@ -102,21 +102,21 @@ function buildWebhookFormGroups(t) {
|
|
|
102
102
|
id: "events",
|
|
103
103
|
title: t("webhooks.form.group.events"),
|
|
104
104
|
column: 1,
|
|
105
|
-
component: () => /* @__PURE__ */ jsx(
|
|
105
|
+
component: () => /* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.form.eventsPatternTip") }) }),
|
|
106
106
|
fields: ["subscribedEvents"]
|
|
107
107
|
},
|
|
108
108
|
{
|
|
109
109
|
id: "delivery",
|
|
110
110
|
title: t("webhooks.form.group.delivery"),
|
|
111
111
|
column: 2,
|
|
112
|
-
component: () => /* @__PURE__ */ jsx(
|
|
112
|
+
component: () => /* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.form.deliveryDefaultsTip") }) }),
|
|
113
113
|
fields: ["maxRetries", "timeoutMs", "rateLimitPerMinute", "autoDisableThreshold"]
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
id: "advanced",
|
|
117
117
|
title: t("webhooks.form.group.advanced"),
|
|
118
118
|
column: 2,
|
|
119
|
-
component: () => /* @__PURE__ */ jsx(
|
|
119
|
+
component: () => /* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.form.advancedStrategyTip") }) }),
|
|
120
120
|
fields: ["customHeaders"]
|
|
121
121
|
}
|
|
122
122
|
];
|
|
@@ -169,15 +169,21 @@ function normalizeWebhookFormPayload(values, t) {
|
|
|
169
169
|
}
|
|
170
170
|
function buildWebhookFormContentHeader(t) {
|
|
171
171
|
return /* @__PURE__ */ jsxs("div", { className: "grid gap-3 lg:grid-cols-2", children: [
|
|
172
|
-
/* @__PURE__ */
|
|
173
|
-
|
|
172
|
+
/* @__PURE__ */ jsxs(Alert, { variant: "info", children: [
|
|
173
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: t("webhooks.form.guidanceTitle") }),
|
|
174
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.form.guidanceBody") })
|
|
175
|
+
] }),
|
|
176
|
+
/* @__PURE__ */ jsxs(Alert, { variant: "warning", children: [
|
|
177
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: t("webhooks.form.guidanceSecurityTitle") }),
|
|
178
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.form.guidanceSecurityBody") })
|
|
179
|
+
] })
|
|
174
180
|
] });
|
|
175
181
|
}
|
|
176
182
|
function WebhookCustomHeadersField(props) {
|
|
177
183
|
const t = useT();
|
|
178
184
|
const value = isRecord(props.value) ? props.value : {};
|
|
179
185
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
180
|
-
/* @__PURE__ */ jsx(
|
|
186
|
+
/* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("webhooks.form.customHeadersTip") }) }),
|
|
181
187
|
/* @__PURE__ */ jsx(
|
|
182
188
|
JsonBuilder,
|
|
183
189
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/webhooks/components/webhook-form-config.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport type { CrudCustomFieldRenderProps, CrudField, CrudFieldOption, CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { JsonBuilder } from '@open-mercato/ui/backend/JsonBuilder'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport {
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport type { CrudCustomFieldRenderProps, CrudField, CrudFieldOption, CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { JsonBuilder } from '@open-mercato/ui/backend/JsonBuilder'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\n\nexport type WebhookFormValues = {\n name: string\n description?: string | null\n url: string\n subscribedEvents: string[]\n httpMethod: 'POST' | 'PUT' | 'PATCH'\n maxRetries: number\n timeoutMs: number\n rateLimitPerMinute: number\n autoDisableThreshold: number\n customHeaders: Record<string, string> | null\n}\n\nexport type WebhookFormPayload = {\n name: string\n description: string | null\n url: string\n subscribedEvents: string[]\n httpMethod: 'POST' | 'PUT' | 'PATCH'\n maxRetries: number\n timeoutMs: number\n rateLimitPerMinute: number\n autoDisableThreshold: number\n customHeaders: Record<string, string> | null\n}\n\nexport async function loadWebhookEventOptions(query?: string): Promise<CrudFieldOption[]> {\n const result = await apiCall<{ data: Array<{ id: string; label: string; module?: string; category?: string; description?: string }> }>(\n '/api/webhooks/events',\n undefined,\n { fallback: { data: [] } },\n )\n\n if (!result.ok || !Array.isArray(result.result?.data)) {\n return []\n }\n\n const normalizedQuery = query?.trim().toLowerCase() ?? ''\n return result.result.data\n .filter((event) => {\n if (!normalizedQuery) return true\n return event.id.toLowerCase().includes(normalizedQuery) || event.label.toLowerCase().includes(normalizedQuery)\n })\n .map((event) => ({ value: event.id, label: event.label }))\n}\n\nexport function buildWebhookFormFields(t: TranslateFn): CrudField[] {\n return [\n {\n id: 'name',\n type: 'text',\n label: t('webhooks.form.name'),\n placeholder: t('webhooks.form.namePlaceholder'),\n required: true,\n },\n {\n id: 'description',\n type: 'textarea',\n label: t('webhooks.form.description'),\n placeholder: t('webhooks.form.descriptionPlaceholder'),\n },\n {\n id: 'url',\n type: 'text',\n label: t('webhooks.form.url'),\n placeholder: t('webhooks.form.urlPlaceholder'),\n description: t('webhooks.form.urlHint'),\n required: true,\n },\n {\n id: 'subscribedEvents',\n type: 'tags',\n label: t('webhooks.form.events'),\n placeholder: t('webhooks.form.eventsPlaceholder'),\n description: t('webhooks.form.eventsHint'),\n loadOptions: loadWebhookEventOptions,\n required: true,\n },\n {\n id: 'httpMethod',\n type: 'select',\n label: t('webhooks.form.httpMethod'),\n options: [\n { value: 'POST', label: 'POST' },\n { value: 'PUT', label: 'PUT' },\n { value: 'PATCH', label: 'PATCH' },\n ],\n },\n {\n id: 'maxRetries',\n type: 'number',\n label: t('webhooks.form.maxRetries'),\n description: t('webhooks.form.maxRetriesHint'),\n },\n {\n id: 'timeoutMs',\n type: 'number',\n label: t('webhooks.form.timeoutMs'),\n description: t('webhooks.form.timeoutMsHint'),\n },\n {\n id: 'rateLimitPerMinute',\n type: 'number',\n label: t('webhooks.form.rateLimitPerMinute'),\n description: t('webhooks.form.rateLimitPerMinuteHint'),\n },\n {\n id: 'autoDisableThreshold',\n type: 'number',\n label: t('webhooks.form.autoDisableThreshold'),\n description: t('webhooks.form.autoDisableThresholdHint'),\n },\n {\n id: 'customHeaders',\n type: 'custom',\n label: t('webhooks.form.customHeaders'),\n description: t('webhooks.form.customHeadersHint'),\n component: WebhookCustomHeadersField,\n },\n ]\n}\n\nexport function buildWebhookFormGroups(t: TranslateFn): CrudFormGroup[] {\n return [\n { id: 'general', title: t('webhooks.form.group.general'), column: 1, fields: ['name', 'description', 'url', 'httpMethod'] },\n {\n id: 'events',\n title: t('webhooks.form.group.events'),\n column: 1,\n component: () => (\n <Alert variant=\"info\">\n <AlertDescription>{t('webhooks.form.eventsPatternTip')}</AlertDescription>\n </Alert>\n ),\n fields: ['subscribedEvents'],\n },\n {\n id: 'delivery',\n title: t('webhooks.form.group.delivery'),\n column: 2,\n component: () => (\n <Alert variant=\"info\">\n <AlertDescription>{t('webhooks.form.deliveryDefaultsTip')}</AlertDescription>\n </Alert>\n ),\n fields: ['maxRetries', 'timeoutMs', 'rateLimitPerMinute', 'autoDisableThreshold'],\n },\n {\n id: 'advanced',\n title: t('webhooks.form.group.advanced'),\n column: 2,\n component: () => (\n <Alert variant=\"info\">\n <AlertDescription>{t('webhooks.form.advancedStrategyTip')}</AlertDescription>\n </Alert>\n ),\n fields: ['customHeaders'],\n },\n ]\n}\n\nexport function createWebhookInitialValues(webhook?: Partial<WebhookFormValues> & { customHeaders?: Record<string, string> | null }): WebhookFormValues {\n return {\n name: webhook?.name ?? '',\n description: webhook?.description ?? '',\n url: webhook?.url ?? '',\n subscribedEvents: Array.isArray(webhook?.subscribedEvents) ? webhook.subscribedEvents : [],\n httpMethod: webhook?.httpMethod ?? 'POST',\n maxRetries: typeof webhook?.maxRetries === 'number' ? webhook.maxRetries : 10,\n timeoutMs: typeof webhook?.timeoutMs === 'number' ? webhook.timeoutMs : 15000,\n rateLimitPerMinute: typeof webhook?.rateLimitPerMinute === 'number' ? webhook.rateLimitPerMinute : 0,\n autoDisableThreshold: typeof webhook?.autoDisableThreshold === 'number' ? webhook.autoDisableThreshold : 100,\n customHeaders: webhook?.customHeaders ?? null,\n }\n}\n\nexport function normalizeWebhookFormPayload(values: WebhookFormValues, t: TranslateFn): WebhookFormPayload {\n let customHeaders: Record<string, string> | null = null\n if (values.customHeaders != null) {\n if (!isRecord(values.customHeaders)) {\n const message = t('webhooks.form.customHeadersInvalid')\n throw createCrudFormError(message, { customHeaders: message })\n }\n\n const entries = Object.entries(values.customHeaders)\n .filter(([, value]) => value != null && String(value).trim().length > 0)\n .map(([key, value]) => [key.trim(), value] as const)\n .filter(([key]) => key.length > 0)\n\n const invalidEntry = entries.find(([, value]) => typeof value !== 'string')\n if (invalidEntry) {\n const message = t('webhooks.form.customHeadersInvalid')\n throw createCrudFormError(message, { customHeaders: message })\n }\n\n customHeaders = entries.length > 0 ? Object.fromEntries(entries) : null\n }\n\n if (!Array.isArray(values.subscribedEvents) || values.subscribedEvents.length === 0) {\n const message = t('webhooks.form.eventsRequired')\n throw createCrudFormError(message, { subscribedEvents: message })\n }\n\n return {\n name: values.name,\n description: values.description?.trim() ? values.description : null,\n url: values.url,\n subscribedEvents: values.subscribedEvents,\n httpMethod: values.httpMethod,\n maxRetries: Number(values.maxRetries),\n timeoutMs: Number(values.timeoutMs),\n rateLimitPerMinute: Number(values.rateLimitPerMinute),\n autoDisableThreshold: Number(values.autoDisableThreshold),\n customHeaders,\n }\n}\n\nexport function buildWebhookFormContentHeader(t: TranslateFn) {\n return (\n <div className=\"grid gap-3 lg:grid-cols-2\">\n <Alert variant=\"info\">\n <AlertTitle>{t('webhooks.form.guidanceTitle')}</AlertTitle>\n <AlertDescription>{t('webhooks.form.guidanceBody')}</AlertDescription>\n </Alert>\n <Alert variant=\"warning\">\n <AlertTitle>{t('webhooks.form.guidanceSecurityTitle')}</AlertTitle>\n <AlertDescription>{t('webhooks.form.guidanceSecurityBody')}</AlertDescription>\n </Alert>\n </div>\n )\n}\n\nfunction WebhookCustomHeadersField(props: CrudCustomFieldRenderProps) {\n const t = useT()\n const value = isRecord(props.value) ? props.value : {}\n\n return (\n <div className=\"space-y-3\">\n <Alert variant=\"info\">\n <AlertDescription>{t('webhooks.form.customHeadersTip')}</AlertDescription>\n </Alert>\n <JsonBuilder\n value={value}\n onChange={(nextValue) => {\n if (!isRecord(nextValue)) {\n props.setValue({})\n return\n }\n props.setValue(\n Object.fromEntries(\n Object.entries(nextValue).map(([key, value]) => [key, typeof value === 'string' ? value : String(value ?? '')]),\n ),\n )\n }}\n disabled={props.disabled}\n error={props.error}\n />\n </div>\n )\n}\n\nfunction isRecord(value: unknown): value is Record<string, string> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["value"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/webhooks",
|
|
3
|
-
"version": "0.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.2683.4878a05b8e",
|
|
4
4
|
"description": "Webhooks module for Open Mercato — Standard Webhooks compliant outbound/inbound delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -69,18 +69,18 @@
|
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@open-mercato/core": "0.5.1-develop.
|
|
73
|
-
"@open-mercato/queue": "0.5.1-develop.
|
|
74
|
-
"@open-mercato/ui": "0.5.1-develop.
|
|
72
|
+
"@open-mercato/core": "0.5.1-develop.2683.4878a05b8e",
|
|
73
|
+
"@open-mercato/queue": "0.5.1-develop.2683.4878a05b8e",
|
|
74
|
+
"@open-mercato/ui": "0.5.1-develop.2683.4878a05b8e"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"@mikro-orm/postgresql": "^6.6.10",
|
|
78
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
78
|
+
"@open-mercato/shared": "0.5.1-develop.2683.4878a05b8e",
|
|
79
79
|
"react": "^19.0.0",
|
|
80
80
|
"react-dom": "^19.0.0"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
83
|
+
"@open-mercato/shared": "0.5.1-develop.2683.4878a05b8e",
|
|
84
84
|
"@types/jest": "^30.0.0",
|
|
85
85
|
"esbuild": "^0.28.0",
|
|
86
86
|
"glob": "^13.0.6",
|
|
@@ -16,7 +16,7 @@ import { FormHeader } from '@open-mercato/ui/backend/forms'
|
|
|
16
16
|
import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
17
17
|
import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
|
|
18
18
|
import { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
19
|
-
import {
|
|
19
|
+
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
20
20
|
import {
|
|
21
21
|
buildWebhookFormContentHeader,
|
|
22
22
|
buildWebhookFormFields,
|
|
@@ -520,11 +520,17 @@ export default function WebhookDetailPage() {
|
|
|
520
520
|
|
|
521
521
|
<div className="mt-6 space-y-4">
|
|
522
522
|
{!access.isLoading && !access.canManage && !access.canSecrets && !access.canTest ? (
|
|
523
|
-
<
|
|
523
|
+
<Alert variant="info">
|
|
524
|
+
<AlertDescription>{t('webhooks.detail.readOnlyTip')}</AlertDescription>
|
|
525
|
+
</Alert>
|
|
524
526
|
) : null}
|
|
525
527
|
<div className="grid gap-3 lg:grid-cols-2">
|
|
526
|
-
<
|
|
527
|
-
|
|
528
|
+
<Alert variant="info">
|
|
529
|
+
<AlertDescription>{t('webhooks.detail.deliveryTip')}</AlertDescription>
|
|
530
|
+
</Alert>
|
|
531
|
+
<Alert variant="info">
|
|
532
|
+
<AlertDescription>{t('webhooks.detail.signatureTip')}</AlertDescription>
|
|
533
|
+
</Alert>
|
|
528
534
|
</div>
|
|
529
535
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
530
536
|
<div>
|
|
@@ -13,7 +13,7 @@ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/u
|
|
|
13
13
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
14
14
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
15
15
|
import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
|
|
16
|
-
import {
|
|
16
|
+
import { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'
|
|
17
17
|
import { useWebhookFeatureAccess } from './useWebhookFeatureAccess'
|
|
18
18
|
|
|
19
19
|
type Row = {
|
|
@@ -227,7 +227,10 @@ export default function WebhooksListPage() {
|
|
|
227
227
|
return (
|
|
228
228
|
<Page>
|
|
229
229
|
<PageBody className="space-y-4">
|
|
230
|
-
<
|
|
230
|
+
<Alert variant="info">
|
|
231
|
+
<AlertTitle>{t('webhooks.list.description')}</AlertTitle>
|
|
232
|
+
<AlertDescription>{t('webhooks.list.operatorTip')}</AlertDescription>
|
|
233
|
+
</Alert>
|
|
231
234
|
<DataTable
|
|
232
235
|
title={t('webhooks.list.title')}
|
|
233
236
|
actions={access.canManage ? (
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { Check, Copy } from 'lucide-react'
|
|
5
5
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
6
|
-
import {
|
|
6
|
+
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
7
7
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
8
8
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
9
9
|
|
|
@@ -37,9 +37,9 @@ export function WebhookSecretPanel({ secret, onClose }: WebhookSecretPanelProps)
|
|
|
37
37
|
<p className="mt-2 text-sm text-muted-foreground">{t('webhooks.form.secretVisibleOnce')}</p>
|
|
38
38
|
</div>
|
|
39
39
|
<div className="space-y-4 p-6">
|
|
40
|
-
<
|
|
41
|
-
{t('webhooks.form.secretUsageTip')}
|
|
42
|
-
</
|
|
40
|
+
<Alert variant="warning">
|
|
41
|
+
<AlertDescription>{t('webhooks.form.secretUsageTip')}</AlertDescription>
|
|
42
|
+
</Alert>
|
|
43
43
|
<div className="flex items-start gap-3 rounded-md border bg-muted/40 p-4">
|
|
44
44
|
<div className="min-w-0 flex-1 font-mono text-sm break-all">
|
|
45
45
|
{secret}
|
|
@@ -57,8 +57,12 @@ export function WebhookSecretPanel({ secret, onClose }: WebhookSecretPanelProps)
|
|
|
57
57
|
</Button>
|
|
58
58
|
</div>
|
|
59
59
|
<div className="grid gap-3 md:grid-cols-2">
|
|
60
|
-
<
|
|
61
|
-
|
|
60
|
+
<Alert variant="info">
|
|
61
|
+
<AlertDescription>{t('webhooks.form.secretVerificationTip')}</AlertDescription>
|
|
62
|
+
</Alert>
|
|
63
|
+
<Alert variant="info">
|
|
64
|
+
<AlertDescription>{t('webhooks.form.secretRotationTip')}</AlertDescription>
|
|
65
|
+
</Alert>
|
|
62
66
|
</div>
|
|
63
67
|
<div className="flex flex-wrap justify-end gap-2">
|
|
64
68
|
<Button type="button" variant="outline" onClick={() => { void handleCopySecret() }}>
|
|
@@ -4,7 +4,7 @@ import type { CrudCustomFieldRenderProps, CrudField, CrudFieldOption, CrudFormGr
|
|
|
4
4
|
import { JsonBuilder } from '@open-mercato/ui/backend/JsonBuilder'
|
|
5
5
|
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
6
6
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
7
|
-
import {
|
|
7
|
+
import { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'
|
|
8
8
|
import { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
9
9
|
|
|
10
10
|
export type WebhookFormValues = {
|
|
@@ -136,21 +136,33 @@ export function buildWebhookFormGroups(t: TranslateFn): CrudFormGroup[] {
|
|
|
136
136
|
id: 'events',
|
|
137
137
|
title: t('webhooks.form.group.events'),
|
|
138
138
|
column: 1,
|
|
139
|
-
component: () =>
|
|
139
|
+
component: () => (
|
|
140
|
+
<Alert variant="info">
|
|
141
|
+
<AlertDescription>{t('webhooks.form.eventsPatternTip')}</AlertDescription>
|
|
142
|
+
</Alert>
|
|
143
|
+
),
|
|
140
144
|
fields: ['subscribedEvents'],
|
|
141
145
|
},
|
|
142
146
|
{
|
|
143
147
|
id: 'delivery',
|
|
144
148
|
title: t('webhooks.form.group.delivery'),
|
|
145
149
|
column: 2,
|
|
146
|
-
component: () =>
|
|
150
|
+
component: () => (
|
|
151
|
+
<Alert variant="info">
|
|
152
|
+
<AlertDescription>{t('webhooks.form.deliveryDefaultsTip')}</AlertDescription>
|
|
153
|
+
</Alert>
|
|
154
|
+
),
|
|
147
155
|
fields: ['maxRetries', 'timeoutMs', 'rateLimitPerMinute', 'autoDisableThreshold'],
|
|
148
156
|
},
|
|
149
157
|
{
|
|
150
158
|
id: 'advanced',
|
|
151
159
|
title: t('webhooks.form.group.advanced'),
|
|
152
160
|
column: 2,
|
|
153
|
-
component: () =>
|
|
161
|
+
component: () => (
|
|
162
|
+
<Alert variant="info">
|
|
163
|
+
<AlertDescription>{t('webhooks.form.advancedStrategyTip')}</AlertDescription>
|
|
164
|
+
</Alert>
|
|
165
|
+
),
|
|
154
166
|
fields: ['customHeaders'],
|
|
155
167
|
},
|
|
156
168
|
]
|
|
@@ -215,8 +227,14 @@ export function normalizeWebhookFormPayload(values: WebhookFormValues, t: Transl
|
|
|
215
227
|
export function buildWebhookFormContentHeader(t: TranslateFn) {
|
|
216
228
|
return (
|
|
217
229
|
<div className="grid gap-3 lg:grid-cols-2">
|
|
218
|
-
<
|
|
219
|
-
|
|
230
|
+
<Alert variant="info">
|
|
231
|
+
<AlertTitle>{t('webhooks.form.guidanceTitle')}</AlertTitle>
|
|
232
|
+
<AlertDescription>{t('webhooks.form.guidanceBody')}</AlertDescription>
|
|
233
|
+
</Alert>
|
|
234
|
+
<Alert variant="warning">
|
|
235
|
+
<AlertTitle>{t('webhooks.form.guidanceSecurityTitle')}</AlertTitle>
|
|
236
|
+
<AlertDescription>{t('webhooks.form.guidanceSecurityBody')}</AlertDescription>
|
|
237
|
+
</Alert>
|
|
220
238
|
</div>
|
|
221
239
|
)
|
|
222
240
|
}
|
|
@@ -227,7 +245,9 @@ function WebhookCustomHeadersField(props: CrudCustomFieldRenderProps) {
|
|
|
227
245
|
|
|
228
246
|
return (
|
|
229
247
|
<div className="space-y-3">
|
|
230
|
-
<
|
|
248
|
+
<Alert variant="info">
|
|
249
|
+
<AlertDescription>{t('webhooks.form.customHeadersTip')}</AlertDescription>
|
|
250
|
+
</Alert>
|
|
231
251
|
<JsonBuilder
|
|
232
252
|
value={value}
|
|
233
253
|
onChange={(nextValue) => {
|