@burdenoff/microfe-admin 2026.625.1 → 2026.626.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/components/AccessDenied.js +2 -2
- package/dist/admin/components/AccessDenied.js.map +1 -1
- package/dist/admin/components/ActivityFeed.js +4 -4
- package/dist/admin/components/ActivityFeed.js.map +1 -1
- package/dist/admin/components/Charts.js.map +1 -1
- package/dist/admin/components/ConfirmDialog.js +1 -1
- package/dist/admin/components/ConfirmDialog.js.map +1 -1
- package/dist/admin/components/FormSheet.js +1 -1
- package/dist/admin/components/FormSheet.js.map +1 -1
- package/dist/admin/components/TenantSsoPanel.js +1 -1
- package/dist/admin/components/TenantSsoPanel.js.map +1 -1
- package/dist/admin/components/Toast.js +11 -11
- package/dist/admin/components/Toast.js.map +1 -1
- package/dist/admin/components/forms/SubscriptionForm.js +2 -2
- package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
- package/dist/admin/components/forms/TenantForm.js +1 -1
- package/dist/admin/components/forms/UserForm.js +1 -1
- package/dist/admin/components/ui.js +3 -3
- package/dist/admin/components/ui.js.map +1 -1
- package/dist/admin/constants/index.js +33 -24
- package/dist/admin/constants/index.js.map +1 -1
- package/dist/admin/pages/AuditLogsPage.js +92 -77
- package/dist/admin/pages/AuditLogsPage.js.map +1 -1
- package/dist/admin/pages/BillingPage.js +95 -51
- package/dist/admin/pages/BillingPage.js.map +1 -1
- package/dist/admin/pages/CommunicationsPage.js +97 -92
- package/dist/admin/pages/CommunicationsPage.js.map +1 -1
- package/dist/admin/pages/ContentPagesPage.js +274 -259
- package/dist/admin/pages/ContentPagesPage.js.map +1 -1
- package/dist/admin/pages/DashboardPage.js +113 -84
- package/dist/admin/pages/DashboardPage.js.map +1 -1
- package/dist/admin/pages/FeatureFlagsPage.js +93 -75
- package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
- package/dist/admin/pages/OrganizationsListPage.js +105 -87
- package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
- package/dist/admin/pages/ProductsPage.js +138 -130
- package/dist/admin/pages/ProductsPage.js.map +1 -1
- package/dist/admin/pages/ReferralsPage.js +70 -52
- package/dist/admin/pages/ReferralsPage.js.map +1 -1
- package/dist/admin/pages/RolesPermissionsPage.js +136 -118
- package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
- package/dist/admin/pages/SREPage.js +34 -32
- package/dist/admin/pages/SREPage.js.map +1 -1
- package/dist/admin/pages/SandboxSettingsPage.js +360 -322
- package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage.js +63 -60
- package/dist/admin/pages/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SyncFailuresPage.js +120 -118
- package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
- package/dist/admin/pages/TenantDetailPage.js +261 -218
- package/dist/admin/pages/TenantDetailPage.js.map +1 -1
- package/dist/admin/pages/TenantFormRoutePage.js +65 -61
- package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
- package/dist/admin/pages/TenantsListPage.js +86 -71
- package/dist/admin/pages/TenantsListPage.js.map +1 -1
- package/dist/admin/pages/UsersListPage.js +128 -113
- package/dist/admin/pages/UsersListPage.js.map +1 -1
- package/dist/admin/pages/WaitlistPage.js +78 -63
- package/dist/admin/pages/WaitlistPage.js.map +1 -1
- package/dist/admin/pages/WorkflowsPage.js +107 -102
- package/dist/admin/pages/WorkflowsPage.js.map +1 -1
- package/dist/admin/pages/WorkspacesListPage.js +61 -49
- package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
- package/dist/node_modules/@hookform/resolvers/dist/resolvers.js +2 -2
- package/dist/node_modules/@hookform/resolvers/dist/resolvers.js.map +1 -1
- package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js +35 -26
- package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js.map +1 -1
- package/dist/node_modules/react-hook-form/dist/index.esm.js +609 -516
- package/dist/node_modules/react-hook-form/dist/index.esm.js.map +1 -1
- package/package.json +2 -2
|
@@ -4,86 +4,87 @@ import { Badge as n, Button as r, Card as i, CardContent as a, EmptyState as o }
|
|
|
4
4
|
import { PageLayout as s } from "../components/PageLayout.js";
|
|
5
5
|
import { formatDateTime as c, formatRelativeTime as l } from "../utils/formatters.js";
|
|
6
6
|
import { MetricCard as u, MetricsGrid as d } from "../components/MetricCard.js";
|
|
7
|
-
import { DataTable as
|
|
8
|
-
import { ConfirmDialog as
|
|
9
|
-
import { PermissionGuard as
|
|
7
|
+
import { DataTable as ee, Pagination as te } from "../components/DataTable.js";
|
|
8
|
+
import { ConfirmDialog as ne } from "../components/ConfirmDialog.js";
|
|
9
|
+
import { PermissionGuard as re } from "../components/PermissionGuard.js";
|
|
10
10
|
import "../components/index.js";
|
|
11
|
-
import { useListSyncOutboxEntriesQuery as
|
|
12
|
-
import { SyncOutboxStatus as
|
|
13
|
-
import { useCallback as
|
|
14
|
-
import { AlertOctagon as
|
|
15
|
-
import { Fragment as
|
|
11
|
+
import { useListSyncOutboxEntriesQuery as f, useRetrySyncOutboxEntryMutation as p, useSyncOutboxStatsQuery as ie } from "../../generated/global-operations.js";
|
|
12
|
+
import { SyncOutboxStatus as m } from "../../generated/global-types.js";
|
|
13
|
+
import { useCallback as h, useMemo as g, useState as _ } from "react";
|
|
14
|
+
import { AlertOctagon as v, CheckCircle2 as y, Clock as b, RefreshCw as x, RotateCw as S } from "lucide-react";
|
|
15
|
+
import { Fragment as ae, jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
16
|
+
import { PagePurpose as T } from "@burdenoff/fe-libs/ui";
|
|
16
17
|
//#region src/admin/pages/SyncFailuresPage.tsx
|
|
17
|
-
var
|
|
18
|
+
var E = 25, D = [
|
|
18
19
|
"PENDING",
|
|
19
20
|
"RETRYING",
|
|
20
21
|
"SUCCESS",
|
|
21
22
|
"PERMANENTLY_FAILED"
|
|
22
|
-
],
|
|
23
|
-
PENDING:
|
|
24
|
-
RETRYING:
|
|
25
|
-
SUCCESS:
|
|
26
|
-
PERMANENTLY_FAILED:
|
|
23
|
+
], O = {
|
|
24
|
+
PENDING: m.Pending,
|
|
25
|
+
RETRYING: m.Retrying,
|
|
26
|
+
SUCCESS: m.Success,
|
|
27
|
+
PERMANENTLY_FAILED: m.PermanentlyFailed
|
|
27
28
|
};
|
|
28
|
-
function
|
|
29
|
+
function k(e) {
|
|
29
30
|
let t = String(e);
|
|
30
|
-
return
|
|
31
|
+
return D.includes(t) ? t : "PERMANENTLY_FAILED";
|
|
31
32
|
}
|
|
32
|
-
var
|
|
33
|
-
PENDING: "bg-warning
|
|
34
|
-
RETRYING: "bg-warning
|
|
35
|
-
SUCCESS: "bg-success
|
|
36
|
-
PERMANENTLY_FAILED: "bg-status-error-bg
|
|
37
|
-
},
|
|
33
|
+
var A = {
|
|
34
|
+
PENDING: "bg-status-warning-bg text-status-warning-text border-status-warning-border",
|
|
35
|
+
RETRYING: "bg-status-warning-bg text-status-warning-text border-status-warning-border",
|
|
36
|
+
SUCCESS: "bg-status-success-bg text-status-success-text border-status-success-border",
|
|
37
|
+
PERMANENTLY_FAILED: "bg-status-error-bg text-status-error-text border-status-error-border"
|
|
38
|
+
}, j = {
|
|
38
39
|
PENDING: "Pending",
|
|
39
40
|
RETRYING: "Retrying",
|
|
40
41
|
SUCCESS: "Success",
|
|
41
42
|
PERMANENTLY_FAILED: "Permanently failed"
|
|
42
43
|
};
|
|
43
|
-
function
|
|
44
|
-
let t =
|
|
45
|
-
return /* @__PURE__ */
|
|
44
|
+
function oe({ status: e }) {
|
|
45
|
+
let t = k(e);
|
|
46
|
+
return /* @__PURE__ */ C(n, {
|
|
46
47
|
variant: "outline",
|
|
47
|
-
className:
|
|
48
|
-
children:
|
|
48
|
+
className: A[t],
|
|
49
|
+
children: j[t]
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
|
-
function
|
|
52
|
-
let { canViewSyncFailures: n, canManageSyncFailures:
|
|
53
|
-
if (
|
|
54
|
-
}, [
|
|
52
|
+
function M() {
|
|
53
|
+
let { canViewSyncFailures: n, canManageSyncFailures: m, isLoading: D } = t(), A = e(), [j, M] = _(1), [N, P] = _("PERMANENTLY_FAILED"), [F, I] = _(null), [L, R] = _(null), z = g(() => {
|
|
54
|
+
if (N !== "all") return { statuses: [O[N]] };
|
|
55
|
+
}, [N]), B = D || !n, { data: V, loading: H, error: U, refetch: W } = f({
|
|
55
56
|
variables: {
|
|
56
|
-
filter:
|
|
57
|
-
limit:
|
|
58
|
-
offset: (
|
|
57
|
+
filter: z,
|
|
58
|
+
limit: E,
|
|
59
|
+
offset: (j - 1) * E
|
|
59
60
|
},
|
|
60
61
|
fetchPolicy: "cache-and-network",
|
|
61
|
-
skip:
|
|
62
|
-
}), { data:
|
|
62
|
+
skip: B
|
|
63
|
+
}), { data: G, loading: K, refetch: q } = ie({
|
|
63
64
|
fetchPolicy: "cache-and-network",
|
|
64
|
-
skip:
|
|
65
|
-
}), [
|
|
65
|
+
skip: B
|
|
66
|
+
}), [J, { loading: Y }] = p({
|
|
66
67
|
onCompleted: (e) => {
|
|
67
68
|
let t = e.retrySyncOutboxEntry;
|
|
68
|
-
t.success ? (
|
|
69
|
+
t.success ? (A.success("Requeued for retry", t.message ?? "The reconciler will pick it up on the next tick."), W(), q()) : A.error("Retry failed", t.message ?? "Could not requeue this entry.");
|
|
69
70
|
},
|
|
70
71
|
onError: (e) => {
|
|
71
|
-
|
|
72
|
+
A.error("Retry failed", e.message);
|
|
72
73
|
}
|
|
73
|
-
}),
|
|
74
|
-
|
|
75
|
-
}, [
|
|
76
|
-
if (
|
|
77
|
-
await
|
|
74
|
+
}), X = h(() => {
|
|
75
|
+
W(), q();
|
|
76
|
+
}, [W, q]), se = h(async () => {
|
|
77
|
+
if (F) try {
|
|
78
|
+
await J({ variables: { id: F } });
|
|
78
79
|
} finally {
|
|
79
|
-
|
|
80
|
+
I(null);
|
|
80
81
|
}
|
|
81
|
-
}, [
|
|
82
|
+
}, [F, J]), Z = V?.listSyncOutboxEntries.items ?? [], Q = V?.listSyncOutboxEntries.total ?? 0, ce = Math.max(1, Math.ceil(Q / E)), $ = G?.syncOutboxStats, le = g(() => [
|
|
82
83
|
{
|
|
83
84
|
key: "operation",
|
|
84
85
|
header: "Operation",
|
|
85
86
|
width: "180px",
|
|
86
|
-
render: (e) => /* @__PURE__ */
|
|
87
|
+
render: (e) => /* @__PURE__ */ C("code", {
|
|
87
88
|
className: "text-xs bg-bg-sunken px-1.5 py-0.5 rounded",
|
|
88
89
|
children: e.operation
|
|
89
90
|
})
|
|
@@ -91,7 +92,7 @@ function N() {
|
|
|
91
92
|
{
|
|
92
93
|
key: "workspaceId",
|
|
93
94
|
header: "Workspace",
|
|
94
|
-
render: (e) => /* @__PURE__ */
|
|
95
|
+
render: (e) => /* @__PURE__ */ C("span", {
|
|
95
96
|
className: "font-mono text-xs text-text-secondary",
|
|
96
97
|
children: e.workspaceId
|
|
97
98
|
})
|
|
@@ -100,16 +101,16 @@ function N() {
|
|
|
100
101
|
key: "status",
|
|
101
102
|
header: "Status",
|
|
102
103
|
width: "170px",
|
|
103
|
-
render: (e) => /* @__PURE__ */
|
|
104
|
+
render: (e) => /* @__PURE__ */ C(oe, { status: e.status })
|
|
104
105
|
},
|
|
105
106
|
{
|
|
106
107
|
key: "attempts",
|
|
107
108
|
header: "Attempts",
|
|
108
109
|
width: "100px",
|
|
109
110
|
align: "right",
|
|
110
|
-
render: (e) => /* @__PURE__ */
|
|
111
|
+
render: (e) => /* @__PURE__ */ w("span", {
|
|
111
112
|
className: "text-sm",
|
|
112
|
-
children: [e.attempts, /* @__PURE__ */
|
|
113
|
+
children: [e.attempts, /* @__PURE__ */ w("span", {
|
|
113
114
|
className: "text-text-secondary",
|
|
114
115
|
children: ["/", e.maxAttempts]
|
|
115
116
|
})]
|
|
@@ -119,10 +120,10 @@ function N() {
|
|
|
119
120
|
key: "nextRetryAt",
|
|
120
121
|
header: "Next retry",
|
|
121
122
|
width: "160px",
|
|
122
|
-
render: (e) => e.nextRetryAt ? /* @__PURE__ */
|
|
123
|
+
render: (e) => e.nextRetryAt ? /* @__PURE__ */ C("span", {
|
|
123
124
|
className: "text-sm text-text-secondary",
|
|
124
125
|
children: l(e.nextRetryAt)
|
|
125
|
-
}) : /* @__PURE__ */
|
|
126
|
+
}) : /* @__PURE__ */ C("span", {
|
|
126
127
|
className: "text-xs text-text-secondary",
|
|
127
128
|
children: "—"
|
|
128
129
|
})
|
|
@@ -131,7 +132,7 @@ function N() {
|
|
|
131
132
|
key: "createdAt",
|
|
132
133
|
header: "Created",
|
|
133
134
|
width: "160px",
|
|
134
|
-
render: (e) => /* @__PURE__ */
|
|
135
|
+
render: (e) => /* @__PURE__ */ C("span", {
|
|
135
136
|
className: "text-sm text-text-secondary",
|
|
136
137
|
children: l(e.createdAt)
|
|
137
138
|
})
|
|
@@ -142,70 +143,71 @@ function N() {
|
|
|
142
143
|
width: "120px",
|
|
143
144
|
align: "right",
|
|
144
145
|
render: (e) => {
|
|
145
|
-
let t =
|
|
146
|
-
return !
|
|
146
|
+
let t = k(e.status) === "PERMANENTLY_FAILED";
|
|
147
|
+
return !m || !t ? null : /* @__PURE__ */ w(r, {
|
|
147
148
|
variant: "outline",
|
|
148
149
|
size: "sm",
|
|
149
150
|
onClick: (t) => {
|
|
150
|
-
t.stopPropagation(),
|
|
151
|
+
t.stopPropagation(), I(e.id);
|
|
151
152
|
},
|
|
152
|
-
disabled:
|
|
153
|
-
children: [/* @__PURE__ */
|
|
153
|
+
disabled: Y,
|
|
154
|
+
children: [/* @__PURE__ */ C(S, { className: "mr-1.5 h-3.5 w-3.5" }), "Retry"]
|
|
154
155
|
});
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
|
-
], [
|
|
158
|
-
return /* @__PURE__ */
|
|
158
|
+
], [m, Y]);
|
|
159
|
+
return /* @__PURE__ */ C(re, {
|
|
159
160
|
allowed: n,
|
|
160
|
-
isLoading:
|
|
161
|
+
isLoading: D,
|
|
161
162
|
pageTitle: "Sync Failures",
|
|
162
163
|
denyMessage: "Only super-admins can view cross-layer sync failures.",
|
|
163
|
-
children: /* @__PURE__ */
|
|
164
|
+
children: /* @__PURE__ */ w(s, {
|
|
164
165
|
title: "Sync Failures",
|
|
165
166
|
gradientWord: "Failures",
|
|
166
167
|
description: "Cross-layer sync operations (global → workspace) that did not complete. Automatic retries run in the background; use the manual retry once the underlying issue is fixed.",
|
|
167
|
-
actions: /* @__PURE__ */
|
|
168
|
+
actions: /* @__PURE__ */ w(r, {
|
|
168
169
|
variant: "outline",
|
|
169
|
-
onClick:
|
|
170
|
-
disabled:
|
|
171
|
-
children: [/* @__PURE__ */
|
|
170
|
+
onClick: X,
|
|
171
|
+
disabled: H || K,
|
|
172
|
+
children: [/* @__PURE__ */ C(x, { className: `mr-2 h-4 w-4 ${H ? "animate-spin" : ""}` }), "Refresh"]
|
|
172
173
|
}),
|
|
173
|
-
children: [/* @__PURE__ */
|
|
174
|
+
children: [/* @__PURE__ */ w("div", {
|
|
174
175
|
className: "space-y-6",
|
|
175
176
|
children: [
|
|
176
|
-
/* @__PURE__ */
|
|
177
|
-
|
|
177
|
+
/* @__PURE__ */ C(T, { children: "When the platform can't deliver an event to a downstream system, it lands in this sync outbox. Use this page to see what failed, retry an entry once the issue is fixed, and clear out events that are stuck — keeping integrations in sync across services." }),
|
|
178
|
+
/* @__PURE__ */ w(d, { children: [
|
|
179
|
+
/* @__PURE__ */ C(u, {
|
|
178
180
|
title: "Pending",
|
|
179
181
|
hero: !0,
|
|
180
182
|
value: $?.pending ?? 0,
|
|
181
|
-
icon: /* @__PURE__ */
|
|
183
|
+
icon: /* @__PURE__ */ C(b, { className: "h-5 w-5" }),
|
|
182
184
|
subtitle: "Waiting for first retry",
|
|
183
|
-
loading:
|
|
185
|
+
loading: K
|
|
184
186
|
}),
|
|
185
|
-
/* @__PURE__ */
|
|
187
|
+
/* @__PURE__ */ C(u, {
|
|
186
188
|
title: "Retrying",
|
|
187
189
|
value: $?.retrying ?? 0,
|
|
188
|
-
icon: /* @__PURE__ */
|
|
190
|
+
icon: /* @__PURE__ */ C(S, { className: "h-5 w-5" }),
|
|
189
191
|
subtitle: "Automatic backoff in progress",
|
|
190
|
-
loading:
|
|
192
|
+
loading: K
|
|
191
193
|
}),
|
|
192
|
-
/* @__PURE__ */
|
|
194
|
+
/* @__PURE__ */ C(u, {
|
|
193
195
|
title: "Permanently failed",
|
|
194
196
|
value: $?.permanentlyFailed ?? 0,
|
|
195
|
-
icon: /* @__PURE__ */
|
|
197
|
+
icon: /* @__PURE__ */ C(v, { className: "h-5 w-5" }),
|
|
196
198
|
subtitle: "Exhausted automatic retries",
|
|
197
199
|
priority: $?.permanentlyFailed ? "critical" : "normal",
|
|
198
|
-
loading:
|
|
200
|
+
loading: K
|
|
199
201
|
}),
|
|
200
|
-
/* @__PURE__ */
|
|
202
|
+
/* @__PURE__ */ C(u, {
|
|
201
203
|
title: "Successful",
|
|
202
204
|
value: $?.successful ?? 0,
|
|
203
|
-
icon: /* @__PURE__ */
|
|
205
|
+
icon: /* @__PURE__ */ C(y, { className: "h-5 w-5" }),
|
|
204
206
|
subtitle: "Synced (auto-purged after 7 days)",
|
|
205
|
-
loading:
|
|
207
|
+
loading: K
|
|
206
208
|
})
|
|
207
209
|
] }),
|
|
208
|
-
/* @__PURE__ */
|
|
210
|
+
/* @__PURE__ */ C("div", {
|
|
209
211
|
className: "flex flex-wrap items-center gap-2",
|
|
210
212
|
children: [
|
|
211
213
|
["PERMANENTLY_FAILED", "Permanently failed"],
|
|
@@ -213,88 +215,88 @@ function N() {
|
|
|
213
215
|
["PENDING", "Pending"],
|
|
214
216
|
["SUCCESS", "Successful"],
|
|
215
217
|
["all", "All"]
|
|
216
|
-
].map(([e, t]) => /* @__PURE__ */
|
|
217
|
-
variant:
|
|
218
|
+
].map(([e, t]) => /* @__PURE__ */ C(r, {
|
|
219
|
+
variant: N === e ? "default" : "outline",
|
|
218
220
|
size: "sm",
|
|
219
221
|
onClick: () => {
|
|
220
|
-
|
|
222
|
+
P(e), M(1), R(null);
|
|
221
223
|
},
|
|
222
224
|
children: t
|
|
223
225
|
}, e))
|
|
224
226
|
}),
|
|
225
|
-
|
|
227
|
+
U && /* @__PURE__ */ C(i, { children: /* @__PURE__ */ w(a, {
|
|
226
228
|
className: "py-4 text-sm text-status-error-text",
|
|
227
|
-
children: ["Failed to load sync outbox entries: ",
|
|
229
|
+
children: ["Failed to load sync outbox entries: ", U.message]
|
|
228
230
|
}) }),
|
|
229
|
-
/* @__PURE__ */
|
|
230
|
-
columns:
|
|
231
|
+
/* @__PURE__ */ C(ee, {
|
|
232
|
+
columns: le,
|
|
231
233
|
data: Z,
|
|
232
234
|
keyExtractor: (e) => e.id,
|
|
233
|
-
loading:
|
|
234
|
-
onRowClick: (e) =>
|
|
235
|
-
emptyState: /* @__PURE__ */
|
|
235
|
+
loading: H,
|
|
236
|
+
onRowClick: (e) => R((t) => t === e.id ? null : e.id),
|
|
237
|
+
emptyState: /* @__PURE__ */ C(o, {
|
|
236
238
|
illustration: "empty-data",
|
|
237
|
-
icon: /* @__PURE__ */
|
|
239
|
+
icon: /* @__PURE__ */ C(y, { className: "h-8 w-8" }),
|
|
238
240
|
title: "No sync failures",
|
|
239
|
-
description:
|
|
241
|
+
description: N === "PERMANENTLY_FAILED" ? "Nothing has exhausted automatic retries." : "No outbox entries match the current filter."
|
|
240
242
|
})
|
|
241
243
|
}),
|
|
242
|
-
|
|
244
|
+
L && Z.filter((e) => e.id === L).map((e) => /* @__PURE__ */ C(i, { children: /* @__PURE__ */ w(a, {
|
|
243
245
|
className: "space-y-2 py-4 text-sm",
|
|
244
246
|
children: [
|
|
245
|
-
/* @__PURE__ */
|
|
247
|
+
/* @__PURE__ */ w("div", {
|
|
246
248
|
className: "flex items-center justify-between",
|
|
247
|
-
children: [/* @__PURE__ */
|
|
249
|
+
children: [/* @__PURE__ */ C("span", {
|
|
248
250
|
className: "font-medium",
|
|
249
251
|
children: "Last error"
|
|
250
|
-
}), /* @__PURE__ */
|
|
252
|
+
}), /* @__PURE__ */ C("span", {
|
|
251
253
|
className: "text-xs text-text-secondary",
|
|
252
254
|
children: e.lastAttemptAt ? `Last attempt: ${c(e.lastAttemptAt)}` : "Never attempted"
|
|
253
255
|
})]
|
|
254
256
|
}),
|
|
255
|
-
/* @__PURE__ */
|
|
257
|
+
/* @__PURE__ */ C("pre", {
|
|
256
258
|
className: "whitespace-pre-wrap break-words rounded bg-bg-sunken px-3 py-2 text-xs text-text-secondary",
|
|
257
259
|
children: e.lastError ?? "No error recorded"
|
|
258
260
|
}),
|
|
259
|
-
/* @__PURE__ */
|
|
261
|
+
/* @__PURE__ */ w("div", {
|
|
260
262
|
className: "text-xs text-text-secondary",
|
|
261
|
-
children: ["Entry id: ", /* @__PURE__ */
|
|
263
|
+
children: ["Entry id: ", /* @__PURE__ */ C("code", {
|
|
262
264
|
className: "font-mono",
|
|
263
265
|
children: e.id
|
|
264
266
|
})]
|
|
265
267
|
})
|
|
266
268
|
]
|
|
267
269
|
}) }, e.id)),
|
|
268
|
-
Q >
|
|
269
|
-
page:
|
|
270
|
-
totalPages:
|
|
270
|
+
Q > E && /* @__PURE__ */ C(te, {
|
|
271
|
+
page: j,
|
|
272
|
+
totalPages: ce,
|
|
271
273
|
totalItems: Q,
|
|
272
|
-
pageSize:
|
|
274
|
+
pageSize: E,
|
|
273
275
|
onPageChange: (e) => {
|
|
274
|
-
|
|
276
|
+
M(e), R(null);
|
|
275
277
|
}
|
|
276
278
|
})
|
|
277
279
|
]
|
|
278
|
-
}), /* @__PURE__ */
|
|
279
|
-
open: !!
|
|
280
|
-
onClose: () =>
|
|
281
|
-
onConfirm:
|
|
280
|
+
}), /* @__PURE__ */ C(ne, {
|
|
281
|
+
open: !!F,
|
|
282
|
+
onClose: () => I(null),
|
|
283
|
+
onConfirm: se,
|
|
282
284
|
title: "Retry this sync?",
|
|
283
|
-
description: /* @__PURE__ */
|
|
285
|
+
description: /* @__PURE__ */ w(ae, { children: [
|
|
284
286
|
"The entry will be flipped to ",
|
|
285
|
-
/* @__PURE__ */
|
|
287
|
+
/* @__PURE__ */ C("strong", { children: "RETRYING" }),
|
|
286
288
|
" and the background reconciler will pick it up on its next tick. Attempts will reset to 0 so the entry gets a fresh retry budget.",
|
|
287
|
-
/* @__PURE__ */
|
|
288
|
-
/* @__PURE__ */
|
|
289
|
+
/* @__PURE__ */ C("br", {}),
|
|
290
|
+
/* @__PURE__ */ C("br", {}),
|
|
289
291
|
"Make sure the underlying issue (e.g. a downstream service that was down) is actually resolved — otherwise it will fail again."
|
|
290
292
|
] }),
|
|
291
|
-
confirmLabel:
|
|
292
|
-
loading:
|
|
293
|
+
confirmLabel: Y ? "Requeueing…" : "Retry",
|
|
294
|
+
loading: Y
|
|
293
295
|
})]
|
|
294
296
|
})
|
|
295
297
|
});
|
|
296
298
|
}
|
|
297
299
|
//#endregion
|
|
298
|
-
export {
|
|
300
|
+
export { M as SyncFailuresPage };
|
|
299
301
|
|
|
300
302
|
//# sourceMappingURL=SyncFailuresPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncFailuresPage.js","names":[],"sources":["../../../src/admin/pages/SyncFailuresPage.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { RefreshCw, AlertOctagon, Clock, CheckCircle2, RotateCw } from 'lucide-react';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n MetricCard,\n MetricsGrid,\n Card,\n CardContent,\n Badge,\n Button,\n DataTable,\n Pagination,\n EmptyState,\n PermissionGuard,\n ConfirmDialog,\n useToast,\n} from '../components';\nimport { formatDateTime, formatRelativeTime } from '../utils/formatters';\nimport { SyncOutboxStatus } from '../../generated/global-types';\nimport {\n useListSyncOutboxEntriesQuery,\n useSyncOutboxStatsQuery,\n useRetrySyncOutboxEntryMutation,\n type ListSyncOutboxEntriesQuery,\n} from '../../generated/global-operations';\n\nconst PAGE_SIZE = 25;\n\n// Single source of truth for the underlying string values. Authored as a\n// const-tuple so TypeScript derives both the union type and a runtime guard.\n// The mapping below is type-checked: if codegen ever drops or renames a\n// SyncOutboxStatus enum member, the build fails at STATUS_TO_GQL, not at\n// runtime.\nconst STATUS_VALUES = ['PENDING', 'RETRYING', 'SUCCESS', 'PERMANENTLY_FAILED'] as const;\ntype StatusValue = (typeof STATUS_VALUES)[number];\ntype StatusFilter = 'all' | StatusValue;\n\ntype OutboxEntry = ListSyncOutboxEntriesQuery['listSyncOutboxEntries']['items'][number];\n\n// Typed bridge between our boundary string union and the codegen-generated\n// enum. Explicit mapping makes the codegen casing dependency a compile-time\n// constraint instead of a runtime cast.\nconst STATUS_TO_GQL: Record<StatusValue, SyncOutboxStatus> = {\n PENDING: SyncOutboxStatus.Pending,\n RETRYING: SyncOutboxStatus.Retrying,\n SUCCESS: SyncOutboxStatus.Success,\n PERMANENTLY_FAILED: SyncOutboxStatus.PermanentlyFailed,\n};\n\n// Reverse map: codegen enum value → boundary string. Falls back to\n// PERMANENTLY_FAILED if codegen ever emits an unknown value — a loud-but-safe\n// default that surfaces the drift without breaking the page.\nfunction asStatusValue(s: SyncOutboxStatus): StatusValue {\n const raw = String(s) as StatusValue;\n return (STATUS_VALUES as readonly string[]).includes(raw) ? raw : 'PERMANENTLY_FAILED';\n}\n\nconst STATUS_VARIANTS: Record<StatusValue, string> = {\n PENDING: 'bg-warning/10 text-warning border-warning/30',\n RETRYING: 'bg-warning/10 text-warning border-warning/30',\n SUCCESS: 'bg-success/10 text-success border-success/30',\n PERMANENTLY_FAILED: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/30',\n};\n\nconst STATUS_LABELS: Record<StatusValue, string> = {\n PENDING: 'Pending',\n RETRYING: 'Retrying',\n SUCCESS: 'Success',\n PERMANENTLY_FAILED: 'Permanently failed',\n};\n\nfunction StatusBadge({ status }: { status: SyncOutboxStatus }) {\n const key = asStatusValue(status);\n return (\n <Badge variant=\"outline\" className={STATUS_VARIANTS[key]}>\n {STATUS_LABELS[key]}\n </Badge>\n );\n}\n\n/**\n * Sync Failures admin page.\n *\n * Shows the state of the cross-layer sync outbox so a super-admin can spot\n * users/workspaces that didn't get fully provisioned (e.g. signup happened\n * while a downstream service was down) and manually requeue them once the\n * underlying problem is fixed.\n *\n * Manual retry only flips the row to RETRYING; the background reconciler in\n * global-tenant-svc actually performs the sync on its next tick. That keeps\n * the existing single-writer retry semantics intact.\n */\nexport function SyncFailuresPage() {\n const {\n canViewSyncFailures,\n canManageSyncFailures,\n isLoading: permissionsLoading,\n } = useAdminPermissions();\n const toast = useToast();\n\n const [page, setPage] = useState(1);\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('PERMANENTLY_FAILED');\n const [confirmRetryId, setConfirmRetryId] = useState<string | null>(null);\n const [expandedErrorId, setExpandedErrorId] = useState<string | null>(null);\n\n const filter = useMemo(() => {\n if (statusFilter === 'all') return undefined;\n // Explicit, type-checked mapping — replaces the previous `as unknown as`\n // double-cast. If codegen ever renames a SyncOutboxStatus enum member,\n // the build fails at STATUS_TO_GQL above, not silently at runtime.\n return { statuses: [STATUS_TO_GQL[statusFilter]] };\n }, [statusFilter]);\n\n const skipQueries = permissionsLoading || !canViewSyncFailures;\n\n const {\n data: listData,\n loading: listLoading,\n error: listError,\n refetch: refetchList,\n } = useListSyncOutboxEntriesQuery({\n variables: {\n filter,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const {\n data: statsData,\n loading: statsLoading,\n refetch: refetchStats,\n } = useSyncOutboxStatsQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const [retryEntry, { loading: retrying }] = useRetrySyncOutboxEntryMutation({\n onCompleted: (data) => {\n const result = data.retrySyncOutboxEntry;\n if (result.success) {\n toast.success(\n 'Requeued for retry',\n result.message ?? 'The reconciler will pick it up on the next tick.'\n );\n void refetchList();\n void refetchStats();\n } else {\n toast.error('Retry failed', result.message ?? 'Could not requeue this entry.');\n }\n },\n onError: (err) => {\n toast.error('Retry failed', err.message);\n },\n });\n\n const handleRefresh = useCallback(() => {\n void refetchList();\n void refetchStats();\n }, [refetchList, refetchStats]);\n\n const handleConfirmRetry = useCallback(async () => {\n if (!confirmRetryId) return;\n // Apollo's mutate promise rejects on network/GraphQL error; without the\n // finally the dialog would stay open and ConfirmDialog's awaited\n // onConfirm would see an unhandled rejection. The onError toast still\n // surfaces the failure to the user.\n try {\n await retryEntry({ variables: { id: confirmRetryId } });\n } finally {\n setConfirmRetryId(null);\n }\n }, [confirmRetryId, retryEntry]);\n\n const entries = listData?.listSyncOutboxEntries.items ?? [];\n const total = listData?.listSyncOutboxEntries.total ?? 0;\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));\n const stats = statsData?.syncOutboxStats;\n\n const columns = useMemo(\n () => [\n {\n key: 'operation',\n header: 'Operation',\n width: '180px',\n render: (entry: OutboxEntry) => (\n <code className=\"text-xs bg-bg-sunken px-1.5 py-0.5 rounded\">{entry.operation}</code>\n ),\n },\n {\n key: 'workspaceId',\n header: 'Workspace',\n render: (entry: OutboxEntry) => (\n <span className=\"font-mono text-xs text-text-secondary\">{entry.workspaceId}</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '170px',\n render: (entry: OutboxEntry) => <StatusBadge status={entry.status} />,\n },\n {\n key: 'attempts',\n header: 'Attempts',\n width: '100px',\n align: 'right' as const,\n render: (entry: OutboxEntry) => (\n <span className=\"text-sm\">\n {entry.attempts}\n <span className=\"text-text-secondary\">/{entry.maxAttempts}</span>\n </span>\n ),\n },\n {\n key: 'nextRetryAt',\n header: 'Next retry',\n width: '160px',\n render: (entry: OutboxEntry) =>\n entry.nextRetryAt ? (\n <span className=\"text-sm text-text-secondary\">\n {formatRelativeTime(entry.nextRetryAt)}\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">—</span>\n ),\n },\n {\n key: 'createdAt',\n header: 'Created',\n width: '160px',\n render: (entry: OutboxEntry) => (\n <span className=\"text-sm text-text-secondary\">\n {formatRelativeTime(entry.createdAt)}\n </span>\n ),\n },\n {\n key: 'actions',\n header: '',\n width: '120px',\n align: 'right' as const,\n render: (entry: OutboxEntry) => {\n // Retry is only meaningful for entries that have exhausted automatic\n // retries. PENDING/RETRYING entries already get picked up by the\n // background reconciler. SUCCESS entries don't need retrying.\n const isRetryable = asStatusValue(entry.status) === 'PERMANENTLY_FAILED';\n if (!canManageSyncFailures || !isRetryable) {\n return null;\n }\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={(e) => {\n e.stopPropagation();\n setConfirmRetryId(entry.id);\n }}\n disabled={retrying}\n >\n <RotateCw className=\"mr-1.5 h-3.5 w-3.5\" />\n Retry\n </Button>\n );\n },\n },\n ],\n [canManageSyncFailures, retrying]\n );\n\n return (\n <PermissionGuard\n allowed={canViewSyncFailures}\n isLoading={permissionsLoading}\n pageTitle=\"Sync Failures\"\n denyMessage=\"Only super-admins can view cross-layer sync failures.\"\n >\n <PageLayout\n title=\"Sync Failures\"\n gradientWord=\"Failures\"\n description=\"Cross-layer sync operations (global → workspace) that did not complete. Automatic retries run in the background; use the manual retry once the underlying issue is fixed.\"\n actions={\n <Button variant=\"outline\" onClick={handleRefresh} disabled={listLoading || statsLoading}>\n <RefreshCw className={`mr-2 h-4 w-4 ${listLoading ? 'animate-spin' : ''}`} />\n Refresh\n </Button>\n }\n >\n <div className=\"space-y-6\">\n {/* Stats */}\n <MetricsGrid>\n <MetricCard\n title=\"Pending\"\n hero\n value={stats?.pending ?? 0}\n icon={<Clock className=\"h-5 w-5\" />}\n subtitle=\"Waiting for first retry\"\n loading={statsLoading}\n />\n <MetricCard\n title=\"Retrying\"\n value={stats?.retrying ?? 0}\n icon={<RotateCw className=\"h-5 w-5\" />}\n subtitle=\"Automatic backoff in progress\"\n loading={statsLoading}\n />\n <MetricCard\n title=\"Permanently failed\"\n value={stats?.permanentlyFailed ?? 0}\n icon={<AlertOctagon className=\"h-5 w-5\" />}\n subtitle=\"Exhausted automatic retries\"\n priority={stats?.permanentlyFailed ? 'critical' : 'normal'}\n loading={statsLoading}\n />\n <MetricCard\n title=\"Successful\"\n value={stats?.successful ?? 0}\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n subtitle=\"Synced (auto-purged after 7 days)\"\n loading={statsLoading}\n />\n </MetricsGrid>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {(\n [\n ['PERMANENTLY_FAILED', 'Permanently failed'],\n ['RETRYING', 'Retrying'],\n ['PENDING', 'Pending'],\n ['SUCCESS', 'Successful'],\n ['all', 'All'],\n ] as Array<[StatusFilter, string]>\n ).map(([value, label]) => (\n <Button\n key={value}\n variant={statusFilter === value ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n setStatusFilter(value);\n setPage(1);\n // Clear any expanded error detail card — the row it referenced\n // may not be in the new result set.\n setExpandedErrorId(null);\n }}\n >\n {label}\n </Button>\n ))}\n </div>\n\n {/* Error banner */}\n {listError && (\n <Card>\n <CardContent className=\"py-4 text-sm text-status-error-text\">\n Failed to load sync outbox entries: {listError.message}\n </CardContent>\n </Card>\n )}\n\n {/* Table */}\n <DataTable\n columns={columns}\n data={entries}\n keyExtractor={(entry) => entry.id}\n loading={listLoading}\n onRowClick={(entry: OutboxEntry) =>\n setExpandedErrorId((current) => (current === entry.id ? null : entry.id))\n }\n emptyState={\n <EmptyState\n illustration=\"empty-data\"\n icon={<CheckCircle2 className=\"h-8 w-8\" />}\n title=\"No sync failures\"\n description={\n statusFilter === 'PERMANENTLY_FAILED'\n ? 'Nothing has exhausted automatic retries.'\n : 'No outbox entries match the current filter.'\n }\n />\n }\n />\n\n {/* Expanded error detail */}\n {expandedErrorId &&\n entries\n .filter((e: OutboxEntry) => e.id === expandedErrorId)\n .map((entry: OutboxEntry) => (\n <Card key={entry.id}>\n <CardContent className=\"space-y-2 py-4 text-sm\">\n <div className=\"flex items-center justify-between\">\n <span className=\"font-medium\">Last error</span>\n <span className=\"text-xs text-text-secondary\">\n {entry.lastAttemptAt\n ? `Last attempt: ${formatDateTime(entry.lastAttemptAt)}`\n : 'Never attempted'}\n </span>\n </div>\n <pre className=\"whitespace-pre-wrap break-words rounded bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {entry.lastError ?? 'No error recorded'}\n </pre>\n <div className=\"text-xs text-text-secondary\">\n Entry id: <code className=\"font-mono\">{entry.id}</code>\n </div>\n </CardContent>\n </Card>\n ))}\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={(p) => {\n setPage(p);\n // Same rationale as the filter buttons — the previously\n // expanded row may not be in the new page's result set.\n setExpandedErrorId(null);\n }}\n />\n )}\n </div>\n\n {/* Retry confirm */}\n <ConfirmDialog\n open={!!confirmRetryId}\n onClose={() => setConfirmRetryId(null)}\n onConfirm={handleConfirmRetry}\n title=\"Retry this sync?\"\n description={\n <>\n The entry will be flipped to <strong>RETRYING</strong> and the background reconciler\n will pick it up on its next tick. Attempts will reset to 0 so the entry gets a fresh\n retry budget.\n <br />\n <br />\n Make sure the underlying issue (e.g. a downstream service that was down) is actually\n resolved — otherwise it will fail again.\n </>\n }\n confirmLabel={retrying ? 'Requeueing…' : 'Retry'}\n loading={retrying}\n />\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SyncFailuresPage.js","names":[],"sources":["../../../src/admin/pages/SyncFailuresPage.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { RefreshCw, AlertOctagon, Clock, CheckCircle2, RotateCw } from 'lucide-react';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n MetricCard,\n MetricsGrid,\n Card,\n CardContent,\n Badge,\n Button,\n DataTable,\n Pagination,\n EmptyState,\n PermissionGuard,\n ConfirmDialog,\n useToast,\n} from '../components';\nimport { formatDateTime, formatRelativeTime } from '../utils/formatters';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { SyncOutboxStatus } from '../../generated/global-types';\nimport {\n useListSyncOutboxEntriesQuery,\n useSyncOutboxStatsQuery,\n useRetrySyncOutboxEntryMutation,\n type ListSyncOutboxEntriesQuery,\n} from '../../generated/global-operations';\n\nconst PAGE_SIZE = 25;\n\n// Single source of truth for the underlying string values. Authored as a\n// const-tuple so TypeScript derives both the union type and a runtime guard.\n// The mapping below is type-checked: if codegen ever drops or renames a\n// SyncOutboxStatus enum member, the build fails at STATUS_TO_GQL, not at\n// runtime.\nconst STATUS_VALUES = ['PENDING', 'RETRYING', 'SUCCESS', 'PERMANENTLY_FAILED'] as const;\ntype StatusValue = (typeof STATUS_VALUES)[number];\ntype StatusFilter = 'all' | StatusValue;\n\ntype OutboxEntry = ListSyncOutboxEntriesQuery['listSyncOutboxEntries']['items'][number];\n\n// Typed bridge between our boundary string union and the codegen-generated\n// enum. Explicit mapping makes the codegen casing dependency a compile-time\n// constraint instead of a runtime cast.\nconst STATUS_TO_GQL: Record<StatusValue, SyncOutboxStatus> = {\n PENDING: SyncOutboxStatus.Pending,\n RETRYING: SyncOutboxStatus.Retrying,\n SUCCESS: SyncOutboxStatus.Success,\n PERMANENTLY_FAILED: SyncOutboxStatus.PermanentlyFailed,\n};\n\n// Reverse map: codegen enum value → boundary string. Falls back to\n// PERMANENTLY_FAILED if codegen ever emits an unknown value — a loud-but-safe\n// default that surfaces the drift without breaking the page.\nfunction asStatusValue(s: SyncOutboxStatus): StatusValue {\n const raw = String(s) as StatusValue;\n return (STATUS_VALUES as readonly string[]).includes(raw) ? raw : 'PERMANENTLY_FAILED';\n}\n\nconst STATUS_VARIANTS: Record<StatusValue, string> = {\n PENDING: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n RETRYING: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n SUCCESS: 'bg-status-success-bg text-status-success-text border-status-success-border',\n PERMANENTLY_FAILED: 'bg-status-error-bg text-status-error-text border-status-error-border',\n};\n\nconst STATUS_LABELS: Record<StatusValue, string> = {\n PENDING: 'Pending',\n RETRYING: 'Retrying',\n SUCCESS: 'Success',\n PERMANENTLY_FAILED: 'Permanently failed',\n};\n\nfunction StatusBadge({ status }: { status: SyncOutboxStatus }) {\n const key = asStatusValue(status);\n return (\n <Badge variant=\"outline\" className={STATUS_VARIANTS[key]}>\n {STATUS_LABELS[key]}\n </Badge>\n );\n}\n\n/**\n * Sync Failures admin page.\n *\n * Shows the state of the cross-layer sync outbox so a super-admin can spot\n * users/workspaces that didn't get fully provisioned (e.g. signup happened\n * while a downstream service was down) and manually requeue them once the\n * underlying problem is fixed.\n *\n * Manual retry only flips the row to RETRYING; the background reconciler in\n * global-tenant-svc actually performs the sync on its next tick. That keeps\n * the existing single-writer retry semantics intact.\n */\nexport function SyncFailuresPage() {\n const {\n canViewSyncFailures,\n canManageSyncFailures,\n isLoading: permissionsLoading,\n } = useAdminPermissions();\n const toast = useToast();\n\n const [page, setPage] = useState(1);\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('PERMANENTLY_FAILED');\n const [confirmRetryId, setConfirmRetryId] = useState<string | null>(null);\n const [expandedErrorId, setExpandedErrorId] = useState<string | null>(null);\n\n const filter = useMemo(() => {\n if (statusFilter === 'all') return undefined;\n // Explicit, type-checked mapping — replaces the previous `as unknown as`\n // double-cast. If codegen ever renames a SyncOutboxStatus enum member,\n // the build fails at STATUS_TO_GQL above, not silently at runtime.\n return { statuses: [STATUS_TO_GQL[statusFilter]] };\n }, [statusFilter]);\n\n const skipQueries = permissionsLoading || !canViewSyncFailures;\n\n const {\n data: listData,\n loading: listLoading,\n error: listError,\n refetch: refetchList,\n } = useListSyncOutboxEntriesQuery({\n variables: {\n filter,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const {\n data: statsData,\n loading: statsLoading,\n refetch: refetchStats,\n } = useSyncOutboxStatsQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const [retryEntry, { loading: retrying }] = useRetrySyncOutboxEntryMutation({\n onCompleted: (data) => {\n const result = data.retrySyncOutboxEntry;\n if (result.success) {\n toast.success(\n 'Requeued for retry',\n result.message ?? 'The reconciler will pick it up on the next tick.'\n );\n void refetchList();\n void refetchStats();\n } else {\n toast.error('Retry failed', result.message ?? 'Could not requeue this entry.');\n }\n },\n onError: (err) => {\n toast.error('Retry failed', err.message);\n },\n });\n\n const handleRefresh = useCallback(() => {\n void refetchList();\n void refetchStats();\n }, [refetchList, refetchStats]);\n\n const handleConfirmRetry = useCallback(async () => {\n if (!confirmRetryId) return;\n // Apollo's mutate promise rejects on network/GraphQL error; without the\n // finally the dialog would stay open and ConfirmDialog's awaited\n // onConfirm would see an unhandled rejection. The onError toast still\n // surfaces the failure to the user.\n try {\n await retryEntry({ variables: { id: confirmRetryId } });\n } finally {\n setConfirmRetryId(null);\n }\n }, [confirmRetryId, retryEntry]);\n\n const entries = listData?.listSyncOutboxEntries.items ?? [];\n const total = listData?.listSyncOutboxEntries.total ?? 0;\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));\n const stats = statsData?.syncOutboxStats;\n\n const columns = useMemo(\n () => [\n {\n key: 'operation',\n header: 'Operation',\n width: '180px',\n render: (entry: OutboxEntry) => (\n <code className=\"text-xs bg-bg-sunken px-1.5 py-0.5 rounded\">{entry.operation}</code>\n ),\n },\n {\n key: 'workspaceId',\n header: 'Workspace',\n render: (entry: OutboxEntry) => (\n <span className=\"font-mono text-xs text-text-secondary\">{entry.workspaceId}</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '170px',\n render: (entry: OutboxEntry) => <StatusBadge status={entry.status} />,\n },\n {\n key: 'attempts',\n header: 'Attempts',\n width: '100px',\n align: 'right' as const,\n render: (entry: OutboxEntry) => (\n <span className=\"text-sm\">\n {entry.attempts}\n <span className=\"text-text-secondary\">/{entry.maxAttempts}</span>\n </span>\n ),\n },\n {\n key: 'nextRetryAt',\n header: 'Next retry',\n width: '160px',\n render: (entry: OutboxEntry) =>\n entry.nextRetryAt ? (\n <span className=\"text-sm text-text-secondary\">\n {formatRelativeTime(entry.nextRetryAt)}\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">—</span>\n ),\n },\n {\n key: 'createdAt',\n header: 'Created',\n width: '160px',\n render: (entry: OutboxEntry) => (\n <span className=\"text-sm text-text-secondary\">\n {formatRelativeTime(entry.createdAt)}\n </span>\n ),\n },\n {\n key: 'actions',\n header: '',\n width: '120px',\n align: 'right' as const,\n render: (entry: OutboxEntry) => {\n // Retry is only meaningful for entries that have exhausted automatic\n // retries. PENDING/RETRYING entries already get picked up by the\n // background reconciler. SUCCESS entries don't need retrying.\n const isRetryable = asStatusValue(entry.status) === 'PERMANENTLY_FAILED';\n if (!canManageSyncFailures || !isRetryable) {\n return null;\n }\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={(e) => {\n e.stopPropagation();\n setConfirmRetryId(entry.id);\n }}\n disabled={retrying}\n >\n <RotateCw className=\"mr-1.5 h-3.5 w-3.5\" />\n Retry\n </Button>\n );\n },\n },\n ],\n [canManageSyncFailures, retrying]\n );\n\n return (\n <PermissionGuard\n allowed={canViewSyncFailures}\n isLoading={permissionsLoading}\n pageTitle=\"Sync Failures\"\n denyMessage=\"Only super-admins can view cross-layer sync failures.\"\n >\n <PageLayout\n title=\"Sync Failures\"\n gradientWord=\"Failures\"\n description=\"Cross-layer sync operations (global → workspace) that did not complete. Automatic retries run in the background; use the manual retry once the underlying issue is fixed.\"\n actions={\n <Button variant=\"outline\" onClick={handleRefresh} disabled={listLoading || statsLoading}>\n <RefreshCw className={`mr-2 h-4 w-4 ${listLoading ? 'animate-spin' : ''}`} />\n Refresh\n </Button>\n }\n >\n <div className=\"space-y-6\">\n <PagePurpose>\n When the platform can't deliver an event to a downstream system, it lands in this\n sync outbox. Use this page to see what failed, retry an entry once the issue is fixed,\n and clear out events that are stuck — keeping integrations in sync across services.\n </PagePurpose>\n\n {/* Stats */}\n <MetricsGrid>\n <MetricCard\n title=\"Pending\"\n hero\n value={stats?.pending ?? 0}\n icon={<Clock className=\"h-5 w-5\" />}\n subtitle=\"Waiting for first retry\"\n loading={statsLoading}\n />\n <MetricCard\n title=\"Retrying\"\n value={stats?.retrying ?? 0}\n icon={<RotateCw className=\"h-5 w-5\" />}\n subtitle=\"Automatic backoff in progress\"\n loading={statsLoading}\n />\n <MetricCard\n title=\"Permanently failed\"\n value={stats?.permanentlyFailed ?? 0}\n icon={<AlertOctagon className=\"h-5 w-5\" />}\n subtitle=\"Exhausted automatic retries\"\n priority={stats?.permanentlyFailed ? 'critical' : 'normal'}\n loading={statsLoading}\n />\n <MetricCard\n title=\"Successful\"\n value={stats?.successful ?? 0}\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n subtitle=\"Synced (auto-purged after 7 days)\"\n loading={statsLoading}\n />\n </MetricsGrid>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {(\n [\n ['PERMANENTLY_FAILED', 'Permanently failed'],\n ['RETRYING', 'Retrying'],\n ['PENDING', 'Pending'],\n ['SUCCESS', 'Successful'],\n ['all', 'All'],\n ] as Array<[StatusFilter, string]>\n ).map(([value, label]) => (\n <Button\n key={value}\n variant={statusFilter === value ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n setStatusFilter(value);\n setPage(1);\n // Clear any expanded error detail card — the row it referenced\n // may not be in the new result set.\n setExpandedErrorId(null);\n }}\n >\n {label}\n </Button>\n ))}\n </div>\n\n {/* Error banner */}\n {listError && (\n <Card>\n <CardContent className=\"py-4 text-sm text-status-error-text\">\n Failed to load sync outbox entries: {listError.message}\n </CardContent>\n </Card>\n )}\n\n {/* Table */}\n <DataTable\n columns={columns}\n data={entries}\n keyExtractor={(entry) => entry.id}\n loading={listLoading}\n onRowClick={(entry: OutboxEntry) =>\n setExpandedErrorId((current) => (current === entry.id ? null : entry.id))\n }\n emptyState={\n <EmptyState\n illustration=\"empty-data\"\n icon={<CheckCircle2 className=\"h-8 w-8\" />}\n title=\"No sync failures\"\n description={\n statusFilter === 'PERMANENTLY_FAILED'\n ? 'Nothing has exhausted automatic retries.'\n : 'No outbox entries match the current filter.'\n }\n />\n }\n />\n\n {/* Expanded error detail */}\n {expandedErrorId &&\n entries\n .filter((e: OutboxEntry) => e.id === expandedErrorId)\n .map((entry: OutboxEntry) => (\n <Card key={entry.id}>\n <CardContent className=\"space-y-2 py-4 text-sm\">\n <div className=\"flex items-center justify-between\">\n <span className=\"font-medium\">Last error</span>\n <span className=\"text-xs text-text-secondary\">\n {entry.lastAttemptAt\n ? `Last attempt: ${formatDateTime(entry.lastAttemptAt)}`\n : 'Never attempted'}\n </span>\n </div>\n <pre className=\"whitespace-pre-wrap break-words rounded bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {entry.lastError ?? 'No error recorded'}\n </pre>\n <div className=\"text-xs text-text-secondary\">\n Entry id: <code className=\"font-mono\">{entry.id}</code>\n </div>\n </CardContent>\n </Card>\n ))}\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={(p) => {\n setPage(p);\n // Same rationale as the filter buttons — the previously\n // expanded row may not be in the new page's result set.\n setExpandedErrorId(null);\n }}\n />\n )}\n </div>\n\n {/* Retry confirm */}\n <ConfirmDialog\n open={!!confirmRetryId}\n onClose={() => setConfirmRetryId(null)}\n onConfirm={handleConfirmRetry}\n title=\"Retry this sync?\"\n description={\n <>\n The entry will be flipped to <strong>RETRYING</strong> and the background reconciler\n will pick it up on its next tick. Attempts will reset to 0 so the entry gets a fresh\n retry budget.\n <br />\n <br />\n Make sure the underlying issue (e.g. a downstream service that was down) is actually\n resolved — otherwise it will fail again.\n </>\n }\n confirmLabel={retrying ? 'Requeueing…' : 'Retry'}\n loading={retrying}\n />\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|