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.
Files changed (49) hide show
  1. package/README.md +30 -12
  2. package/agent/flow/routes.ts +2 -0
  3. package/agent/flow/service.ts +7 -2
  4. package/agent/managedConnection.ts +35 -0
  5. package/agent/manager/config.ts +1 -1
  6. package/agent/queue/runtime.ts +2 -2
  7. package/agent/workflow/runtime.ts +2 -6
  8. package/agent/workflow/runtimeConfig.ts +2 -2
  9. package/dist/.vite/manifest.json +40 -39
  10. package/dist/assets/{Alerts-BvWBv-z7.js → Alerts-C961Al5D.js} +1 -1
  11. package/dist/assets/{Benchmark-BywalSmU.js → Benchmark-DQNZV7dM.js} +1 -1
  12. package/dist/assets/{CopilotPanel-CH2jbbYA.js → CopilotPanel-Dlm8XwBj.js} +1 -1
  13. package/dist/assets/{Database-BzEkXws0.js → Database-DGM2BIHy.js} +1 -1
  14. package/dist/assets/{Diagnostics-CPqhV3er.js → Diagnostics-_Sh3UyaT.js} +1 -1
  15. package/dist/assets/{Dlq-CbLN0zQ5.js → Dlq-mtInIoTh.js} +1 -1
  16. package/dist/assets/{DlqControl-C5Bgzmk0.js → DlqControl-Lq9ISb9b.js} +1 -1
  17. package/dist/assets/{DlqPro-Cgaxtuwh.js → DlqPro-0_75vrd1.js} +1 -1
  18. package/dist/assets/Flows-CE7KzaFN.js +2 -0
  19. package/dist/assets/{JobInspector-DsOt4mLJ.js → JobInspector-DyWZk1eW.js} +1 -1
  20. package/dist/assets/{Jobs-BTMpbAHD.js → Jobs-ELSH1Wbd.js} +1 -1
  21. package/dist/assets/JobsPro-BlWwDeFN.js +1 -0
  22. package/dist/assets/{Logs-uuzU5ohI.js → Logs-DMukqmHy.js} +1 -1
  23. package/dist/assets/{LogsPro-DHWbhWyq.js → LogsPro-BfYbmZHi.js} +1 -1
  24. package/dist/assets/{McpServer-BqmbhrKh.js → McpServer-DNrOEp9j.js} +1 -1
  25. package/dist/assets/{Metrics-DKyBZoFa.js → Metrics-Cm5ZvfTo.js} +1 -1
  26. package/dist/assets/{MetricsPro-BR2ULqIc.js → MetricsPro-C9g1sp-u.js} +1 -1
  27. package/dist/assets/{Overview-ClsyxH2e.js → Overview-CKAA3h9f.js} +1 -1
  28. package/dist/assets/{QueueControl-BW0XXd0B.js → QueueControl-Bvm8GUQa.js} +1 -1
  29. package/dist/assets/{QueueDetail-C1dXXN70.js → QueueDetail-BRDATlp0.js} +1 -1
  30. package/dist/assets/{QueueDetailPro-DDGxs2J2.js → QueueDetailPro-Dkz4-1wn.js} +1 -1
  31. package/dist/assets/{Queues-DkPuIy1C.js → Queues-BV99vr68.js} +1 -1
  32. package/dist/assets/{QueuesOverview-COo-V8D_.js → QueuesOverview-CT6JBaim.js} +1 -1
  33. package/dist/assets/{S3BackupPro-DxNVFvY1.js → S3BackupPro-CjR0y18n.js} +1 -1
  34. package/dist/assets/{ServerControl-B0Po974t.js → ServerControl-BH6Doax0.js} +1 -1
  35. package/dist/assets/{Settings-z9k4EVa_.js → Settings-CL7odwoU.js} +1 -1
  36. package/dist/assets/{Usage-cdWDQUaq.js → Usage-BWZkz7Se.js} +1 -1
  37. package/dist/assets/{UsagePro-DLuwnM8n.js → UsagePro-CoBbI-Cl.js} +1 -1
  38. package/dist/assets/{Workers-CVo9rJ_V.js → Workers-CBEDvXm_.js} +1 -1
  39. package/dist/assets/{WorkersPro-BYaredE9.js → WorkersPro-Dk4NfplT.js} +1 -1
  40. package/dist/assets/flowOperationPolicy-B2Etcb3f.js +1 -0
  41. package/dist/assets/{index-mID5W7vq.js → index-BFW2_47n.js} +3 -3
  42. package/dist/assets/{install-D4uRZ7XA.js → install-oydLpc1T.js} +1 -1
  43. package/dist/index.html +1 -1
  44. package/package.json +8 -5
  45. package/scripts/embedded.gen.ts +164 -164
  46. package/shared/flowOperationPolicy.ts +66 -0
  47. package/dist/assets/Flows-BtUGOaif.js +0 -2
  48. package/dist/assets/JobsPro-BfXTm9gH.js +0 -1
  49. 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
- **The only queue dashboard that also _runs_ the server.** Monitor and control queues, jobs, DLQ,
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)**, one detailed, screenshot-backed page per dashboard section.
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.3 npm client
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
- - **Process lifecycle**, the one thing HTTP can't do, is delegated to the local control agent.
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:upgrade` | Real npm Bunqueue 2.9.2 SQLite schema-35 → 2.9.3 schema-37 migration and retention semantics |
266
- | `bun run test:e2e` | Real Flow, Workflow, Queue SDK, and three-broker PostgreSQL runtime tests |
267
- | `bun run test:e2e:postgres-fleet` | Three authenticated Bunqueue 2.9.3 brokers + three agents sharing disposable PostgreSQL 18.6 |
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
- The canonical gate must be green before a change is considered done; CI runs the same command:
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 quality # architecture, lint/build, size, docs, coverage, real E2E, packed-bin smoke, audit
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.3 processes and exercises the complete FlowProducer,
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, and automated WCAG A/AA checks. Install the browser
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
- CI enforces this on every push and pull request. See [Contributing](#contributing).
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
 
@@ -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)
@@ -7,7 +7,8 @@ import {
7
7
  type JobNode,
8
8
  } from 'bunqueue/client';
9
9
  import type { ServerConfig } from '../manager';
10
- import { managedAuthToken } from '../managedTarget';
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 { host: '127.0.0.1', port: config.tcpPort, token: managedAuthToken(config) };
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
+ }
@@ -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.3 start',
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',
@@ -1,5 +1,5 @@
1
1
  import { Queue, type QueueMetricType, type QueueMetrics } from 'bunqueue/client';
2
- import { managedAuthToken } from '../managedTarget';
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 { host: '127.0.0.1', port: config.tcpPort, token: managedAuthToken(config) };
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 { managedAuthToken } from '../managedTarget';
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 { managedAuthToken } from '../managedTarget';
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
- managedAuthToken(config),
56
+ managedConnection(config),
57
57
  options.queueName,
58
58
  options.concurrency,
59
59
  ]);
@@ -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-mID5W7vq.js",
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-CH2jbbYA.js",
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-D4uRZ7XA.js",
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-BvWBv-z7.js",
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-CbLN0zQ5.js",
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-BTMpbAHD.js",
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-uuzU5ohI.js",
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-DKyBZoFa.js",
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-ClsyxH2e.js",
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-C1dXXN70.js",
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-DkPuIy1C.js",
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-z9k4EVa_.js",
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-cdWDQUaq.js",
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-CVo9rJ_V.js",
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-BywalSmU.js",
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-BzEkXws0.js",
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-CPqhV3er.js",
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-C5Bgzmk0.js",
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-Cgaxtuwh.js",
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-BtUGOaif.js",
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-DsOt4mLJ.js",
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
- "_jobActions-LQD4KfCc.js"
838
+ "_flowOperationPolicy-B2Etcb3f.js"
838
839
  ]
839
840
  },
840
841
  "src/pages/control/JobsPro.tsx": {
841
- "file": "assets/JobsPro-BfXTm9gH.js",
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
- "_jobActions-LQD4KfCc.js"
863
+ "_flowOperationPolicy-B2Etcb3f.js"
863
864
  ]
864
865
  },
865
866
  "src/pages/control/LogsPro.tsx": {
866
- "file": "assets/LogsPro-DHWbhWyq.js",
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-BqmbhrKh.js",
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-BR2ULqIc.js",
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-BW0XXd0B.js",
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-DDGxs2J2.js",
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-COo-V8D_.js",
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-DxNVFvY1.js",
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-B0Po974t.js",
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-DLuwnM8n.js",
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-BYaredE9.js",
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};