bunqueue-dashboard 0.0.42 → 0.0.43

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 +5 -4
  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
@@ -96,7 +96,7 @@ server *process* through a separate guarded agent.
96
96
 
97
97
  It uses Bunqueue's public HTTP API (`:6790`) for ordinary remote observability and a small local
98
98
  **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
99
+ agent is pinned to the server it manages and uses the exact installable Bunqueue 2.9.4 npm client
100
100
  contracts; it never patches Bunqueue internals.
101
101
 
102
102
  ## Features
@@ -262,9 +262,10 @@ target-pinned Flow, Workflow, Queue and Backup agent operations.
262
262
  | `bun run check` | Oxlint + Oxfmt validation (the CI gate) |
263
263
  | `bun run check:fix` | Apply safe Oxlint fixes, then format with Oxfmt |
264
264
  | `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 |
265
+ | `bun run test:e2e:tls` | Queue, Flow and Workflow over verified native TLS, including untrusted-certificate rejection |
266
+ | `bun run test:e2e:upgrade` | Real npm Bunqueue 2.9.2 SQLite schema-35 → 2.9.4 schema-37 migration and retention semantics |
266
267
  | `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 |
268
+ | `bun run test:e2e:postgres-fleet` | Three authenticated Bunqueue 2.9.4 brokers + three agents sharing disposable PostgreSQL 18.6 |
268
269
 
269
270
  ## Docker
270
271
 
@@ -311,7 +312,7 @@ The canonical gate must be green before a change is considered done; CI runs the
311
312
  bun run quality # architecture, lint/build, size, docs, coverage, real E2E, packed-bin smoke, audit
312
313
  ```
313
314
 
314
- The E2E stage starts disposable Bunqueue 2.9.3 processes and exercises the complete FlowProducer,
315
+ The E2E stage starts disposable Bunqueue 2.9.4 processes and exercises the complete FlowProducer,
315
316
  Workflow Engine, and Queue SDK operator bridges. It also starts PostgreSQL 18.6 in Docker with
316
317
  three authenticated brokers and paired agents, proving cross-broker enqueue/inspect/leased
317
318
  pull+ack, pause/resume, cron, rate limits, group admission, group priority and group pause/resume
@@ -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};