bunqueue-dashboard 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -10
- package/agent/backup/routes.ts +20 -12
- package/agent/manager/config.ts +2 -1
- package/agent/server/handler.ts +2 -1
- package/agent/server/router.ts +6 -1
- package/agent/server/types.ts +1 -1
- package/agent/workflow/runtime.ts +25 -7
- package/agent/workflow/runtimeRoutes.ts +4 -1
- package/dist/.vite/manifest.json +500 -489
- package/dist/assets/{AddJob-CiwWycYG.js → AddJob-jq-VZ_YE.js} +1 -1
- package/dist/assets/{Alerts-DzKKoA9U.js → Alerts-Bi8zLKXV.js} +1 -1
- package/dist/assets/{AreaChart-Db2UVnaK.js → AreaChart-BMYp7z5y.js} +1 -1
- package/dist/assets/{Benchmark-CHYDL31K.js → Benchmark-CWYdqm55.js} +1 -1
- package/dist/assets/{BulkAddJobs-GRMVnf0j.js → BulkAddJobs-0cdWqkEF.js} +1 -1
- package/dist/assets/{Button-_aSJvRoW.js → Button-BCjgbAhY.js} +1 -1
- package/dist/assets/{Card-egIDLwVk.js → Card-DptfvL2O.js} +1 -1
- package/dist/assets/{Cron-XL0PdUEc.js → Cron-dPGwJXqY.js} +1 -1
- package/dist/assets/{CronManager-Cqtg9Zr8.js → CronManager-C0-Y2QDg.js} +1 -1
- package/dist/assets/{Database-BoxVpjts.js → Database-CseJOrvD.js} +1 -1
- package/dist/assets/{Diagnostics-B3QSQuFF.js → Diagnostics-cZTy4Y2t.js} +1 -1
- package/dist/assets/{Dlq-DHOfgORa.js → Dlq-W2xOJiZM.js} +1 -1
- package/dist/assets/{DlqControl-BY1e55-x.js → DlqControl-CIV5278D.js} +1 -1
- package/dist/assets/DlqPro-BuRxGtGt.js +1 -0
- package/dist/assets/{Fleet-Bkbq0dFB.js → Fleet-0zKJSjCw.js} +1 -1
- package/dist/assets/{Flows-CDCQrPit.js → Flows-CEfm102Y.js} +1 -1
- package/dist/assets/{JobInspector-Bw3Gs1Iy.js → JobInspector-BlMj5-Vr.js} +1 -1
- package/dist/assets/{Jobs-IrlneOy6.js → Jobs-D_TId_Oa.js} +1 -1
- package/dist/assets/JobsPro-A06ZG5Xi.js +1 -0
- package/dist/assets/{Logs-DG7cK-tF.js → Logs-hbh7XhQU.js} +1 -1
- package/dist/assets/{LogsPro-ClSqFKys.js → LogsPro-BgEIjgfj.js} +1 -1
- package/dist/assets/McpServer-ffXr4nFW.js +22 -0
- package/dist/assets/{Metrics-BkePZiCZ.js → Metrics-CIKZGgf0.js} +1 -1
- package/dist/assets/{MetricsPro-ooSvpQ3H.js → MetricsPro-CK3WfLCl.js} +1 -1
- package/dist/assets/{Overview-CuWmPRra.js → Overview-BBgwyUn4.js} +1 -1
- package/dist/assets/{PageHeader-CQzNsw8r.js → PageHeader-DD4vnVog.js} +1 -1
- package/dist/assets/{Pagination-CSkzhNW6.js → Pagination-Cj7GzYNX.js} +1 -1
- package/dist/assets/{QueueActions-BBxRt-t0.js → QueueActions-BgdW95rN.js} +1 -1
- package/dist/assets/{QueueControl-C01l6bBJ.js → QueueControl-g-9kwrpt.js} +1 -1
- package/dist/assets/{QueueDetail-gSf9Tb7G.js → QueueDetail-Cv3MLDoE.js} +1 -1
- package/dist/assets/{QueueDetailPro-BKszqI0M.js → QueueDetailPro-AhdOAzEF.js} +1 -1
- package/dist/assets/{Queues-BAg2nSz0.js → Queues-Bbvh_OCE.js} +1 -1
- package/dist/assets/{QueuesOverview-BLSjiYsA.js → QueuesOverview-sPQILfMU.js} +1 -1
- package/dist/assets/{S3Backup-Ci4dJPP_.js → S3Backup-Cg6sc2JG.js} +1 -1
- package/dist/assets/{S3BackupPro-DcjlrzC5.js → S3BackupPro-BWV_R_Vj.js} +1 -1
- package/dist/assets/ServerControl-Dr2gXbaU.js +3 -0
- package/dist/assets/{Settings-DR_iqW0T.js → Settings-BsDDlu-h.js} +1 -1
- package/dist/assets/{StatusBadge-DereLzOj.js → StatusBadge-cYUkK-4P.js} +1 -1
- package/dist/assets/{Usage-ZWfB7PcR.js → Usage-Cxx6pZfb.js} +1 -1
- package/dist/assets/{UsagePro-BjdeS2zb.js → UsagePro-CkC8WYcS.js} +1 -1
- package/dist/assets/{Webhooks-BonYUgMo.js → Webhooks-BKWnALGC.js} +1 -1
- package/dist/assets/{Workers-C1h6hCPp.js → Workers-SJ9MApx3.js} +1 -1
- package/dist/assets/WorkersPro-BDgHBTpY.js +1 -0
- package/dist/assets/Workflows-Cp6jqKeM.js +1 -0
- package/dist/assets/{api-D9aT0ND7.js → api-Bbo86MB6.js} +1 -1
- package/dist/assets/{bq-DR2BTISO.js → bq-CSX8DSME.js} +1 -1
- package/dist/assets/{classicJobsData-Csj4iihY.js → classicJobsData-BimIyQky.js} +1 -1
- package/dist/assets/{connectionStore-CVjQyIZv.js → connectionStore-DdLzJyut.js} +1 -1
- package/dist/assets/{controlConnectionEpoch-ChAeMMLk.js → controlConnectionEpoch-D4HtriLM.js} +1 -1
- package/dist/assets/{feedback-CtGN_kqB.js → feedback-Dxfj-Fy1.js} +1 -1
- package/dist/assets/{form-DG3X6cLL.js → form-Dy-an7ew.js} +1 -1
- package/dist/assets/{icons-DH9SS1DA.js → icons-C1HVBnFn.js} +1 -1
- package/dist/assets/{index-Bat1DN5U.js → index-CxzFSZj3.js} +3 -3
- package/dist/assets/index-DZXf58c2.css +1 -0
- package/dist/assets/install-DUkXE5kb.js +3 -0
- package/dist/assets/package-C7CtgBEy.js +1 -0
- package/dist/assets/react-vendor-DfC0iyIS.js +11 -0
- package/dist/assets/{toastStore-XmMX7A5W.js → toastStore-DnqA_aL-.js} +1 -1
- package/dist/assets/{useControlActionGuard-1i7aFA7E.js → useControlActionGuard-DImMgJtj.js} +1 -1
- package/dist/assets/{usePolledData-CEyyLRYa.js → usePolledData-Dp_ynL9f.js} +1 -1
- package/dist/assets/{useServerActionGuard-CUx8TOAC.js → useServerActionGuard-Cr4KUOeZ.js} +1 -1
- package/dist/assets/{useThroughputSeries-CeFx-Mwq.js → useThroughputSeries-CrTeR9s3.js} +1 -1
- package/dist/index.html +14 -14
- package/package.json +40 -33
- package/scripts/bunVersion.ts +1 -1
- package/scripts/embedded.gen.ts +146 -144
- package/dist/assets/DlqPro-CfwKDcpc.js +0 -1
- package/dist/assets/JobsPro-vlPd6Ows.js +0 -1
- package/dist/assets/McpServer-DoNFY0Pj.js +0 -24
- package/dist/assets/ServerControl-SiZBNo9P.js +0 -3
- package/dist/assets/WorkersPro-B0f_Nfdz.js +0 -1
- package/dist/assets/Workflows-CqZZ96EK.js +0 -1
- package/dist/assets/index-C9yGKhQ8.css +0 -1
- package/dist/assets/install-oydLpc1T.js +0 -3
- package/dist/assets/react-vendor-CFL9l8LK.js +0 -11
package/README.md
CHANGED
|
@@ -32,13 +32,15 @@ The live demo uses sample data. The docs explain how to connect and operate a re
|
|
|
32
32
|

|
|
33
33
|

|
|
34
34
|

|
|
35
|
-

|
|
36
36
|
|
|
37
37
|
<br />
|
|
38
38
|
|
|
39
|
-
<
|
|
39
|
+
<a href="https://www.youtube.com/watch?v=1bQRFXGClcc" title="Watch the full bunqueue dashboard tour on YouTube"><img src="docs/public/video-tour.png" alt="Watch the narrated 4K tour of bunqueue dashboard on YouTube" width="900" /></a>
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
### [▶ Watch the full tour on YouTube](https://www.youtube.com/watch?v=1bQRFXGClcc)
|
|
42
|
+
|
|
43
|
+
<sub>A narrated 4K walkthrough (7:45, English with subtitles) recorded against a real bunqueue server under live traffic. <a href="https://egeominotti.github.io/bunqueue-dashboard/">Or try the live demo →</a></sub>
|
|
42
44
|
|
|
43
45
|
<br /><br />
|
|
44
46
|
|
|
@@ -134,7 +136,7 @@ contracts; it never patches Bunqueue internals.
|
|
|
134
136
|
|
|
135
137
|
## Quick start
|
|
136
138
|
|
|
137
|
-
**Prerequisites:** [Bun](https://bun.sh) 1.4.
|
|
139
|
+
**Prerequisites:** [Bun](https://bun.sh) 1.4.4 and a reachable bunqueue server (or let the control
|
|
138
140
|
agent start one for you from the **Server** page).
|
|
139
141
|
|
|
140
142
|
### Run from npm (no clone)
|
|
@@ -261,20 +263,21 @@ target-pinned Flow, Workflow, Queue and Backup agent operations.
|
|
|
261
263
|
| `bun start` | **Agent + dashboard together** (one-command dev) |
|
|
262
264
|
| `bun dev` | Dashboard only (Vite dev server) |
|
|
263
265
|
| `bun run agent` | Control agent only |
|
|
264
|
-
| `bun run build` |
|
|
266
|
+
| `bun run build` | Native Bun typecheck + production build → `dist/` |
|
|
267
|
+
| `bun run typecheck` | Native `bun check` for the frontend, agent/scripts, examples and browser tests |
|
|
265
268
|
| `bun run preview` | Preview the production build |
|
|
266
269
|
| `bun run lint` | Lint with Oxlint, including type-aware rules and the implicit-`any` parity guard |
|
|
267
|
-
| `bun run format:check` | Check formatting with
|
|
268
|
-
| `bun run check` |
|
|
269
|
-
| `bun run check:fix` | Apply safe Oxlint fixes, then format with
|
|
270
|
+
| `bun run format:check` | Check formatting with native `bun fmt --check` |
|
|
271
|
+
| `bun run check` | Native typechecking, Oxlint and native formatting validation |
|
|
272
|
+
| `bun run check:fix` | Apply safe Oxlint fixes, then format with native `bun fmt` |
|
|
270
273
|
| `bun test` | Unit + agent-lifecycle tests |
|
|
271
274
|
| `bun run test:e2e:tls` | Queue, Flow and Workflow over verified native TLS, including untrusted-certificate rejection |
|
|
272
|
-
| `bun run test:e2e:upgrade` | Real npm Bunqueue 2.9.2 SQLite schema-35 → 2.9.
|
|
275
|
+
| `bun run test:e2e:upgrade` | Real npm Bunqueue 2.9.2 SQLite schema-35 → 2.9.20 schema-37 migration and retention semantics |
|
|
273
276
|
| `bun run test:e2e` | Real TLS, SQLite upgrade, Flow, Workflow, Queue SDK, and three-broker PostgreSQL runtime tests |
|
|
274
277
|
| `bun run test:e2e:browser` | Production UI tests on Chromium, Firefox and WebKit |
|
|
275
278
|
| `bun run test:e2e:browser:postgres-fleet` | Real UI operations across three PostgreSQL brokers (requires Docker and Chromium) |
|
|
276
279
|
| `bun run quality` | Complete static, build, docs, coverage, runtime, package and dependency gate |
|
|
277
|
-
| `bun run test:e2e:postgres-fleet` | Three authenticated Bunqueue 2.9.
|
|
280
|
+
| `bun run test:e2e:postgres-fleet` | Three authenticated Bunqueue 2.9.20 brokers + three agents sharing disposable PostgreSQL 18.6 |
|
|
278
281
|
|
|
279
282
|
## Docker
|
|
280
283
|
|
package/agent/backup/routes.ts
CHANGED
|
@@ -190,18 +190,26 @@ function assertDatabaseSnapshot(value: unknown, current: DbStats): void {
|
|
|
190
190
|
throw new Error('A database snapshot is required to authorize restore');
|
|
191
191
|
}
|
|
192
192
|
const expected = value as Record<string, unknown>;
|
|
193
|
-
const allowed = ['path', 'exists', 'size', 'walSize', 'shmSize', 'totalSize', 'mtimeMs'];
|
|
194
|
-
const unknown = Object.keys(expected).find((key) => !allowed.includes(key));
|
|
193
|
+
const allowed = ['path', 'exists', 'size', 'walSize', 'shmSize', 'totalSize', 'mtimeMs'] as const;
|
|
194
|
+
const unknown = Object.keys(expected).find((key) => !(allowed as readonly string[]).includes(key));
|
|
195
195
|
if (unknown) throw new Error(`Unknown database snapshot option: ${unknown}`);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
196
|
+
// Compare what reflects the data. The -shm file is SQLite's shared-memory WAL
|
|
197
|
+
// index: it appears and disappears as connections open and close (the last one
|
|
198
|
+
// deletes it) without any write, so it, and the total that includes it, would
|
|
199
|
+
// reject valid confirmations. Writes still move the main file or the WAL.
|
|
200
|
+
const compared = ['path', 'exists', 'size', 'walSize', 'mtimeMs'] as const;
|
|
201
|
+
// Name every field that moved so a rejected restore is diagnosable.
|
|
202
|
+
const changed = compared
|
|
203
|
+
.filter((field) => expected[field] !== current[field])
|
|
204
|
+
.map((field) => `${field} ${shown(expected[field])} → ${shown(current[field])}`);
|
|
205
|
+
if (changed.length > 0) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Database changed after restore confirmation (${changed.join(', ')}); reload status and confirm again`
|
|
208
|
+
);
|
|
206
209
|
}
|
|
207
210
|
}
|
|
211
|
+
|
|
212
|
+
function shown(value: unknown): string {
|
|
213
|
+
const text = String(value);
|
|
214
|
+
return text.length > 120 ? `${text.slice(0, 117)}...` : text;
|
|
215
|
+
}
|
package/agent/manager/config.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import manifest from '../../package.json';
|
|
1
2
|
import type { ServerConfig } from './types';
|
|
2
3
|
|
|
3
4
|
const CONFIG_KEYS = new Set<keyof ServerConfig>([
|
|
@@ -10,7 +11,7 @@ const CONFIG_KEYS = new Set<keyof ServerConfig>([
|
|
|
10
11
|
|
|
11
12
|
export function defaultConfig(): ServerConfig {
|
|
12
13
|
return {
|
|
13
|
-
command: process.env.BUNQUEUE_START_CMD ||
|
|
14
|
+
command: process.env.BUNQUEUE_START_CMD || `bunx bunqueue@${manifest.dependencies.bunqueue} start`,
|
|
14
15
|
httpPort: Number(process.env.HTTP_PORT) || 6790,
|
|
15
16
|
tcpPort: Number(process.env.TCP_PORT) || 6789,
|
|
16
17
|
dataPath: process.env.BUNQUEUE_DATA_PATH || './data/bunq.db',
|
package/agent/server/handler.ts
CHANGED
|
@@ -65,7 +65,8 @@ export function createFetchHandler(
|
|
|
65
65
|
return new Response(null, { status: 204, headers: corsHeaders(origin, allowedOrigins) });
|
|
66
66
|
}
|
|
67
67
|
const mutating = method === 'POST' || method === 'PUT';
|
|
68
|
-
const
|
|
68
|
+
const configurationRead = pathname === '/control/config' || pathname === '/control/status';
|
|
69
|
+
const authRequired = requireTokenForAll || ((mutating || configurationRead) && Boolean(token));
|
|
69
70
|
if (authRequired && !tokenOk(request, token)) {
|
|
70
71
|
return json({ ok: false, error: 'Unauthorized' }, 401, origin);
|
|
71
72
|
}
|
package/agent/server/router.ts
CHANGED
|
@@ -123,12 +123,16 @@ export async function routeAgentRequest(
|
|
|
123
123
|
return sqliteOnly('S3 backup', currentStorageMode);
|
|
124
124
|
}
|
|
125
125
|
return manager.withStoppedMaintenance('restoring a backup', async () => {
|
|
126
|
+
// Measure what the operator confirmed before our own cleanup: releasing
|
|
127
|
+
// the Workflow Engine's connection checkpoints the WAL, which changes the
|
|
128
|
+
// files without any other writer and would void a valid confirmation.
|
|
129
|
+
const confirmedDatabase = await manager.dbStats(restoreConfig.dataPath);
|
|
126
130
|
await closeWorkflowRuntime(runtime);
|
|
127
131
|
return executePreparedBackupRequest(
|
|
128
132
|
prepared,
|
|
129
133
|
restoreConfig,
|
|
130
134
|
current.status === 'running',
|
|
131
|
-
|
|
135
|
+
confirmedDatabase,
|
|
132
136
|
backupRunner,
|
|
133
137
|
undefined,
|
|
134
138
|
managedTargetPolicy
|
|
@@ -197,6 +201,7 @@ function isWorkflowRuntimeRoute(pathname: string, method: string): boolean {
|
|
|
197
201
|
if (method !== 'POST') return false;
|
|
198
202
|
return (
|
|
199
203
|
[
|
|
204
|
+
'/workflows/runtime/initialize',
|
|
200
205
|
'/workflows/runtime/reload',
|
|
201
206
|
'/workflows/start',
|
|
202
207
|
'/workflows/recover',
|
package/agent/server/types.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface AgentOptions {
|
|
|
5
5
|
allowedOrigins: string[];
|
|
6
6
|
/** DNS-rebinding defense; compared by hostname only. */
|
|
7
7
|
allowedHosts?: string[];
|
|
8
|
-
/** On loopback, when set,
|
|
8
|
+
/** On loopback, when set, mutations and complete configuration reads require this token. */
|
|
9
9
|
token?: string;
|
|
10
10
|
/** Network exposure: require token on every non-OPTIONS request. */
|
|
11
11
|
requireTokenForAll?: boolean;
|
|
@@ -29,10 +29,12 @@ export interface WorkflowRuntimeStatus {
|
|
|
29
29
|
queueName?: string;
|
|
30
30
|
concurrency?: number;
|
|
31
31
|
error?: string;
|
|
32
|
+
needsInitialization?: boolean;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export interface WorkflowRuntimePort {
|
|
35
|
-
status(config: ServerConfig,
|
|
36
|
+
status(config: ServerConfig, serverRunning?: boolean): Promise<WorkflowRuntimeStatus>;
|
|
37
|
+
initialize(config: ServerConfig): Promise<WorkflowRuntimeStatus>;
|
|
36
38
|
reload(config: ServerConfig): Promise<WorkflowRuntimeStatus>;
|
|
37
39
|
start(config: ServerConfig, workflowName: string, input?: unknown): Promise<unknown>;
|
|
38
40
|
signal(config: ServerConfig, id: string, event: string, payload?: unknown): Promise<void>;
|
|
@@ -72,20 +74,32 @@ export class WorkflowRuntime implements WorkflowRuntimePort {
|
|
|
72
74
|
private readonly createEngine: WorkflowEngineFactory = (options) => new Engine(options)
|
|
73
75
|
) {}
|
|
74
76
|
|
|
75
|
-
status(config: ServerConfig,
|
|
77
|
+
status(config: ServerConfig, serverRunning = true): Promise<WorkflowRuntimeStatus> {
|
|
76
78
|
return this.serial(async () => {
|
|
77
79
|
const modulePath = configuredWorkflowModule(config);
|
|
78
80
|
try {
|
|
79
81
|
if (!modulePath) {
|
|
80
|
-
await this.closeActive();
|
|
81
82
|
return { configured: false, ready: false, workflowNames: [] };
|
|
82
83
|
}
|
|
83
|
-
if (!
|
|
84
|
-
await this.closeActive();
|
|
84
|
+
if (!serverRunning) {
|
|
85
85
|
return stoppedRuntimeStatus(config, modulePath);
|
|
86
86
|
}
|
|
87
|
-
const active =
|
|
88
|
-
|
|
87
|
+
const active = this.active;
|
|
88
|
+
if (active?.closeFailure !== undefined) throw quarantinedRuntimeError(active);
|
|
89
|
+
const path = await realpath(modulePath);
|
|
90
|
+
const modified = (await stat(path)).mtimeMs;
|
|
91
|
+
const options = workflowRuntimeOptions(config);
|
|
92
|
+
if (active?.signature === workflowRuntimeSignature(config, path, modified, options)) {
|
|
93
|
+
return readyRuntimeStatus(active);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
configured: true,
|
|
97
|
+
ready: false,
|
|
98
|
+
needsInitialization: true,
|
|
99
|
+
moduleName: basename(modulePath),
|
|
100
|
+
workflowNames: [],
|
|
101
|
+
...options,
|
|
102
|
+
};
|
|
89
103
|
} catch (error) {
|
|
90
104
|
const active = this.active;
|
|
91
105
|
return {
|
|
@@ -101,6 +115,10 @@ export class WorkflowRuntime implements WorkflowRuntimePort {
|
|
|
101
115
|
});
|
|
102
116
|
}
|
|
103
117
|
|
|
118
|
+
initialize(config: ServerConfig): Promise<WorkflowRuntimeStatus> {
|
|
119
|
+
return this.serial(async () => readyRuntimeStatus(await this.ensure(config)));
|
|
120
|
+
}
|
|
121
|
+
|
|
104
122
|
reload(config: ServerConfig): Promise<WorkflowRuntimeStatus> {
|
|
105
123
|
return this.serial(async () => {
|
|
106
124
|
await this.closeActive();
|
|
@@ -49,6 +49,9 @@ export async function routeWorkflowRuntimeRequest(
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
if (!serverRunning) throw new Error('Start the managed Bunqueue server before workflow control.');
|
|
52
|
+
if (pathname === '/workflows/runtime/initialize') {
|
|
53
|
+
return success(await admitted(true, ({ config: current }) => runtime.initialize(current)));
|
|
54
|
+
}
|
|
52
55
|
if (pathname === '/workflows/runtime/reload') {
|
|
53
56
|
return success(await admitted(true, ({ config: current }) => runtime.reload(current)));
|
|
54
57
|
}
|
|
@@ -98,7 +101,7 @@ function isRuntimePath(pathname: string, method: string): boolean {
|
|
|
98
101
|
if (method === 'GET') return pathname === '/workflows/runtime';
|
|
99
102
|
if (method !== 'POST') return false;
|
|
100
103
|
return (
|
|
101
|
-
['/workflows/runtime/reload', '/workflows/start', '/workflows/recover', '/workflows/archive', '/workflows/cleanup'].includes(pathname) ||
|
|
104
|
+
['/workflows/runtime/initialize', '/workflows/runtime/reload', '/workflows/start', '/workflows/recover', '/workflows/archive', '/workflows/cleanup'].includes(pathname) ||
|
|
102
105
|
/^\/workflows\/[^/]+\/(signal|resume-compensation|abandon-compensation)$/.test(pathname)
|
|
103
106
|
);
|
|
104
107
|
}
|