bunqueue-dashboard 0.0.42 → 0.0.44
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 +30 -12
- package/agent/flow/routes.ts +2 -0
- package/agent/flow/service.ts +7 -2
- package/agent/managedConnection.ts +35 -0
- package/agent/manager/config.ts +1 -1
- package/agent/queue/runtime.ts +2 -2
- package/agent/workflow/runtime.ts +2 -6
- package/agent/workflow/runtimeConfig.ts +2 -2
- package/dist/.vite/manifest.json +40 -39
- package/dist/assets/{Alerts-BvWBv-z7.js → Alerts-C961Al5D.js} +1 -1
- package/dist/assets/{Benchmark-BywalSmU.js → Benchmark-DQNZV7dM.js} +1 -1
- package/dist/assets/{CopilotPanel-CH2jbbYA.js → CopilotPanel-Dlm8XwBj.js} +1 -1
- package/dist/assets/{Database-BzEkXws0.js → Database-DGM2BIHy.js} +1 -1
- package/dist/assets/{Diagnostics-CPqhV3er.js → Diagnostics-_Sh3UyaT.js} +1 -1
- package/dist/assets/{Dlq-CbLN0zQ5.js → Dlq-mtInIoTh.js} +1 -1
- package/dist/assets/{DlqControl-C5Bgzmk0.js → DlqControl-Lq9ISb9b.js} +1 -1
- package/dist/assets/{DlqPro-Cgaxtuwh.js → DlqPro-0_75vrd1.js} +1 -1
- package/dist/assets/Flows-CE7KzaFN.js +2 -0
- package/dist/assets/{JobInspector-DsOt4mLJ.js → JobInspector-DyWZk1eW.js} +1 -1
- package/dist/assets/{Jobs-BTMpbAHD.js → Jobs-ELSH1Wbd.js} +1 -1
- package/dist/assets/JobsPro-BlWwDeFN.js +1 -0
- package/dist/assets/{Logs-uuzU5ohI.js → Logs-DMukqmHy.js} +1 -1
- package/dist/assets/{LogsPro-DHWbhWyq.js → LogsPro-BfYbmZHi.js} +1 -1
- package/dist/assets/{McpServer-BqmbhrKh.js → McpServer-DNrOEp9j.js} +1 -1
- package/dist/assets/{Metrics-DKyBZoFa.js → Metrics-Cm5ZvfTo.js} +1 -1
- package/dist/assets/{MetricsPro-BR2ULqIc.js → MetricsPro-C9g1sp-u.js} +1 -1
- package/dist/assets/{Overview-ClsyxH2e.js → Overview-CKAA3h9f.js} +1 -1
- package/dist/assets/{QueueControl-BW0XXd0B.js → QueueControl-Bvm8GUQa.js} +1 -1
- package/dist/assets/{QueueDetail-C1dXXN70.js → QueueDetail-BRDATlp0.js} +1 -1
- package/dist/assets/{QueueDetailPro-DDGxs2J2.js → QueueDetailPro-Dkz4-1wn.js} +1 -1
- package/dist/assets/{Queues-DkPuIy1C.js → Queues-BV99vr68.js} +1 -1
- package/dist/assets/{QueuesOverview-COo-V8D_.js → QueuesOverview-CT6JBaim.js} +1 -1
- package/dist/assets/{S3BackupPro-DxNVFvY1.js → S3BackupPro-CjR0y18n.js} +1 -1
- package/dist/assets/{ServerControl-B0Po974t.js → ServerControl-BH6Doax0.js} +1 -1
- package/dist/assets/{Settings-z9k4EVa_.js → Settings-CL7odwoU.js} +1 -1
- package/dist/assets/{Usage-cdWDQUaq.js → Usage-BWZkz7Se.js} +1 -1
- package/dist/assets/{UsagePro-DLuwnM8n.js → UsagePro-CoBbI-Cl.js} +1 -1
- package/dist/assets/{Workers-CVo9rJ_V.js → Workers-CBEDvXm_.js} +1 -1
- package/dist/assets/{WorkersPro-BYaredE9.js → WorkersPro-Dk4NfplT.js} +1 -1
- package/dist/assets/flowOperationPolicy-B2Etcb3f.js +1 -0
- package/dist/assets/{index-mID5W7vq.js → index-BFW2_47n.js} +3 -3
- package/dist/assets/{install-D4uRZ7XA.js → install-oydLpc1T.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +8 -5
- package/scripts/embedded.gen.ts +164 -164
- package/shared/flowOperationPolicy.ts +66 -0
- package/dist/assets/Flows-BtUGOaif.js +0 -2
- package/dist/assets/JobsPro-BfXTm9gH.js +0 -1
- package/dist/assets/jobActions-LQD4KfCc.js +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
# bunqueue dashboard
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Monitor jobs and operate your Bunqueue server from one dashboard.**
|
|
8
|
+
|
|
9
|
+
Manage queues, jobs, DLQ,
|
|
8
10
|
cron, webhooks, workers, and a live activity stream for a [bunqueue](https://bunqueue.dev) server
|
|
9
11
|
(a fast, Redis-free, Bun-native background-job queue), plus **start / stop / restart of the server
|
|
10
12
|
process itself**, all from one place.
|
|
@@ -63,9 +65,10 @@ The full dashboard running on sample data, no server needed.
|
|
|
63
65
|
**Full, illustrated docs live at
|
|
64
66
|
[egeominotti.github.io/bunqueue-dashboard/docs](https://egeominotti.github.io/bunqueue-dashboard/docs/).**
|
|
65
67
|
|
|
66
|
-
- **[Illustrated user guide](https://egeominotti.github.io/bunqueue-dashboard/docs/user-guide)**,
|
|
68
|
+
- **[Illustrated user guide](https://egeominotti.github.io/bunqueue-dashboard/docs/user-guide)**, section guides with screenshots, operations and limitations.
|
|
67
69
|
- **[Deployment](https://egeominotti.github.io/bunqueue-dashboard/docs/deploy/)**, Docker (Caddy), Kubernetes, PM2, and hosting platforms (Vercel, Netlify, Cloudflare, Fly.io, Render, Cloud Run).
|
|
68
70
|
- **[Architecture](https://egeominotti.github.io/bunqueue-dashboard/docs/architecture)** and **[API mapping](https://egeominotti.github.io/bunqueue-dashboard/docs/api-mapping)**, how it fits together and every endpoint it drives.
|
|
71
|
+
- **[Testing matrix](docs/testing.md)**, real-server coverage, reproduction steps and verification limits.
|
|
69
72
|
- **[llms.txt](https://egeominotti.github.io/bunqueue-dashboard/docs/llms.txt)**, the whole site as a single file for LLMs.
|
|
70
73
|
|
|
71
74
|
## Table of contents
|
|
@@ -96,7 +99,7 @@ server *process* through a separate guarded agent.
|
|
|
96
99
|
|
|
97
100
|
It uses Bunqueue's public HTTP API (`:6790`) for ordinary remote observability and a small local
|
|
98
101
|
**control agent** for process, FlowProducer, Workflow Engine, database, and backup operations. The
|
|
99
|
-
agent is pinned to the server it manages and uses the exact installable Bunqueue 2.9.
|
|
102
|
+
agent is pinned to the server it manages and uses the exact installable Bunqueue 2.9.4 npm client
|
|
100
103
|
contracts; it never patches Bunqueue internals.
|
|
101
104
|
|
|
102
105
|
## Features
|
|
@@ -191,7 +194,8 @@ flowchart LR
|
|
|
191
194
|
|
|
192
195
|
- **Reads** by polling (`usePolledData`) and a Server-Sent Events stream (`useActivityStream`).
|
|
193
196
|
- **Writes** through the same HTTP API, with every mutation shape-verified against the live server.
|
|
194
|
-
- **
|
|
197
|
+
- **Managed operations** use the local control agent for process lifecycle, Queue SDK controls,
|
|
198
|
+
FlowProducer, Workflow Engine, SQLite inspection, and backups.
|
|
195
199
|
|
|
196
200
|
For multiple brokers, create one named Dashboard profile and one paired control
|
|
197
201
|
agent per broker. The Fleet page probes all profiles without retargeting the
|
|
@@ -262,9 +266,13 @@ target-pinned Flow, Workflow, Queue and Backup agent operations.
|
|
|
262
266
|
| `bun run check` | Oxlint + Oxfmt validation (the CI gate) |
|
|
263
267
|
| `bun run check:fix` | Apply safe Oxlint fixes, then format with Oxfmt |
|
|
264
268
|
| `bun test` | Unit + agent-lifecycle tests |
|
|
265
|
-
| `bun run test:e2e:
|
|
266
|
-
| `bun run test:e2e` | Real
|
|
267
|
-
| `bun run test:e2e
|
|
269
|
+
| `bun run test:e2e:tls` | Queue, Flow and Workflow over verified native TLS, including untrusted-certificate rejection |
|
|
270
|
+
| `bun run test:e2e:upgrade` | Real npm Bunqueue 2.9.2 SQLite schema-35 → 2.9.4 schema-37 migration and retention semantics |
|
|
271
|
+
| `bun run test:e2e` | Real TLS, SQLite upgrade, Flow, Workflow, Queue SDK, and three-broker PostgreSQL runtime tests |
|
|
272
|
+
| `bun run test:e2e:browser` | Production UI tests on Chromium, Firefox and WebKit |
|
|
273
|
+
| `bun run test:e2e:browser:postgres-fleet` | Real UI operations across three PostgreSQL brokers (requires Docker and Chromium) |
|
|
274
|
+
| `bun run quality` | Complete static, build, docs, coverage, runtime, package and dependency gate |
|
|
275
|
+
| `bun run test:e2e:postgres-fleet` | Three authenticated Bunqueue 2.9.4 brokers + three agents sharing disposable PostgreSQL 18.6 |
|
|
268
276
|
|
|
269
277
|
## Docker
|
|
270
278
|
|
|
@@ -305,13 +313,17 @@ Because the deployed build is a static shell, point it at a reachable bunqueue s
|
|
|
305
313
|
|
|
306
314
|
## Testing & quality gate
|
|
307
315
|
|
|
308
|
-
|
|
316
|
+
Use both the canonical gate and browser tests to validate a change. Browser tests are a separate
|
|
317
|
+
CI job; `quality` alone does not run Playwright:
|
|
309
318
|
|
|
310
319
|
```bash
|
|
311
|
-
bun run
|
|
320
|
+
bun run test:e2e:browser:install # first run only
|
|
321
|
+
bun run quality # includes runtime E2E; requires Docker
|
|
322
|
+
bun run test:e2e:browser # production UI on all three browser engines
|
|
323
|
+
bun run test:e2e:browser:postgres-fleet # cross-node UI; separate from both commands above
|
|
312
324
|
```
|
|
313
325
|
|
|
314
|
-
The E2E stage starts disposable Bunqueue 2.9.
|
|
326
|
+
The E2E stage starts disposable Bunqueue 2.9.4 processes and exercises the complete FlowProducer,
|
|
315
327
|
Workflow Engine, and Queue SDK operator bridges. It also starts PostgreSQL 18.6 in Docker with
|
|
316
328
|
three authenticated brokers and paired agents, proving cross-broker enqueue/inspect/leased
|
|
317
329
|
pull+ack, pause/resume, cron, rate limits, group admission, group priority and group pause/resume
|
|
@@ -321,10 +333,16 @@ PostgreSQL stage).
|
|
|
321
333
|
The blocking browser job builds the production bundle under `/e2e/dashboard`, starts an
|
|
322
334
|
authenticated disposable Bunqueue server, and drives the UI with Playwright on Chromium, Firefox,
|
|
323
335
|
and WebKit. It covers the token gate, every sidebar route, SSE recovery across a real server
|
|
324
|
-
restart, confirmed Cron creation/deletion,
|
|
336
|
+
restart, confirmed Cron creation/deletion, job submission and bulk import, persisted job inspection,
|
|
337
|
+
DLQ failures, live monitoring and alerts, SQLite queries, webhook registry mutations, a real
|
|
338
|
+
producer/worker benchmark, and automated WCAG A/AA checks. Install the browser
|
|
325
339
|
binaries once with `bun run test:e2e:browser:install`, then run `bun run test:e2e:browser`.
|
|
326
340
|
|
|
327
|
-
|
|
341
|
+
See the [section-by-section validation matrix](docs/testing.md) for reproducible commands,
|
|
342
|
+
prerequisites, and the distinction between live operations, navigation checks, and external
|
|
343
|
+
integrations that need their own environment. The live demo uses sample data and is not test evidence.
|
|
344
|
+
|
|
345
|
+
CI enforces the quality gate and browser matrix on every push and pull request. See [Contributing](#contributing).
|
|
328
346
|
|
|
329
347
|
## Project structure
|
|
330
348
|
|
package/agent/flow/routes.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertFlowOperationAllowed } from '../../shared/flowOperationPolicy';
|
|
1
2
|
import type { ServerConfig } from '../manager';
|
|
2
3
|
import { assertManagedTarget, type ManagedTargetPolicy } from '../managedTarget';
|
|
3
4
|
import type {
|
|
@@ -160,6 +161,7 @@ export async function routeFlowRequest(
|
|
|
160
161
|
const payload = MUTATIONS_WITH_BODY.has(mutation)
|
|
161
162
|
? await readFlowJsonBody(request)
|
|
162
163
|
: (assertNoFlowBody(request), {});
|
|
164
|
+
assertFlowOperationAllowed(mutation);
|
|
163
165
|
return success(
|
|
164
166
|
await admitted(query, config, serverRunning, dependencies, ({ config: current }) =>
|
|
165
167
|
operations(dependencies).mutate(current, target, mutation, payload)
|
package/agent/flow/service.ts
CHANGED
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
type JobNode,
|
|
8
8
|
} from 'bunqueue/client';
|
|
9
9
|
import type { ServerConfig } from '../manager';
|
|
10
|
-
import {
|
|
10
|
+
import { managedConnection } from '../managedConnection';
|
|
11
|
+
import { assertFlowOperationAllowed, assertFlowOperationState } from '../../shared/flowOperationPolicy';
|
|
11
12
|
import {
|
|
12
13
|
asFlowRecord,
|
|
13
14
|
assertFlowBodySize,
|
|
@@ -119,11 +120,15 @@ export async function mutateFlowJob(
|
|
|
119
120
|
operation: FlowMutationOperation,
|
|
120
121
|
input: unknown
|
|
121
122
|
): Promise<unknown> {
|
|
123
|
+
assertFlowOperationAllowed(operation);
|
|
122
124
|
assertFlowBodySize(input);
|
|
123
125
|
const payload = asFlowRecord(input);
|
|
124
126
|
validateMutationPayload(operation, payload);
|
|
125
127
|
const { producer, job } = await loadJob(config, target);
|
|
126
128
|
try {
|
|
129
|
+
if (['promote', 'changeDelay', 'changePriority'].includes(operation)) {
|
|
130
|
+
assertFlowOperationState(operation, await job.getState());
|
|
131
|
+
}
|
|
127
132
|
switch (operation) {
|
|
128
133
|
case 'removeChildDependency': return { removed: await job.removeChildDependency() };
|
|
129
134
|
case 'removeUnprocessedChildren': await job.removeUnprocessedChildren(); break;
|
|
@@ -173,7 +178,7 @@ function producerFor(
|
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
function connectionFor(config: ServerConfig) {
|
|
176
|
-
return
|
|
181
|
+
return managedConnection(config);
|
|
177
182
|
}
|
|
178
183
|
|
|
179
184
|
async function loadJob(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isAbsolute } from 'node:path';
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
|
+
import type { ConnectionOptions } from 'bunqueue/client';
|
|
4
|
+
import type { ServerConfig } from './manager';
|
|
5
|
+
import { managedAuthToken } from './managedTarget';
|
|
6
|
+
|
|
7
|
+
/** Same environment precedence as the managed child; never disables verification. */
|
|
8
|
+
export function managedConnection(config: ServerConfig): ConnectionOptions {
|
|
9
|
+
const env = (key: string) =>
|
|
10
|
+
(Object.hasOwn(config.extraEnv, key) ? config.extraEnv[key] : process.env[key])?.trim();
|
|
11
|
+
const mode = env('BUNQUEUE_AGENT_TCP_TLS');
|
|
12
|
+
if (mode && mode !== 'true' && mode !== 'false') {
|
|
13
|
+
throw new Error('BUNQUEUE_AGENT_TCP_TLS must be true or false.');
|
|
14
|
+
}
|
|
15
|
+
const caFile = env('BUNQUEUE_AGENT_TCP_CA_FILE');
|
|
16
|
+
const serverTls = Boolean(env('TLS_CERT_FILE') || env('TLS_KEY_FILE'));
|
|
17
|
+
if (mode === 'false' && (serverTls || caFile)) {
|
|
18
|
+
throw new Error('Cannot disable agent TCP TLS when server TLS or a CA is configured.');
|
|
19
|
+
}
|
|
20
|
+
if (caFile) {
|
|
21
|
+
if (!isAbsolute(caFile)) throw new Error('BUNQUEUE_AGENT_TCP_CA_FILE must be absolute.');
|
|
22
|
+
try {
|
|
23
|
+
if (!statSync(caFile).isFile()) throw new Error();
|
|
24
|
+
} catch {
|
|
25
|
+
throw new Error('BUNQUEUE_AGENT_TCP_CA_FILE must point to a readable CA file.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const tls = mode === 'true' || serverTls || Boolean(caFile);
|
|
29
|
+
return {
|
|
30
|
+
host: '127.0.0.1',
|
|
31
|
+
port: config.tcpPort,
|
|
32
|
+
token: managedAuthToken(config),
|
|
33
|
+
...(tls ? { tls: { rejectUnauthorized: true, ...(caFile ? { caFile } : {}) } } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
package/agent/manager/config.ts
CHANGED
|
@@ -10,7 +10,7 @@ const CONFIG_KEYS = new Set<keyof ServerConfig>([
|
|
|
10
10
|
|
|
11
11
|
export function defaultConfig(): ServerConfig {
|
|
12
12
|
return {
|
|
13
|
-
command: process.env.BUNQUEUE_START_CMD || 'bunx bunqueue@2.9.
|
|
13
|
+
command: process.env.BUNQUEUE_START_CMD || 'bunx bunqueue@2.9.4 start',
|
|
14
14
|
httpPort: Number(process.env.HTTP_PORT) || 6790,
|
|
15
15
|
tcpPort: Number(process.env.TCP_PORT) || 6789,
|
|
16
16
|
dataPath: process.env.BUNQUEUE_DATA_PATH || './data/bunq.db',
|
package/agent/queue/runtime.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Queue, type QueueMetricType, type QueueMetrics } from 'bunqueue/client';
|
|
2
|
-
import {
|
|
2
|
+
import { managedConnection } from '../managedConnection';
|
|
3
3
|
import type { ServerConfig } from '../manager';
|
|
4
4
|
import type { QueueGroupSnapshot, QueueLimitSnapshot, QueueOperationsPort } from './types';
|
|
5
5
|
import { QueueOperationsUnavailableError } from './types';
|
|
@@ -220,7 +220,7 @@ export class QueueOperationsRuntime implements QueueOperationsPort {
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
function connectionFor(config: ServerConfig) {
|
|
223
|
-
return
|
|
223
|
+
return managedConnection(config);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
function defaultClient(config: ServerConfig, queue: string): QueueOperationsClient {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type ExecutionState,
|
|
8
8
|
type Workflow,
|
|
9
9
|
} from 'bunqueue/workflow';
|
|
10
|
-
import {
|
|
10
|
+
import { managedConnection } from '../managedConnection';
|
|
11
11
|
import type { ServerConfig } from '../manager';
|
|
12
12
|
import {
|
|
13
13
|
configuredWorkflowModule,
|
|
@@ -188,11 +188,7 @@ export class WorkflowRuntime implements WorkflowRuntimePort {
|
|
|
188
188
|
dataPath: config.dataPath,
|
|
189
189
|
queueName: options.queueName,
|
|
190
190
|
concurrency: options.concurrency,
|
|
191
|
-
connection:
|
|
192
|
-
host: '127.0.0.1',
|
|
193
|
-
port: config.tcpPort,
|
|
194
|
-
token: managedAuthToken(config),
|
|
195
|
-
},
|
|
191
|
+
connection: managedConnection(config),
|
|
196
192
|
});
|
|
197
193
|
const active: ActiveRuntime = {
|
|
198
194
|
engine,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { basename } from 'node:path';
|
|
2
2
|
import { safeErrorMessage } from '../errorMessage';
|
|
3
|
-
import {
|
|
3
|
+
import { managedConnection } from '../managedConnection';
|
|
4
4
|
import type { ServerConfig } from '../manager';
|
|
5
5
|
import type { WorkflowRuntimeStatus } from './runtime';
|
|
6
6
|
|
|
@@ -53,7 +53,7 @@ export function workflowRuntimeSignature(
|
|
|
53
53
|
modified,
|
|
54
54
|
config.dataPath,
|
|
55
55
|
config.tcpPort,
|
|
56
|
-
|
|
56
|
+
managedConnection(config),
|
|
57
57
|
options.queueName,
|
|
58
58
|
options.concurrency,
|
|
59
59
|
]);
|
package/dist/.vite/manifest.json
CHANGED
|
@@ -138,6 +138,10 @@
|
|
|
138
138
|
"file": "assets/flowMutationSafety-YKTHxqKv.js",
|
|
139
139
|
"name": "flowMutationSafety"
|
|
140
140
|
},
|
|
141
|
+
"_flowOperationPolicy-B2Etcb3f.js": {
|
|
142
|
+
"file": "assets/flowOperationPolicy-B2Etcb3f.js",
|
|
143
|
+
"name": "flowOperationPolicy"
|
|
144
|
+
},
|
|
141
145
|
"_form-C4oby9pI.js": {
|
|
142
146
|
"file": "assets/form-C4oby9pI.js",
|
|
143
147
|
"name": "form",
|
|
@@ -157,10 +161,6 @@
|
|
|
157
161
|
"_react-vendor-CvwrP0tU.js"
|
|
158
162
|
]
|
|
159
163
|
},
|
|
160
|
-
"_jobActions-LQD4KfCc.js": {
|
|
161
|
-
"file": "assets/jobActions-LQD4KfCc.js",
|
|
162
|
-
"name": "jobActions"
|
|
163
|
-
},
|
|
164
164
|
"_promisePool-BVfQ9n9Q.js": {
|
|
165
165
|
"file": "assets/promisePool-BVfQ9n9Q.js",
|
|
166
166
|
"name": "promisePool"
|
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
]
|
|
230
230
|
},
|
|
231
231
|
"index.html": {
|
|
232
|
-
"file": "assets/index-
|
|
232
|
+
"file": "assets/index-BFW2_47n.js",
|
|
233
233
|
"name": "index",
|
|
234
234
|
"src": "index.html",
|
|
235
235
|
"isEntry": true,
|
|
@@ -352,7 +352,7 @@
|
|
|
352
352
|
"src": "node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-latin-wght-normal.woff2"
|
|
353
353
|
},
|
|
354
354
|
"src/components/copilot/CopilotPanel.tsx": {
|
|
355
|
-
"file": "assets/CopilotPanel-
|
|
355
|
+
"file": "assets/CopilotPanel-Dlm8XwBj.js",
|
|
356
356
|
"name": "CopilotPanel",
|
|
357
357
|
"src": "src/components/copilot/CopilotPanel.tsx",
|
|
358
358
|
"isDynamicEntry": true,
|
|
@@ -372,13 +372,13 @@
|
|
|
372
372
|
]
|
|
373
373
|
},
|
|
374
374
|
"src/lib/demo/install.ts": {
|
|
375
|
-
"file": "assets/install-
|
|
375
|
+
"file": "assets/install-oydLpc1T.js",
|
|
376
376
|
"name": "install",
|
|
377
377
|
"src": "src/lib/demo/install.ts",
|
|
378
378
|
"isDynamicEntry": true
|
|
379
379
|
},
|
|
380
380
|
"src/pages/Alerts.tsx": {
|
|
381
|
-
"file": "assets/Alerts-
|
|
381
|
+
"file": "assets/Alerts-C961Al5D.js",
|
|
382
382
|
"name": "Alerts",
|
|
383
383
|
"src": "src/pages/Alerts.tsx",
|
|
384
384
|
"isDynamicEntry": true,
|
|
@@ -416,7 +416,7 @@
|
|
|
416
416
|
]
|
|
417
417
|
},
|
|
418
418
|
"src/pages/Dlq.tsx": {
|
|
419
|
-
"file": "assets/Dlq-
|
|
419
|
+
"file": "assets/Dlq-mtInIoTh.js",
|
|
420
420
|
"name": "Dlq",
|
|
421
421
|
"src": "src/pages/Dlq.tsx",
|
|
422
422
|
"isDynamicEntry": true,
|
|
@@ -454,7 +454,7 @@
|
|
|
454
454
|
]
|
|
455
455
|
},
|
|
456
456
|
"src/pages/Jobs.tsx": {
|
|
457
|
-
"file": "assets/Jobs-
|
|
457
|
+
"file": "assets/Jobs-ELSH1Wbd.js",
|
|
458
458
|
"name": "Jobs",
|
|
459
459
|
"src": "src/pages/Jobs.tsx",
|
|
460
460
|
"isDynamicEntry": true,
|
|
@@ -476,7 +476,7 @@
|
|
|
476
476
|
]
|
|
477
477
|
},
|
|
478
478
|
"src/pages/Logs.tsx": {
|
|
479
|
-
"file": "assets/Logs-
|
|
479
|
+
"file": "assets/Logs-DMukqmHy.js",
|
|
480
480
|
"name": "Logs",
|
|
481
481
|
"src": "src/pages/Logs.tsx",
|
|
482
482
|
"isDynamicEntry": true,
|
|
@@ -496,7 +496,7 @@
|
|
|
496
496
|
]
|
|
497
497
|
},
|
|
498
498
|
"src/pages/Metrics.tsx": {
|
|
499
|
-
"file": "assets/Metrics-
|
|
499
|
+
"file": "assets/Metrics-Cm5ZvfTo.js",
|
|
500
500
|
"name": "Metrics",
|
|
501
501
|
"src": "src/pages/Metrics.tsx",
|
|
502
502
|
"isDynamicEntry": true,
|
|
@@ -512,7 +512,7 @@
|
|
|
512
512
|
]
|
|
513
513
|
},
|
|
514
514
|
"src/pages/Overview.tsx": {
|
|
515
|
-
"file": "assets/Overview-
|
|
515
|
+
"file": "assets/Overview-CKAA3h9f.js",
|
|
516
516
|
"name": "Overview",
|
|
517
517
|
"src": "src/pages/Overview.tsx",
|
|
518
518
|
"isDynamicEntry": true,
|
|
@@ -529,7 +529,7 @@
|
|
|
529
529
|
]
|
|
530
530
|
},
|
|
531
531
|
"src/pages/QueueDetail.tsx": {
|
|
532
|
-
"file": "assets/QueueDetail-
|
|
532
|
+
"file": "assets/QueueDetail-BRDATlp0.js",
|
|
533
533
|
"name": "QueueDetail",
|
|
534
534
|
"src": "src/pages/QueueDetail.tsx",
|
|
535
535
|
"isDynamicEntry": true,
|
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
]
|
|
553
553
|
},
|
|
554
554
|
"src/pages/Queues.tsx": {
|
|
555
|
-
"file": "assets/Queues-
|
|
555
|
+
"file": "assets/Queues-BV99vr68.js",
|
|
556
556
|
"name": "Queues",
|
|
557
557
|
"src": "src/pages/Queues.tsx",
|
|
558
558
|
"isDynamicEntry": true,
|
|
@@ -584,7 +584,7 @@
|
|
|
584
584
|
]
|
|
585
585
|
},
|
|
586
586
|
"src/pages/Settings.tsx": {
|
|
587
|
-
"file": "assets/Settings-
|
|
587
|
+
"file": "assets/Settings-CL7odwoU.js",
|
|
588
588
|
"name": "Settings",
|
|
589
589
|
"src": "src/pages/Settings.tsx",
|
|
590
590
|
"isDynamicEntry": true,
|
|
@@ -601,7 +601,7 @@
|
|
|
601
601
|
]
|
|
602
602
|
},
|
|
603
603
|
"src/pages/Usage.tsx": {
|
|
604
|
-
"file": "assets/Usage-
|
|
604
|
+
"file": "assets/Usage-BWZkz7Se.js",
|
|
605
605
|
"name": "Usage",
|
|
606
606
|
"src": "src/pages/Usage.tsx",
|
|
607
607
|
"isDynamicEntry": true,
|
|
@@ -617,7 +617,7 @@
|
|
|
617
617
|
]
|
|
618
618
|
},
|
|
619
619
|
"src/pages/Workers.tsx": {
|
|
620
|
-
"file": "assets/Workers-
|
|
620
|
+
"file": "assets/Workers-CBEDvXm_.js",
|
|
621
621
|
"name": "Workers",
|
|
622
622
|
"src": "src/pages/Workers.tsx",
|
|
623
623
|
"isDynamicEntry": true,
|
|
@@ -635,7 +635,7 @@
|
|
|
635
635
|
]
|
|
636
636
|
},
|
|
637
637
|
"src/pages/control/Benchmark.tsx": {
|
|
638
|
-
"file": "assets/Benchmark-
|
|
638
|
+
"file": "assets/Benchmark-DQNZV7dM.js",
|
|
639
639
|
"name": "Benchmark",
|
|
640
640
|
"src": "src/pages/control/Benchmark.tsx",
|
|
641
641
|
"isDynamicEntry": true,
|
|
@@ -701,7 +701,7 @@
|
|
|
701
701
|
]
|
|
702
702
|
},
|
|
703
703
|
"src/pages/control/Database.tsx": {
|
|
704
|
-
"file": "assets/Database-
|
|
704
|
+
"file": "assets/Database-DGM2BIHy.js",
|
|
705
705
|
"name": "Database",
|
|
706
706
|
"src": "src/pages/control/Database.tsx",
|
|
707
707
|
"isDynamicEntry": true,
|
|
@@ -724,7 +724,7 @@
|
|
|
724
724
|
]
|
|
725
725
|
},
|
|
726
726
|
"src/pages/control/Diagnostics.tsx": {
|
|
727
|
-
"file": "assets/Diagnostics-
|
|
727
|
+
"file": "assets/Diagnostics-_Sh3UyaT.js",
|
|
728
728
|
"name": "Diagnostics",
|
|
729
729
|
"src": "src/pages/control/Diagnostics.tsx",
|
|
730
730
|
"isDynamicEntry": true,
|
|
@@ -746,7 +746,7 @@
|
|
|
746
746
|
]
|
|
747
747
|
},
|
|
748
748
|
"src/pages/control/DlqControl.tsx": {
|
|
749
|
-
"file": "assets/DlqControl-
|
|
749
|
+
"file": "assets/DlqControl-Lq9ISb9b.js",
|
|
750
750
|
"name": "DlqControl",
|
|
751
751
|
"src": "src/pages/control/DlqControl.tsx",
|
|
752
752
|
"isDynamicEntry": true,
|
|
@@ -770,7 +770,7 @@
|
|
|
770
770
|
]
|
|
771
771
|
},
|
|
772
772
|
"src/pages/control/DlqPro.tsx": {
|
|
773
|
-
"file": "assets/DlqPro-
|
|
773
|
+
"file": "assets/DlqPro-0_75vrd1.js",
|
|
774
774
|
"name": "DlqPro",
|
|
775
775
|
"src": "src/pages/control/DlqPro.tsx",
|
|
776
776
|
"isDynamicEntry": true,
|
|
@@ -794,7 +794,7 @@
|
|
|
794
794
|
]
|
|
795
795
|
},
|
|
796
796
|
"src/pages/control/Flows.tsx": {
|
|
797
|
-
"file": "assets/Flows-
|
|
797
|
+
"file": "assets/Flows-CE7KzaFN.js",
|
|
798
798
|
"name": "Flows",
|
|
799
799
|
"src": "src/pages/control/Flows.tsx",
|
|
800
800
|
"isDynamicEntry": true,
|
|
@@ -809,11 +809,12 @@
|
|
|
809
809
|
"_PageHeader-CvIyaFOO.js",
|
|
810
810
|
"index.html",
|
|
811
811
|
"_controlConnectionEpoch-CZsS0wAb.js",
|
|
812
|
-
"_useControlActionGuard-B-bzKZKI.js"
|
|
812
|
+
"_useControlActionGuard-B-bzKZKI.js",
|
|
813
|
+
"_flowOperationPolicy-B2Etcb3f.js"
|
|
813
814
|
]
|
|
814
815
|
},
|
|
815
816
|
"src/pages/control/JobInspector.tsx": {
|
|
816
|
-
"file": "assets/JobInspector-
|
|
817
|
+
"file": "assets/JobInspector-DyWZk1eW.js",
|
|
817
818
|
"name": "JobInspector",
|
|
818
819
|
"src": "src/pages/control/JobInspector.tsx",
|
|
819
820
|
"isDynamicEntry": true,
|
|
@@ -834,11 +835,11 @@
|
|
|
834
835
|
"_form-C4oby9pI.js",
|
|
835
836
|
"_flowMutationSafety-YKTHxqKv.js",
|
|
836
837
|
"_exportFile-C7xhE8Yh.js",
|
|
837
|
-
"
|
|
838
|
+
"_flowOperationPolicy-B2Etcb3f.js"
|
|
838
839
|
]
|
|
839
840
|
},
|
|
840
841
|
"src/pages/control/JobsPro.tsx": {
|
|
841
|
-
"file": "assets/JobsPro-
|
|
842
|
+
"file": "assets/JobsPro-BlWwDeFN.js",
|
|
842
843
|
"name": "JobsPro",
|
|
843
844
|
"src": "src/pages/control/JobsPro.tsx",
|
|
844
845
|
"isDynamicEntry": true,
|
|
@@ -859,11 +860,11 @@
|
|
|
859
860
|
"_Pagination-x-Cwgrul.js",
|
|
860
861
|
"_useServerActionGuard-uGePilQQ.js",
|
|
861
862
|
"_exportFile-C7xhE8Yh.js",
|
|
862
|
-
"
|
|
863
|
+
"_flowOperationPolicy-B2Etcb3f.js"
|
|
863
864
|
]
|
|
864
865
|
},
|
|
865
866
|
"src/pages/control/LogsPro.tsx": {
|
|
866
|
-
"file": "assets/LogsPro-
|
|
867
|
+
"file": "assets/LogsPro-BfYbmZHi.js",
|
|
867
868
|
"name": "LogsPro",
|
|
868
869
|
"src": "src/pages/control/LogsPro.tsx",
|
|
869
870
|
"isDynamicEntry": true,
|
|
@@ -885,7 +886,7 @@
|
|
|
885
886
|
]
|
|
886
887
|
},
|
|
887
888
|
"src/pages/control/McpServer.tsx": {
|
|
888
|
-
"file": "assets/McpServer-
|
|
889
|
+
"file": "assets/McpServer-DNrOEp9j.js",
|
|
889
890
|
"name": "McpServer",
|
|
890
891
|
"src": "src/pages/control/McpServer.tsx",
|
|
891
892
|
"isDynamicEntry": true,
|
|
@@ -897,7 +898,7 @@
|
|
|
897
898
|
]
|
|
898
899
|
},
|
|
899
900
|
"src/pages/control/MetricsPro.tsx": {
|
|
900
|
-
"file": "assets/MetricsPro-
|
|
901
|
+
"file": "assets/MetricsPro-C9g1sp-u.js",
|
|
901
902
|
"name": "MetricsPro",
|
|
902
903
|
"src": "src/pages/control/MetricsPro.tsx",
|
|
903
904
|
"isDynamicEntry": true,
|
|
@@ -917,7 +918,7 @@
|
|
|
917
918
|
]
|
|
918
919
|
},
|
|
919
920
|
"src/pages/control/QueueControl.tsx": {
|
|
920
|
-
"file": "assets/QueueControl-
|
|
921
|
+
"file": "assets/QueueControl-Bvm8GUQa.js",
|
|
921
922
|
"name": "QueueControl",
|
|
922
923
|
"src": "src/pages/control/QueueControl.tsx",
|
|
923
924
|
"isDynamicEntry": true,
|
|
@@ -941,7 +942,7 @@
|
|
|
941
942
|
]
|
|
942
943
|
},
|
|
943
944
|
"src/pages/control/QueueDetailPro.tsx": {
|
|
944
|
-
"file": "assets/QueueDetailPro-
|
|
945
|
+
"file": "assets/QueueDetailPro-Dkz4-1wn.js",
|
|
945
946
|
"name": "QueueDetailPro",
|
|
946
947
|
"src": "src/pages/control/QueueDetailPro.tsx",
|
|
947
948
|
"isDynamicEntry": true,
|
|
@@ -968,7 +969,7 @@
|
|
|
968
969
|
]
|
|
969
970
|
},
|
|
970
971
|
"src/pages/control/QueuesOverview.tsx": {
|
|
971
|
-
"file": "assets/QueuesOverview-
|
|
972
|
+
"file": "assets/QueuesOverview-CT6JBaim.js",
|
|
972
973
|
"name": "QueuesOverview",
|
|
973
974
|
"src": "src/pages/control/QueuesOverview.tsx",
|
|
974
975
|
"isDynamicEntry": true,
|
|
@@ -990,7 +991,7 @@
|
|
|
990
991
|
]
|
|
991
992
|
},
|
|
992
993
|
"src/pages/control/S3BackupPro.tsx": {
|
|
993
|
-
"file": "assets/S3BackupPro-
|
|
994
|
+
"file": "assets/S3BackupPro-CjR0y18n.js",
|
|
994
995
|
"name": "S3BackupPro",
|
|
995
996
|
"src": "src/pages/control/S3BackupPro.tsx",
|
|
996
997
|
"isDynamicEntry": true,
|
|
@@ -1009,7 +1010,7 @@
|
|
|
1009
1010
|
]
|
|
1010
1011
|
},
|
|
1011
1012
|
"src/pages/control/ServerControl.tsx": {
|
|
1012
|
-
"file": "assets/ServerControl-
|
|
1013
|
+
"file": "assets/ServerControl-BH6Doax0.js",
|
|
1013
1014
|
"name": "ServerControl",
|
|
1014
1015
|
"src": "src/pages/control/ServerControl.tsx",
|
|
1015
1016
|
"isDynamicEntry": true,
|
|
@@ -1030,7 +1031,7 @@
|
|
|
1030
1031
|
]
|
|
1031
1032
|
},
|
|
1032
1033
|
"src/pages/control/UsagePro.tsx": {
|
|
1033
|
-
"file": "assets/UsagePro-
|
|
1034
|
+
"file": "assets/UsagePro-CoBbI-Cl.js",
|
|
1034
1035
|
"name": "UsagePro",
|
|
1035
1036
|
"src": "src/pages/control/UsagePro.tsx",
|
|
1036
1037
|
"isDynamicEntry": true,
|
|
@@ -1068,7 +1069,7 @@
|
|
|
1068
1069
|
]
|
|
1069
1070
|
},
|
|
1070
1071
|
"src/pages/control/WorkersPro.tsx": {
|
|
1071
|
-
"file": "assets/WorkersPro-
|
|
1072
|
+
"file": "assets/WorkersPro-Dk4NfplT.js",
|
|
1072
1073
|
"name": "WorkersPro",
|
|
1073
1074
|
"src": "src/pages/control/WorkersPro.tsx",
|
|
1074
1075
|
"isDynamicEntry": true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as e}from"./cn-zFEt5SO8.js";import{g as t,t as n}from"./react-vendor-CvwrP0tU.js";import{o as r}from"./connectionStore-BJLihGaC.js";import{O as i,r as a}from"./icons-BVzwsfbG.js";import{n as o,t as s}from"./Button-Ch1W94fJ.js";import{t as c}from"./Card-CWxrC8OY.js";import{i as l,t as u}from"./feedback-QDN1AsYt.js";import{t as d}from"./PageHeader-CvIyaFOO.js";import{l as f}from"./format-Cg7VCIZW.js";import{d as p,f as m,l as h,p as g,u as _}from"./index-mID5W7vq.js";import{a as v,n as y,o as b,t as x}from"./form-C4oby9pI.js";var S=e(t(),1),C=[`error_rate`,`p99_latency`,`waiting`,`failed`,`dlq`],w=[`>=`,`>`,`<=`,`<`],T=[`email`,`webhook`,`slack`],E=/^[a-zA-Z0-9_\-.:]+$/,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`}};function O(e){let t=e.name.trim();if(!C.includes(e.metric))return{ok:!1,error:`Invalid metric`,field:`metric`};if(!w.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||!E.test(n))?{ok:!1,error:`Queue contains unsupported characters or is too long`,field:`queue`}:T.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`}}var k=n();function A({onAdd:e}){let[t,n]=(0,S.useState)(``),[r,i]=(0,S.useState)(`error_rate`),[a,o]=(0,S.useState)(`>=`),[c,l]=(0,S.useState)(``),[u,d]=(0,S.useState)(``),[f,p]=(0,S.useState)(null),h=(0,S.useRef)(!1),g=(0,S.useId)(),_=e=>f?.field===e;return(0,k.jsxs)(`div`,{className:`grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-5`,children:[(0,k.jsx)(x,{label:`Name`,children:(0,k.jsx)(y,{name:`alert-rule-name`,autoComplete:`off`,maxLength:200,value:t,"aria-invalid":_(`name`),"aria-describedby":_(`name`)?g:void 0,onChange:e=>{n(e.target.value),p(null)},placeholder:`High error rate`})}),(0,k.jsx)(x,{label:`Metric`,children:(0,k.jsx)(v,{name:`alert-rule-metric`,value:r,"aria-invalid":_(`metric`),"aria-describedby":_(`metric`)?g:void 0,onChange:e=>{i(e.target.value),p(null)},children:C.map(e=>(0,k.jsx)(`option`,{value:e,children:m[e]},e))})}),(0,k.jsx)(x,{label:`Operator`,children:(0,k.jsx)(v,{name:`alert-rule-operator`,value:a,"aria-invalid":_(`operator`),"aria-describedby":_(`operator`)?g:void 0,onChange:e=>{o(e.target.value),p(null)},children:w.map(e=>(0,k.jsx)(`option`,{value:e,children:e},e))})}),(0,k.jsx)(x,{label:`Threshold`,hint:D[r].unit,children:(0,k.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:c,"aria-invalid":_(`threshold`),"aria-describedby":_(`threshold`)?g:void 0,onChange:e=>{l(e.target.value),p(null)},placeholder:D[r].placeholder})}),(0,k.jsx)(x,{label:`Queue (optional)`,hint:r===`p99_latency`?`global only — latency is not per queue`:void 0,children:(0,k.jsx)(y,{name:`alert-rule-queue`,autoComplete:`off`,spellCheck:!1,maxLength:256,value:u,"aria-invalid":_(`queue`),"aria-describedby":_(`queue`)?g:void 0,onChange:e=>{d(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,k.jsxs)(`div`,{className:`col-span-full`,children:[(0,k.jsx)(s,{variant:`accent`,disabled:!t.trim()||!c.trim(),onClick:()=>{if(h.current)return;let n=O({name:t,metric:r,operator:a,threshold:c,queue:u,channel:`email`});if(!n.ok)return p({field:n.field,message:n.error});h.current=!0;try{let t=e(n.rule);t.ok||(h.current=!1,p({field:`name`,message:t.error}))}catch(e){h.current=!1,p({field:`name`,message:e.message})}},children:`Save rule`}),f&&(0,k.jsx)(`span`,{id:g,role:`alert`,className:`ml-3 text-xs text-danger`,children:f.message})]})]})}function j(){return typeof Notification>`u`?`unsupported`:Notification.permission}function M(){let{rules:e,addRule:t,removeRule:n,toggleRule:v}=g(),y=_(e=>e.breaching),x=_(e=>e.status),C=_(e=>e.error),w=_(e=>e.connectionIdentity),T=r(e=>p(e.baseUrl,e.token)),E=e.some(e=>e.enabled),D=w===T,O=D?y:[],M=D?x:E?`checking`:`idle`,N=D?C:null,[P,F]=(0,S.useState)(!1),[I,L]=(0,S.useState)(j);return(0,k.jsxs)(`div`,{children:[(0,k.jsx)(d,{title:`Alerts`,description:`Threshold rules evaluated live in your browser.`,live:E&&M===`live`,actions:(0,k.jsxs)(k.Fragment,{children:[I===`granted`?(0,k.jsxs)(`span`,{className:`flex items-center gap-1.5 text-sm text-success`,children:[(0,k.jsx)(a,{className:`size-4`}),` Desktop alerts on`]}):I===`unsupported`?(0,k.jsx)(`span`,{className:`text-sm text-faint`,children:`Notifications unsupported`}):I===`denied`?(0,k.jsx)(`span`,{className:`text-sm text-faint`,children:`Desktop alerts blocked — allow notifications in browser settings`}):(0,k.jsx)(s,{size:`sm`,onClick:async()=>{L(await h())},children:`Enable desktop notifications`}),(0,k.jsx)(s,{variant:`accent`,size:`sm`,"aria-expanded":P,"aria-controls":`alert-rule-form`,onClick:()=>F(e=>!e),children:`+ Create Alert Rule`})]})}),E&&M===`checking`&&(0,k.jsx)(`p`,{role:`status`,className:`mb-4 text-sm text-muted`,children:`Checking alert metrics for the connected server…`}),E&&M===`degraded`&&(0,k.jsx)(l,{message:N??`Alert metrics are unavailable; current rule results may be stale.`}),(0,k.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,k.jsxs)(`section`,{className:`mb-8`,children:[(0,k.jsx)(`h2`,{className:`mb-3 text-lg font-semibold text-fg`,children:`Triggered Alerts`}),O.length===0?(0,k.jsx)(u,{icon:(0,k.jsx)(a,{}),title:`No triggered alerts`,hint:E?M===`live`?`All enabled rules are within their thresholds.`:M===`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,k.jsx)(`div`,{className:`overflow-x-auto rounded-xl border border-danger/30 bg-red-500/5`,children:(0,k.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,k.jsx)(`thead`,{children:(0,k.jsxs)(`tr`,{className:`border-b border-line text-left text-[11px] uppercase tracking-wider text-faint`,children:[(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Rule`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Condition`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Queue`}),(0,k.jsx)(`th`,{className:`px-5 py-3 text-right font-medium`,children:`Current`}),(0,k.jsx)(`th`,{className:`px-5 py-3 text-right font-medium`,children:`Since`})]})}),(0,k.jsx)(`tbody`,{children:O.map(e=>(0,k.jsxs)(`tr`,{className:`border-b border-line last:border-0`,children:[(0,k.jsx)(`td`,{className:`px-5 py-3 font-medium text-danger`,children:e.ruleName}),(0,k.jsxs)(`td`,{className:`px-5 py-3 text-muted`,children:[e.metricLabel,` `,e.operator,` `,(0,k.jsx)(`span`,{className:`font-semibold text-fg`,children:e.threshold})]}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-muted`,children:e.queue||`All queues`}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-right tnum font-semibold text-danger`,children:Math.round(e.value*100)/100}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-right text-faint`,children:f(e.at)})]},e.ruleId))})]})})]}),(0,k.jsxs)(`section`,{children:[(0,k.jsx)(`h2`,{className:`mb-3 text-lg font-semibold text-fg`,children:`Alert Rules`}),P&&(0,k.jsx)(`div`,{id:`alert-rule-form`,children:(0,k.jsx)(c,{className:`mb-4`,children:(0,k.jsx)(A,{onAdd:e=>{let n=t(e);return n.ok&&F(!1),n}})})}),e.length===0?(0,k.jsx)(u,{title:`No alert rules`,hint:`Create a rule to be notified when a threshold is crossed.`}):(0,k.jsx)(`div`,{className:`overflow-x-auto rounded-xl border border-line bg-surface`,children:(0,k.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,k.jsx)(`thead`,{children:(0,k.jsxs)(`tr`,{className:`border-b border-line text-left text-[11px] uppercase tracking-wider text-faint`,children:[(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Rule`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Condition`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Queue`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Delivery`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Status`}),(0,k.jsx)(`th`,{className:`w-12 px-5 py-3`})]})}),(0,k.jsx)(`tbody`,{children:e.map(e=>(0,k.jsxs)(`tr`,{className:`border-b border-line last:border-0`,children:[(0,k.jsx)(`td`,{className:`px-5 py-3 font-medium text-fg`,children:e.name}),(0,k.jsxs)(`td`,{className:`px-5 py-3 text-muted`,children:[m[e.metric],` `,e.operator,` `,(0,k.jsx)(`span`,{className:`font-semibold text-accent`,children:e.threshold})]}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-muted`,children:e.queue||`All queues`}),(0,k.jsx)(`td`,{className:`px-5 py-3`,children:(0,k.jsx)(`span`,{className:`rounded-md bg-surface-2 px-2 py-0.5 text-xs text-muted`,children:`Browser`})}),(0,k.jsx)(`td`,{className:`px-5 py-3`,children:(0,k.jsx)(b,{checked:e.enabled,onChange:()=>v(e.id),label:`Enabled`})}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-right`,children:(0,k.jsx)(o,{"aria-label":`Delete rule ${e.name}`,onClick:()=>window.confirm(`Delete alert rule "${e.name}"?`)&&n(e.id),children:(0,k.jsx)(i,{className:`size-3.5`})})})]},e.id))})]})})]})]})}export{M as Alerts};
|
|
1
|
+
import{i as e}from"./cn-zFEt5SO8.js";import{g as t,t as n}from"./react-vendor-CvwrP0tU.js";import{o as r}from"./connectionStore-BJLihGaC.js";import{O as i,r as a}from"./icons-BVzwsfbG.js";import{n as o,t as s}from"./Button-Ch1W94fJ.js";import{t as c}from"./Card-CWxrC8OY.js";import{i as l,t as u}from"./feedback-QDN1AsYt.js";import{t as d}from"./PageHeader-CvIyaFOO.js";import{l as f}from"./format-Cg7VCIZW.js";import{d as p,f as m,l as h,p as g,u as _}from"./index-BFW2_47n.js";import{a as v,n as y,o as b,t as x}from"./form-C4oby9pI.js";var S=e(t(),1),C=[`error_rate`,`p99_latency`,`waiting`,`failed`,`dlq`],w=[`>=`,`>`,`<=`,`<`],T=[`email`,`webhook`,`slack`],E=/^[a-zA-Z0-9_\-.:]+$/,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`}};function O(e){let t=e.name.trim();if(!C.includes(e.metric))return{ok:!1,error:`Invalid metric`,field:`metric`};if(!w.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||!E.test(n))?{ok:!1,error:`Queue contains unsupported characters or is too long`,field:`queue`}:T.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`}}var k=n();function A({onAdd:e}){let[t,n]=(0,S.useState)(``),[r,i]=(0,S.useState)(`error_rate`),[a,o]=(0,S.useState)(`>=`),[c,l]=(0,S.useState)(``),[u,d]=(0,S.useState)(``),[f,p]=(0,S.useState)(null),h=(0,S.useRef)(!1),g=(0,S.useId)(),_=e=>f?.field===e;return(0,k.jsxs)(`div`,{className:`grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-5`,children:[(0,k.jsx)(x,{label:`Name`,children:(0,k.jsx)(y,{name:`alert-rule-name`,autoComplete:`off`,maxLength:200,value:t,"aria-invalid":_(`name`),"aria-describedby":_(`name`)?g:void 0,onChange:e=>{n(e.target.value),p(null)},placeholder:`High error rate`})}),(0,k.jsx)(x,{label:`Metric`,children:(0,k.jsx)(v,{name:`alert-rule-metric`,value:r,"aria-invalid":_(`metric`),"aria-describedby":_(`metric`)?g:void 0,onChange:e=>{i(e.target.value),p(null)},children:C.map(e=>(0,k.jsx)(`option`,{value:e,children:m[e]},e))})}),(0,k.jsx)(x,{label:`Operator`,children:(0,k.jsx)(v,{name:`alert-rule-operator`,value:a,"aria-invalid":_(`operator`),"aria-describedby":_(`operator`)?g:void 0,onChange:e=>{o(e.target.value),p(null)},children:w.map(e=>(0,k.jsx)(`option`,{value:e,children:e},e))})}),(0,k.jsx)(x,{label:`Threshold`,hint:D[r].unit,children:(0,k.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:c,"aria-invalid":_(`threshold`),"aria-describedby":_(`threshold`)?g:void 0,onChange:e=>{l(e.target.value),p(null)},placeholder:D[r].placeholder})}),(0,k.jsx)(x,{label:`Queue (optional)`,hint:r===`p99_latency`?`global only — latency is not per queue`:void 0,children:(0,k.jsx)(y,{name:`alert-rule-queue`,autoComplete:`off`,spellCheck:!1,maxLength:256,value:u,"aria-invalid":_(`queue`),"aria-describedby":_(`queue`)?g:void 0,onChange:e=>{d(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,k.jsxs)(`div`,{className:`col-span-full`,children:[(0,k.jsx)(s,{variant:`accent`,disabled:!t.trim()||!c.trim(),onClick:()=>{if(h.current)return;let n=O({name:t,metric:r,operator:a,threshold:c,queue:u,channel:`email`});if(!n.ok)return p({field:n.field,message:n.error});h.current=!0;try{let t=e(n.rule);t.ok||(h.current=!1,p({field:`name`,message:t.error}))}catch(e){h.current=!1,p({field:`name`,message:e.message})}},children:`Save rule`}),f&&(0,k.jsx)(`span`,{id:g,role:`alert`,className:`ml-3 text-xs text-danger`,children:f.message})]})]})}function j(){return typeof Notification>`u`?`unsupported`:Notification.permission}function M(){let{rules:e,addRule:t,removeRule:n,toggleRule:v}=g(),y=_(e=>e.breaching),x=_(e=>e.status),C=_(e=>e.error),w=_(e=>e.connectionIdentity),T=r(e=>p(e.baseUrl,e.token)),E=e.some(e=>e.enabled),D=w===T,O=D?y:[],M=D?x:E?`checking`:`idle`,N=D?C:null,[P,F]=(0,S.useState)(!1),[I,L]=(0,S.useState)(j);return(0,k.jsxs)(`div`,{children:[(0,k.jsx)(d,{title:`Alerts`,description:`Threshold rules evaluated live in your browser.`,live:E&&M===`live`,actions:(0,k.jsxs)(k.Fragment,{children:[I===`granted`?(0,k.jsxs)(`span`,{className:`flex items-center gap-1.5 text-sm text-success`,children:[(0,k.jsx)(a,{className:`size-4`}),` Desktop alerts on`]}):I===`unsupported`?(0,k.jsx)(`span`,{className:`text-sm text-faint`,children:`Notifications unsupported`}):I===`denied`?(0,k.jsx)(`span`,{className:`text-sm text-faint`,children:`Desktop alerts blocked — allow notifications in browser settings`}):(0,k.jsx)(s,{size:`sm`,onClick:async()=>{L(await h())},children:`Enable desktop notifications`}),(0,k.jsx)(s,{variant:`accent`,size:`sm`,"aria-expanded":P,"aria-controls":`alert-rule-form`,onClick:()=>F(e=>!e),children:`+ Create Alert Rule`})]})}),E&&M===`checking`&&(0,k.jsx)(`p`,{role:`status`,className:`mb-4 text-sm text-muted`,children:`Checking alert metrics for the connected server…`}),E&&M===`degraded`&&(0,k.jsx)(l,{message:N??`Alert metrics are unavailable; current rule results may be stale.`}),(0,k.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,k.jsxs)(`section`,{className:`mb-8`,children:[(0,k.jsx)(`h2`,{className:`mb-3 text-lg font-semibold text-fg`,children:`Triggered Alerts`}),O.length===0?(0,k.jsx)(u,{icon:(0,k.jsx)(a,{}),title:`No triggered alerts`,hint:E?M===`live`?`All enabled rules are within their thresholds.`:M===`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,k.jsx)(`div`,{className:`overflow-x-auto rounded-xl border border-danger/30 bg-red-500/5`,children:(0,k.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,k.jsx)(`thead`,{children:(0,k.jsxs)(`tr`,{className:`border-b border-line text-left text-[11px] uppercase tracking-wider text-faint`,children:[(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Rule`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Condition`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Queue`}),(0,k.jsx)(`th`,{className:`px-5 py-3 text-right font-medium`,children:`Current`}),(0,k.jsx)(`th`,{className:`px-5 py-3 text-right font-medium`,children:`Since`})]})}),(0,k.jsx)(`tbody`,{children:O.map(e=>(0,k.jsxs)(`tr`,{className:`border-b border-line last:border-0`,children:[(0,k.jsx)(`td`,{className:`px-5 py-3 font-medium text-danger`,children:e.ruleName}),(0,k.jsxs)(`td`,{className:`px-5 py-3 text-muted`,children:[e.metricLabel,` `,e.operator,` `,(0,k.jsx)(`span`,{className:`font-semibold text-fg`,children:e.threshold})]}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-muted`,children:e.queue||`All queues`}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-right tnum font-semibold text-danger`,children:Math.round(e.value*100)/100}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-right text-faint`,children:f(e.at)})]},e.ruleId))})]})})]}),(0,k.jsxs)(`section`,{children:[(0,k.jsx)(`h2`,{className:`mb-3 text-lg font-semibold text-fg`,children:`Alert Rules`}),P&&(0,k.jsx)(`div`,{id:`alert-rule-form`,children:(0,k.jsx)(c,{className:`mb-4`,children:(0,k.jsx)(A,{onAdd:e=>{let n=t(e);return n.ok&&F(!1),n}})})}),e.length===0?(0,k.jsx)(u,{title:`No alert rules`,hint:`Create a rule to be notified when a threshold is crossed.`}):(0,k.jsx)(`div`,{className:`overflow-x-auto rounded-xl border border-line bg-surface`,children:(0,k.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,k.jsx)(`thead`,{children:(0,k.jsxs)(`tr`,{className:`border-b border-line text-left text-[11px] uppercase tracking-wider text-faint`,children:[(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Rule`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Condition`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Queue`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Delivery`}),(0,k.jsx)(`th`,{className:`px-5 py-3 font-medium`,children:`Status`}),(0,k.jsx)(`th`,{className:`w-12 px-5 py-3`})]})}),(0,k.jsx)(`tbody`,{children:e.map(e=>(0,k.jsxs)(`tr`,{className:`border-b border-line last:border-0`,children:[(0,k.jsx)(`td`,{className:`px-5 py-3 font-medium text-fg`,children:e.name}),(0,k.jsxs)(`td`,{className:`px-5 py-3 text-muted`,children:[m[e.metric],` `,e.operator,` `,(0,k.jsx)(`span`,{className:`font-semibold text-accent`,children:e.threshold})]}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-muted`,children:e.queue||`All queues`}),(0,k.jsx)(`td`,{className:`px-5 py-3`,children:(0,k.jsx)(`span`,{className:`rounded-md bg-surface-2 px-2 py-0.5 text-xs text-muted`,children:`Browser`})}),(0,k.jsx)(`td`,{className:`px-5 py-3`,children:(0,k.jsx)(b,{checked:e.enabled,onChange:()=>v(e.id),label:`Enabled`})}),(0,k.jsx)(`td`,{className:`px-5 py-3 text-right`,children:(0,k.jsx)(o,{"aria-label":`Delete rule ${e.name}`,onClick:()=>window.confirm(`Delete alert rule "${e.name}"?`)&&n(e.id),children:(0,k.jsx)(i,{className:`size-3.5`})})})]},e.id))})]})})]})]})}export{M as Alerts};
|