bunqueue-dashboard 0.0.35 → 0.0.37
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/README.md +36 -16
- package/agent/backup/processExecutor.ts +90 -0
- package/agent/backup/resultValidation.ts +85 -0
- package/agent/backup/routes.ts +161 -0
- package/agent/backup/runner.ts +91 -0
- package/agent/backup/standaloneWorker.ts +23 -0
- package/agent/backup/workerExecutor.ts +40 -0
- package/agent/backup/workerFactory.ts +21 -0
- package/agent/backup/workerProtocol.ts +10 -0
- package/agent/db/core.ts +141 -0
- package/agent/db/csvExport.ts +172 -0
- package/agent/db/exportTimeout.ts +190 -0
- package/agent/db/query.ts +82 -0
- package/agent/db/queryTimeout.ts +76 -0
- package/agent/db/schema.ts +79 -0
- package/agent/db/tables.ts +157 -0
- package/agent/db/types.ts +118 -0
- package/agent/db/workerFactory.ts +25 -0
- package/agent/db.ts +43 -1099
- package/agent/errorMessage.ts +9 -0
- package/agent/flow/routes.ts +251 -0
- package/agent/flow/service.ts +266 -0
- package/agent/flow/types.ts +34 -0
- package/agent/flow/validation.ts +230 -0
- package/agent/index.ts +22 -21
- package/agent/logger.ts +121 -17
- package/agent/managedTarget.ts +82 -0
- package/agent/manager/config.ts +84 -0
- package/agent/manager/logs.ts +83 -0
- package/agent/manager/process.ts +233 -0
- package/agent/manager/storage.ts +28 -0
- package/agent/manager/types.ts +38 -0
- package/agent/manager.ts +10 -490
- package/agent/queue/routes.ts +135 -0
- package/agent/queue/runtime.ts +119 -0
- package/agent/queue/types.ts +34 -0
- package/agent/queue/validation.ts +100 -0
- package/agent/server/controlRoutes.ts +185 -0
- package/agent/server/controlTarget.ts +116 -0
- package/agent/server/databaseRoutes.ts +165 -0
- package/agent/server/errors.ts +49 -0
- package/agent/server/handler.ts +155 -0
- package/agent/server/jsonBody.ts +109 -0
- package/agent/server/lifecycle.ts +72 -0
- package/agent/server/managedRuntime.ts +41 -0
- package/agent/server/policy.ts +60 -0
- package/agent/server/router.ts +161 -0
- package/agent/server/types.ts +46 -0
- package/agent/server/workflowReadRoutes.ts +80 -0
- package/agent/server.ts +26 -441
- package/agent/shutdown.ts +156 -0
- package/agent/workflow/codec.ts +78 -0
- package/agent/workflow/detail.ts +63 -0
- package/agent/workflow/list.ts +87 -0
- package/agent/workflow/runtime.ts +274 -0
- package/agent/workflow/runtimeConfig.ts +103 -0
- package/agent/workflow/runtimeRoutes.ts +163 -0
- package/agent/workflow/sqlite.ts +52 -0
- package/agent/workflow/stats.ts +79 -0
- package/agent/workflow/types.ts +81 -0
- package/agent/workflow/validation.ts +114 -0
- package/agent/workflows.ts +14 -489
- package/dist/.vite/manifest.json +611 -250
- package/dist/assets/AddJob-CWkkVHmf.js +3 -0
- package/dist/assets/Alerts-Gh1pVbqj.js +1 -0
- package/dist/assets/{AreaChart-REK4ZsTC.js → AreaChart-w0ruozXH.js} +1 -1
- package/dist/assets/Benchmark-Dir2A1CQ.js +1 -0
- package/dist/assets/BulkAddJobs-Di_IgRSH.js +5 -0
- package/dist/assets/Button-BPqpRZJD.js +1 -0
- package/dist/assets/Card-C7NMhqUt.js +1 -0
- package/dist/assets/{CopilotPanel-COLw-atS.js → CopilotPanel-Db5nYpo1.js} +3 -3
- package/dist/assets/Cron-BRgWcBGk.js +1 -0
- package/dist/assets/CronManager-CbM0x_s0.js +1 -0
- package/dist/assets/Database-BeoEreW0.js +3 -0
- package/dist/assets/Diagnostics-Cw9iJ84f.js +1 -0
- package/dist/assets/Dlq-DfCg04Jd.js +1 -0
- package/dist/assets/DlqControl-BRCkUXvb.js +1 -0
- package/dist/assets/DlqPro-BHJwBYsN.js +1 -0
- package/dist/assets/Flows-BAzbFP_W.js +2 -0
- package/dist/assets/JobInspector-qvW_APO6.js +2 -0
- package/dist/assets/Jobs-URpnFjC6.js +1 -0
- package/dist/assets/JobsPro-pQ_RFSU3.js +1 -0
- package/dist/assets/Logs-C5jX2OPE.js +1 -0
- package/dist/assets/LogsPro-Cq_npmmS.js +1 -0
- package/dist/assets/{McpServer-CbaU52l2.js → McpServer-DtCTmQBu.js} +2 -2
- package/dist/assets/Metrics-BdxitePr.js +1 -0
- package/dist/assets/MetricsPro-orHfW0mg.js +1 -0
- package/dist/assets/Overview-DaNns1aj.js +1 -0
- package/dist/assets/PageHeader-BXlZSBcc.js +1 -0
- package/dist/assets/Pagination-Q-vf4hsX.js +1 -0
- package/dist/assets/QueueActions-jiTzndPH.js +1 -0
- package/dist/assets/QueueControl-l5bSdMgb.js +1 -0
- package/dist/assets/QueueDetail-URK2pjtH.js +1 -0
- package/dist/assets/QueueDetailPro-C9M5CV4K.js +1 -0
- package/dist/assets/Queues-DeLXlww2.js +1 -0
- package/dist/assets/QueuesOverview-DoQ-MUfT.js +1 -0
- package/dist/assets/S3Backup-D2agV9_z.js +1 -0
- package/dist/assets/S3BackupPro--WSZWTQn.js +3 -0
- package/dist/assets/ServerControl-QVd6m_iR.js +3 -0
- package/dist/assets/Settings-CiJRj_SM.js +1 -0
- package/dist/assets/StatusBadge-B0I7hNBX.js +1 -0
- package/dist/assets/Usage-DbHJgA4t.js +1 -0
- package/dist/assets/UsagePro-LoPo3k1L.js +1 -0
- package/dist/assets/Webhooks-CuzIJHwk.js +1 -0
- package/dist/assets/Workers-BpUgpwUH.js +1 -0
- package/dist/assets/WorkersPro-BYY0P1Pt.js +1 -0
- package/dist/assets/Workflows-ByJw4FFH.js +1 -0
- package/dist/assets/api-V_sVpHDo.js +1 -0
- package/dist/assets/bq-DYmbVkgR.js +1 -0
- package/dist/assets/cn-zFEt5SO8.js +1 -0
- package/dist/assets/connectionStore-CKFQrxDo.js +1 -0
- package/dist/assets/controlConnectionEpoch-JM4omZ6j.js +1 -0
- package/dist/assets/feedback-BnGE2oW2.js +1 -0
- package/dist/assets/{flowMutationSafety-CoOp94wS.js → flowMutationSafety-DObp9nJo.js} +1 -1
- package/dist/assets/{form-DsUq0w8s.js → form-tC20r4Ma.js} +1 -1
- package/dist/assets/format-Cg7VCIZW.js +1 -0
- package/dist/assets/icons-CIvFfU2b.js +1 -0
- package/dist/assets/index-D64v2vNO.js +8 -0
- package/dist/assets/index-DOze4TKg.css +1 -0
- package/dist/assets/install-jXi1SOYY.js +3 -0
- package/dist/assets/{react-vendor-Bjui4wS4.js → react-vendor-CyjgmGTq.js} +3 -3
- package/dist/assets/responses-CBLyy1Ob.js +1 -0
- package/dist/assets/toastStore-B7n0zS0y.js +1 -0
- package/dist/assets/{upstreamPaths-D_LPsv7b.js → upstreamPaths-CDVsu6IM.js} +1 -1
- package/dist/assets/usePolledData-c53tx1jw.js +1 -0
- package/dist/assets/{useServerActionGuard-CItj3hvh.js → useServerActionGuard-OTv6_fU0.js} +1 -1
- package/dist/assets/useThroughputSeries-DWHQl7cM.js +1 -0
- package/dist/index.html +17 -6
- package/package.json +15 -4
- package/scripts/embedded.gen.ts +160 -134
- package/scripts/serve.ts +127 -484
- package/scripts/serveHandler.ts +237 -0
- package/scripts/servePolicy.ts +187 -0
- package/dist/assets/AddJob-CG28MSxU.js +0 -3
- package/dist/assets/Alerts-D9JojJrj.js +0 -1
- package/dist/assets/Benchmark-BVjUH9dG.js +0 -1
- package/dist/assets/BulkAddJobs-BHzUCMj_.js +0 -5
- package/dist/assets/Cron-DoEHDHwy.js +0 -1
- package/dist/assets/CronManager-vulcesqJ.js +0 -1
- package/dist/assets/Database-DlB_t04Z.js +0 -3
- package/dist/assets/Diagnostics-DddCUgeR.js +0 -1
- package/dist/assets/Dlq-DMKrsSMP.js +0 -1
- package/dist/assets/DlqControl-9m0Re_TH.js +0 -1
- package/dist/assets/DlqPro-Dmw-0wta.js +0 -1
- package/dist/assets/Flows-C3-9nXW4.js +0 -1
- package/dist/assets/JobInspector-DwIfABmI.js +0 -2
- package/dist/assets/Jobs-j8tTS3NC.js +0 -1
- package/dist/assets/JobsPro-CS__CJJl.js +0 -1
- package/dist/assets/Logs-BB-VDPA1.js +0 -1
- package/dist/assets/LogsPro-RNtSQZRT.js +0 -1
- package/dist/assets/Metrics-ivcddPwT.js +0 -1
- package/dist/assets/MetricsPro-xjZno4vg.js +0 -1
- package/dist/assets/Overview-D84aD2ph.js +0 -1
- package/dist/assets/Pagination-Bfi4vkpp.js +0 -1
- package/dist/assets/QueueActions-DTbD6uek.js +0 -1
- package/dist/assets/QueueControl-B5xK_SQ-.js +0 -1
- package/dist/assets/QueueDetail-BH59kwm9.js +0 -1
- package/dist/assets/QueueDetailPro-DoN25ndy.js +0 -1
- package/dist/assets/Queues-CKMJqFHX.js +0 -1
- package/dist/assets/QueuesOverview-BX1lzojl.js +0 -1
- package/dist/assets/S3Backup-Bz3GfoZ9.js +0 -1
- package/dist/assets/S3BackupPro-BC_6-uX-.js +0 -2
- package/dist/assets/ServerControl-CSSrN8G5.js +0 -3
- package/dist/assets/Settings-DkqmZUIS.js +0 -1
- package/dist/assets/Usage-Ba20p0xd.js +0 -1
- package/dist/assets/UsagePro-C2pQwjN9.js +0 -1
- package/dist/assets/Webhooks-CVuuvb0L.js +0 -1
- package/dist/assets/Workers-C1wPU6CS.js +0 -1
- package/dist/assets/WorkersPro-B0vEkC0T.js +0 -1
- package/dist/assets/Workflows-C8Qmkflf.js +0 -1
- package/dist/assets/cn-CMFYpumL.js +0 -1
- package/dist/assets/connectionStore-Cd9-3Fw0.js +0 -1
- package/dist/assets/index-BCNzbhhD.css +0 -1
- package/dist/assets/index-CAtsVmyh.js +0 -8
- package/dist/assets/install-D0eRLf9F.js +0 -3
- package/dist/assets/useThroughputSeries-DjxGN8ob.js +0 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { isHostAllowed, isOriginAllowed } from '../agent/server';
|
|
2
|
+
import { safeErrorMessage } from '../agent/errorMessage';
|
|
3
|
+
import {
|
|
4
|
+
agentSubUrl,
|
|
5
|
+
apiTokenOk,
|
|
6
|
+
isRemoteBridgeRequest,
|
|
7
|
+
prefixCssAssetUrls,
|
|
8
|
+
stripBasePath,
|
|
9
|
+
type ServeHandlerOptions,
|
|
10
|
+
withSecurityHeaders,
|
|
11
|
+
} from './servePolicy';
|
|
12
|
+
|
|
13
|
+
/** Static assets, the admin proxy, and the same-origin agent bridge. */
|
|
14
|
+
export function createServeHandler(opts: ServeHandlerOptions) {
|
|
15
|
+
const {
|
|
16
|
+
api,
|
|
17
|
+
indexHtml,
|
|
18
|
+
assets,
|
|
19
|
+
agentHandle,
|
|
20
|
+
remoteAgentHandle,
|
|
21
|
+
allowedOrigins,
|
|
22
|
+
allowedHosts,
|
|
23
|
+
agentBridge,
|
|
24
|
+
agentTokenConfigured,
|
|
25
|
+
apiToken,
|
|
26
|
+
apiShutdownSignal,
|
|
27
|
+
remoteBridgePolicy = false,
|
|
28
|
+
trustProxy = false,
|
|
29
|
+
basePath = '',
|
|
30
|
+
} = opts;
|
|
31
|
+
const secure = withSecurityHeaders;
|
|
32
|
+
const rewrittenCss = new Map<string, Promise<string>>();
|
|
33
|
+
const indexResponse = () =>
|
|
34
|
+
new Response(indexHtml, { headers: { 'content-type': 'text/html; charset=utf-8' } });
|
|
35
|
+
|
|
36
|
+
return async function handle(req: Request): Promise<Response> {
|
|
37
|
+
const url = new URL(req.url);
|
|
38
|
+
if (!isHostAllowed(req.headers.get('host'), allowedHosts)) {
|
|
39
|
+
return secure(new Response('Host not allowed', { status: 403 }));
|
|
40
|
+
}
|
|
41
|
+
const pathname = stripBasePath(url.pathname, basePath);
|
|
42
|
+
if (pathname === null) return secure(new Response('Not found', { status: 404 }));
|
|
43
|
+
|
|
44
|
+
const origin = req.headers.get('origin');
|
|
45
|
+
let originHost = '';
|
|
46
|
+
try {
|
|
47
|
+
originHost = origin ? new URL(origin).host.toLowerCase() : '';
|
|
48
|
+
} catch {
|
|
49
|
+
originHost = '';
|
|
50
|
+
}
|
|
51
|
+
// Forwarded hosts are only trustworthy when an operator declares the proxy boundary.
|
|
52
|
+
const fwd = trustProxy ? req.headers.get('x-forwarded-host')?.split(',') : undefined;
|
|
53
|
+
const forwardedHost = fwd?.[fwd.length - 1]?.trim().toLowerCase();
|
|
54
|
+
const sameOrigin =
|
|
55
|
+
originHost !== '' &&
|
|
56
|
+
(originHost === url.host.toLowerCase() || originHost === forwardedHost);
|
|
57
|
+
|
|
58
|
+
if (pathname === '/agent' || pathname.startsWith('/agent/')) {
|
|
59
|
+
const remoteRequest = isRemoteBridgeRequest(req, remoteBridgePolicy);
|
|
60
|
+
if (!agentBridge || (remoteRequest && !agentTokenConfigured)) {
|
|
61
|
+
return secure(
|
|
62
|
+
Response.json(
|
|
63
|
+
{
|
|
64
|
+
ok: false,
|
|
65
|
+
error:
|
|
66
|
+
'Control agent disabled for remote or proxied access. Set AGENT_TOKEN to expose it.',
|
|
67
|
+
},
|
|
68
|
+
{ status: 403 }
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const sub = agentSubUrl(pathname, url.search);
|
|
73
|
+
const headers = new Headers(req.headers);
|
|
74
|
+
if (sameOrigin && origin && !isOriginAllowed(origin, allowedOrigins)) headers.delete('origin');
|
|
75
|
+
const handleAgent = remoteRequest ? remoteAgentHandle : agentHandle;
|
|
76
|
+
return secure(
|
|
77
|
+
await handleAgent(
|
|
78
|
+
new Request(sub.href, {
|
|
79
|
+
method: req.method,
|
|
80
|
+
headers,
|
|
81
|
+
body: req.body,
|
|
82
|
+
signal: req.signal,
|
|
83
|
+
})
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (pathname === '/api' || pathname.startsWith('/api/')) {
|
|
89
|
+
if (!sameOrigin && !isOriginAllowed(origin, allowedOrigins)) {
|
|
90
|
+
return secure(
|
|
91
|
+
Response.json({ ok: false, error: 'Origin not allowed' }, { status: 403 })
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (isRemoteBridgeRequest(req, remoteBridgePolicy)) {
|
|
95
|
+
if (!apiToken) {
|
|
96
|
+
return secure(
|
|
97
|
+
Response.json(
|
|
98
|
+
{
|
|
99
|
+
ok: false,
|
|
100
|
+
error:
|
|
101
|
+
'Admin API proxy disabled for remote or proxied access. Set BUNQUEUE_TOKEN to expose it.',
|
|
102
|
+
},
|
|
103
|
+
{ status: 403 }
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (!apiTokenOk(req, apiToken)) {
|
|
108
|
+
return secure(
|
|
109
|
+
Response.json(
|
|
110
|
+
{ ok: false, error: 'A valid BUNQUEUE_TOKEN bearer token is required.' },
|
|
111
|
+
{ status: 401, headers: { 'WWW-Authenticate': 'Bearer' } }
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const target = api + (pathname.slice(4) || '/') + url.search;
|
|
117
|
+
let res: Response;
|
|
118
|
+
const signal = apiShutdownSignal
|
|
119
|
+
? AbortSignal.any([req.signal, apiShutdownSignal])
|
|
120
|
+
: req.signal;
|
|
121
|
+
try {
|
|
122
|
+
res = await fetch(target, {
|
|
123
|
+
method: req.method,
|
|
124
|
+
headers: req.headers,
|
|
125
|
+
body: req.body,
|
|
126
|
+
redirect: 'manual',
|
|
127
|
+
signal,
|
|
128
|
+
});
|
|
129
|
+
} catch (err) {
|
|
130
|
+
return secure(
|
|
131
|
+
Response.json(
|
|
132
|
+
{ ok: false, error: `bunqueue unreachable at ${api}: ${safeErrorMessage(err)}` },
|
|
133
|
+
{ status: 502 }
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
// Bun decompresses fetch bodies but can retain the upstream encoding headers.
|
|
138
|
+
const headers = new Headers(res.headers);
|
|
139
|
+
headers.delete('content-encoding');
|
|
140
|
+
headers.delete('content-length');
|
|
141
|
+
headers.delete('transfer-encoding');
|
|
142
|
+
return secure(
|
|
143
|
+
new Response(ownedProxyBody(res.body, signal), {
|
|
144
|
+
status: res.status,
|
|
145
|
+
statusText: res.statusText,
|
|
146
|
+
headers,
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const key = pathname === '/' ? '/index.html' : pathname;
|
|
152
|
+
if (key === '/index.html') return secure(indexResponse());
|
|
153
|
+
const asset = assets[key];
|
|
154
|
+
if (asset) {
|
|
155
|
+
if (basePath && key.endsWith('.css')) {
|
|
156
|
+
let css = rewrittenCss.get(asset);
|
|
157
|
+
if (!css) {
|
|
158
|
+
css = Bun.file(asset)
|
|
159
|
+
.text()
|
|
160
|
+
.then((contents) => prefixCssAssetUrls(contents, basePath));
|
|
161
|
+
rewrittenCss.set(asset, css);
|
|
162
|
+
}
|
|
163
|
+
return secure(
|
|
164
|
+
new Response(await css, { headers: { 'content-type': 'text/css; charset=utf-8' } })
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return secure(new Response(Bun.file(asset)));
|
|
168
|
+
}
|
|
169
|
+
if (pathname.startsWith('/assets/')) {
|
|
170
|
+
return secure(new Response('Not found', { status: 404 }));
|
|
171
|
+
}
|
|
172
|
+
return secure(indexResponse());
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Own a proxied response after fetch headers settle so shutdown also ends its body. */
|
|
177
|
+
export function ownedProxyBody(
|
|
178
|
+
body: ReadableStream<Uint8Array> | null,
|
|
179
|
+
signal: AbortSignal
|
|
180
|
+
): ReadableStream<Uint8Array> | null {
|
|
181
|
+
if (!body) return null;
|
|
182
|
+
const reader = body.getReader();
|
|
183
|
+
let controller: ReadableStreamDefaultController<Uint8Array> | null = null;
|
|
184
|
+
let terminal = false;
|
|
185
|
+
|
|
186
|
+
const detach = () => signal.removeEventListener('abort', abort);
|
|
187
|
+
const cancelReader = async (reason: unknown) => {
|
|
188
|
+
try {
|
|
189
|
+
await reader.cancel(reason);
|
|
190
|
+
} catch {
|
|
191
|
+
// Cancellation is best-effort; the owned outbound stream still terminates.
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const abort = () => {
|
|
195
|
+
if (terminal) return;
|
|
196
|
+
terminal = true;
|
|
197
|
+
detach();
|
|
198
|
+
try {
|
|
199
|
+
controller?.close();
|
|
200
|
+
} catch {
|
|
201
|
+
// A concurrent consumer cancellation may already have closed the stream.
|
|
202
|
+
}
|
|
203
|
+
void cancelReader(signal.reason);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return new ReadableStream<Uint8Array>({
|
|
207
|
+
start(nextController) {
|
|
208
|
+
controller = nextController;
|
|
209
|
+
if (signal.aborted) abort();
|
|
210
|
+
else signal.addEventListener('abort', abort, { once: true });
|
|
211
|
+
},
|
|
212
|
+
async pull(nextController) {
|
|
213
|
+
try {
|
|
214
|
+
const chunk = await reader.read();
|
|
215
|
+
if (terminal) return;
|
|
216
|
+
if (chunk.done) {
|
|
217
|
+
terminal = true;
|
|
218
|
+
detach();
|
|
219
|
+
nextController.close();
|
|
220
|
+
} else {
|
|
221
|
+
nextController.enqueue(chunk.value);
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (terminal) return;
|
|
225
|
+
terminal = true;
|
|
226
|
+
detach();
|
|
227
|
+
nextController.error(error);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
async cancel(reason) {
|
|
231
|
+
if (terminal) return;
|
|
232
|
+
terminal = true;
|
|
233
|
+
detach();
|
|
234
|
+
await cancelReader(reason);
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { hostnameOf, resolveAllowedHosts } from '../agent/server';
|
|
3
|
+
|
|
4
|
+
const BASE_PATH_PATTERN = /^\/(?:[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*)?\/?$/;
|
|
5
|
+
|
|
6
|
+
export const RESPONSE_SECURITY_HEADERS = {
|
|
7
|
+
'Content-Security-Policy': "frame-ancestors 'none'; object-src 'none'; base-uri 'self'",
|
|
8
|
+
'X-Frame-Options': 'DENY',
|
|
9
|
+
'X-Content-Type-Options': 'nosniff',
|
|
10
|
+
'Referrer-Policy': 'no-referrer',
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export function withSecurityHeaders(response: Response): Response {
|
|
14
|
+
const headers = new Headers(response.headers);
|
|
15
|
+
for (const [name, value] of Object.entries(RESPONSE_SECURITY_HEADERS)) headers.set(name, value);
|
|
16
|
+
return new Response(response.body, {
|
|
17
|
+
status: response.status,
|
|
18
|
+
statusText: response.statusText,
|
|
19
|
+
headers,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** True loopback names/addresses. 0.0.0.0 is a wildcard, never local-only. */
|
|
24
|
+
export function isLoopbackHost(host: string): boolean {
|
|
25
|
+
const value = hostnameOf(host);
|
|
26
|
+
const octets = value.split('.');
|
|
27
|
+
const ipv4Loopback =
|
|
28
|
+
octets.length === 4 &&
|
|
29
|
+
octets[0] === '127' &&
|
|
30
|
+
octets.every((octet) => /^\d{1,3}$/.test(octet) && Number(octet) <= 255);
|
|
31
|
+
return (
|
|
32
|
+
value === 'localhost' ||
|
|
33
|
+
value === '::1' ||
|
|
34
|
+
value === '0:0:0:0:0:0:0:1' ||
|
|
35
|
+
ipv4Loopback
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isLoopbackBind(host: string): boolean {
|
|
40
|
+
return isLoopbackHost(host);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function configuredValues(value: string | undefined): string[] {
|
|
44
|
+
return (value ?? '')
|
|
45
|
+
.split(',')
|
|
46
|
+
.map((entry) => entry.trim())
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function remoteBridgeRequiresToken(
|
|
51
|
+
loopbackBind: boolean,
|
|
52
|
+
env: Record<string, string | undefined> = process.env
|
|
53
|
+
): boolean {
|
|
54
|
+
if (!loopbackBind || env.TRUST_PROXY === '1') return true;
|
|
55
|
+
const configured = [
|
|
56
|
+
...configuredValues(env.AGENT_ALLOWED_HOSTS),
|
|
57
|
+
...configuredValues(env.AGENT_ALLOWED_ORIGINS),
|
|
58
|
+
];
|
|
59
|
+
return configured.some((value) => !isLoopbackHost(value));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const PROXY_HINT_HEADERS = [
|
|
63
|
+
'forwarded',
|
|
64
|
+
'x-forwarded-for',
|
|
65
|
+
'x-forwarded-host',
|
|
66
|
+
'x-forwarded-proto',
|
|
67
|
+
] as const;
|
|
68
|
+
|
|
69
|
+
export function isRemoteBridgeRequest(req: Request, remotePolicy = false): boolean {
|
|
70
|
+
if (remotePolicy || PROXY_HINT_HEADERS.some((name) => req.headers.has(name))) return true;
|
|
71
|
+
const origin = req.headers.get('origin');
|
|
72
|
+
if (origin && !isLoopbackHost(origin)) return true;
|
|
73
|
+
const host = req.headers.get('host') ?? new URL(req.url).hostname;
|
|
74
|
+
return !isLoopbackHost(host);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function apiTokenOk(req: Request, token: string | undefined): boolean {
|
|
78
|
+
if (!token) return false;
|
|
79
|
+
const authorization = req.headers.get('authorization');
|
|
80
|
+
if (!authorization?.startsWith('Bearer ')) return false;
|
|
81
|
+
const supplied = Buffer.from(authorization.slice('Bearer '.length));
|
|
82
|
+
const expected = Buffer.from(token);
|
|
83
|
+
return supplied.length === expected.length && timingSafeEqual(supplied, expected);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolveServeAllowedHosts(
|
|
87
|
+
bindHost: string,
|
|
88
|
+
allowedOrigins: string[],
|
|
89
|
+
env: Record<string, string | undefined> = process.env
|
|
90
|
+
): string[] {
|
|
91
|
+
const originHosts = allowedOrigins
|
|
92
|
+
.map((origin) => {
|
|
93
|
+
try {
|
|
94
|
+
return new URL(origin).hostname;
|
|
95
|
+
} catch {
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.filter(Boolean);
|
|
100
|
+
const concreteBindHosts = bindHost === '0.0.0.0' || bindHost === '::' ? [] : [bindHost];
|
|
101
|
+
return resolveAllowedHosts(env, [...originHosts, ...concreteBindHosts]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function remoteControlEnabled(
|
|
105
|
+
remotePolicy: boolean,
|
|
106
|
+
env: Record<string, string | undefined> = process.env
|
|
107
|
+
): boolean {
|
|
108
|
+
if (!remotePolicy) return true;
|
|
109
|
+
return Boolean(env.AGENT_TOKEN?.trim());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Normalize the runtime mount point to either an empty root prefix or `/a/b`. */
|
|
113
|
+
export function resolveBasePath(value: unknown): string {
|
|
114
|
+
if (value === undefined || value === null || value === '' || value === '/') return '';
|
|
115
|
+
if (typeof value !== 'string') throw new Error('BASE_PATH must be a URL path');
|
|
116
|
+
const candidate = value.trim();
|
|
117
|
+
if (!BASE_PATH_PATTERN.test(candidate)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'BASE_PATH must start with one slash and contain only URL-safe path segments'
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (candidate.split('/').some((segment) => segment === '.' || segment === '..')) {
|
|
123
|
+
throw new Error('BASE_PATH cannot contain dot path segments');
|
|
124
|
+
}
|
|
125
|
+
return candidate.replace(/\/+$/, '');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Return the path as seen by the mounted app, or null when it is outside the mount. */
|
|
129
|
+
export function stripBasePath(pathname: string, basePath: string): string | null {
|
|
130
|
+
if (!basePath) return pathname;
|
|
131
|
+
if (pathname === basePath) return '/';
|
|
132
|
+
if (!pathname.startsWith(`${basePath}/`)) return null;
|
|
133
|
+
return pathname.slice(basePath.length) || '/';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Prefix root-relative Vite URLs and inject the standalone runtime endpoints. */
|
|
137
|
+
export function prepareRuntimeIndexHtml(indexHtml: string, basePath: string): string {
|
|
138
|
+
const mount = basePath || '/';
|
|
139
|
+
const agentUrl = `${basePath}/agent`;
|
|
140
|
+
const apiUrl = `${basePath}/api`;
|
|
141
|
+
const prefixed = basePath
|
|
142
|
+
? indexHtml.replace(/\b(href|src)=(['"])\/(?!\/)/g, `$1=$2${basePath}/`)
|
|
143
|
+
: indexHtml;
|
|
144
|
+
const runtimeConfig =
|
|
145
|
+
'<script>' +
|
|
146
|
+
`window.__BUNQUEUE_BASE_PATH__=${JSON.stringify(mount)};` +
|
|
147
|
+
`window.__BUNQUEUE_AGENT_URL__=${JSON.stringify(agentUrl)};` +
|
|
148
|
+
`window.__BUNQUEUE_API_URL__=${JSON.stringify(apiUrl)}` +
|
|
149
|
+
'</script>';
|
|
150
|
+
if (!prefixed.includes('</head>')) {
|
|
151
|
+
throw new Error('Embedded index.html is missing </head>');
|
|
152
|
+
}
|
|
153
|
+
return prefixed.replace('</head>', `${runtimeConfig}</head>`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Vite emits root-relative font URLs in CSS when its build base is `/`. */
|
|
157
|
+
export function prefixCssAssetUrls(css: string, basePath: string): string {
|
|
158
|
+
if (!basePath) return css;
|
|
159
|
+
return css.replace(/url\((['"]?)\/assets\//g, `url($1${basePath}/assets/`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function agentSubUrl(pathname: string, search: string): URL {
|
|
163
|
+
const rest = pathname.slice('/agent'.length) || '/';
|
|
164
|
+
const sub = new URL('http://agent.internal');
|
|
165
|
+
sub.pathname = rest.startsWith('/') ? rest : `/${rest}`;
|
|
166
|
+
sub.search = search;
|
|
167
|
+
return sub;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface ServeHandlerOptions {
|
|
171
|
+
api: string;
|
|
172
|
+
indexHtml: string;
|
|
173
|
+
assets: Record<string, string>;
|
|
174
|
+
agentHandle: (req: Request) => Response | Promise<Response>;
|
|
175
|
+
remoteAgentHandle: (req: Request) => Response | Promise<Response>;
|
|
176
|
+
allowedOrigins: string[];
|
|
177
|
+
allowedHosts?: string[];
|
|
178
|
+
agentBridge: boolean;
|
|
179
|
+
agentTokenConfigured: boolean;
|
|
180
|
+
apiToken?: string;
|
|
181
|
+
/** Aborts upstream API streams during terminal listener drain. */
|
|
182
|
+
apiShutdownSignal?: AbortSignal;
|
|
183
|
+
remoteBridgePolicy?: boolean;
|
|
184
|
+
trustProxy?: boolean;
|
|
185
|
+
/** Normalized mount prefix (`''` for root, otherwise no trailing slash). */
|
|
186
|
+
basePath?: string;
|
|
187
|
+
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./cn-CMFYpumL.js";import{g as t,l as n,r,t as i}from"./react-vendor-Bjui4wS4.js";import{i as a,o}from"./upstreamPaths-D_LPsv7b.js";import{A as s,C as c,S as l,ct as ee,ft as u,h as d,st as f,w as p}from"./index-CAtsVmyh.js";import{a as m,n as h,o as g,t as _}from"./form-DsUq0w8s.js";import{r as v}from"./useServerActionGuard-CItj3hvh.js";var y=e(t(),1),b=i(),x=e=>e==null?``:String(e),S=365*24*60*60*1e3,C=1440*60*1e3,w=10*1024*1024,T=10*1024*1024;function E(e,t=1/0){let n=0;for(let r=0;r<e.length;r++){let i=e.charCodeAt(r);if(i<=127?n+=1:i<=2047?n+=2:i>=55296&&i<=56319&&r+1<e.length&&e.charCodeAt(r+1)>=56320&&e.charCodeAt(r+1)<=57343?(n+=4,r+=1):n+=3,n>t)return n}return n}function D(e){return e.length>10485760||E(e,10485760)>10485760?`Job data is too large (maximum 10 MiB UTF-8 / 10,485,760 characters)`:null}function O(e){let t=D(e);if(t)return{ok:!1,kind:`size`,msg:t};let n;try{n=JSON.parse(e)}catch(e){return{ok:!1,kind:`json`,msg:`Invalid JSON: ${e.message}`}}let r=JSON.stringify(n);return r.length>10485760||E(r,10485760)>10485760?{ok:!1,kind:`size`,msg:`Job data is too large after JSON encoding (maximum 10 MiB UTF-8)`}:{ok:!0,data:n}}function te(e){let t=[{key:`priority`,label:`Priority`,min:-1e6,max:1e6,integer:!0},{key:`delay`,label:`Delay`,min:0,max:S,integer:!0},{key:`maxAttempts`,label:`Max attempts`,min:1,max:1e3,integer:!0},{key:`backoff`,label:`Backoff`,min:0,max:C,integer:!0},{key:`timeout`,label:`Timeout`,min:0,max:C,integer:!0},{key:`ttl`,label:`TTL`,min:0,max:S,integer:!0}],n={};for(let r of t){let t=(e[r.key]??``).trim();if(!t)continue;let i=Number(t);if(!Number.isFinite(i))return{ok:!1,msg:`${r.label} must be a number`};if(r.integer&&!Number.isSafeInteger(i))return{ok:!1,msg:`${r.label} must be a whole, safe integer`};if(i<r.min||i>r.max)return{ok:!1,msg:`${r.label} must be between ${r.min} and ${r.max}`};n[r.key]=i}return{ok:!0,options:n}}function ne(e){let t=o(e);return!e&&t?`Choose a queue`:t}function re(e,t){return e==null?t?{ok:!1,msg:`Backoff strategy needs a base delay in Backoff (ms)`}:{ok:!0,backoff:void 0}:{ok:!0,backoff:t?{type:t,delay:e}:e}}function ie(e,t){return{ok:!0,msg:`Accepted ${t} job submission${t===1?``:`s`}; server returned ${e} distinct job ID${e===1?``:`s`} (deduplication may reuse existing jobs)`}}function ae(e){if(typeof e!=`object`||!e||e.ok!==!0||typeof e.id!=`string`||!e.id||e.id.length>1024)throw Error(`Add job returned a malformed success response`);return e.id}function oe(e,t){let n=e?.ids;if(typeof e!=`object`||!e||e.ok!==!0||!Array.isArray(n)||n.length!==t||n.some(e=>typeof e!=`string`||e.length===0||e.length>1024))throw Error(`Bulk add returned a malformed success response`);return n}function se(e){if(!e.trim())return{ok:!0,repeat:void 0};try{let t=JSON.parse(e);if(typeof t!=`object`||!t||Array.isArray(t))return{ok:!1,msg:`Repeat must be a JSON object`};let n=t,r=new Set([`every`,`limit`,`pattern`]),i=Object.keys(n).filter(e=>!r.has(e));if(i.length)return{ok:!1,msg:`Unknown repeat option${i.length===1?``:`s`}: ${i.join(`, `)}`};if(n.pattern!==void 0)return{ok:!1,msg:`repeat.pattern is unsafe in bunqueue v2.8.57; use repeat.every or a Cron schedule`};let a=n.every;return!Number.isSafeInteger(a)||a<=0||a>S?{ok:!1,msg:`Repeat "every" must be a whole number from 1 to ${S} ms`}:n.limit!==void 0&&(!Number.isSafeInteger(n.limit)||n.limit<1)?{ok:!1,msg:`Repeat "limit" must be a whole number of at least 1`}:{ok:!0,repeat:{every:a,...typeof n.limit==`number`?{limit:n.limit}:{}}}}catch(e){return{ok:!1,msg:`Repeat is not valid JSON: ${e.message}`}}}function ce(e){let t=typeof e.backoff==`object`?e.backoff:void 0,n=e.backoffConfig??t;return{backoff:x(n?.delay??(typeof e.backoff==`number`?e.backoff:void 0)),backoffType:n?.type??``,tags:e.tags?.join(`, `)??``,groupId:e.groupId??``,ttl:x(e.ttl)}}function k(){let{data:e,error:t,refetch:i}=s(()=>f.queues(),[],{intervalMs:3e4}),o=n().state?.clone,g=o?.options??{},T=ce(g),[E,D]=(0,y.useState)(o?.queue??``),[k,le]=(0,y.useState)(o?.name??`default`),[j,ue]=(0,y.useState)(o?.dataText??`{
|
|
2
|
-
"hello": "world"
|
|
3
|
-
}`),[M,de]=(0,y.useState)(`1`),[N,fe]=(0,y.useState)(x(g.priority)),[P,pe]=(0,y.useState)(``),[F,me]=(0,y.useState)(``),[I,he]=(0,y.useState)(x(g.maxAttempts)),[L,ge]=(0,y.useState)(T.backoff),[R,_e]=(0,y.useState)(x(g.timeout)),[z,ve]=(0,y.useState)(T.ttl),[B,ye]=(0,y.useState)(``),[V,be]=(0,y.useState)(g.removeOnComplete??!1),[H,xe]=(0,y.useState)(g.removeOnFail??!1),[U,Se]=(0,y.useState)(!1),[W,Ce]=(0,y.useState)(!1),[G,we]=(0,y.useState)(T.backoffType),[K,Te]=(0,y.useState)(T.tags),[q,Ee]=(0,y.useState)(T.groupId),[De,Oe]=(0,y.useState)(``),[ke,Ae]=(0,y.useState)(``),[je,Me]=(0,y.useState)(``),[J,Y]=(0,y.useState)(!1),[X,Z]=(0,y.useState)(null),[Ne,Q]=(0,y.useState)(null),$=v(`add-job`);(0,y.useEffect)(()=>{Y(!1),Z(null),Q(null)},[$.scopeKey]);let Pe=async()=>{Z(null),Q(null);let e=E.trim(),t=ne(e);if(t){Z({ok:!1,msg:t});return}let n=k.trim();if(!n||n.length>256){Z({ok:!1,msg:`Job name must be a non-empty string of at most 256 characters`});return}let r=O(j);if(!r.ok){r.kind===`json`?Q(r.msg):Z({ok:!1,msg:r.msg});return}let i=r.data,o=te({priority:N,delay:F.trim()?``:P,maxAttempts:I,backoff:L,timeout:R,ttl:z});if(!o.ok){Z({ok:!1,msg:o.msg});return}let s=o.options.delay;if(F.trim()){let e=new Date(F).getTime();if(!Number.isFinite(e)){Z({ok:!1,msg:`Run at is not a valid date/time`});return}if(s=Math.max(0,e-Date.now()),s>S){Z({ok:!1,msg:`Run at must be within the next 365 days`});return}}let c=re(o.options.backoff,G);if(!c.ok){Z({ok:!1,msg:c.msg});return}let l=c.backoff,d=K.split(`,`).map(e=>e.trim()).filter(Boolean),p=De.split(`,`).map(e=>e.trim()).filter(Boolean),m=B.trim();if(m){let e=a(m);if(e){Z({ok:!1,msg:`Job ID: ${e}`});return}}for(let e of p){let t=a(e);if(t){Z({ok:!1,msg:`Dependency ID "${e}": ${t}`});return}}let h=se(je);if(!h.ok){Z({ok:!1,msg:h.msg});return}let g={name:n,data:i,priority:o.options.priority,delay:s,maxAttempts:o.options.maxAttempts,backoff:l,timeout:o.options.timeout,ttl:o.options.ttl,jobId:m||void 0,removeOnComplete:V||void 0,removeOnFail:H||void 0,durable:U||void 0,lifo:W||void 0,tags:d.length?d:void 0,groupId:q.trim()||void 0,dependsOn:p.length?p:void 0,uniqueKey:ke.trim()||void 0,repeat:h.repeat},_=Number(M);if(!Number.isSafeInteger(_)||_<1||_>1e4){Z({ok:!1,msg:`Count must be a whole number from 1 to 10000`});return}let v=$.begin();if(v){Y(!0);try{if(_===1){let t=ae(await f.addJob(e,g));if(!v.isCurrent())return;Z({ok:!0,msg:`Accepted job ID ${t} (it may be an existing deduplicated job)`}),u.success(`Job submission accepted`,`${e} · ${t}`)}else{let t=Array.from({length:_},()=>g),n=ee(t);if(n){v.isCurrent()&&Z({ok:!1,msg:n});return}let r=oe(await f.addJobsBulk(e,t),_);if(!v.isCurrent())return;let i=ie(new Set(r).size,_);Z(i),u.success(i.msg,`in ${e}`)}}catch(e){if(!v.isCurrent())return;Z({ok:!1,msg:e.message}),u.error(`Add job failed`,e.message)}finally{v.finish()&&Y(!1)}}};return(0,b.jsxs)(`div`,{children:[(0,b.jsx)(d,{title:`Add Job`,description:`Enqueue a job with full options.`,actions:(0,b.jsx)(r,{to:`/jobs/bulk-add`,className:`rounded-lg border border-line px-3 py-1.5 text-sm text-muted hover:bg-surface-2 hover:text-fg`,children:`Bulk import`})}),o&&(0,b.jsx)(`div`,{className:`mb-4 flex items-center gap-2 rounded-lg border border-accent/30 bg-accent/[0.06] px-4 py-2 text-sm text-accent`,children:`Pre-filled from an existing job. Review the data and options, then enqueue a fresh job.`}),(0,b.jsxs)(`form`,{className:`grid grid-cols-1 gap-6 lg:grid-cols-2`,onSubmit:e=>{e.preventDefault(),Pe()},children:[(0,b.jsxs)(l,{children:[(0,b.jsx)(c,{title:`Job`}),(0,b.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,b.jsxs)(_,{label:`Queue`,children:[(0,b.jsx)(h,{list:`queue-options`,"aria-label":`Queue`,name:`target-queue`,autoComplete:`off`,value:E,onChange:e=>D(e.target.value),placeholder:`queue name (existing or new)`}),(0,b.jsx)(`datalist`,{id:`queue-options`,children:(e?.queues??[]).map(e=>(0,b.jsx)(`option`,{value:e.name},e.name))})]}),(0,b.jsx)(_,{label:`Job name`,hint:`Worker routing name in Bunqueue 2.8.57; separate from the JSON payload.`,children:(0,b.jsx)(h,{name:`job-name`,autoComplete:`off`,maxLength:256,value:k,onChange:e=>le(e.target.value),placeholder:`default`})}),t&&(0,b.jsxs)(`p`,{role:`status`,className:`-mt-2 text-xs text-warning`,children:[`Existing queue suggestions unavailable — `,t.message,`. You can still enter a queue name manually.`,` `,(0,b.jsx)(`button`,{type:`button`,onClick:()=>void i(),className:`font-medium underline underline-offset-2 hover:text-fg`,children:`Retry`})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(_,{label:`Data (JSON)`,children:(0,b.jsx)(`textarea`,{name:`job-data`,value:j,onChange:e=>ue(e.target.value),onBlur:()=>{let e=O(j);Q(e.ok?null:e.msg)},maxLength:w,spellCheck:!1,rows:7,className:`w-full rounded-lg border border-line bg-surface-2 p-3 font-mono text-sm text-fg focus:border-accent/60 focus:outline-none focus:ring-2 focus:ring-accent/30`})}),Ne&&(0,b.jsx)(`p`,{role:`alert`,className:`mt-2 text-xs text-danger`,children:Ne})]})]})]}),(0,b.jsxs)(l,{children:[(0,b.jsx)(c,{title:`Options`}),(0,b.jsxs)(`div`,{className:`grid grid-cols-2 gap-4 md:grid-cols-3`,children:[(0,b.jsx)(_,{label:`Priority`,children:(0,b.jsx)(h,{type:`number`,min:-1e6,max:1e6,step:1,name:`priority`,value:N,onChange:e=>fe(e.target.value),placeholder:`0`})}),(0,b.jsx)(_,{label:`Delay (ms)`,children:(0,b.jsx)(h,{type:`number`,min:0,max:S,step:1,name:`delay`,value:P,onChange:e=>pe(e.target.value),placeholder:`0`,disabled:F.trim()!==``})}),(0,b.jsx)(_,{label:`Run at`,hint:F.trim()?`Overrides Delay — derived from this time (local time).`:`(local time)`,children:(0,b.jsx)(h,{type:`datetime-local`,name:`run-at`,value:F,onChange:e=>me(e.target.value)})}),(0,b.jsx)(_,{label:`Max attempts`,hint:`blank = server default`,children:(0,b.jsx)(h,{type:`number`,min:1,max:1e3,step:1,name:`max-attempts`,value:I,onChange:e=>he(e.target.value),placeholder:`3`})}),(0,b.jsx)(_,{label:`Backoff (ms)`,hint:`blank = server default`,children:(0,b.jsx)(h,{type:`number`,min:0,max:C,step:1,name:`backoff`,value:L,onChange:e=>ge(e.target.value),placeholder:`1000`})}),(0,b.jsx)(_,{label:`Backoff strategy`,hint:`flat delay unless set`,children:(0,b.jsxs)(m,{name:`backoff-strategy`,value:G,onChange:e=>we(e.target.value),children:[(0,b.jsx)(`option`,{value:``,children:`flat`}),(0,b.jsx)(`option`,{value:`fixed`,children:`fixed`}),(0,b.jsx)(`option`,{value:`exponential`,children:`exponential`})]})}),(0,b.jsx)(_,{label:`Timeout (ms)`,children:(0,b.jsx)(h,{type:`number`,min:0,max:C,step:1,name:`timeout`,value:R,onChange:e=>_e(e.target.value),placeholder:`—`})}),(0,b.jsx)(_,{label:`TTL (ms)`,hint:`blank = no expiry`,children:(0,b.jsx)(h,{type:`number`,min:0,max:S,step:1,name:`ttl`,value:z,onChange:e=>ve(e.target.value),placeholder:`—`})}),(0,b.jsx)(_,{label:`Custom job ID`,children:(0,b.jsx)(h,{name:`custom-job-id`,value:B,onChange:e=>ye(e.target.value),placeholder:`idempotency key`})})]}),(0,b.jsxs)(`div`,{className:`mt-4 flex flex-wrap gap-6`,children:[(0,b.jsx)(A,{label:`removeOnComplete`,checked:V,onChange:be}),(0,b.jsx)(A,{label:`removeOnFail`,checked:H,onChange:xe}),(0,b.jsx)(A,{label:`durable`,checked:U,onChange:Se}),(0,b.jsx)(A,{label:`lifo`,checked:W,onChange:Ce})]}),(0,b.jsxs)(`div`,{className:`mt-4 border-t border-line pt-4`,children:[(0,b.jsx)(`div`,{className:`mb-3 text-[11px] uppercase tracking-wider text-faint`,children:`Advanced`}),(0,b.jsxs)(`div`,{className:`grid grid-cols-2 gap-4 md:grid-cols-3`,children:[(0,b.jsx)(_,{label:`Tags`,hint:`comma-separated`,children:(0,b.jsx)(h,{name:`tags`,value:K,onChange:e=>Te(e.target.value),placeholder:`email, urgent`})}),(0,b.jsx)(_,{label:`Group ID`,children:(0,b.jsx)(h,{name:`group-id`,value:q,onChange:e=>Ee(e.target.value),placeholder:`—`})}),(0,b.jsx)(_,{label:`Unique key`,hint:`dedup key`,children:(0,b.jsx)(h,{name:`unique-key`,value:ke,onChange:e=>Ae(e.target.value),placeholder:`—`})}),(0,b.jsx)(_,{label:`Depends on`,hint:`parent job ids, comma-separated`,children:(0,b.jsx)(h,{name:`depends-on`,value:De,onChange:e=>Oe(e.target.value),placeholder:`job-id-1, job-id-2`})}),(0,b.jsx)(`div`,{className:`col-span-2 md:col-span-3`,children:(0,b.jsx)(_,{label:`Repeat policy (JSON)`,hint:`v2.8.57-safe form: e.g. {"every":60000,"limit":10}. Use Cron Manager for cron patterns.`,children:(0,b.jsx)(`textarea`,{name:`repeat-policy`,value:je,onChange:e=>Me(e.target.value),rows:2,spellCheck:!1,placeholder:`{"every": 60000}`,className:`w-full rounded-lg border border-line bg-surface-2 px-3 py-2 font-mono text-xs text-fg placeholder:text-faint focus:border-accent/60 focus:outline-none focus:ring-2 focus:ring-accent/30`})})})]})]})]}),(0,b.jsxs)(`div`,{className:`flex flex-wrap items-end gap-3 lg:col-span-2`,children:[(0,b.jsx)(`div`,{className:`w-28`,children:(0,b.jsx)(_,{label:`Count`,hint:`1–10000`,children:(0,b.jsx)(h,{type:`number`,min:1,max:1e4,step:1,name:`count`,value:M,onChange:e=>de(e.target.value)})})}),(0,b.jsx)(p,{type:`submit`,variant:`accent`,disabled:J,children:J?`Adding…`:`Add job`}),X&&(0,b.jsx)(`span`,{role:X.ok?`status`:`alert`,className:X.ok?`text-sm text-success`:`text-sm text-danger`,children:X.msg})]})]})]})}function A({label:e,checked:t,onChange:n}){return(0,b.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,b.jsx)(g,{checked:t,onChange:n,label:e}),(0,b.jsx)(`span`,{className:`font-mono text-xs text-muted`,children:e})]})}export{k as AddJob,T as MAX_JOB_DATA_BYTES,w as MAX_JOB_DATA_CHARS,oe as acceptedBulkIds,O as parseJobData,se as parseRepeat,ne as queueNameError,E as utf8ByteLength};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./cn-CMFYpumL.js";import{g as t,t as n}from"./react-vendor-Bjui4wS4.js";import{o as r}from"./connectionStore-Cd9-3Fw0.js";import{N as i,S as a,T as o,Y as s,et as c,h as l,mt as u,nt as d,pt as f,tt as p,u as m,v as h,w as g,x as _}from"./index-CAtsVmyh.js";import{a as v,n as y,o as b,t as x}from"./form-DsUq0w8s.js";var S=e(t(),1),C=n(),w=[`email`,`webhook`,`slack`],T=Object.keys(f),E=[`>=`,`>`,`<=`,`<`],D={error_rate:{unit:`% (0–100)`,placeholder:`e.g. 5`},p99_latency:{unit:`ms`,placeholder:`e.g. 250`},waiting:{unit:`jobs`,placeholder:`e.g. 100`},failed:{unit:`jobs`,placeholder:`e.g. 10`},dlq:{unit:`jobs`,placeholder:`e.g. 1`}},O=/^[a-zA-Z0-9_\-.:]+$/;function k(e){let t=e.name.trim();if(!T.includes(e.metric))return{ok:!1,error:`Invalid metric`,field:`metric`};if(!E.includes(e.operator))return{ok:!1,error:`Invalid operator`,field:`operator`};let n=e.metric===`p99_latency`?``:e.queue.trim();if(!t)return{ok:!1,error:`Rule name is required`,field:`name`};if(t.length>200)return{ok:!1,error:`Rule name must be 200 characters or fewer`,field:`name`};if(!e.threshold.trim())return{ok:!1,error:`Threshold is required`,field:`threshold`};let r=Number(e.threshold);return!Number.isFinite(r)||r<0?{ok:!1,error:`Threshold must be a non-negative number`,field:`threshold`}:e.metric===`error_rate`&&r>100?{ok:!1,error:`Error-rate threshold must be between 0 and 100%`,field:`threshold`}:[`waiting`,`failed`,`dlq`].includes(e.metric)&&!Number.isSafeInteger(r)?{ok:!1,error:`Job-count thresholds must be whole numbers`,field:`threshold`}:n&&(n.length>256||!O.test(n))?{ok:!1,error:`Queue contains unsupported characters or is too long`,field:`queue`}:w.includes(e.channel)?{ok:!0,rule:{name:t,metric:e.metric,operator:e.operator,threshold:r,queue:n,channel:e.channel,enabled:!0}}:{ok:!1,error:`Invalid channel type`,field:`channel`}}function A(){return typeof Notification>`u`?`unsupported`:Notification.permission}function j(){let{rules:e,addRule:t,removeRule:n,toggleRule:v}=u(),y=d(e=>e.breaching),x=d(e=>e.status),w=d(e=>e.error),T=d(e=>e.connectionIdentity),E=r(e=>c(e.baseUrl,e.token)),D=e.some(e=>e.enabled),O=T===E,k=O?y:[],j=O?x:D?`checking`:`idle`,N=O?w:null,[P,F]=(0,S.useState)(!1),[I,L]=(0,S.useState)(A);return(0,C.jsxs)(`div`,{children:[(0,C.jsx)(l,{title:`Alerts`,description:`Threshold rules evaluated live in your browser.`,live:D&&j===`live`,actions:(0,C.jsxs)(C.Fragment,{children:[I===`granted`?(0,C.jsxs)(`span`,{className:`flex items-center gap-1.5 text-sm text-success`,children:[(0,C.jsx)(i,{className:`size-4`}),` Desktop alerts on`]}):I===`unsupported`?(0,C.jsx)(`span`,{className:`text-sm text-faint`,children:`Notifications unsupported`}):I===`denied`?(0,C.jsx)(`span`,{className:`text-sm text-faint`,children:`Desktop alerts blocked — allow notifications in browser settings`}):(0,C.jsx)(g,{size:`sm`,onClick:async()=>{L(await p())},children:`Enable desktop notifications`}),(0,C.jsx)(g,{variant:`accent`,size:`sm`,"aria-expanded":P,"aria-controls":`alert-rule-form`,onClick:()=>F(e=>!e),children:`+ Create Alert Rule`})]})}),D&&j===`checking`&&(0,C.jsx)(`p`,{role:`status`,className:`mb-4 text-sm text-muted`,children:`Checking alert metrics for the connected server…`}),D&&j===`degraded`&&(0,C.jsx)(_,{message:N??`Alert metrics are unavailable; current rule results may be stale.`}),(0,C.jsx)(`div`,{className:`mb-6 rounded-lg border border-amber-500/20 bg-amber-500/5 px-4 py-3 text-sm text-amber-400/90`,children:`Rules are evaluated in this browser while a tab is open (even backgrounded), raising an in-app toast and — if enabled — a desktop notification. bunqueue OSS has no alerting backend, so email/webhook/slack delivery still needs your own monitoring or hosted bunqueue Cloud.`}),(0,C.jsxs)(`section`,{className:`mb-8`,children:[(0,C.jsx)(`h2`,{className:`mb-3 text-lg font-semibold text-fg`,children:`Triggered Alerts`}),k.length===0?(0,C.jsx)(h,{icon:(0,C.jsx)(i,{}),title:`No triggered alerts`,hint:D?j===`live`?`All enabled rules are within their thresholds.`:j===`checking`?`Alert metrics are still being checked.`:`Alert metrics are unavailable; no all-clear result is available.`:`No enabled rules yet. Create one below.`}):(0,C.jsx)(`div`,{className:`overflow-x-auto rounded-xl border border-danger/30 bg-red-500/5`,children:(0,C.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,C.jsx)(`thead`,{children:(0,C.jsxs)(`tr`,{className:`border-b border-line text-left text-[11px] uppercase tracking-wider text-faint`,children:[(0,C.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Rule`}),(0,C.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Condition`}),(0,C.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Queue`}),(0,C.jsx)(`th`,{className:`px-5 py-3 text-right font-medium`,children:`Current`}),(0,C.jsx)(`th`,{className:`px-5 py-3 text-right font-medium`,children:`Since`})]})}),(0,C.jsx)(`tbody`,{children:k.map(e=>(0,C.jsxs)(`tr`,{className:`border-b border-line last:border-0`,children:[(0,C.jsx)(`td`,{className:`px-5 py-3 font-medium text-danger`,children:e.ruleName}),(0,C.jsxs)(`td`,{className:`px-5 py-3 text-muted`,children:[e.metricLabel,` `,e.operator,` `,(0,C.jsx)(`span`,{className:`font-semibold text-fg`,children:e.threshold})]}),(0,C.jsx)(`td`,{className:`px-5 py-3 text-muted`,children:e.queue||`All queues`}),(0,C.jsx)(`td`,{className:`px-5 py-3 text-right tnum font-semibold text-danger`,children:Math.round(e.value*100)/100}),(0,C.jsx)(`td`,{className:`px-5 py-3 text-right text-faint`,children:m(e.at)})]},e.ruleId))})]})})]}),(0,C.jsxs)(`section`,{children:[(0,C.jsx)(`h2`,{className:`mb-3 text-lg font-semibold text-fg`,children:`Alert Rules`}),P&&(0,C.jsx)(`div`,{id:`alert-rule-form`,children:(0,C.jsx)(a,{className:`mb-4`,children:(0,C.jsx)(M,{onAdd:e=>{let n=t(e);return n.ok&&F(!1),n}})})}),e.length===0?(0,C.jsx)(h,{title:`No alert rules`,hint:`Create a rule to be notified when a threshold is crossed.`}):(0,C.jsx)(`div`,{className:`overflow-x-auto rounded-xl border border-line bg-surface`,children:(0,C.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,C.jsx)(`thead`,{children:(0,C.jsxs)(`tr`,{className:`border-b border-line text-left text-[11px] uppercase tracking-wider text-faint`,children:[(0,C.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Rule`}),(0,C.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Condition`}),(0,C.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Queue`}),(0,C.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Delivery`}),(0,C.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Status`}),(0,C.jsx)(`th`,{className:`w-12 px-5 py-3`})]})}),(0,C.jsx)(`tbody`,{children:e.map(e=>(0,C.jsxs)(`tr`,{className:`border-b border-line last:border-0`,children:[(0,C.jsx)(`td`,{className:`px-5 py-3 font-medium text-fg`,children:e.name}),(0,C.jsxs)(`td`,{className:`px-5 py-3 text-muted`,children:[f[e.metric],` `,e.operator,` `,(0,C.jsx)(`span`,{className:`font-semibold text-accent`,children:e.threshold})]}),(0,C.jsx)(`td`,{className:`px-5 py-3 text-muted`,children:e.queue||`All queues`}),(0,C.jsx)(`td`,{className:`px-5 py-3`,children:(0,C.jsx)(`span`,{className:`rounded-md bg-surface-2 px-2 py-0.5 text-xs text-muted`,children:`Browser`})}),(0,C.jsx)(`td`,{className:`px-5 py-3`,children:(0,C.jsx)(b,{checked:e.enabled,onChange:()=>v(e.id),label:`Enabled`})}),(0,C.jsx)(`td`,{className:`px-5 py-3 text-right`,children:(0,C.jsx)(o,{"aria-label":`Delete rule ${e.name}`,onClick:()=>window.confirm(`Delete alert rule "${e.name}"?`)&&n(e.id),children:(0,C.jsx)(s,{className:`size-3.5`})})})]},e.id))})]})})]})]})}function M({onAdd:e}){let[t,n]=(0,S.useState)(``),[r,i]=(0,S.useState)(`error_rate`),[a,o]=(0,S.useState)(`>=`),[s,c]=(0,S.useState)(``),[l,u]=(0,S.useState)(``),[d,p]=(0,S.useState)(null),m=(0,S.useRef)(!1),h=(0,S.useId)(),_=e=>d?.field===e;return(0,C.jsxs)(`div`,{className:`grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-5`,children:[(0,C.jsx)(x,{label:`Name`,children:(0,C.jsx)(y,{name:`alert-rule-name`,autoComplete:`off`,maxLength:200,value:t,"aria-invalid":_(`name`),"aria-describedby":_(`name`)?h:void 0,onChange:e=>{n(e.target.value),p(null)},placeholder:`High error rate`})}),(0,C.jsx)(x,{label:`Metric`,children:(0,C.jsx)(v,{name:`alert-rule-metric`,value:r,"aria-invalid":_(`metric`),"aria-describedby":_(`metric`)?h:void 0,onChange:e=>{i(e.target.value),p(null)},children:T.map(e=>(0,C.jsx)(`option`,{value:e,children:f[e]},e))})}),(0,C.jsx)(x,{label:`Operator`,children:(0,C.jsx)(v,{name:`alert-rule-operator`,value:a,"aria-invalid":_(`operator`),"aria-describedby":_(`operator`)?h:void 0,onChange:e=>{o(e.target.value),p(null)},children:E.map(e=>(0,C.jsx)(`option`,{value:e,children:e},e))})}),(0,C.jsx)(x,{label:`Threshold`,hint:D[r].unit,children:(0,C.jsx)(y,{name:`alert-rule-threshold`,autoComplete:`off`,type:`number`,min:0,max:r===`error_rate`?100:void 0,step:[`waiting`,`failed`,`dlq`].includes(r)?1:`any`,value:s,"aria-invalid":_(`threshold`),"aria-describedby":_(`threshold`)?h:void 0,onChange:e=>{c(e.target.value),p(null)},placeholder:D[r].placeholder})}),(0,C.jsx)(x,{label:`Queue (optional)`,hint:r===`p99_latency`?`global only — latency is not per queue`:void 0,children:(0,C.jsx)(y,{name:`alert-rule-queue`,autoComplete:`off`,spellCheck:!1,maxLength:256,value:l,"aria-invalid":_(`queue`),"aria-describedby":_(`queue`)?h:void 0,onChange:e=>{u(e.target.value),p(null)},disabled:r===`p99_latency`,placeholder:r===`p99_latency`?`Global`:`All queues`,className:`disabled:cursor-not-allowed disabled:opacity-40`})}),(0,C.jsxs)(`div`,{className:`col-span-full`,children:[(0,C.jsx)(g,{variant:`accent`,disabled:!t.trim()||!s.trim(),onClick:()=>{if(m.current)return;let n=k({name:t,metric:r,operator:a,threshold:s,queue:l,channel:`email`});if(!n.ok){p({field:n.field,message:n.error});return}m.current=!0;try{let t=e(n.rule);t.ok||(m.current=!1,p({field:`name`,message:t.error}))}catch(e){m.current=!1,p({field:`name`,message:e.message})}},children:`Save rule`}),d&&(0,C.jsx)(`span`,{id:h,role:`alert`,className:`ml-3 text-xs text-danger`,children:d.message})]})]})}export{j as Alerts};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e,t}from"./cn-CMFYpumL.js";import{g as n,t as r}from"./react-vendor-Bjui4wS4.js";import{o as i}from"./connectionStore-Cd9-3Fw0.js";import{A as a,C as o,O as s,S as c,U as ee,W as l,c as u,h as d,m as f,r as p,s as m,st as h,w as g}from"./index-CAtsVmyh.js";import{i as _,n as v,o as y,t as b}from"./form-DsUq0w8s.js";import{t as x}from"./AreaChart-REK4ZsTC.js";var S=e(n(),1),C={total:1e6,batch:5e3,producers:64,payload:65536,workers:64,workerBatch:1e3,processMs:6e4,durationS:600},w={queue:`benchmark`,mode:`count`,total:5e3,durationS:30,batch:100,producers:4,payload:128,workers:4,workerBatch:100,processMs:5,durable:!1,removeOnComplete:!0},T=`bq-dashboard-benchmark-`;function E(){if(typeof globalThis.crypto?.randomUUID!=`function`)throw Error(`A cryptographically secure browser is required to create a benchmark queue.`);return`${T}${globalThis.crypto.randomUUID()}`}function D(e){return RegExp(`^${T}[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`,`i`).test(e)}var O={Smoke:{mode:`count`,total:200,batch:50,producers:2,workers:1,workerBatch:50,processMs:0},Standard:{mode:`count`,total:5e3,batch:100,producers:4,workers:4,workerBatch:100,processMs:5},Stress:{mode:`count`,total:5e4,batch:500,producers:16,workers:16,workerBatch:200,processMs:0},Soak:{mode:`duration`,durationS:30,batch:100,producers:4,workers:8,workerBatch:100,processMs:10}},k=(e,t,n)=>Number.isFinite(e)?Math.max(t,Math.min(n,Math.floor(e))):t;function te(e){return e?e.length>256?`Queue name is too long (maximum 256 characters).`:/^[a-zA-Z0-9_\-.:]+$/.test(e)?null:`Queue name contains unsupported characters.`:`Queue name is required.`}var A=(e,t)=>e.length?e[Math.min(e.length-1,Math.max(0,Math.ceil(t/100*e.length)-1))]:0,j=(e,t,n,r,i,a,o)=>Array.from({length:t},(t,s)=>({data:{i:e+s,blob:n},durable:r,removeOnComplete:i,...o===void 0?{}:{stallTimeout:o},...a?{jobId:`${a}-${e+s}`,tags:[`bunqueue-dashboard-benchmark`,a]}:{}})),M=e=>new Promise(t=>setTimeout(t,e)),N=async(e,t)=>{let n=performance.now()+e;for(;t()&&performance.now()<n;)await M(Math.min(100,Math.max(0,n-performance.now())))},P=e=>e instanceof Error?e.message:String(e),F=e=>e>=1e6?`${(e/1e6).toFixed(2)}M`:e>=1e3?`${(e/1e3).toFixed(1)}K`:`${Math.round(e)}`,I=r();function ne({history:e,onClear:t}){if(!e.length)return null;let n=JSON.stringify(e,null,2);return(0,I.jsxs)(c,{padded:!1,className:`mt-6 overflow-hidden`,children:[(0,I.jsxs)(`div`,{className:`flex items-center justify-between border-b border-line px-5 py-3`,children:[(0,I.jsx)(`h3`,{className:`text-base font-semibold text-fg`,children:`Run history`}),(0,I.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,I.jsx)(s,{value:n}),(0,I.jsx)(g,{size:`sm`,variant:`ghost`,onClick:()=>{let e=new Blob([n],{type:`application/json`}),t=URL.createObjectURL(e),r=document.createElement(`a`);r.href=t,r.download=`benchmark-history-${Date.now()}.json`,r.click(),URL.revokeObjectURL(t)},children:`Export JSON`}),(0,I.jsx)(g,{size:`sm`,variant:`ghost`,onClick:t,children:`Clear`})]})]}),(0,I.jsx)(`div`,{className:`overflow-x-auto`,children:(0,I.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,I.jsx)(`thead`,{children:(0,I.jsxs)(`tr`,{className:`border-b border-line text-left text-[11px] uppercase tracking-wider text-faint`,children:[(0,I.jsx)(`th`,{className:`px-5 py-2 font-medium`,children:`Mode`}),(0,I.jsx)(`th`,{className:`px-5 py-2 text-right font-medium`,children:`Prod`}),(0,I.jsx)(`th`,{className:`px-5 py-2 text-right font-medium`,children:`Wkr`}),(0,I.jsx)(`th`,{className:`px-5 py-2 text-right font-medium`,children:`Pushed`}),(0,I.jsx)(`th`,{className:`px-5 py-2 text-right font-medium`,children:`Done`}),(0,I.jsx)(`th`,{className:`px-5 py-2 text-right font-medium`,children:`Push/s`}),(0,I.jsx)(`th`,{className:`px-5 py-2 text-right font-medium`,children:`Done/s`}),(0,I.jsx)(`th`,{className:`px-5 py-2 text-right font-medium`,children:`p95`}),(0,I.jsx)(`th`,{className:`px-5 py-2 text-right font-medium`,children:`Dur`})]})}),(0,I.jsx)(`tbody`,{children:e.map(e=>(0,I.jsxs)(`tr`,{className:`border-b border-line last:border-0 hover:bg-surface-2/40`,children:[(0,I.jsx)(`td`,{className:`px-5 py-2 capitalize text-muted`,children:e.mode}),(0,I.jsx)(`td`,{className:`px-5 py-2 text-right tnum text-muted`,children:e.producers}),(0,I.jsx)(`td`,{className:`px-5 py-2 text-right tnum text-muted`,children:e.workers}),(0,I.jsx)(`td`,{className:`px-5 py-2 text-right tnum text-fg`,children:u(e.pushed)}),(0,I.jsx)(`td`,{className:`px-5 py-2 text-right tnum text-success`,children:u(e.completed)}),(0,I.jsxs)(`td`,{className:`px-5 py-2 text-right tnum text-accent`,children:[F(e.pushPerSec),`/s`]}),(0,I.jsxs)(`td`,{className:`px-5 py-2 text-right tnum text-success`,children:[F(e.donePerSec),`/s`]}),(0,I.jsx)(`td`,{className:`px-5 py-2 text-right tnum text-muted`,children:m(e.p95)}),(0,I.jsx)(`td`,{className:`px-5 py-2 text-right tnum text-muted`,children:m(e.durationMs)})]},e.id))})]})})]})}var L={pushed:0,completed:0,pushFailed:0,ackFailed:0,bytes:0,elapsedMs:0,pushPerSec:0,donePerSec:0,activeWorkers:0,pushSeries:[],doneSeries:[],etaMs:null,error:null},R=()=>({pushed:0,completed:0,pushFailed:0,ackFailed:0,bytes:0,assigned:0,startedAt:0,pushLat:[],activeWorkers:0,error:null,lastAt:0,lastPushed:0,lastDone:0,pushSeries:[],doneSeries:[]}),z=0,B=[`waiting`,`prioritized`,`delayed`,`active`,`paused`,`waiting-children`,`completed`,`failed`];function V(e){return B.reduce((t,n)=>{let r=e[n];if(!Number.isSafeInteger(r)||r<0)throw Error(`Malformed queue count for "${n}".`);return t+r},0)}function H(e,t){if(!e||typeof e!=`object`||e.ok!==!0)throw Error(`${t} returned a malformed success response.`)}function re(){let[e,t]=(0,S.useState)(`idle`),[n,r]=(0,S.useState)(L),[i,a]=(0,S.useState)(null),[o,s]=(0,S.useState)([]),[c,ee]=(0,S.useState)(null),l=(0,S.useRef)(!1),u=(0,S.useRef)(!0),d=(0,S.useRef)(0),f=(0,S.useRef)(!1),p=(0,S.useRef)(!1),m=(0,S.useRef)(null),g=(0,S.useRef)(`idle`);g.current=e;let _=(0,S.useRef)(R());return(0,S.useEffect)(()=>(u.current=!0,()=>{u.current=!1,l.current=!0,d.current++}),[]),(0,S.useEffect)(()=>{if(e!==`running`&&e!==`draining`)return;let t=setInterval(()=>{let e=_.current,t=performance.now(),n=t-e.startedAt,i=(t-e.lastAt)/1e3,a=i>0?Math.max(0,(e.pushed-e.lastPushed)/i):0,o=i>0?Math.max(0,(e.completed-e.lastDone)/i):0;e.lastAt=t,e.lastPushed=e.pushed,e.lastDone=e.completed,e.pushSeries=[...e.pushSeries,a].slice(-60),e.doneSeries=[...e.doneSeries,o].slice(-60);let s=null,c=m.current;if(c&&c.mode===`count`){let t=k(c.total,1,C.total);if(g.current===`draining`){let r=e.doneSeries.slice(-10),i=r.length?r.reduce((e,t)=>e+t,0)/r.length:0;i<=0&&n>0&&(i=e.completed/(n/1e3)),i>0&&(s=Math.max(0,t-e.completed)/i*1e3)}else a>0&&(s=Math.max(0,t-e.pushed)/a*1e3)}else c&&c.mode===`duration`&&(s=Math.max(0,k(c.durationS,1,C.durationS)*1e3-n));r({pushed:e.pushed,completed:e.completed,pushFailed:e.pushFailed,ackFailed:e.ackFailed,bytes:e.bytes,elapsedMs:n,pushPerSec:a,donePerSec:o,activeWorkers:e.activeWorkers,pushSeries:e.pushSeries,doneSeries:e.doneSeries,etaMs:s,error:e.error})},200);return()=>clearInterval(t)},[e]),{phase:e,live:n,summary:i,history:o,runCfg:c,run:async(e,n)=>{if(f.current||!u.current)return;f.current=!0;let i=++d.current,o=n??h.createServerTargetClient(h.captureServerRequestTarget()),c=()=>u.current&&d.current===i,v=()=>c()&&!l.current;l.current=!1;try{a(null);let n=e.queue.trim(),i=te(n);if(i){r({...L,error:i}),g.current=`error`,t(`error`);return}if(![`count`,`duration`].includes(e.mode)){r({...L,error:`Benchmark mode is invalid.`}),g.current=`error`,t(`error`);return}if(typeof e.durable!=`boolean`||typeof e.removeOnComplete!=`boolean`){r({...L,error:`Benchmark job options are invalid.`}),g.current=`error`,t(`error`);return}let u=k(e.total,1,C.total),d=k(e.durationS,1,C.durationS),f=d*1e3,h=k(e.batch,1,C.batch),y=k(e.producers,1,C.producers),b=k(e.payload,0,C.payload),x=k(e.workers,0,C.workers),S=k(e.workerBatch,1,C.workerBatch),w=k(e.processMs,0,C.processMs),T=Object.freeze({...e,queue:n,total:u,durationS:d,batch:h,producers:y,payload:b,workers:x,workerBatch:S,processMs:w});try{H(await o.overview(),`Dashboard preflight`)}catch(e){if(!c())return;r({...L,error:`Server unreachable — start it on the Server page first. (${P(e)})`}),t(`error`);return}if(!v())return;try{let e=await o.counts(n);if(!v())return;if(H(e,`Queue-count preflight`),!e?.counts||typeof e.counts!=`object`||Array.isArray(e.counts))throw Error(`Malformed queue-count response.`);let i=V(e.counts);if(i>0){r({...L,error:`Dedicated benchmark queue "${n}" is not empty (${i} job(s) across all states). Clean it before running so no external work can be consumed or deleted.`}),t(`error`);return}}catch(e){if(!c())return;r({...L,error:`Could not verify that dedicated queue "${n}" is empty. (${P(e)})`}),t(`error`);return}if(!v())return;p.current=!1,m.current=T,ee(T),_.current=R(),_.current.startedAt=performance.now(),_.current.lastAt=performance.now(),r({...L}),g.current=`running`,t(`running`);let E=`x`.repeat(b),D=performance.now()+f,O=`bqbench-${globalThis.crypto.randomUUID()}`,M=new Set,F=new Set,I=async()=>{for(;v();){let e;if(T.mode===`count`){if(_.current.assigned>=u)break;e=Math.min(h,u-_.current.assigned)}else{if(performance.now()>=D)break;e=h}let t=_.current.assigned;_.current.assigned+=e;let r=j(t,e,E,T.durable,T.removeOnComplete,O,Math.max(12e4,w+6e4)),i=performance.now(),a=o.addJobsBulk(n,r).then(t=>{if(!c())return;if(H(t,`Bulk enqueue`),!Array.isArray(t.ids)||t.ids.length!==e||t.ids.some(e=>typeof e!=`string`||e.length===0)||new Set(t.ids).size!==t.ids.length)throw Error(`Bulk enqueue returned invalid job IDs.`);let n=t.ids;for(let e of n)M.add(e);_.current.pushed+=n.length,_.current.bytes+=n.length*b;let r=Math.max(0,e-n.length);r>0&&(_.current.pushFailed+=r,_.current.error??=`Server created ${n.length} of ${e} jobs in a benchmark batch.`)}).catch(t=>{c()&&(_.current.pushFailed+=e,_.current.error??=P(t))});F.add(a);try{await a}finally{F.delete(a)}c()&&_.current.pushLat.push(performance.now()-i)}},ne=async()=>{for(;v()&&!(T.mode===`duration`&&performance.now()>=D||T.mode===`count`&&_.current.completed>=u);){_.current.activeWorkers++;let e=[];try{try{let t=await o.pullBatch(n,S);if(!v())break;if(t?.ok!==!0||!Array.isArray(t?.jobs)||t.jobs.length>S||t.jobs.some(e=>!e||typeof e!=`object`||typeof e.id!=`string`||e.id.length===0)||new Set(t.jobs.map(e=>e.id)).size!==t.jobs.length)throw Error(`Malformed pull-batch response.`);e=t.jobs}catch(e){if(!c())break;_.current.error??=P(e)}if(!v())break;if(e.length>0){if(e.some(e=>!M.has(e.id))&&F.size>0&&await Promise.allSettled([...F]),!v())break;let t=e.filter(e=>M.has(e.id)),n=e.filter(e=>!M.has(e.id));if(n.length>0){if(!v())break;let e=await Promise.allSettled(n.map(async e=>{H(await o.retryJob(e.id),`Restore job ${e.id}`)}));if(!c())break;let t=e.filter(e=>e.status===`rejected`);_.current.error??=t.length>0?`Detected ${n.length} foreign job(s); ${t.length} could not be returned to waiting. Benchmark stopped.`:`Detected ${n.length} foreign job(s); returned them to waiting and stopped without counting them.`,l.current=!0}if(t.length>0&&v()){if(w>0&&await N(w,v),!v())break;try{let e=await o.heartbeatBatch(t.map(e=>e.id));H(e,`Heartbeat benchmark jobs`);let n=e.data;if(!n||typeof n!=`object`||n.ok!==!0||!Number.isSafeInteger(n.count)||n.count!==t.length)throw Error(`Heartbeat confirmed ${String(n?.count)} of ${t.length} benchmark jobs; refusing a lossy batch ACK.`);if(!v()||(H(await o.ackBatch(t.map(e=>e.id)),`Acknowledge benchmark jobs`),!c()))break;_.current.completed+=t.length;for(let e of t)M.delete(e.id)}catch(e){if(!c())break;_.current.ackFailed+=t.length,_.current.error??=P(e)}}}}finally{c()&&_.current.activeWorkers--}if(!v())break;if(e.length===0){if(T.mode===`count`&&p.current){M.size>0&&(_.current.error??=`${M.size} benchmark job(s) could not be drained.`);break}await N(50,v)}}},B=Array.from({length:y},I),re=Array.from({length:x},ne),ie=Promise.all(B).then(()=>{c()&&(p.current=!0,v()&&T.mode===`count`&&x>0&&g.current===`running`&&(g.current=`draining`,t(`draining`)))});if(await Promise.all([ie,...re]),!c())return;let U=_.current,W=performance.now()-U.startedAt,G=W/1e3||1,K=[...U.pushLat].sort((e,t)=>e-t),q={pushed:U.pushed,completed:U.completed,pushFailed:U.pushFailed,ackFailed:U.ackFailed,bytes:U.bytes,durationMs:W,pushPerSec:U.pushed/G,donePerSec:U.completed/G,mbPerSec:U.bytes/G,avg:K.length?K.reduce((e,t)=>e+t,0)/K.length:0,p50:A(K,50),p95:A(K,95),p99:A(K,99),max:K.length?K[K.length-1]:0,error:U.error};a(q),r(e=>({...e,pushed:U.pushed,completed:U.completed,pushFailed:U.pushFailed,ackFailed:U.ackFailed,bytes:U.bytes,elapsedMs:W,pushPerSec:0,donePerSec:0,activeWorkers:0,etaMs:0,error:U.error})),s(e=>[{...q,id:++z,at:Date.now(),mode:T.mode,producers:y,workers:x},...e].slice(0,12)),g.current=l.current?`stopped`:U.error?`error`:`done`,t(g.current)}finally{f.current=!1}},stop:()=>{l.current=!0,(g.current===`running`||g.current===`draining`)&&(g.current=`stopping`,t(`stopping`))},reset:()=>{l.current=!0,d.current++,g.current=`idle`,a(null),r(L),t(`idle`)},clearHistory:()=>s([])}}var ie=[`count`,`duration`],U=[`waiting`,`completed`,`failed`],W=null;function G(){return W??=E(),W}var K=[`total`,`durationS`,`batch`,`producers`,`payload`,`workers`,`workerBatch`,`processMs`],q=e=>({...e,total:String(e.total),durationS:String(e.durationS),batch:String(e.batch),producers:String(e.producers),payload:String(e.payload),workers:String(e.workers),workerBatch:String(e.workerBatch),processMs:String(e.processMs)}),ae=e=>({...e,total:Number(e.total),durationS:Number(e.durationS),batch:Number(e.batch),producers:Number(e.producers),payload:Number(e.payload),workers:Number(e.workers),workerBatch:Number(e.workerBatch),processMs:Number(e.processMs)});function oe(){let[e]=(0,S.useState)(G),[n,r]=(0,S.useState)(()=>q({...w,queue:e})),s=re(),T=i(e=>e.baseUrl),[E,A]=(0,S.useState)(null),j=(0,S.useRef)(null),M=(0,S.useRef)(!0);(0,S.useEffect)(()=>(M.current=!0,()=>{M.current=!1,j.current=null}),[]);let{phase:N,live:P,summary:L,history:R}=s,z=N===`running`||N===`draining`||N===`stopping`,B=(e,t)=>r(n=>({...n,[e]:t})),W=e=>t=>B(e,t.target.value),oe=e=>r(t=>{let n={...t};for(let[t,r]of Object.entries(e))n[t]=K.includes(t)?String(r):r;return n}),[J,le]=(0,S.useState)(e);(0,S.useEffect)(()=>{let e=setTimeout(()=>le(n.queue.trim()||w.queue),400);return()=>clearTimeout(e)},[n.queue]);let ue=E?.target.baseUrl??T,{data:de,error:fe}=a(()=>E?E.client.counts(J):h.counts(J),[J,E],{intervalMs:1e3}),Y=de?.counts??null,[X,pe]=(0,S.useState)(!1),[Z,Q]=(0,S.useState)(null),me=async()=>{let t=n.queue.trim();if(j.current||!t)return;if(!D(t)||t!==e){Q({error:`refusing to clean a queue not owned by this dashboard session`});return}j.current=`clean`;let r=E;if(!r){let e=h.captureServerRequestTarget();r={target:e,client:h.createServerTargetClient(e)}}let i=Y&&J===t?` Currently ${u(Y.waiting??0)} waiting / ${u(Y.completed??0)} completed.`:``;if(!window.confirm(`Clean every cleanable job from the dedicated dashboard benchmark queue "${t}" on ${r.target.baseUrl}? This is queue-wide; the cryptographic queue name is owned by this browser session.${i}`)){j.current=null;return}pe(!0),Q(null);try{let e=null;for(let n of U){if(!M.current)return;try{let e=await r.client.clean(t,{state:n,limit:C.total});if(H(e,`Clean ${n} jobs`),!Number.isSafeInteger(e.count)||e.count<0)throw Error(`Clean ${n} jobs returned an invalid count.`)}catch(t){e=t.message}}if(!M.current)return;let n=await r.client.counts(t).catch(t=>(e=t.message,null));if(!M.current)return;if(n==null)Q({error:e??`server unreachable — could not verify`});else if(n.ok!==!0||!n.counts||typeof n.counts!=`object`||Array.isArray(n.counts))Q({error:`server returned a malformed queue-count response`});else{let t=n.counts,r;try{r=V(t)}catch{Q({error:`server returned malformed queue counts`});return}Q(e||r>0?{error:e??`${u(r)} job(s) still present`}:{remaining:0})}}finally{M.current&&pe(!1),j.current===`clean`&&(j.current=null)}},$=s.runCfg??ae(n),he=k($.total,1,C.total),ge=k($.workers,0,C.workers),_e=$.mode===`count`&&(z||L)?Math.min(100,P.pushed/he*100):0,ve=$.mode===`count`&&(z||L)?Math.min(100,P.completed/he*100):0,ye=$.mode===`duration`?Math.min(100,P.elapsedMs/(k($.durationS,1,C.durationS)*1e3)*100):0,be=N===`running`?$.mode===`duration`?`Running…`:`Producing…`:N===`draining`?`Draining…`:N===`stopping`?`Stopping…`:N===`error`?`Error`:L?`Result`:`Ready`,xe=(N===`running`||N===`draining`)&&P.etaMs!=null?P.etaMs>0?`ETA ${m(P.etaMs)}`:`finishing…`:N===`stopped`?`stopped early`:N===`done`?`complete`:``;return(0,I.jsxs)(`div`,{children:[(0,I.jsx)(d,{title:`Benchmark`,description:`Drive real load against the server — producers bulk-enqueue jobs while simulated workers pull, process and ack them. Throughput is measured client-side; the queue genuinely fills and drains.`,actions:N===`stopping`?(0,I.jsxs)(g,{variant:`warning`,size:`sm`,disabled:!0,children:[(0,I.jsx)(ee,{className:`size-3.5`}),` Stopping…`]}):z?(0,I.jsxs)(g,{variant:`warning`,size:`sm`,onClick:s.stop,children:[(0,I.jsx)(ee,{className:`size-3.5`}),` Stop`]}):(0,I.jsxs)(g,{variant:`success`,size:`sm`,disabled:X,onClick:()=>{if(j.current)return;j.current=`run`;let e=ae(n),t=e.queue.trim(),r=h.captureServerRequestTarget(),i=h.createServerTargetClient(r);if(te(t)){s.run({...e,queue:t},i).finally(()=>{j.current===`run`&&(j.current=null)});return}let a=0;if(!E&&Y&&J===t)try{a=V(Y)}catch{}let o=a>0?` Warning: it already holds ${u(a)} job(s) across all states; the safety preflight will refuse the run until the dedicated queue is empty.`:``;if(!window.confirm(`Enqueue real jobs into "${t}" on ${r.baseUrl}?${o}`)){j.current=null;return}A({target:r,client:i}),s.run(e,i).finally(()=>{j.current===`run`&&(j.current=null)})},children:[(0,I.jsx)(l,{className:`size-3.5`}),` Run benchmark`]})}),P.error&&(0,I.jsx)(`div`,{className:`mb-4 rounded-lg border border-red-500/20 bg-red-500/5 px-4 py-2 text-sm text-danger`,children:P.error}),(0,I.jsxs)(`div`,{className:`mb-4 flex flex-wrap items-center gap-2`,children:[(0,I.jsx)(`span`,{className:`text-xs font-medium uppercase tracking-wider text-faint`,children:`Presets`}),Object.keys(O).map(e=>(0,I.jsx)(`button`,{type:`button`,disabled:z,onClick:()=>oe(O[e]),className:`rounded-md border border-line bg-surface-2 px-2.5 py-1 text-xs text-muted transition-colors hover:border-line-strong hover:text-fg disabled:opacity-40`,children:e},e))]}),(0,I.jsxs)(`div`,{className:`grid grid-cols-1 gap-6 lg:grid-cols-3`,children:[(0,I.jsxs)(c,{className:`lg:col-span-1`,children:[(0,I.jsx)(o,{title:`Configuration`}),(0,I.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,I.jsx)(b,{label:`Dedicated queue`,hint:`Cryptographically generated for this browser tab; production queue names cannot be entered here.`,children:(0,I.jsx)(v,{name:`benchmark-queue`,autoComplete:`off`,value:n.queue,readOnly:!0,"aria-readonly":`true`})}),(0,I.jsx)(b,{label:`Mode`,hint:n.mode===`count`?`Push a fixed number of jobs.`:`Run producers + workers for a fixed time.`,children:(0,I.jsx)(_,{options:ie,value:n.mode,onChange:e=>B(`mode`,e),disabled:z})}),n.mode===`count`?(0,I.jsx)(b,{label:`Total jobs`,hint:`max ${u(C.total)}`,children:(0,I.jsx)(v,{name:`benchmark-total`,autoComplete:`off`,type:`number`,min:1,max:C.total,value:n.total,disabled:z,onChange:W(`total`)})}):(0,I.jsx)(b,{label:`Duration (s)`,hint:`max ${C.durationS}s`,children:(0,I.jsx)(v,{name:`benchmark-duration-seconds`,autoComplete:`off`,type:`number`,min:1,max:C.durationS,value:n.durationS,disabled:z,onChange:W(`durationS`)})}),(0,I.jsx)(`div`,{className:`mt-1 text-[11px] font-semibold uppercase tracking-wider text-faint`,children:`Producers`}),(0,I.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,I.jsx)(b,{label:`Producers`,hint:`parallel, max ${C.producers}`,children:(0,I.jsx)(v,{name:`benchmark-producers`,autoComplete:`off`,type:`number`,min:1,max:C.producers,value:n.producers,disabled:z,onChange:W(`producers`)})}),(0,I.jsx)(b,{label:`Push batch`,hint:`jobs/req, max ${C.batch}`,children:(0,I.jsx)(v,{name:`benchmark-push-batch`,autoComplete:`off`,type:`number`,min:1,max:C.batch,value:n.batch,disabled:z,onChange:W(`batch`)})}),(0,I.jsx)(b,{label:`Payload`,hint:`bytes/job, max ${u(C.payload)}`,children:(0,I.jsx)(v,{name:`benchmark-payload-bytes`,autoComplete:`off`,type:`number`,min:0,max:C.payload,value:n.payload,disabled:z,onChange:W(`payload`)})})]}),(0,I.jsx)(`div`,{className:`mt-1 text-[11px] font-semibold uppercase tracking-wider text-faint`,children:`Workers (simulated)`}),(0,I.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,I.jsx)(b,{label:`Workers`,hint:`0 = produce only, max ${C.workers}`,children:(0,I.jsx)(v,{name:`benchmark-workers`,autoComplete:`off`,type:`number`,min:0,max:C.workers,value:n.workers,disabled:z,onChange:W(`workers`)})}),(0,I.jsx)(b,{label:`Pull batch`,hint:`jobs/pull, max ${C.workerBatch}`,children:(0,I.jsx)(v,{name:`benchmark-pull-batch`,autoComplete:`off`,type:`number`,min:1,max:C.workerBatch,value:n.workerBatch,disabled:z,onChange:W(`workerBatch`)})}),(0,I.jsx)(b,{label:`Process (ms)`,hint:`simulated work per pull`,children:(0,I.jsx)(v,{name:`benchmark-process-ms`,autoComplete:`off`,type:`number`,min:0,max:C.processMs,value:n.processMs,disabled:z,onChange:W(`processMs`)})})]}),(0,I.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,I.jsx)(y,{checked:n.durable,onChange:e=>B(`durable`,e),disabled:z,label:`Durable (fsync each job)`}),(0,I.jsx)(`span`,{className:`text-sm text-muted`,children:`Durable (fsync each job)`})]}),(0,I.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,I.jsx)(y,{checked:n.removeOnComplete,onChange:e=>B(`removeOnComplete`,e),disabled:z,label:`Remove on complete`}),(0,I.jsx)(`span`,{className:`text-sm text-muted`,children:`Remove on complete`})]}),(0,I.jsxs)(`div`,{className:`flex items-center gap-3 pt-1`,children:[(0,I.jsx)(g,{variant:`ghost`,size:`sm`,disabled:z||X,onClick:me,children:X?`Cleaning…`:`Clean queue`}),Z&&(`error`in Z?(0,I.jsxs)(`span`,{className:`text-xs text-danger`,children:[`Clean unverified — `,Z.error]}):(0,I.jsx)(`span`,{className:t(`text-xs`,Z.remaining>0?`text-warning`:`text-success`),children:Z.remaining>0?`Cleaned — ${u(Z.remaining)} active job(s) remain (requeued after the stall timeout)`:`Cleaned — 0 jobs remain`}))]})]})]}),(0,I.jsxs)(`div`,{className:`flex flex-col gap-6 lg:col-span-2`,children:[(0,I.jsxs)(c,{children:[(0,I.jsxs)(`div`,{className:`mb-3 flex items-center justify-between`,children:[(0,I.jsx)(`h3`,{className:`text-base font-semibold text-fg`,children:be}),(0,I.jsxs)(`span`,{className:`text-right text-xs text-faint`,children:[(0,I.jsxs)(`span`,{className:`block`,children:[`Benchmark target: `,ue]}),xe&&(0,I.jsx)(`span`,{className:`block`,children:xe})]})]}),(0,I.jsx)(se,{label:$.mode===`duration`?`Elapsed`:`Produced`,pct:$.mode===`duration`?ye:_e,tone:`accent`}),ge>0&&$.mode===`count`&&(0,I.jsx)(se,{label:`Completed`,pct:ve,tone:`emerald`}),(0,I.jsxs)(`div`,{className:`mt-4 grid grid-cols-2 gap-4 md:grid-cols-4`,children:[(0,I.jsx)(f,{label:`Pushed`,value:u(P.pushed),tone:`accent`,compact:!0}),(0,I.jsx)(f,{label:`Completed`,value:u(P.completed),tone:`green`,compact:!0}),(0,I.jsx)(f,{label:`Push/sec`,value:F(P.pushPerSec),compact:!0}),(0,I.jsx)(f,{label:`Done/sec`,value:F(P.donePerSec),tone:`green`,compact:!0}),(0,I.jsx)(f,{label:`Elapsed`,value:m(P.elapsedMs),compact:!0}),(0,I.jsx)(f,{label:`Active workers`,value:`${P.activeWorkers}/${ge}`,tone:`blue`,compact:!0}),(0,I.jsx)(f,{label:`Data`,value:p(L?.bytes??P.bytes),compact:!0}),(0,I.jsx)(f,{label:`Errors`,value:u((L?.pushFailed??P.pushFailed)+(L?.ackFailed??P.ackFailed)),tone:P.pushFailed+P.ackFailed?`red`:`default`,compact:!0})]})]}),(0,I.jsxs)(c,{children:[(0,I.jsx)(o,{title:`Throughput`}),(0,I.jsx)(`p`,{className:`-mt-3 mb-4 text-xs text-faint`,children:`Enqueue (client → server) vs completion (workers) per second`}),(0,I.jsx)(x,{series:[{label:`Push/sec`,color:`#38bdf8`,points:P.pushSeries,area:!0},{label:`Done/sec`,color:`#34d399`,points:P.doneSeries,area:!0}]}),(0,I.jsxs)(`div`,{className:`mt-2 flex items-center gap-4 font-mono text-[11px] text-faint`,children:[(0,I.jsx)(ce,{color:`#38bdf8`,label:`Push/sec`,value:P.pushPerSec}),(0,I.jsx)(ce,{color:`#34d399`,label:`Done/sec`,value:P.donePerSec})]})]})]})]}),L&&(0,I.jsxs)(c,{className:`mt-6`,children:[(0,I.jsx)(o,{title:`Summary`}),(0,I.jsxs)(`div`,{className:`grid grid-cols-2 gap-4 md:grid-cols-4 xl:grid-cols-8`,children:[(0,I.jsx)(f,{label:`Pushed`,value:u(L.pushed),tone:`accent`,compact:!0}),(0,I.jsx)(f,{label:`Completed`,value:u(L.completed),tone:`green`,compact:!0}),(0,I.jsx)(f,{label:`Duration`,value:m(L.durationMs),compact:!0}),(0,I.jsx)(f,{label:`Avg push/s`,value:F(L.pushPerSec),tone:`accent`,compact:!0}),(0,I.jsx)(f,{label:`Avg done/s`,value:F(L.donePerSec),tone:`green`,compact:!0}),(0,I.jsx)(f,{label:`Data rate`,value:`${p(L.mbPerSec)}/s`,compact:!0}),(0,I.jsx)(f,{label:`Push p95`,value:m(L.p95),compact:!0}),(0,I.jsx)(f,{label:`Push p99`,value:m(L.p99),compact:!0})]}),(0,I.jsxs)(`p`,{className:`mt-3 text-xs text-faint`,children:[`Push-batch latency avg `,m(L.avg),` · p50 `,m(L.p50),` · p95`,` `,m(L.p95),` · p99 `,m(L.p99),` · max `,m(L.max),`.`,L.error?` First error: ${L.error}`:``]})]}),(0,I.jsxs)(c,{className:`mt-6`,children:[(0,I.jsx)(o,{title:`Server queue: ${J}`,action:fe?(0,I.jsx)(`span`,{className:`text-xs font-medium text-warning`,children:`stale — server unreachable`}):void 0}),(0,I.jsxs)(`p`,{className:`-mt-3 mb-4 text-xs text-faint`,children:[`Live counts from `,ue,` (poll 1s).`,E?` Pinned to the current or last benchmark run.`:``]}),Y?(0,I.jsxs)(`div`,{className:`grid grid-cols-2 gap-4 md:grid-cols-5`,children:[(0,I.jsx)(f,{label:`Waiting`,value:u(Y.waiting??0),tone:`amber`,compact:!0}),(0,I.jsx)(f,{label:`Active`,value:u(Y.active??0),tone:`blue`,compact:!0}),(0,I.jsx)(f,{label:`Completed`,value:u(Y.completed??0),tone:`green`,compact:!0}),(0,I.jsx)(f,{label:`Failed`,value:u(Y.failed??0),tone:Y.failed?`red`:`default`,compact:!0}),(0,I.jsx)(f,{label:`Delayed`,value:u(Y.delayed??0),compact:!0})]}):(0,I.jsx)(`p`,{className:`text-sm text-faint`,children:`No counts yet — waiting for the server to answer for this queue.`})]}),(0,I.jsx)(ne,{history:R,onClear:s.clearHistory})]})}function se({label:e,pct:n,tone:r}){return(0,I.jsxs)(`div`,{className:`mb-2`,children:[(0,I.jsxs)(`div`,{className:`mb-1 flex items-center justify-between text-[11px] text-faint`,children:[(0,I.jsx)(`span`,{children:e}),(0,I.jsxs)(`span`,{className:`tnum`,children:[n.toFixed(0),`%`]})]}),(0,I.jsx)(`div`,{className:`h-1.5 overflow-hidden rounded-full bg-surface-2`,role:`progressbar`,"aria-label":e,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(n),children:(0,I.jsx)(`div`,{className:t(`h-full rounded-full transition-[width] motion-reduce:transition-none`,r===`accent`?`bg-accent`:`bg-emerald-500`),style:{width:`${n}%`}})})]})}function ce({color:e,label:t,value:n}){return(0,I.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,I.jsx)(`span`,{className:`size-2 rounded-full`,style:{background:e}}),t,` `,(0,I.jsxs)(`span`,{className:`text-fg`,children:[F(n),`/s`]})]})}export{oe as Benchmark};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./cn-CMFYpumL.js";import{g as t,t as n}from"./react-vendor-Bjui4wS4.js";import{i as r}from"./upstreamPaths-D_LPsv7b.js";import{A as i,C as a,S as o,ct as s,ft as c,h as l,st as u,w as d}from"./index-CAtsVmyh.js";import{i as f,n as p,t as m}from"./form-DsUq0w8s.js";import{r as h}from"./useServerActionGuard-CItj3hvh.js";import{MAX_JOB_DATA_BYTES as g,acceptedBulkIds as _,parseRepeat as v,queueNameError as y,utf8ByteLength as b}from"./AddJob-CG28MSxU.js";var x=e(t(),1),S=n(),C=1e4,w=365*24*60*60*1e3,T=1440*60*1e3,E=64*1024*1024,D=64*1024*1024,O=`[
|
|
2
|
-
{ "data": { "to": "a@example.com", "template": "welcome" }, "priority": 1 },
|
|
3
|
-
{ "data": { "to": "b@example.com", "template": "welcome" } }
|
|
4
|
-
]`,k=e=>{let t=typeof e==`string`&&e.trim()!==``?Number(e.trim()):e;return typeof t==`number`&&Number.isFinite(t)?t:void 0},A=e=>typeof e==`boolean`?e:void 0,j=e=>typeof e==`string`?e.trim()===``?void 0:e.trim():typeof e==`number`&&Number.isFinite(e)?String(e):void 0,M=e=>{if(!Array.isArray(e))return;let t=e.map(j);return t.every(e=>e!==void 0)?t:void 0},N=e=>typeof e==`object`&&e&&!Array.isArray(e)?e:void 0,P=e=>{let t=k(e);if(t!==void 0)return t;let n=N(e);if(!n||n.type!==`fixed`&&n.type!==`exponential`||Object.keys(n).some(e=>e!==`type`&&e!==`delay`))return;let r=k(n.delay);return r===void 0?void 0:{type:n.type,delay:r}},F=e=>{let t=N(e);if(!t)return;let n=v(JSON.stringify(t));return n.ok?n.repeat:void 0};function I(e){let t=N(e);if(!t)return{ok:!1,msg:`must be an object`};let n=new Set([`ttl`,`extend`,`replace`]),r=Object.keys(t).filter(e=>!n.has(e));if(r.length)return{ok:!1,msg:`has unknown option(s): ${r.join(`, `)}`};if(t.ttl!==void 0&&(!Number.isSafeInteger(t.ttl)||t.ttl<=0||t.ttl>w))return{ok:!1,msg:`ttl must be a whole number from 1 to ${w} ms`};for(let e of[`extend`,`replace`])if(t[e]!==void 0&&typeof t[e]!=`boolean`)return{ok:!1,msg:`${e} must be a boolean`};return t.extend===!0&&t.replace===!0?{ok:!1,msg:`extend and replace are mutually exclusive`}:t.extend===!0&&t.ttl===void 0?{ok:!1,msg:`extend requires ttl`}:{ok:!0,dedup:{...typeof t.ttl==`number`?{ttl:t.ttl}:{},...typeof t.extend==`boolean`?{extend:t.extend}:{},...typeof t.replace==`boolean`?{replace:t.replace}:{}}}}var L=e=>{let t=I(e);return t.ok?t.dedup:void 0};function R(e,t,n){if(n===`spec`&&e&&typeof e==`object`&&!Array.isArray(e)&&`data`in e){let n=e;return{data:n.data,priority:k(n.priority)??t.priority,delay:k(n.delay),maxAttempts:k(n.maxAttempts)??t.maxAttempts,backoff:P(n.backoff)??t.backoff,timeout:k(n.timeout)??t.timeout,jobId:j(n.jobId)??j(n.customId),removeOnComplete:A(n.removeOnComplete),removeOnFail:A(n.removeOnFail),durable:A(n.durable),lifo:A(n.lifo),ttl:k(n.ttl),uniqueKey:j(n.uniqueKey),tags:M(n.tags),groupId:j(n.groupId),dependsOn:M(n.dependsOn),stallTimeout:k(n.stallTimeout),repeat:F(n.repeat),dedup:L(n.dedup),stackTraceLimit:k(n.stackTraceLimit),timestamp:k(n.timestamp)}}return{data:e,...t}}var z=new Set([`data`,`priority`,`delay`,`maxAttempts`,`backoff`,`timeout`,`jobId`,`customId`,`removeOnComplete`,`removeOnFail`,`durable`,`lifo`,`ttl`,`uniqueKey`,`tags`,`groupId`,`dependsOn`,`stallTimeout`,`repeat`,`dedup`,`stackTraceLimit`,`timestamp`]),B=(e,t,n,r,i)=>{if(e===void 0)return null;let a=k(e);return a===void 0?`${t} must be a number`:i&&!Number.isSafeInteger(a)?`${t} must be a whole, safe integer`:a<n||a>r?`${t} must be between ${n} and ${r}`:null};function V(e){let t={priority:e.priority.trim()||void 0,maxAttempts:e.maxAttempts.trim()||void 0,backoff:e.backoff.trim()||void 0,timeout:e.timeout.trim()||void 0},n=[[`priority`,`Default priority`,-1e6,1e6,!0],[`maxAttempts`,`Default max attempts`,1,1e3,!0],[`backoff`,`Default backoff`,0,T,!0],[`timeout`,`Default timeout`,0,T,!0]],r={};for(let[e,i,a,o,s]of n){let n=B(t[e],i,a,o,s);if(n)return{ok:!1,msg:n};let c=k(t[e]);c!==void 0&&(r[e]=c)}return{ok:!0,defaults:r}}function H(e){let t=Object.keys(e).filter(e=>!z.has(e));if(t.length)return`unknown job option(s): ${t.join(`, `)}`;let n=[[`priority`,`priority`,-1e6,1e6,!0],[`delay`,`delay`,0,w,!0],[`maxAttempts`,`maxAttempts`,1,1e3,!0],[`timeout`,`timeout`,0,T,!0],[`ttl`,`ttl`,0,w,!0],[`stallTimeout`,`stallTimeout`,0,T,!0],[`stackTraceLimit`,`stackTraceLimit`,0,1e4,!0],[`timestamp`,`timestamp`,0,2**53-1,!0]];for(let[t,r,i,a,o]of n){let n=B(e[t],r,i,a,o);if(n)return n}if(e.backoff!==void 0){let t=P(e.backoff);if(t===void 0)return`backoff must be a number or {"type":"fixed|exponential","delay":number} with no extra keys`;let n=B(typeof t==`number`?t:t.delay,`backoff delay`,0,T,!0);if(n)return n}if(e.repeat!==void 0){let t=N(e.repeat);if(!t)return`repeat must be an object`;let n=v(JSON.stringify(t));if(!n.ok)return n.msg}if(e.dedup!==void 0){let t=I(e.dedup);if(!t.ok)return`dedup ${t.msg}`;if(j(e.uniqueKey)===void 0)return`dedup requires a non-empty uniqueKey`}for(let t of[`removeOnComplete`,`removeOnFail`,`durable`,`lifo`])if(e[t]!==void 0&&typeof e[t]!=`boolean`)return`${t} must be a boolean`;for(let t of[`jobId`,`customId`,`uniqueKey`]){let n=e[t];if(n===void 0)continue;let i=j(n);if(i===void 0||i.length>1024)return`${t} must be a non-empty string (or finite numeric id) of at most 1024 characters`;if((t===`jobId`||t===`customId`)&&r(i))return`${t}: ${r(i)}`}let i=j(e.jobId),a=j(e.customId);if(i!==void 0&&a!==void 0&&i!==a)return`jobId and customId disagree; provide only one spelling`;if(e.groupId!==void 0){let t=j(e.groupId);if(t===void 0||t.length>256)return`groupId must be a non-empty string of at most 256 characters`}for(let t of[`tags`,`dependsOn`]){if(e[t]===void 0)continue;let n=M(e[t]),i=t===`tags`?256:1024;if(!n||n.some(e=>e.length>i))return`${t} must be an array of non-empty strings of at most ${i} characters`;if(t===`dependsOn`){let e=n?.find(e=>r(e));if(e)return`dependsOn ID "${e}": ${r(e)}`}}return null}function U(e){let t=new Map;for(let n of e)if(n.jobId){if(t.has(n.jobId))return`duplicate jobId/customId "${n.jobId}" makes dependency resolution ambiguous`;t.set(n.jobId,n)}let n=new Set,r=new Set,i=e=>{if(n.has(e))return!0;if(r.has(e))return!1;n.add(e);for(let n of t.get(e)?.dependsOn??[])if(t.has(n)&&i(n))return!0;return n.delete(e),r.add(e),!1};for(let e of t.keys())if(i(e))return`dependency cycle detected in this batch (involving "${e}")`;return null}function W(e,t,n){if(e.length>C)return{ok:!1,msg:`Too many jobs (${e.length}). Limit is ${C}.`};let r=[],i=0;for(let a=0;a<e.length;a++){let o=e[a];if(n===`spec`&&typeof o==`object`&&o&&!Array.isArray(o)&&`data`in o){let e=H(o);if(e)return{ok:!1,msg:`Job ${a+1}: ${e}`}}let s=R(o,t,n),c;try{c=JSON.stringify(s.data)}catch{return{ok:!1,msg:`Job ${a+1}: data must be JSON serializable`}}if(c===void 0)return{ok:!1,msg:`Job ${a+1}: data must be JSON serializable`};if(c.length>10485760)return{ok:!1,msg:`Job ${a+1}: data exceeds the 10 MiB UTF-8 limit`};let l=b(c,g);if(l>10485760)return{ok:!1,msg:`Job ${a+1}: data exceeds the 10 MiB UTF-8 limit`};if(i+=l,i>67108864)return{ok:!1,msg:`Combined job data exceeds the 64 MiB import safety limit`};r.push(s)}let a=U(r);return a?{ok:!1,msg:a}:{ok:!0,bodies:r}}function G(e){return e.length>67108864||b(e,67108864)>67108864?`Import text exceeds the 64 MiB UTF-8 safety limit`:null}function K(e,t=D){return s(e,t)}function q(e,t,n){return{ok:!0,msg:`Accepted ${t} job submission${t===1?``:`s`} in ${n}; server returned ${e} distinct job ID${e===1?``:`s`} (deduplication may reuse existing jobs)`}}function J(e){let t=G(e);if(t)return{items:[],error:t};let n=e.trim();if(!n)return{items:[],error:null};try{let e=JSON.parse(n);return Array.isArray(e)?{items:e,error:null}:{items:[e],error:null}}catch{}let r=[],i=e.split(`
|
|
5
|
-
`);for(let e=0;e<i.length;e++){let t=i[e].trim();if(t)try{r.push(JSON.parse(t))}catch(t){return{items:[],error:`Line ${e+1}: ${t.message}`}}}return{items:r,error:null}}function Y(){let{data:e,error:t,refetch:n}=i(()=>u.queues(),[],{intervalMs:3e4}),[r,s]=(0,x.useState)(``),[g,v]=(0,x.useState)(``),[b,w]=(0,x.useState)(`spec`),[D,k]=(0,x.useState)(``),[A,j]=(0,x.useState)(``),[M,N]=(0,x.useState)(``),[P,F]=(0,x.useState)(``),[I,L]=(0,x.useState)(!1),[R,z]=(0,x.useState)(null),B=(0,x.useRef)(null),H=(0,x.useRef)(0),U=h(`bulk-add-jobs`);(0,x.useEffect)(()=>{L(!1),z(null)},[U.scopeKey]),(0,x.useEffect)(()=>()=>{H.current+=1;let e=B.current;B.current=null;try{e?.abort()}catch{}},[]);let{items:Y,error:X}=(0,x.useMemo)(()=>J(g),[g]),Z=(0,x.useMemo)(()=>{if(X)return{ok:!1,msg:X};if(Y.length>C)return{ok:!1,msg:`Too many jobs (${Y.length}). Limit is ${C}.`};let e=V({priority:D,maxAttempts:A,backoff:M,timeout:P});return e.ok?W(Y,e.defaults,b):e},[M,X,Y,A,b,D,P]);return(0,S.jsxs)(`div`,{children:[(0,S.jsx)(l,{title:`Bulk Add Jobs`,description:`Import many distinct jobs at once from a JSON array, NDJSON, or a file.`}),(0,S.jsxs)(`div`,{className:`grid grid-cols-1 gap-6 lg:grid-cols-3`,children:[(0,S.jsxs)(o,{className:`lg:col-span-2`,children:[(0,S.jsx)(a,{title:`Jobs`,action:(0,S.jsxs)(`label`,{className:`cursor-pointer rounded-lg border border-line px-3 py-1.5 text-xs text-muted hover:bg-surface-2 hover:text-fg`,children:[`Upload file`,(0,S.jsx)(`input`,{type:`file`,name:`jobs-file`,accept:`.json,.ndjson,.txt,application/json`,onChange:e=>{let t=e.currentTarget,n=t.files?.[0];if(!n)return;let r=H.current+1;H.current=r;let i=B.current;B.current=null;try{i?.abort()}catch{}if(t.value=``,!Number.isSafeInteger(n.size)||n.size<0||n.size>67108864){z({ok:!1,msg:`File is too large; maximum is 64 MiB (${n.name})`});return}if(g.trim()!==``&&!window.confirm(`Replace the current input with "${n.name}"?`))return;let a=new FileReader,o=()=>H.current===r&&B.current===a,s=()=>{o()&&(B.current=null)};a.onload=()=>{if(!o())return;if(typeof a.result!=`string`){z({ok:!1,msg:`Could not decode file as text (${n.name})`}),s();return}let e=a.result,t=G(e);t?z({ok:!1,msg:`${t} (${n.name})`}):(v(e),z(null)),s()},a.onerror=()=>{o()&&(z({ok:!1,msg:`Could not read file (${n.name})`}),s())},a.onabort=s,B.current=a,z(null);try{a.readAsText(n)}catch{if(!o())return;z({ok:!1,msg:`Could not read file (${n.name})`}),s()}},className:`hidden`})]})}),(0,S.jsx)(`textarea`,{name:`jobs-json`,value:g,onChange:e=>{H.current+=1;let t=B.current;B.current=null;try{t?.abort()}catch{}v(e.target.value)},maxLength:E,spellCheck:!1,rows:16,placeholder:O,"aria-label":`Jobs JSON`,className:`w-full rounded-lg border border-line bg-surface-2 p-3 font-mono text-xs text-fg placeholder:text-faint focus:border-accent/60 focus:outline-none focus:ring-2 focus:ring-accent/30`}),(0,S.jsxs)(`fieldset`,{className:`mt-3 flex flex-wrap items-center gap-3`,children:[(0,S.jsx)(`legend`,{className:`sr-only`,children:`Import interpretation`}),(0,S.jsx)(`span`,{"aria-hidden":`true`,className:`text-xs font-medium text-faint`,children:`Interpret each item as`}),(0,S.jsx)(f,{options:[`spec`,`raw`],value:b,onChange:w}),(0,S.jsx)(`span`,{className:`text-xs text-faint`,children:b===`spec`?`job spec: an object with a "data" key sets options from its sibling fields.`:`raw data: the whole item becomes the job payload, untouched.`})]}),(0,S.jsx)(`p`,{className:`mt-2 text-xs text-faint`,children:`Accepts a JSON array, a single object, or newline-delimited JSON.`}),(0,S.jsx)(`div`,{className:`mt-2 text-sm`,children:X?(0,S.jsx)(`span`,{role:`alert`,className:`text-danger`,children:X}):Y.length>C?(0,S.jsxs)(`span`,{role:`alert`,className:`text-danger`,children:[`Too many jobs (`,Y.length,`). Limit is `,C,`.`]}):Y.length>0?(0,S.jsxs)(`span`,{className:`text-success`,children:[Y.length,` job(s) parsed ✓`]}):(0,S.jsx)(`span`,{className:`text-faint`,children:`Nothing parsed yet.`})}),!X&&Y.length>0&&!Z.ok&&(0,S.jsxs)(`p`,{role:`alert`,className:`mt-2 text-xs text-danger`,children:[`Import blocked — `,Z.msg,`. Fix the spec or switch to “raw” when the whole object is job data.`]})]}),(0,S.jsxs)(o,{children:[(0,S.jsx)(a,{title:`Target & defaults`}),(0,S.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,S.jsxs)(m,{label:`Queue`,children:[(0,S.jsx)(p,{list:`bulk-queue-options`,"aria-label":`Queue`,name:`bulk-target-queue`,autoComplete:`off`,value:r,onChange:e=>s(e.target.value),placeholder:`queue name (existing or new)`}),(0,S.jsx)(`datalist`,{id:`bulk-queue-options`,children:(e?.queues??[]).map(e=>(0,S.jsx)(`option`,{value:e.name},e.name))})]}),t&&(0,S.jsxs)(`p`,{role:`status`,className:`-mt-2 text-xs text-warning`,children:[`Existing queue suggestions unavailable — `,t.message,`. You can still enter a queue name manually.`,` `,(0,S.jsx)(`button`,{type:`button`,onClick:()=>void n(),className:`font-medium underline underline-offset-2 hover:text-fg`,children:`Retry`})]}),(0,S.jsx)(`p`,{className:`-mt-2 text-xs text-faint`,children:`Defaults below fill any field an item omits.`}),(0,S.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,S.jsx)(m,{label:`Priority`,children:(0,S.jsx)(p,{type:`number`,min:-1e6,max:1e6,step:1,name:`default-priority`,value:D,onChange:e=>k(e.target.value),placeholder:`0`})}),(0,S.jsx)(m,{label:`Max attempts`,children:(0,S.jsx)(p,{type:`number`,min:1,max:1e3,step:1,name:`default-max-attempts`,value:A,onChange:e=>j(e.target.value),placeholder:`3`})}),(0,S.jsx)(m,{label:`Backoff (ms)`,children:(0,S.jsx)(p,{type:`number`,min:0,max:T,step:1,name:`default-backoff`,value:M,onChange:e=>N(e.target.value),placeholder:`1000`})}),(0,S.jsx)(m,{label:`Timeout (ms)`,children:(0,S.jsx)(p,{type:`number`,min:0,max:T,step:1,name:`default-timeout`,value:P,onChange:e=>F(e.target.value),placeholder:`—`})})]}),(0,S.jsx)(`div`,{className:`flex items-center gap-3`,children:(0,S.jsx)(d,{variant:`accent`,disabled:I||Y.length===0||Y.length>C||X!=null||!Z.ok,onClick:async()=>{z(null);let e=r.trim(),t=y(e);if(t){z({ok:!1,msg:t});return}let n=G(g);if(n){z({ok:!1,msg:n});return}if(X){z({ok:!1,msg:X});return}if(Y.length===0){z({ok:!1,msg:`Nothing to enqueue — paste a JSON array or NDJSON.`});return}if(Y.length>C){z({ok:!1,msg:`Too many jobs (${Y.length}). Limit is ${C}.`});return}if(!Z.ok){z({ok:!1,msg:Z.msg});return}let i=Z.bodies,a=K(i);if(a){z({ok:!1,msg:a});return}let o=U.begin();if(o){L(!0);try{let t=_(await u.addJobsBulk(e,i),i.length);if(!o.isCurrent())return;let n=q(new Set(t).size,i.length,e);z(n),c.success(n.msg)}catch(e){if(!o.isCurrent())return;let t=e.message;z({ok:!1,msg:t}),c.error(`Bulk import failed`,t)}finally{o.finish()&&L(!1)}}},children:I?`Importing…`:`Import ${Y.length||``}`})}),R&&(0,S.jsx)(`span`,{role:R.ok?`status`:`alert`,className:R.ok?`text-sm text-success`:`text-sm text-danger`,children:R.msg})]})]})]})]})}export{Y as BulkAddJobs};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./cn-CMFYpumL.js";import{g as t,t as n}from"./react-vendor-Bjui4wS4.js";import{A as r,L as i,T as a,Y as o,a as s,b as c,c as l,h as u,j as d,v as f,x as p,y as m}from"./index-CAtsVmyh.js";import{t as h}from"./Pagination-Bfi4vkpp.js";import{r as g,t as _}from"./useServerActionGuard-CItj3hvh.js";var v=e(t(),1),y=n();function b(){let{data:e,error:t,loading:n,refetch:b}=r(()=>d.crons(),[]),x=(e?.crons??[]).slice(),[S,C]=(0,v.useState)(0),w=Math.max(1,Math.ceil(x.length/15)),T=Math.min(S,w-1),[E,D]=(0,v.useState)(null),[O,k]=(0,v.useState)(null),A=g(`classic-cron`);(0,v.useEffect)(()=>{D(null),k(null)},[A.scopeKey]);let j=async e=>{if(!window.confirm(`Delete cron "${e}"?`))return;let t=A.begin(`delete:${e}`);if(t){D(null),k(e);try{if(_(await d.deleteCron(e),`Delete cron`),!t.isCurrent())return;await b()}catch(e){t.isCurrent()&&D(`Delete failed: ${e.message}`)}finally{t.finish()&&k(null)}}};return(0,y.jsxs)(`div`,{children:[t&&e&&(0,y.jsx)(p,{message:`Cron refresh failed — showing the last successful schedule list.`,onRetry:b}),(0,y.jsx)(u,{title:`Cron Jobs`,description:e?`${x.length} scheduled`:`Schedule inventory unavailable`,live:!!e&&!t}),E&&(0,y.jsx)(`p`,{role:`alert`,className:`mb-4 text-sm text-danger`,children:E}),t&&!e?(0,y.jsx)(m,{error:t,onRetry:b}):n&&!e?(0,y.jsx)(c,{label:`Loading cron jobs…`}):x.length===0?(0,y.jsx)(f,{icon:(0,y.jsx)(i,{}),title:`No scheduled jobs`,hint:`Repeatable jobs created with upsertJobScheduler appear here.`}):(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(`div`,{className:`overflow-x-auto rounded-xl border border-line bg-surface`,children:(0,y.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,y.jsx)(`thead`,{children:(0,y.jsxs)(`tr`,{className:`border-b border-line text-left text-[11px] uppercase tracking-wider text-faint`,children:[(0,y.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Name`}),(0,y.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Queue`}),(0,y.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Schedule`}),(0,y.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Next Run`}),(0,y.jsx)(`th`,{className:`px-5 py-3 text-right font-medium`,children:`Runs`}),(0,y.jsx)(`th`,{className:`w-12 px-5 py-3`})]})}),(0,y.jsx)(`tbody`,{children:x.slice(T*15,T*15+15).map(e=>(0,y.jsxs)(`tr`,{className:`border-b border-line last:border-0 hover:bg-surface-2/40`,children:[(0,y.jsx)(`td`,{className:`px-5 py-3 font-medium text-fg`,children:e.name}),(0,y.jsx)(`td`,{className:`px-5 py-3 font-mono text-xs text-muted`,children:e.queue??`—`}),(0,y.jsx)(`td`,{className:`px-5 py-3 font-mono text-xs text-muted`,children:e.schedule??(e.repeatEvery?`every ${e.repeatEvery}ms`:`—`)}),(0,y.jsx)(`td`,{className:`px-5 py-3 text-faint`,children:s(e.nextRun)}),(0,y.jsx)(`td`,{className:`px-5 py-3 text-right tnum text-muted`,children:l(e.executions??0)}),(0,y.jsx)(`td`,{className:`px-5 py-3 text-right`,children:(0,y.jsx)(a,{"aria-label":`Delete cron`,disabled:O!==null,onClick:()=>j(e.name),children:(0,y.jsx)(o,{className:`size-3.5`})})})]},e.name))})]})}),(0,y.jsx)(h,{page:T,pageSize:15,total:x.length,onPageChange:C,label:`crons`})]})]})}export{b as Cron};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e,t}from"./cn-CMFYpumL.js";import{g as n,t as r}from"./react-vendor-Bjui4wS4.js";import{t as i}from"./upstreamPaths-D_LPsv7b.js";import{A as a,C as o,L as s,S as c,T as l,Y as u,a as d,b as f,c as p,ft as m,h,st as g,v as _,w as ee,x as v,y}from"./index-CAtsVmyh.js";import{i as b,n as x,o as S,t as C}from"./form-DsUq0w8s.js";import{t as w}from"./Pagination-Bfi4vkpp.js";import{r as T}from"./useServerActionGuard-CItj3hvh.js";import{MAX_JOB_DATA_CHARS as E,parseJobData as D,queueNameError as O}from"./AddJob-CG28MSxU.js";var k=e(n(),1),A=[``,`jan`,`feb`,`mar`,`apr`,`may`,`jun`,`jul`,`aug`,`sep`,`oct`,`nov`,`dec`],te=[`sun`,`mon`,`tue`,`wed`,`thu`,`fri`,`sat`];function j(e,t,n,r){let i=e===`*`||/^\*\/\d+$/.test(e),a=new Set;for(let i of e.split(`,`)){let e=i.trim().toLowerCase();if(e===``)return null;let o=e.match(/^(.+)\/(\d+)$/),s=o?Number(o[2]):1,c=o?o[1]:e;if(s<1)return null;let l,u;if(c===`*`)l=t,u=n;else if(c.includes(`-`)){let[e,t]=c.split(`-`),n=M(e,r),i=M(t,r);if(n==null||i==null)return null;l=n,u=i}else{let e=M(c,r);if(e==null)return null;l=e,u=o?n:e}if(l<t||u>n||l>u)return null;for(let e=l;e<=u;e+=s)a.add(e)}return a.size?{values:a,star:i}:null}function M(e,t){let n=e.trim().toLowerCase();if(n===``)return null;if(t){let e=t.indexOf(n.slice(0,3));if(e!==-1)return e}return/^\d+$/.test(n)?Number(n):null}function ne(e,t,n){let r=e.trim().split(/\s+/);if(r.length!==5)return{valid:!1,error:`Expected 5 fields (min hour day month weekday), got ${r.length}.`,runs:[]};let i=j(r[0],0,59),a=j(r[1],0,23),o=j(r[2],1,31),s=j(r[3],1,12,A),c=j(r[4],0,7,te);if(!i||!a||!o||!s||!c)return{valid:!1,error:`Invalid field syntax.`,runs:[]};let l=new Set;for(let e of c.values)l.add(e===7?0:e);let u={values:l,star:c.star},d=e=>{let t=o.values.has(e.getDate()),n=u.values.has(e.getDay());return o.star||u.star?t&&n:t||n},f=[],p=new Date(n);p.setSeconds(0,0),p.setMinutes(p.getMinutes()+1);let m=n+5*366*864e5;for(let e=0;e<22e5&&f.length<t&&!(p.getTime()>m);e++){if(!s.values.has(p.getMonth()+1)){p.setMonth(p.getMonth()+1,1),p.setHours(0,0,0,0);continue}if(!d(p)){p.setDate(p.getDate()+1),p.setHours(0,0,0,0);continue}if(!a.values.has(p.getHours())){p.setHours(p.getHours()+1,0,0,0);continue}if(!i.values.has(p.getMinutes())){p.setMinutes(p.getMinutes()+1,0,0);continue}f.push(p.getTime()),p.setMinutes(p.getMinutes()+1,0,0)}return{valid:!0,runs:f}}var N=r(),P=365*24*60*60*1e3,F=1440*60*1e3;function I(e){let[t,n]=(0,k.useState)(0);return(0,k.useEffect)(()=>{t>e-1&&n(e-1)},[t,e]),[Math.min(t,e-1),n]}function re(e){let[t,n]=(0,k.useState)(!1),r=(0,k.useRef)(null),i=()=>{r.current&&clearTimeout(r.current),r.current=null};return(0,k.useEffect)(()=>()=>{r.current&&clearTimeout(r.current)},[]),{on:t,fire:()=>{i(),n(!0),r.current=setTimeout(()=>n(!1),e)},reset:()=>{i(),n(!1)}}}function ie(){let{data:e,error:t,loading:n,refetch:r}=a(()=>g.crons(),[]),i=e?.crons??[],ee=(0,k.useMemo)(()=>new Set(i.map(e=>e.name)),[i]),[b,x]=I(Math.max(1,Math.ceil(i.length/15))),[S,C]=(0,k.useState)(null),[E,D]=(0,k.useState)(new Set),O=T(`cron-manager`);(0,k.useEffect)(()=>{C(null),D(new Set)},[O.scopeKey]);let A=async e=>{if(!window.confirm(`Delete cron "${e}"? It will not be recreated automatically; creating a replacement is a separate action.`))return;let t=O.begin(`cron:${e}`);if(t){C(null),D(t=>new Set(t).add(e));try{if(L(await g.deleteCron(e)),!t.isCurrent())return;m.success(`Cron deleted`,e),r()}catch(e){if(!t.isCurrent())return;C(e.message),m.error(`Delete cron failed`,e.message)}finally{t.finish()&&D(t=>{let n=new Set(t);return n.delete(e),n})}}};return(0,N.jsxs)(`div`,{children:[t&&e&&(0,N.jsx)(v,{message:`Cron refresh failed — showing the last successful schedule list.`,onRetry:r}),(0,N.jsx)(h,{title:`Cron Manager`,description:`Submit explicitly acknowledged schedule upserts and manage repeatable jobs.`,live:!!e&&!t}),S&&(0,N.jsx)(`div`,{role:`status`,className:`mb-4 rounded-lg border border-red-500/20 bg-red-500/5 px-4 py-2 text-sm text-danger`,children:S}),(0,N.jsxs)(c,{className:`mb-6`,children:[(0,N.jsx)(o,{title:`Create schedule via upstream upsert`}),(0,N.jsx)(H,{existingNames:ee,onCreate:async(e,t)=>{let n=await g.crons();if(!t())throw Error(`Cron creation target changed during preflight`);return z(n,e.name),g.createCron(e)},onAccepted:r,beginCreate:e=>O.begin(`cron:${e}`),scopeKey:O.scopeKey})]}),t&&!e?(0,N.jsx)(y,{error:t,onRetry:r}):n&&!e?(0,N.jsx)(f,{label:`Loading crons…`}):i.length===0?(0,N.jsx)(_,{icon:(0,N.jsx)(s,{}),title:`No scheduled jobs`,hint:`Submit one above.`}):(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`div`,{className:`overflow-x-auto rounded-xl border border-line bg-surface`,children:(0,N.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,N.jsx)(`thead`,{children:(0,N.jsxs)(`tr`,{className:`border-b border-line text-left text-[11px] uppercase tracking-wider text-faint`,children:[(0,N.jsx)(`th`,{scope:`col`,className:`px-5 py-3 font-medium`,children:`Name`}),(0,N.jsx)(`th`,{scope:`col`,className:`px-5 py-3 font-medium`,children:`Queue`}),(0,N.jsx)(`th`,{scope:`col`,className:`px-5 py-3 font-medium`,children:`Job name`}),(0,N.jsx)(`th`,{scope:`col`,className:`px-5 py-3 font-medium`,children:`Schedule`}),(0,N.jsx)(`th`,{scope:`col`,className:`px-5 py-3 font-medium`,children:`Next Run`}),(0,N.jsx)(`th`,{scope:`col`,className:`px-5 py-3 text-right font-medium`,children:`Runs`}),(0,N.jsx)(`th`,{scope:`col`,className:`w-12 px-5 py-3`})]})}),(0,N.jsx)(`tbody`,{children:i.slice(b*15,b*15+15).map(e=>(0,N.jsxs)(`tr`,{className:`border-b border-line last:border-0 hover:bg-surface-2/40`,children:[(0,N.jsx)(`td`,{className:`px-5 py-3 font-medium text-fg`,children:e.name}),(0,N.jsx)(`td`,{className:`px-5 py-3 font-mono text-xs text-muted`,children:e.queue}),(0,N.jsx)(`td`,{className:`px-5 py-3 font-mono text-xs text-muted`,children:e.jobName??`default`}),(0,N.jsx)(`td`,{className:`px-5 py-3 font-mono text-xs text-muted`,children:e.schedule??(e.repeatEvery?`every ${e.repeatEvery}ms`:`—`)}),(0,N.jsx)(`td`,{className:`px-5 py-3 text-faint`,children:d(e.nextRun)}),(0,N.jsx)(`td`,{className:`px-5 py-3 text-right tnum text-muted`,children:p(e.executions)}),(0,N.jsx)(`td`,{className:`px-5 py-3 text-right`,children:(0,N.jsx)(l,{"aria-label":`Delete cron ${e.name}`,disabled:E.has(e.name),onClick:()=>A(e.name),children:(0,N.jsx)(u,{className:`size-3.5`})})})]},e.name))})]})}),(0,N.jsx)(w,{page:b,pageSize:15,total:i.length,onPageChange:x,label:`crons`})]})]})}function L(e){if(typeof e!=`object`||!e||e.ok!==!0)throw Error(`Delete cron returned a malformed success response`)}function ae(e,t){let n=e?.cron;if(typeof e!=`object`||!e||e.ok!==!0||typeof n!=`object`||!n||n.name!==t.name||n.queue!==t.queue)throw Error(`Create cron returned a malformed success response`)}var R=e=>`Cron "${e}" already exists. Bunqueue v2.8.57 does not return complete cron definitions, so editing could reset hidden options. Delete it explicitly, wait for the list to refresh, then create the replacement as a separate action.`;function oe(e,t){let n=e.trim();return n&&t.has(n)?R(n):null}function z(e,t){let n=e;if(typeof n!=`object`||!n||n.ok!==!0||!Array.isArray(n.crons)||n.crons.some(e=>typeof e!=`object`||!e||typeof e.name!=`string`))throw Error(`Cannot verify that the cron name is unused; creation was not attempted`);if(n.crons.some(e=>e.name===t.trim()))throw Error(R(t.trim()))}function B(e){let t=Number(e);if(!Number.isInteger(t)||t<=0)return null;if(t<1e3)return`every ${t}ms`;for(let[e,n]of[[864e5,`d`],[36e5,`h`],[6e4,`m`],[1e3,`s`]])if(t>=e){let r=t/e;return`${Number.isInteger(r)?``:`≈ `}every ${Math.round(r*10)/10}${n}`}return null}function V(e,t,n,r){if(!e.trim())return{ok:!0};let i=Number(e);return!Number.isSafeInteger(i)||i<n||i>r?{ok:!1,msg:`${t} must be a whole number from ${n} to ${r}`}:{ok:!0,value:i}}function se(e,t=Date.now()){let n=e.name.trim(),r=e.queue.trim();if(!n)return{ok:!1,msg:`Name is required`};if(n.length>256)return{ok:!1,msg:`Name must be 256 characters or fewer`};let a=i(n,`Name`,256);if(a)return{ok:!1,msg:a};let o=O(r);if(o)return{ok:!1,msg:o};let s=e.jobName?.trim()||`default`;if(s.length>256)return{ok:!1,msg:`Spawned job name must be 256 characters or fewer`};let c={};if(e.dataText.trim()){let t=D(e.dataText);if(!t.ok)return{ok:!1,msg:t.kind===`json`?`Data is not valid JSON`:t.msg};c=t.data}let l={name:n,jobName:s,queue:r,data:c,preventOverlap:e.preventOverlap,skipIfNoWorker:e.skipIfNoWorker,immediately:e.immediately,skipMissedOnRestart:e.skipMissedOnRestart};if(e.mode===`cron`){let t=e.schedule.trim();if(!t)return{ok:!1,msg:`Cron expression required`};l.schedule=t}else{let t=V(e.every,`Interval`,1,P);if(!t.ok||t.value===void 0)return{ok:!1,msg:t.ok?`Interval is required`:t.msg};l.repeatEvery=t.value}let u=e.timezone.trim();if(u){if(e.mode!==`cron`)return{ok:!1,msg:`Timezone applies only to cron expressions, not intervals`};try{new Intl.DateTimeFormat(`en`,{timeZone:u}).format(t)}catch{return{ok:!1,msg:`Timezone must be a valid IANA timezone`}}l.timezone=u}let d=V(e.priority,`Priority`,-1e6,1e6);if(!d.ok)return d;d.value!==void 0&&(l.priority=d.value);let f=V(e.maxLimit,`Max executions`,1,2**53-1);if(!f.ok)return f;f.value!==void 0&&(l.maxLimit=f.value);let p=e.uniqueKey.trim();if(p.length>1024)return{ok:!1,msg:`Unique key must be 1024 characters or fewer`};p&&(l.uniqueKey=p);let m=V(e.dedupTtl,`Dedup TTL`,1,P);if(!m.ok)return m;let h=m.value!==void 0||e.dedupExtend||e.dedupReplace;if(e.dedupExtend&&e.dedupReplace)return{ok:!1,msg:`Dedup extend and replace are mutually exclusive`};if(e.dedupExtend&&m.value===void 0)return{ok:!1,msg:`Dedup extend requires a TTL`};if(h&&!p&&!e.preventOverlap)return{ok:!1,msg:`Dedup options require a unique key or Prevent overlap`};h&&(l.dedup={...m.value===void 0?{}:{ttl:m.value},...e.dedupExtend?{extend:!0}:{},...e.dedupReplace?{replace:!0}:{}});let g=[{raw:e.jobMaxAttempts,label:`Spawned-job max attempts`,min:1,max:1e3,key:`maxAttempts`},{raw:e.jobBackoff,label:`Spawned-job backoff`,min:0,max:F,key:`backoff`},{raw:e.jobTimeout,label:`Spawned-job timeout`,min:0,max:F,key:`timeout`},{raw:e.jobDelay,label:`Spawned-job delay`,min:0,max:P,key:`delay`},{raw:e.jobStallTimeout,label:`Spawned-job stall timeout`,min:0,max:F,key:`stallTimeout`}],_={};for(let e of g){let t=V(e.raw,e.label,e.min,e.max);if(!t.ok)return t;t.value!==void 0&&(_[e.key]=t.value)}return e.jobRemoveOnComplete&&(_.removeOnComplete=!0),e.jobRemoveOnFail&&(_.removeOnFail=!0),Object.keys(_).length&&(l.jobOptions=_),{ok:!0,body:l}}function H({onCreate:e,onAccepted:n,beginCreate:r,scopeKey:i,existingNames:a}){let[o,s]=(0,k.useState)(``),[c,l]=(0,k.useState)(`default`),[u,f]=(0,k.useState)(``),[p,h]=(0,k.useState)(`cron`),[g,_]=(0,k.useState)(``),[v,y]=(0,k.useState)(``),[w,T]=(0,k.useState)(`{}`),[D,O]=(0,k.useState)(!1),[A,te]=(0,k.useState)(``),[j,M]=(0,k.useState)(``),[I,ie]=(0,k.useState)(!0),[L,R]=(0,k.useState)(!1),[z,V]=(0,k.useState)(``),[H,ce]=(0,k.useState)(!1),[U,le]=(0,k.useState)(!0),[W,ue]=(0,k.useState)(``),[G,de]=(0,k.useState)(``),[K,fe]=(0,k.useState)(!1),[q,pe]=(0,k.useState)(!1),[me,he]=(0,k.useState)(``),[ge,_e]=(0,k.useState)(``),[ve,ye]=(0,k.useState)(``),[be,xe]=(0,k.useState)(``),[Se,Ce]=(0,k.useState)(``),[we,Te]=(0,k.useState)(!1),[J,Ee]=(0,k.useState)(!1),[Y,X]=(0,k.useState)(null),[De,Z]=(0,k.useState)(!1),{on:Oe,fire:ke,reset:Ae}=re(3e3);(0,k.useEffect)(()=>{Z(!1),X(null),Ae()},[i]);let Q=(0,k.useMemo)(()=>p!==`cron`||!g.trim()?null:ne(g.trim(),3,Date.now()),[p,g]),$=oe(o,a),je=async()=>{X(null),Ae();let t=se({name:o,jobName:c,queue:u,mode:p,schedule:g,every:v,dataText:w,timezone:A,priority:j,preventOverlap:I,skipIfNoWorker:L,maxLimit:z,immediately:H,skipMissedOnRestart:U,uniqueKey:W,dedupTtl:G,dedupExtend:K,dedupReplace:q,jobMaxAttempts:me,jobBackoff:ge,jobTimeout:ve,jobDelay:be,jobStallTimeout:Se,jobRemoveOnComplete:we,jobRemoveOnFail:J});if(!t.ok){X(t.msg);return}let i=t.body,l=oe(i.name,a);if(l){X(l);return}if(!window.confirm(`Submit an upsert for cron "${i.name}"? Bunqueue v2.8.57 has no atomic create-only condition. The dashboard will recheck immediately before writing, but another client using the same name at the same time could still be replaced. Continue only if you authorize last-writer-wins behavior for this globally unique name.`))return;let d=r(i.name);if(d){Z(!0);try{if(ae(await e(i,d.isCurrent),i),!d.isCurrent())return;s(``),_(``),y(``),ke(),m.success(`Cron upsert acknowledged`,i.name),n()}catch(e){if(!d.isCurrent())return;X(e.message)}finally{d.finish()&&Z(!1)}}};return(0,N.jsxs)(`form`,{className:`flex flex-col gap-4`,onSubmit:e=>{e.preventDefault(),je()},children:[(0,N.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,N.jsx)(C,{label:`Name`,children:(0,N.jsx)(x,{name:`cron-name`,autoComplete:`off`,maxLength:256,value:o,onChange:e=>s(e.target.value),placeholder:`daily-report`})}),(0,N.jsx)(C,{label:`Queue`,children:(0,N.jsx)(x,{name:`cron-queue`,autoComplete:`off`,maxLength:256,value:u,onChange:e=>f(e.target.value),placeholder:`reports`})}),(0,N.jsx)(C,{label:`Spawned job name`,hint:`First-class worker routing name; separate from schedule data.`,children:(0,N.jsx)(x,{name:`cron-job-name`,autoComplete:`off`,maxLength:256,value:c,onChange:e=>l(e.target.value),placeholder:`default`})})]}),(0,N.jsxs)(`div`,{className:`flex flex-wrap items-end gap-3`,children:[(0,N.jsxs)(`fieldset`,{children:[(0,N.jsx)(`legend`,{className:`sr-only`,children:`Schedule type`}),(0,N.jsx)(b,{options:[`cron`,`every`],value:p,onChange:h})]}),p===`cron`?(0,N.jsx)(`div`,{className:`min-w-56 flex-1`,children:(0,N.jsx)(C,{label:`Cron expression`,children:(0,N.jsx)(x,{name:`cron-expression`,value:g,onChange:e=>_(e.target.value),placeholder:`0 9 * * *`})})}):(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`div`,{className:`w-40`,children:(0,N.jsx)(C,{label:`Every (ms)`,children:(0,N.jsx)(x,{type:`number`,min:1,max:P,step:1,name:`cron-interval`,value:v,onChange:e=>y(e.target.value),placeholder:`30000`})})}),v.trim()!==``&&(0,N.jsx)(`span`,{className:t(`pb-2 text-xs`,B(v)?`text-muted`:`text-danger`),children:B(v)??`not a valid interval`})]})]}),p===`cron`&&g.trim()&&Q&&(0,N.jsx)(`div`,{className:`rounded-lg border border-line bg-surface-2 px-3 py-2 text-xs`,children:Q.valid?Q.runs.length?(0,N.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-2 gap-y-1`,children:[(0,N.jsxs)(`span`,{className:`text-faint`,children:[`Next runs`,A.trim()?` (local)`:``,`:`]}),Q.runs.map(e=>(0,N.jsx)(`span`,{className:`font-mono text-muted`,children:d(e)},e)),A.trim()&&(0,N.jsxs)(`span`,{className:`text-faint`,children:[`— server evaluates in `,A.trim()]})]}):(0,N.jsx)(`span`,{className:`text-warning`,children:`Valid, but no runs in the next few years.`}):(0,N.jsxs)(`span`,{className:`text-warning`,children:[`Preview unavailable: `,Q.error,` Bunqueue will validate this expression when you create the schedule.`]})}),(0,N.jsx)(C,{label:`Data (JSON)`,children:(0,N.jsx)(`textarea`,{name:`cron-data`,value:w,onChange:e=>T(e.target.value),maxLength:E,rows:3,spellCheck:!1,className:`w-full rounded-lg border border-line bg-surface-2 px-3 py-2 font-mono text-sm text-fg placeholder:text-faint transition-colors focus:border-accent/60 focus:outline-none focus:ring-2 focus:ring-accent/30`})}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`button`,{type:`button`,onClick:()=>O(e=>!e),"aria-expanded":D,"aria-controls":`cron-advanced-options`,className:`text-xs font-medium text-muted hover:text-fg`,children:D?`− Hide advanced`:`+ Advanced options`}),D&&(0,N.jsxs)(`div`,{id:`cron-advanced-options`,className:`mt-3 grid grid-cols-2 gap-3`,children:[(0,N.jsx)(C,{label:`Timezone (IANA)`,hint:`e.g. Europe/Rome. Default: server timezone.`,children:(0,N.jsx)(x,{name:`cron-timezone`,autoComplete:`off`,value:A,onChange:e=>te(e.target.value),placeholder:`Europe/Rome`})}),(0,N.jsx)(C,{label:`Priority`,children:(0,N.jsx)(x,{type:`number`,min:-1e6,max:1e6,step:1,name:`cron-priority`,value:j,onChange:e=>M(e.target.value),placeholder:`0`})}),(0,N.jsx)(C,{label:`Max executions`,hint:`blank = unlimited`,children:(0,N.jsx)(x,{type:`number`,min:1,max:2**53-1,step:1,name:`cron-max-executions`,value:z,onChange:e=>V(e.target.value),placeholder:`∞`})}),(0,N.jsx)(C,{label:`Unique key`,hint:`optional deduplication key`,children:(0,N.jsx)(x,{name:`cron-unique-key`,autoComplete:`off`,maxLength:1024,value:W,onChange:e=>ue(e.target.value),placeholder:`daily-report`})}),(0,N.jsxs)(`div`,{className:`col-span-2 flex flex-wrap gap-6`,children:[(0,N.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,N.jsx)(S,{checked:I,onChange:ie,label:`prevent overlap`}),(0,N.jsx)(`span`,{className:`text-sm text-muted`,children:`prevent overlap`})]}),(0,N.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,N.jsx)(S,{checked:L,onChange:R,label:`skip if no worker`}),(0,N.jsx)(`span`,{className:`text-sm text-muted`,children:`skip if no worker`})]}),(0,N.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,N.jsx)(S,{checked:H,onChange:ce,label:`run immediately`}),(0,N.jsx)(`span`,{className:`text-sm text-muted`,children:`run immediately`})]}),(0,N.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,N.jsx)(S,{checked:U,onChange:le,label:`skip missed on restart`}),(0,N.jsx)(`span`,{className:`text-sm text-muted`,children:`skip missed on restart`})]})]}),(0,N.jsxs)(`div`,{className:`col-span-2`,children:[(0,N.jsx)(`div`,{className:`mb-2 text-[11px] uppercase tracking-wider text-faint`,children:`Cron deduplication`}),(0,N.jsxs)(`div`,{className:`grid grid-cols-1 gap-3 sm:grid-cols-3`,children:[(0,N.jsx)(C,{label:`Dedup TTL (ms)`,hint:`blank = no TTL`,children:(0,N.jsx)(x,{type:`number`,min:1,max:P,step:1,name:`cron-dedup-ttl`,value:G,onChange:e=>de(e.target.value),placeholder:`60000`})}),(0,N.jsxs)(`div`,{className:`flex items-end gap-2 pb-2`,children:[(0,N.jsx)(S,{checked:K,onChange:fe,label:`extend dedup TTL`}),(0,N.jsx)(`span`,{className:`text-sm text-muted`,children:`extend TTL`})]}),(0,N.jsxs)(`div`,{className:`flex items-end gap-2 pb-2`,children:[(0,N.jsx)(S,{checked:q,onChange:pe,label:`replace duplicate`}),(0,N.jsx)(`span`,{className:`text-sm text-muted`,children:`replace duplicate`})]})]})]}),(0,N.jsxs)(`div`,{className:`col-span-2`,children:[(0,N.jsx)(`div`,{className:`mb-2 text-[11px] uppercase tracking-wider text-faint`,children:`Spawned-job options`}),(0,N.jsxs)(`div`,{className:`grid grid-cols-2 gap-3 lg:grid-cols-5`,children:[(0,N.jsx)(C,{label:`Max attempts`,children:(0,N.jsx)(x,{type:`number`,min:1,max:1e3,step:1,name:`cron-job-max-attempts`,value:me,onChange:e=>he(e.target.value),placeholder:`3`})}),(0,N.jsx)(C,{label:`Backoff (ms)`,children:(0,N.jsx)(x,{type:`number`,min:0,max:F,step:1,name:`cron-job-backoff`,value:ge,onChange:e=>_e(e.target.value),placeholder:`1000`})}),(0,N.jsx)(C,{label:`Timeout (ms)`,children:(0,N.jsx)(x,{type:`number`,min:0,max:F,step:1,name:`cron-job-timeout`,value:ve,onChange:e=>ye(e.target.value),placeholder:`—`})}),(0,N.jsx)(C,{label:`Delay (ms)`,children:(0,N.jsx)(x,{type:`number`,min:0,max:P,step:1,name:`cron-job-delay`,value:be,onChange:e=>xe(e.target.value),placeholder:`0`})}),(0,N.jsx)(C,{label:`Stall timeout (ms)`,children:(0,N.jsx)(x,{type:`number`,min:0,max:F,step:1,name:`cron-job-stall-timeout`,value:Se,onChange:e=>Ce(e.target.value),placeholder:`server default`})})]}),(0,N.jsxs)(`div`,{className:`mt-3 flex flex-wrap gap-6`,children:[(0,N.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,N.jsx)(S,{checked:we,onChange:Te,label:`remove spawned jobs on complete`}),(0,N.jsx)(`span`,{className:`text-sm text-muted`,children:`remove on complete`})]}),(0,N.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,N.jsx)(S,{checked:J,onChange:Ee,label:`remove spawned jobs on fail`}),(0,N.jsx)(`span`,{className:`text-sm text-muted`,children:`remove on fail`})]})]})]})]})]}),(0,N.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,N.jsx)(ee,{type:`submit`,variant:`accent`,size:`sm`,disabled:De||!!$,children:De?`Saving…`:$?`Name already exists`:`Submit upsert`}),$&&!Y&&(0,N.jsx)(`span`,{role:`alert`,className:`max-w-2xl text-xs text-warning`,children:$}),Y&&(0,N.jsx)(`span`,{role:`alert`,className:`text-xs text-danger`,children:Y}),Oe&&(0,N.jsx)(`span`,{role:`status`,className:`text-xs text-success`,children:`Cron upsert acknowledged ✓`})]}),(0,N.jsx)(`p`,{className:`text-xs text-warning`,children:`Creation is an upstream upsert, not an atomic create-only operation. Use a globally unique name; the fail-closed preflight cannot eliminate a simultaneous write from another client.`})]})}export{ie as CronManager};
|