bunqueue-dashboard 1.0.4 → 1.1.0
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 +173 -360
- package/agent/queue/validation.ts +6 -3
- package/dist/.vite/manifest.json +177 -82
- package/dist/assets/AddJob-jmrtBSrz.js +3 -0
- package/dist/assets/{Alerts-Lyzbq0qw.js → Alerts-DrwYvhbk.js} +1 -1
- package/dist/assets/{Benchmark-B4NfYkAi.js → Benchmark-7ULYlqmK.js} +1 -1
- package/dist/assets/{BulkAddJobs-BAN5wT8A.js → BulkAddJobs-C2sxF-St.js} +3 -3
- package/dist/assets/{Cron-Bz46oVXB.js → Cron-BY9EGj2n.js} +1 -1
- package/dist/assets/{CronManager-Cl5RW2cB.js → CronManager-CZvC_uox.js} +1 -1
- package/dist/assets/Database-B1Dx5OV2.js +3 -0
- package/dist/assets/{Diagnostics-DuzTPfyb.js → Diagnostics-D1LEl6O4.js} +1 -1
- package/dist/assets/{Dlq-D8s-NhV2.js → Dlq-CPxeigKg.js} +1 -1
- package/dist/assets/{DlqControl-DDxRMM8i.js → DlqControl-Dhcz7LY7.js} +1 -1
- package/dist/assets/DlqPro-CW80tQdN.js +1 -0
- package/dist/assets/FailureDiagnostics-DkLd6szC.js +1 -0
- package/dist/assets/{Fleet-BAziMWfo.js → Fleet-CsscadVQ.js} +1 -1
- package/dist/assets/Flows-Bzq3onQ3.js +2 -0
- package/dist/assets/GlobalSearch-CeCvA6Tm.js +1 -0
- package/dist/assets/{JobInspector-BXuXxDwR.js → JobInspector-nTUU1VOU.js} +2 -2
- package/dist/assets/{Jobs-pQBmffni.js → Jobs-OwKndzhg.js} +1 -1
- package/dist/assets/{JobsPro-k20Pas24.js → JobsPro-CSHP2AYC.js} +1 -1
- package/dist/assets/{Logs-DA4JFvpM.js → Logs-CiYuY8ix.js} +1 -1
- package/dist/assets/{LogsPro-hna5L6sP.js → LogsPro-BA-wZ8Dr.js} +1 -1
- package/dist/assets/{McpServer-Cqem-L3z.js → McpServer-BvOGxNnE.js} +1 -1
- package/dist/assets/MetricHistory-Dmu-XqaO.js +1 -0
- package/dist/assets/{Metrics-CJ9dW0me.js → Metrics-BF38GgYm.js} +1 -1
- package/dist/assets/{MetricsPro-BslYrx1A.js → MetricsPro-DHPmT4Md.js} +1 -1
- package/dist/assets/MonitoringSetup-60_ib3rf.js +3 -0
- package/dist/assets/{Overview-Cmg_2NAt.js → Overview-DBLxV_vL.js} +1 -1
- package/dist/assets/{Pagination-WVfHLHDS.js → Pagination-CmVnmrFt.js} +1 -1
- package/dist/assets/{QueueActions-BQf1-g5m.js → QueueActions-uNol2MD7.js} +1 -1
- package/dist/assets/QueueControl-DbQwvvtX.js +1 -0
- package/dist/assets/{QueueDetail-DfRY4jS4.js → QueueDetail-CA33Kebs.js} +1 -1
- package/dist/assets/{QueueDetailPro-CpSMSo8A.js → QueueDetailPro-AUf6FktH.js} +1 -1
- package/dist/assets/{Queues-C5lElTjp.js → Queues-C_KeSyQs.js} +1 -1
- package/dist/assets/{QueuesOverview-DRohFtuC.js → QueuesOverview-DQ6ta4wA.js} +1 -1
- package/dist/assets/{S3Backup-DUHmp2Xw.js → S3Backup-BgPBXJVZ.js} +1 -1
- package/dist/assets/{S3BackupPro-Cr2ZJZkw.js → S3BackupPro-BUzcmhnj.js} +3 -3
- package/dist/assets/{ServerControl-m58ooJT2.js → ServerControl-WUZjdjPX.js} +1 -1
- package/dist/assets/{Settings-DXsjoOqD.js → Settings-Dwh79r-B.js} +1 -1
- package/dist/assets/{Usage-CY2uEJEu.js → Usage-DeWofLpa.js} +1 -1
- package/dist/assets/{UsagePro-DtlB1dJc.js → UsagePro-BQJWWwW3.js} +1 -1
- package/dist/assets/{Webhooks-B_GwTCBW.js → Webhooks-DS3xmbEi.js} +1 -1
- package/dist/assets/{Workers-Bu8aJlZf.js → Workers-DyAVvmbR.js} +1 -1
- package/dist/assets/{WorkersPro-C6gNYGfo.js → WorkersPro-BJgab7of.js} +1 -1
- package/dist/assets/Workflows-BLXlNIxO.js +1 -0
- package/dist/assets/bqQueueOperationsRepository-BXoBdFQc.js +1 -0
- package/dist/assets/exportFile-B6puuPQx.js +2 -0
- package/dist/assets/index-D7iDxOBp.css +1 -0
- package/dist/assets/index-DwsYIYdu.js +8 -0
- package/dist/assets/{install-hFu5e6YM.js → install-DVeS3d31.js} +2 -2
- package/dist/assets/{package-Bb3FkjoS.js → package-D11d0FBn.js} +1 -1
- package/dist/assets/targetClient-DvzvwnGp.js +1 -0
- package/dist/assets/useDiscovery-BbWXsy6R.js +1 -0
- package/dist/assets/{useThroughputSeries-BN5QmKPt.js → useThroughputSeries-DQo7JaT-.js} +1 -1
- package/dist/favicon.svg +11 -22
- package/dist/index.html +3 -2
- package/package.json +1 -1
- package/scripts/embedded.gen.ts +146 -132
- package/dist/assets/AddJob-Dgjlhzn2.js +0 -3
- package/dist/assets/Database-CeUKS6Nf.js +0 -3
- package/dist/assets/DlqPro-xj-MM6u8.js +0 -1
- package/dist/assets/Flows-BFUMTSWv.js +0 -2
- package/dist/assets/QueueControl-BiMM0jLp.js +0 -1
- package/dist/assets/Workflows-ClF4YzhW.js +0 -1
- package/dist/assets/exportFile-C7xhE8Yh.js +0 -2
- package/dist/assets/index-DR8flo6r.css +0 -1
- package/dist/assets/index-X_fHGbXZ.js +0 -8
package/README.md
CHANGED
|
@@ -1,397 +1,210 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
<img src="docs/logo.svg" alt="bunqueue
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
[
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-

|
|
34
|
-

|
|
35
|
-

|
|
36
|
-
|
|
37
|
-
<br />
|
|
38
|
-
|
|
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
|
-
|
|
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>
|
|
44
|
-
|
|
45
|
-
<br /><br />
|
|
46
|
-
|
|
47
|
-
<img src="docs/screenshots/overview.png" alt="bunqueue dashboard, real-time overview: health, throughput, per-queue status, and a live activity feed" width="900" />
|
|
48
|
-
|
|
49
|
-
<sub>The Overview page. <a href="https://egeominotti.github.io/bunqueue-dashboard/docs/user-guide">See every screen in the illustrated guide →</a></sub>
|
|
50
|
-
|
|
51
|
-
<details>
|
|
52
|
-
<summary><b>More screenshots</b></summary>
|
|
53
|
-
<br />
|
|
54
|
-
<img src="docs/screenshots/server.png" alt="Server Control, start/stop/restart the bunqueue process with live logs" width="900" />
|
|
55
|
-
<img src="docs/screenshots/jobs.png" alt="Jobs Explorer, filter, inspect, and bulk-action jobs" width="900" />
|
|
56
|
-
<img src="docs/screenshots/database.png" alt="Read-only SQLite inspector, browse tables and run queries" width="900" />
|
|
57
|
-
</details>
|
|
58
|
-
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
> ✅ **Stable (1.x).** The dashboard follows [Semantic Versioning](https://semver.org/):
|
|
64
|
-
> breaking changes to its configuration, routes or agent API only ship in a new
|
|
65
|
-
> major version.
|
|
66
|
-
|
|
67
|
-
## 📚 Documentation
|
|
68
|
-
|
|
69
|
-
**Full, illustrated docs live at
|
|
70
|
-
[egeominotti.github.io/bunqueue-dashboard/docs](https://egeominotti.github.io/bunqueue-dashboard/docs/).**
|
|
71
|
-
|
|
72
|
-
- **[Illustrated user guide](https://egeominotti.github.io/bunqueue-dashboard/docs/user-guide)**, section guides with screenshots, operations and limitations.
|
|
73
|
-
- **[Deployment](https://egeominotti.github.io/bunqueue-dashboard/docs/deploy/)**, Docker (Caddy), Kubernetes, PM2, and hosting platforms (Vercel, Netlify, Cloudflare, Fly.io, Render, Cloud Run).
|
|
74
|
-
- **[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.
|
|
75
|
-
- **[Testing matrix](docs/testing.md)**, real-server coverage, reproduction steps and verification limits.
|
|
76
|
-
- **[llms.txt](https://egeominotti.github.io/bunqueue-dashboard/docs/llms.txt)**, the documentation index for LLMs; **[llms-full.txt](https://egeominotti.github.io/bunqueue-dashboard/docs/llms-full.txt)** contains the full text.
|
|
77
|
-
|
|
78
|
-
## Table of contents
|
|
79
|
-
|
|
80
|
-
- [Documentation](#-documentation)
|
|
81
|
-
- [Why](#why)
|
|
82
|
-
- [Features](#features)
|
|
83
|
-
- [Quick start](#quick-start)
|
|
84
|
-
- [Architecture](#architecture)
|
|
85
|
-
- [Configuration](#configuration)
|
|
86
|
-
- [Scripts](#scripts)
|
|
87
|
-
- [Docker](#docker)
|
|
88
|
-
- [Deployment](#deployment)
|
|
89
|
-
- [Testing & quality gate](#testing--quality-gate)
|
|
90
|
-
- [Project structure](#project-structure)
|
|
91
|
-
- [Security](#security)
|
|
92
|
-
- [Contributing](#contributing)
|
|
93
|
-
- [License](#license)
|
|
94
|
-
|
|
95
|
-
## Why
|
|
96
|
-
|
|
97
|
-
bunqueue exposes a rich HTTP API, but operating it by hand (curl, ad-hoc scripts) is slow and
|
|
98
|
-
error-prone. This dashboard is a production-oriented operator console: it exposes the verified,
|
|
99
|
-
safe subset of the API, fails closed where
|
|
100
|
-
[Bunqueue 2.9.x](https://github.com/egeominotti/bunqueue/releases/tag/v2.9.3) lacks atomic
|
|
101
|
-
flow-safety guarantees, and also manages the
|
|
102
|
-
server *process* through a separate guarded agent.
|
|
103
|
-
|
|
104
|
-
It uses Bunqueue's public HTTP API (`:6790`) for ordinary remote observability and a small local
|
|
105
|
-
**control agent** for process, FlowProducer, Workflow Engine, database, and backup operations. The
|
|
106
|
-
agent is pinned to the server it manages and uses the exact installable Bunqueue 2.9.4 npm client
|
|
107
|
-
contracts; it never patches Bunqueue internals.
|
|
108
|
-
|
|
109
|
-
## Features
|
|
110
|
-
|
|
111
|
-
| Area | Where | What you can do |
|
|
112
|
-
| --- | --- | --- |
|
|
113
|
-
| **Home** | Overview | Live health banner, throughput, queue health, recent activity |
|
|
114
|
-
| **Fleet** | Fleet | Probe and operate multiple Bunqueue APIs + paired agents; verify shared PostgreSQL target/namespace topology |
|
|
115
|
-
| **Server** | Control ▸ Server | **Start / stop / restart** the server process, edit its config, tail process logs |
|
|
116
|
-
| **Enqueue** | Control ▸ Add Job | Add jobs (single or bulk) with every option |
|
|
117
|
-
| **Inspect** | Control ▸ Job Inspector | Look up any job; promote / re-prioritize / delay; view data & result |
|
|
118
|
-
| **Queues** | Control ▸ Queue Control | Pause/resume/promote; desired-state writes plus live SDK limits, group jobs/priorities/pause/policies, deduplication, metrics and journal retention |
|
|
119
|
-
| **Cron** | Control ▸ Cron Manager | Submit acknowledged cron/interval upserts and delete schedules |
|
|
120
|
-
| **Workflow Engine** | Workflow ▸ Overview / Executions / Waiting / Compensation / Archive | Start, recover, signal, inspect, compensate, archive and clean durable executions |
|
|
121
|
-
| **Job Flows** | Workflow ▸ Job Flows | Explore DAGs, run all FlowProducer create modes and operate safe Flow Job methods |
|
|
122
|
-
| **DLQ** | Control ▸ DLQ | Inspect/export failures; removal, retry and purge stay unavailable without atomic generation/topology preconditions |
|
|
123
|
-
| **Webhooks** | Control ▸ Webhooks | Create / enable / delete job-event webhooks |
|
|
124
|
-
| **Ops** | Control ▸ Diagnostics | Health, ping, storage, memory, connections, totals |
|
|
125
|
-
| **S3 backup** | Management ▸ S3 Backup | Configure, inspect, list, create and guarded-restore official snapshots |
|
|
126
|
-
| **Browse** | Queues / Jobs / DLQ / Cron / Metrics / Workers / Logs | Read-only browsing with basic actions |
|
|
127
|
-
|
|
128
|
-
> Job actions are gated by the v2.9.3 flow contract. Every DLQ retry and completed-job requeue is
|
|
129
|
-
> unavailable: the DLQ GET + POST sequence has no atomic generation/state/topology precondition and
|
|
130
|
-
> can target a job recreated under the same ID, while `retryCompleted` does not rebuild dependency
|
|
131
|
-
> registration or flow order. Cancel, Discard, Drain, Clean, Obliterate and DLQ Purge also fail
|
|
132
|
-
> closed. Bunqueue 2.9's `Queue.removeDlqJob()` also accepts only queue + job ID, so custom-ID reuse
|
|
133
|
-
> prevents the dashboard from proving that a later deletion still targets the observed generation.
|
|
134
|
-
> DLQ `maxAge`/`maxEntries` are read-only, and auto-retry can only be disabled. The in-app Copilot
|
|
135
|
-
> is switched off in official builds (`VITE_ENABLE_COPILOT=1` opts a custom build back in).
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://bunqueue.dev/">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/egeominotti/bunqueue/main/docs/src/assets/logo.svg" alt="bunqueue logo" width="88" height="88" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<h1 align="center">bunqueue dashboard</h1>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
A web console for your Bunqueue jobs, workflows and servers.
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://egeominotti.github.io/bunqueue-dashboard/">Live demo</a> ·
|
|
15
|
+
<a href="https://egeominotti.github.io/bunqueue-dashboard/docs/">Documentation</a> ·
|
|
16
|
+
<a href="https://github.com/egeominotti/bunqueue-dashboard/releases">Downloads</a> ·
|
|
17
|
+
<a href="https://www.youtube.com/watch?v=1bQRFXGClcc">Video tour</a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="https://www.npmjs.com/package/bunqueue-dashboard"><img src="https://img.shields.io/npm/v/bunqueue-dashboard?color=db2777" alt="npm version" /></a>
|
|
22
|
+
<a href="https://github.com/egeominotti/bunqueue-dashboard/actions/workflows/ci.yml"><img src="https://github.com/egeominotti/bunqueue-dashboard/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
|
23
|
+
<a href="https://bun.com/"><img src="https://img.shields.io/badge/Bun-1.4.4-14151a?logo=bun&logoColor=white" alt="Bun 1.4.4" /></a>
|
|
24
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license" /></a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
Monitor and operate [Bunqueue](https://bunqueue.dev/) from your browser: find jobs,
|
|
28
|
+
inspect failures, manage schedules, follow durable workflows and control the server
|
|
29
|
+
process. Connect to one broker or keep several broker/agent profiles in Settings.
|
|
30
|
+
|
|
31
|
+
The dashboard targets **Bunqueue 2.9.20**. The live demo uses sample data; the
|
|
32
|
+
published package connects to your own server.
|
|
136
33
|
|
|
137
34
|
## Quick start
|
|
138
35
|
|
|
139
|
-
|
|
140
|
-
agent start one for you from the **Server** page).
|
|
141
|
-
|
|
142
|
-
### Run from npm (no clone)
|
|
36
|
+
Install [Bun 1.4.4](https://bun.com/docs/installation), then run:
|
|
143
37
|
|
|
144
38
|
```bash
|
|
145
39
|
bunx bunqueue-dashboard
|
|
146
40
|
```
|
|
147
41
|
|
|
148
|
-
|
|
149
|
-
http://127.0.0.1:8080, proxies `/api/*` to your bunqueue server (`BUNQUEUE_URL`, default
|
|
150
|
-
`http://localhost:6790`), and runs the control agent on `127.0.0.1:6800`. Same env knobs as the
|
|
151
|
-
standalone binaries: `PORT` · `BIND_ADDR` · `BUNQUEUE_URL` · `AGENT_PORT` ·
|
|
152
|
-
`AGENT_ALLOWED_ORIGINS` · `AGENT_ALLOWED_HOSTS` · `AGENT_TOKEN` · `BUNQUEUE_TOKEN` ·
|
|
153
|
-
`TRUST_PROXY` · `BASE_PATH` · `BUNQUEUE_MANAGED` · `BUNQUEUE_START_CMD`.
|
|
42
|
+
Open **[http://127.0.0.1:8080](http://127.0.0.1:8080)**.
|
|
154
43
|
|
|
155
|
-
|
|
44
|
+
- To start a local Bunqueue server, open **Server** and select **Start**. The
|
|
45
|
+
default command runs the compatible Bunqueue 2.9.20 release.
|
|
46
|
+
- To connect an existing server, configure its API URL and token in **Settings**.
|
|
47
|
+
For a broker managed by another supervisor, use the command below.
|
|
156
48
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
bunqueue-dashboard
|
|
160
|
-
```
|
|
49
|
+
The npm package serves the built UI, proxies API requests and includes the local
|
|
50
|
+
control agent. Its default agent address is `http://127.0.0.1:6800`.
|
|
161
51
|
|
|
162
|
-
|
|
52
|
+
<p align="center">
|
|
53
|
+
<img src="https://raw.githubusercontent.com/egeominotti/bunqueue-dashboard/main/docs/screenshots/overview.png" alt="Dashboard overview with server health, job counts, throughput and queue activity" width="1000" />
|
|
54
|
+
</p>
|
|
163
55
|
|
|
164
|
-
|
|
165
|
-
git clone https://github.com/egeominotti/bunqueue-dashboard.git
|
|
166
|
-
cd bunqueue-dashboard
|
|
167
|
-
bun install
|
|
168
|
-
bun start
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
`bun start` boots **both** the control agent and the dashboard in one terminal and shuts both down
|
|
172
|
-
on `Ctrl-C`:
|
|
173
|
-
|
|
174
|
-
| Service | URL | Role |
|
|
175
|
-
| --- | --- | --- |
|
|
176
|
-
| Dashboard | http://localhost:5273 | The UI (`/api/*` is proxied to `:6790` in dev) |
|
|
177
|
-
| Control agent | http://127.0.0.1:6800 | Starts / stops / restarts the server process |
|
|
178
|
-
| bunqueue server | http://localhost:6790 | Your queue server, started from the **Server** page or run separately |
|
|
179
|
-
|
|
180
|
-
Prefer separate terminals? The individual commands still exist, see [Scripts](#scripts).
|
|
181
|
-
|
|
182
|
-
## Architecture
|
|
183
|
-
|
|
184
|
-
```mermaid
|
|
185
|
-
flowchart LR
|
|
186
|
-
subgraph Browser
|
|
187
|
-
UI["Dashboard (React 19 SPA)"]
|
|
188
|
-
end
|
|
189
|
-
subgraph Local
|
|
190
|
-
Agent["Control agent (Bun)<br/>127.0.0.1:6800"]
|
|
191
|
-
Server["bunqueue server<br/>:6790 HTTP · :6789 TCP"]
|
|
192
|
-
end
|
|
193
|
-
UI -- "HTTP: queues, jobs, DLQ, cron…" --> Server
|
|
194
|
-
UI -- "SSE: live activity" --> Server
|
|
195
|
-
UI -- "/control/* start·stop·restart" --> Agent
|
|
196
|
-
Agent -- "spawn / kill process" --> Server
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
- **Reads** by polling (`usePolledData`) and a Server-Sent Events stream (`useActivityStream`).
|
|
200
|
-
- **Writes** through the same HTTP API, with every mutation shape-verified against the live server.
|
|
201
|
-
- **Managed operations** use the local control agent for process lifecycle, Queue SDK controls,
|
|
202
|
-
FlowProducer, Workflow Engine, SQLite inspection, and backups.
|
|
56
|
+
## What you can do
|
|
203
57
|
|
|
204
|
-
|
|
205
|
-
agent per broker. The Fleet page probes all profiles without retargeting the
|
|
206
|
-
active UI, groups brokers that share an exact PostgreSQL target + namespace,
|
|
207
|
-
and drives each node's Start/Stop/Restart controls. Queue state is shared by
|
|
208
|
-
PostgreSQL; lifecycle, process logs/config, in-memory webhooks, and each agent's
|
|
209
|
-
Workflow Engine store remain node-local. See the
|
|
210
|
-
[Fleet guide](https://egeominotti.github.io/bunqueue-dashboard/docs/guide/fleet).
|
|
211
|
-
|
|
212
|
-
Two HTTP clients coexist on purpose: `src/lib/api.ts` (first-generation view pages) and
|
|
213
|
-
`src/lib/bq.ts` (the complete, shape-verified client used by every `Control ▸ *` page). See
|
|
214
|
-
[`docs/`](docs/README.md) for the full, source-verified reference.
|
|
215
|
-
|
|
216
|
-
## Configuration
|
|
217
|
-
|
|
218
|
-
The `VITE_*` dashboard defaults are build-time values and can **also** seed the first named profile
|
|
219
|
-
in **Settings**. Add up to 32 broker/agent profiles at runtime. The all-in-one server reads the non-`VITE_*` runtime variables below.
|
|
220
|
-
Copy [`.env.example`](.env.example) to `.env` to set defaults.
|
|
221
|
-
|
|
222
|
-
| Variable | Purpose | Default |
|
|
223
|
-
| --- | --- | --- |
|
|
224
|
-
| `VITE_BUNQUEUE_URL` | bunqueue server origin | `/api` (dev proxy → `:6790`) |
|
|
225
|
-
| `VITE_BUNQUEUE_AGENT_URL` | Control-agent origin | `http://localhost:6800` |
|
|
226
|
-
| `BIND_ADDR` | All-in-one dashboard bind address | `127.0.0.1` |
|
|
227
|
-
| `BASE_PATH` | Runtime mount for the all-in-one server (for example `/internal/queue`) | `/` |
|
|
228
|
-
| `BUNQUEUE_MANAGED` | `0` attaches to the externally supervised `BUNQUEUE_URL`; `1` enables local lifecycle controls | `1` |
|
|
229
|
-
| `AGENT_PORT` | Control-agent port | `6800` |
|
|
230
|
-
| `AGENT_ALLOWED_ORIGINS` | Extra browser origins allowed to drive the agent (comma-separated) | dev defaults |
|
|
231
|
-
| `AGENT_ALLOWED_HOSTS` | Extra Host names/IPs accepted by the all-in-one server and agent | loopback names |
|
|
232
|
-
| `AGENT_TOKEN` | Agent bearer token; mandatory on every bridged route for LAN/proxy access | _none_ (local-only) |
|
|
233
|
-
| `BUNQUEUE_TOKEN` | Admin-API bearer; mandatory on every `/api/*` route for LAN/proxy access | _none_ (`/api` remote-disabled) |
|
|
234
|
-
| `TRUST_PROXY` | Trust an overwritten `X-Forwarded-Host` from a Host-rewriting proxy (`1`) | _off_ |
|
|
235
|
-
|
|
236
|
-
Server and agent bearer tokens are secrets: enter them in **Settings** or the
|
|
237
|
-
authentication prompt for the current browser session. Do not put secrets in a
|
|
238
|
-
`VITE_*` variable; Vite values are compiled as plaintext into the public bundle.
|
|
239
|
-
|
|
240
|
-
For `BIND_ADDR=0.0.0.0`, list every LAN hostname/IP in
|
|
241
|
-
`AGENT_ALLOWED_HOSTS` (or its full origin in `AGENT_ALLOWED_ORIGINS`) and set
|
|
242
|
-
both `AGENT_TOKEN` and `BUNQUEUE_TOKEN`. Enter `BUNQUEUE_TOKEN` as the Server
|
|
243
|
-
token in Settings; when Bunqueue itself uses `AUTH_TOKENS`, use the same token
|
|
244
|
-
there so the forwarded Authorization header is valid upstream. Behind a reverse
|
|
245
|
-
proxy, allowlist the public origin; if it rewrites `Host`, also allowlist the
|
|
246
|
-
rewritten Host and use `TRUST_PROXY=1` only when the proxy overwrites
|
|
247
|
-
`X-Forwarded-Host`.
|
|
248
|
-
|
|
249
|
-
For a broker already owned by systemd, Docker or Kubernetes, run the all-in-one
|
|
250
|
-
dashboard with `BUNQUEUE_MANAGED=0`. **Control ▸ Server** then reports health
|
|
251
|
-
from `BUNQUEUE_URL` and cannot spawn, stop, restart or reconfigure a child.
|
|
252
|
-
Stopped-only backup restore is also rejected because the dashboard cannot prove
|
|
253
|
-
that an externally supervised broker is inactive.
|
|
254
|
-
To mount the same process below a reverse-proxy prefix, set for example
|
|
255
|
-
`BASE_PATH=/internal/queue` and preserve that prefix when proxying; the SPA,
|
|
256
|
-
assets, `/api` proxy and `/agent` bridge all move below it, including the
|
|
257
|
-
target-pinned Flow, Workflow, Queue and Backup agent operations.
|
|
258
|
-
|
|
259
|
-
## Scripts
|
|
260
|
-
|
|
261
|
-
| Command | Does |
|
|
58
|
+
| Area | Capabilities |
|
|
262
59
|
| --- | --- |
|
|
263
|
-
|
|
|
264
|
-
|
|
|
265
|
-
|
|
|
266
|
-
|
|
|
267
|
-
|
|
|
268
|
-
|
|
|
269
|
-
|
|
|
270
|
-
|
|
|
271
|
-
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
60
|
+
| Jobs | Submit single or bulk jobs, inspect data and results, change eligible jobs, and export the complete filtered inventory to CSV. |
|
|
61
|
+
| Discovery and failures | Search configured profiles by job metadata or correlation ID, save local views and compare observed error signatures. |
|
|
62
|
+
| Queues and schedules | Browse queue state, pause and resume processing, configure limits and groups, and manage cron or interval schedules. |
|
|
63
|
+
| Workflows and flows | Start and inspect durable executions, send signals, recover or compensate workflows, and explore dependency graphs. |
|
|
64
|
+
| Monitoring | Follow live events, read retained native queue history, inspect workers and diagnostics, and export Prometheus scrape configuration and alert rules. |
|
|
65
|
+
| Fleet | Save multiple broker/agent profiles and inspect shared PostgreSQL topology while keeping each node's lifecycle controls separate. |
|
|
66
|
+
| Server control | Start, stop and restart a managed Bunqueue process, edit its launch settings, and read its logs. |
|
|
67
|
+
| Data and backups | Browse SQLite tables, run read-only SQL, export rows, and create or restore official S3 snapshots. |
|
|
68
|
+
| MCP | Browse Bunqueue's tool catalog and configure external MCP clients. |
|
|
69
|
+
|
|
70
|
+
SQLite inspection and S3 snapshots apply to SQLite-backed servers. PostgreSQL
|
|
71
|
+
profiles use the queue and fleet controls.
|
|
72
|
+
|
|
73
|
+
Discovery reports scan limits and partial coverage. Metric history requires a
|
|
74
|
+
paired managed agent; retention belongs to Bunqueue. Native Webhooks delivers
|
|
75
|
+
job events, external Prometheus/Alertmanager evaluates exported alert rules,
|
|
76
|
+
and dashboard Alerts runs only while its browser tab is open. See
|
|
77
|
+
[native operations](https://egeominotti.github.io/bunqueue-dashboard/docs/guide/native-operations).
|
|
78
|
+
|
|
79
|
+
Job actions follow Bunqueue's safety contracts. Operations without atomic job
|
|
80
|
+
identity or flow-topology safeguards remain disabled, including DLQ retry,
|
|
81
|
+
completed-job requeue, cancel, drain and purge. See the
|
|
82
|
+
[verified limitations](https://egeominotti.github.io/bunqueue-dashboard/docs/known-issues).
|
|
83
|
+
|
|
84
|
+
An optional [Copilot](https://egeominotti.github.io/bunqueue-dashboard/docs/guide/copilot)
|
|
85
|
+
supports AI-assisted operations in custom builds with `VITE_ENABLE_COPILOT=1`.
|
|
86
|
+
It is disabled in the default build.
|
|
87
|
+
|
|
88
|
+
## Connect an existing server
|
|
89
|
+
|
|
90
|
+
When Docker, Kubernetes or systemd owns your Bunqueue process, launch the dashboard
|
|
91
|
+
in external-server mode:
|
|
285
92
|
|
|
286
93
|
```bash
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
# …or build locally, optionally baking in a default server origin
|
|
292
|
-
docker build --build-arg VITE_BUNQUEUE_URL=https://queue.example.com -t bunqueue-dashboard .
|
|
293
|
-
docker run --rm -p 8080:80 bunqueue-dashboard
|
|
94
|
+
BUNQUEUE_MANAGED=0 \
|
|
95
|
+
BUNQUEUE_URL=http://127.0.0.1:6790 \
|
|
96
|
+
bunx bunqueue-dashboard
|
|
294
97
|
```
|
|
295
98
|
|
|
296
|
-
|
|
99
|
+
This mode reports the server's health and keeps process management with its
|
|
100
|
+
existing supervisor. Stopped-only backup restore is also unavailable in this mode.
|
|
297
101
|
|
|
298
|
-
|
|
102
|
+
For an authenticated broker, set `BUNQUEUE_TOKEN` in the dashboard launch
|
|
103
|
+
environment and enter the matching server token in Settings. The launch value
|
|
104
|
+
also authenticates the agent's health probe. Pair an agent with the same broker
|
|
105
|
+
for SDK, workflow, database and backup operations. See
|
|
106
|
+
[connection and deployment options](https://egeominotti.github.io/bunqueue-dashboard/docs/deploy/).
|
|
299
107
|
|
|
300
|
-
|
|
301
|
-
ships self-contained binaries for **Linux (x64/arm64), macOS (x64/arm64) and Windows (x64)**:
|
|
302
|
-
download one file and run it, it serves the dashboard (assets embedded), proxies `/api` to your
|
|
303
|
-
bunqueue server (`BUNQUEUE_URL`, default `:6790`) and includes the control agent.
|
|
304
|
-
```bash
|
|
305
|
-
./bunqueue-dashboard-vX.Y.Z-darwin-arm64 # → http://localhost:8080
|
|
306
|
-
PORT=3000 BUNQUEUE_URL=https://queue.example.com ./bunqueue-dashboard-vX.Y.Z-linux-x64
|
|
307
|
-
```
|
|
308
|
-
- **GitHub Pages**, every push to `main` builds and publishes the static SPA
|
|
309
|
-
(`.github/workflows/pages.yml`). The workflow **attempts** to auto-provision Pages; if the first
|
|
310
|
-
run reports `Get Pages site failed`, enable it once under **Settings ▸ Pages ▸ Source → GitHub
|
|
311
|
-
Actions** (the default `GITHUB_TOKEN` can't create a Pages site on its own). The build sets the
|
|
312
|
-
correct sub-path base and a `404.html` SPA fallback automatically.
|
|
313
|
-
- **Container**, self-host the published `ghcr.io` image behind any reverse proxy.
|
|
314
|
-
- **Static host**, `bun run build` emits a plain `dist/` you can serve from any CDN or static host.
|
|
108
|
+
## Deployment options
|
|
315
109
|
|
|
316
|
-
|
|
317
|
-
|
|
110
|
+
| Option | What it runs | Requirements |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| `bunx bunqueue-dashboard` | UI, API proxy and control agent | Bun 1.4.4 |
|
|
113
|
+
| [Standalone executable](https://github.com/egeominotti/bunqueue-dashboard/releases/latest) | UI, API proxy and control agent in one binary | Linux or macOS x64/arm64, or Windows x64 |
|
|
114
|
+
| [Docker image](https://egeominotti.github.io/bunqueue-dashboard/docs/deploy/docker) | Static UI served by Caddy | An existing Bunqueue API and any paired agents |
|
|
115
|
+
| [Static hosting](https://egeominotti.github.io/bunqueue-dashboard/docs/deploy/platforms) | Built `dist/` directory | A reachable API or a configured same-origin proxy |
|
|
318
116
|
|
|
319
|
-
|
|
117
|
+
The standalone dashboard executable runs without an installed Bun runtime.
|
|
118
|
+
Starting a managed Bunqueue process still requires its launch command to be
|
|
119
|
+
available: install Bun for the default `bunx` command, or configure
|
|
120
|
+
`BUNQUEUE_START_CMD` to use a Bunqueue executable.
|
|
320
121
|
|
|
321
|
-
|
|
322
|
-
CI job; `quality` alone does not run Playwright:
|
|
122
|
+
To run the published static image locally:
|
|
323
123
|
|
|
324
124
|
```bash
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
bun run test:e2e:browser:postgres-fleet # cross-node UI; separate from both commands above
|
|
125
|
+
docker run --rm \
|
|
126
|
+
-p 127.0.0.1:8080:80 \
|
|
127
|
+
ghcr.io/egeominotti/bunqueue-dashboard:edge
|
|
329
128
|
```
|
|
330
129
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
on PostgreSQL schema 20. Run it alone with `bun run test:e2e` (Docker is required for the
|
|
336
|
-
PostgreSQL stage).
|
|
337
|
-
|
|
338
|
-
The blocking browser job builds the production bundle under `/e2e/dashboard`, starts an
|
|
339
|
-
authenticated disposable Bunqueue server, and drives the UI with Playwright on Chromium, Firefox,
|
|
340
|
-
and WebKit. It covers the token gate, every sidebar route, SSE recovery across a real server
|
|
341
|
-
restart, confirmed Cron creation/deletion, job submission and bulk import, persisted job inspection,
|
|
342
|
-
DLQ failures, live monitoring and alerts, SQLite queries, webhook registry mutations, a real
|
|
343
|
-
producer/worker benchmark, and automated WCAG A/AA checks. Install the browser
|
|
344
|
-
binaries once with `bun run test:e2e:browser:install`, then run `bun run test:e2e:browser`.
|
|
130
|
+
Open `http://127.0.0.1:8080` and set the browser-reachable server URL in Settings.
|
|
131
|
+
The `edge` image follows `main`. The
|
|
132
|
+
[Docker guide](https://egeominotti.github.io/bunqueue-dashboard/docs/deploy/docker)
|
|
133
|
+
explains API proxying, CORS and connecting a separately running agent.
|
|
345
134
|
|
|
346
|
-
|
|
347
|
-
prerequisites, and the distinction between live operations, navigation checks, and external
|
|
348
|
-
integrations that need their own environment. The live demo uses sample data and is not test evidence.
|
|
135
|
+
## Configuration
|
|
349
136
|
|
|
350
|
-
|
|
137
|
+
These variables configure the npm package and standalone executable:
|
|
351
138
|
|
|
352
|
-
|
|
139
|
+
| Variable | Purpose | Default |
|
|
140
|
+
| --- | --- | --- |
|
|
141
|
+
| `PORT` | Dashboard listener | `8080` |
|
|
142
|
+
| `BIND_ADDR` | Dashboard bind address | `127.0.0.1` |
|
|
143
|
+
| `BUNQUEUE_URL` | Bunqueue HTTP API to proxy | `http://localhost:6790` |
|
|
144
|
+
| `BUNQUEUE_MANAGED` | Enable local process management; use `0` for an external server | `1` |
|
|
145
|
+
| `AGENT_PORT` | Direct loopback control-agent port | `6800` |
|
|
146
|
+
| `BASE_PATH` | URL prefix for the UI, assets and API/agent bridges | `/` |
|
|
147
|
+
| `BUNQUEUE_DATA_PATH` | Managed server's SQLite database | `./data/bunq.db` |
|
|
148
|
+
| `AGENT_CONFIG_PATH` | Persistent agent configuration | `./.bunqueue-dashboard/config.json` |
|
|
149
|
+
|
|
150
|
+
For LAN or reverse-proxy access, configure `AGENT_TOKEN` and `BUNQUEUE_TOKEN`
|
|
151
|
+
together with an explicit host/origin allowlist through `AGENT_ALLOWED_HOSTS`
|
|
152
|
+
and `AGENT_ALLOWED_ORIGINS`. Enter the corresponding tokens in the dashboard's
|
|
153
|
+
authentication prompt or Settings. See
|
|
154
|
+
[the deployment security setup](https://egeominotti.github.io/bunqueue-dashboard/docs/deploy/pm2#configure-it)
|
|
155
|
+
for the complete proxy and token configuration.
|
|
156
|
+
|
|
157
|
+
Static builds use `VITE_BUNQUEUE_URL` and `VITE_BUNQUEUE_AGENT_URL` as initial
|
|
158
|
+
connection defaults; Settings can override them. `VITE_*` values are public bundle
|
|
159
|
+
content, so keep credentials out of them. More options, including PostgreSQL and
|
|
160
|
+
TLS, are documented in [`.env.example`](.env.example) and the
|
|
161
|
+
[agent reference](https://egeominotti.github.io/bunqueue-dashboard/docs/agent).
|
|
162
|
+
|
|
163
|
+
## Development
|
|
164
|
+
|
|
165
|
+
Use the Bun version in [`.bun-version`](.bun-version):
|
|
353
166
|
|
|
167
|
+
```bash
|
|
168
|
+
git clone https://github.com/egeominotti/bunqueue-dashboard.git
|
|
169
|
+
cd bunqueue-dashboard
|
|
170
|
+
bun install --frozen-lockfile
|
|
171
|
+
bun start
|
|
354
172
|
```
|
|
355
|
-
bunqueue-dashboard/
|
|
356
|
-
├── .github/workflows/ # CI, Pages deploy, Docker publish, Release
|
|
357
|
-
├── agent/ # guarded process, Flow, Workflow, Queue, database and backup adapters
|
|
358
|
-
├── docker/ # Caddyfile for the container image
|
|
359
|
-
├── docs/ # source-verified reference (architecture, pages, API mapping, known issues)
|
|
360
|
-
├── scripts/dev.ts # one-command dev launcher (`bun start`)
|
|
361
|
-
├── src/
|
|
362
|
-
│ ├── lib/ # api.ts, bq.ts, hooks, formatters, job-action gating
|
|
363
|
-
│ ├── components/ # layout shell, UI kit, Zustand stores
|
|
364
|
-
│ ├── features/ # feature slices with domain, application, infrastructure and UI layers
|
|
365
|
-
│ └── pages/ # view pages + Control ▸ * operator pages
|
|
366
|
-
└── test/ # unit, contract, UI race, agent lifecycle and integration tests
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
Full walkthrough in [`docs/README.md`](docs/README.md).
|
|
370
|
-
|
|
371
|
-
## Security
|
|
372
|
-
|
|
373
|
-
The control agent can spawn processes, so it is hardened by design (`agent/server.ts`):
|
|
374
173
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
- Requests carrying a **disallowed `Origin` are rejected (403)** before reaching the process manager, blocking drive-by CSRF from a malicious tab.
|
|
378
|
-
- A **Host allowlist** blocks DNS-rebinding requests and fails closed for unknown names.
|
|
379
|
-
- Truly local agent reads stay zero-configuration; any LAN/proxied `/agent` bridge requires **`AGENT_TOKEN` on every route**.
|
|
380
|
-
- The all-in-one server requires **`BUNQUEUE_TOKEN` on every LAN/proxied `/api/*` route**; without it the API proxy returns `403` before contacting Bunqueue.
|
|
381
|
-
- Embedded and Docker responses deny framing, MIME sniffing and referrer leakage.
|
|
174
|
+
The development dashboard runs at `http://localhost:5273`; the control agent runs
|
|
175
|
+
at `http://127.0.0.1:6800`. `Ctrl+C` stops both.
|
|
382
176
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
177
|
+
| Command | Purpose |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| `bun run build` | Typecheck all projects and build the production UI |
|
|
180
|
+
| `bun run check` | Native Bun typechecking, Oxlint and formatting checks |
|
|
181
|
+
| `bun run check:fix` | Apply lint fixes and format with native `bun fmt` |
|
|
182
|
+
| `bun run test:coverage` | Run unit tests and enforce coverage floors |
|
|
183
|
+
| `bun run test:e2e:browser` | Test the production UI in Chromium, Firefox and WebKit |
|
|
184
|
+
| `bun run docs:build` | Build the documentation and check its links |
|
|
185
|
+
| `bun run quality` | Run the static, unit, real-server, package and resilience gates |
|
|
186
|
+
|
|
187
|
+
Install browser binaries once with `bun run test:e2e:browser:install`. PostgreSQL
|
|
188
|
+
and S3 integration tests require Docker. CI also runs the managed browser suite
|
|
189
|
+
and executes native binaries on all five supported platforms.
|
|
190
|
+
|
|
191
|
+
For setup details and contribution guidance, see
|
|
192
|
+
[Development](https://egeominotti.github.io/bunqueue-dashboard/docs/development)
|
|
193
|
+
and [Testing](https://egeominotti.github.io/bunqueue-dashboard/docs/testing).
|
|
194
|
+
|
|
195
|
+
## Documentation
|
|
196
|
+
|
|
197
|
+
- [Quickstart](https://egeominotti.github.io/bunqueue-dashboard/docs/quickstart) — installation and first connection.
|
|
198
|
+
- [User guide](https://egeominotti.github.io/bunqueue-dashboard/docs/user-guide) — every screen and its operations.
|
|
199
|
+
- [Deployment](https://egeominotti.github.io/bunqueue-dashboard/docs/deploy/) — Docker, Kubernetes, PM2 and hosting platforms.
|
|
200
|
+
- [Architecture](https://egeominotti.github.io/bunqueue-dashboard/docs/architecture) — HTTP, SSE, agents and storage boundaries.
|
|
201
|
+
- [API mapping](https://egeominotti.github.io/bunqueue-dashboard/docs/api-mapping) — endpoints, payloads and action contracts.
|
|
202
|
+
- [Known limitations](https://egeominotti.github.io/bunqueue-dashboard/docs/known-issues) — verified upstream constraints and current behavior.
|
|
203
|
+
- [LLM documentation](https://egeominotti.github.io/bunqueue-dashboard/docs/llms.txt) — documentation index for AI tools, with a [full-text version](https://egeominotti.github.io/bunqueue-dashboard/docs/llms-full.txt).
|
|
394
204
|
|
|
395
205
|
## License
|
|
396
206
|
|
|
397
|
-
[MIT](LICENSE) © Egeo Minotti
|
|
207
|
+
[MIT](LICENSE) © Egeo Minotti.
|
|
208
|
+
|
|
209
|
+
The Bunqueue logo is the original asset from
|
|
210
|
+
[egeominotti/bunqueue](https://github.com/egeominotti/bunqueue/blob/main/docs/src/assets/logo.svg).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readLimitedJsonBody } from '../server/jsonBody';
|
|
2
2
|
|
|
3
3
|
const MAX_BODY_BYTES = 8 * 1024;
|
|
4
|
-
const
|
|
4
|
+
const MAX_METRIC_INDEX = 100_000;
|
|
5
5
|
const MAX_EVENT_RETENTION = 1_000_000;
|
|
6
6
|
|
|
7
7
|
export function exactQuery(url: string, allowed: readonly string[]): URLSearchParams {
|
|
@@ -65,10 +65,13 @@ export function positiveSafeInteger(value: unknown, label: string): number {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
export function metricsRange(query: URLSearchParams): { start: number; end: number } {
|
|
68
|
-
const start = optionalInteger(query.get('start'), 'start', 0,
|
|
68
|
+
const start = optionalInteger(query.get('start'), 'start', 0, MAX_METRIC_INDEX, 0);
|
|
69
69
|
const rawEnd = query.get('end');
|
|
70
|
-
const end = rawEnd === null ? -1 : boundedInteger(rawEnd, 'end', -1,
|
|
70
|
+
const end = rawEnd === null ? -1 : boundedInteger(rawEnd, 'end', -1, MAX_METRIC_INDEX);
|
|
71
71
|
if (end !== -1 && end < start) throw new Error('Metrics end must be -1 or at least start');
|
|
72
|
+
if (end !== -1 && end - start >= 10_080) {
|
|
73
|
+
throw new Error('Metrics page must contain at most 10080 buckets');
|
|
74
|
+
}
|
|
72
75
|
return { start, end };
|
|
73
76
|
}
|
|
74
77
|
|