@mayurbhusare/resilient-queue-ui 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +205 -3
- package/package.json +55 -4
- package/src/adapters/express.js +39 -0
- package/src/adapters/fastify.js +192 -0
- package/src/dispatcher.js +936 -0
- package/src/handlers/dlq.js +51 -0
- package/src/handlers/job.js +87 -0
- package/src/handlers/metrics.js +60 -0
- package/src/handlers/queues.js +58 -0
- package/src/index.js +44 -0
- package/src/registry.js +102 -0
- package/src/ui/api-client.js +228 -0
- package/src/ui/app.js +1303 -0
- package/src/ui/index.html +141 -0
- package/src/ui/styles.css +1197 -0
- package/src/ui/views/dlq.js +501 -0
- package/src/ui/views/job.js +549 -0
- package/src/ui/views/overview.js +670 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 resilient-queue
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,205 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @mayurbhusare/resilient-queue-ui
|
|
2
|
+
|
|
3
|
+
An administration dashboard and REST API adapter for [`@mayurbhusare/resilient-queue`](https://www.npmjs.com/package/@mayurbhusare/resilient-queue).
|
|
4
|
+
|
|
5
|
+
Provides real-time queue observability, metrics inspection, Dead-Letter Queue (DLQ) exploration, and deep job diagnostics with zero framework lock-in.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Real-Time Queue Telemetry**: Inspect waiting, active, delayed, and dead-letter job counts in real time via atomic queue metrics.
|
|
12
|
+
- **Dead-Letter Queue (DLQ) Explorer**: Browse and inspect failed jobs, complete with failure reasons, retry counts, stall metadata, and corrupted JSON resilience.
|
|
13
|
+
- **Deep Job Diagnostics**: Search and inspect individual job descriptors by ID, displaying execution progress (0–100%), timestamps, data payloads, and return values.
|
|
14
|
+
- **Framework-Agnostic Adapters**: Mount the dashboard seamlessly into existing **Express** or **Fastify** applications, or run it as a standalone monitoring server.
|
|
15
|
+
- **Distributed Safety**: Supports multi-queue configurations and distributed Redis deployments without requiring invasive hooks in worker processes.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Install `@mayurbhusare/resilient-queue-ui` alongside its peer dependency, `@mayurbhusare/resilient-queue`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# npm
|
|
25
|
+
npm install @mayurbhusare/resilient-queue-ui @mayurbhusare/resilient-queue
|
|
26
|
+
|
|
27
|
+
# yarn
|
|
28
|
+
yarn add @mayurbhusare/resilient-queue-ui @mayurbhusare/resilient-queue
|
|
29
|
+
|
|
30
|
+
# pnpm
|
|
31
|
+
pnpm add @mayurbhusare/resilient-queue-ui @mayurbhusare/resilient-queue
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Runtime & Peer Dependency Requirements
|
|
35
|
+
|
|
36
|
+
| Requirement | Supported Version Range |
|
|
37
|
+
| :--- | :--- |
|
|
38
|
+
| **Node.js** | `>=18.0.0` (LTS 18, 20, 22) |
|
|
39
|
+
| [`@mayurbhusare/resilient-queue`](https://www.npmjs.com/package/@mayurbhusare/resilient-queue) | `^2.2.0` |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Integration Examples
|
|
44
|
+
|
|
45
|
+
The package is designed with a decoupled architecture where core dashboard dispatchers are wrapped by lightweight framework adapters.
|
|
46
|
+
|
|
47
|
+
### 1. Express Integration
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const express = require("express");
|
|
51
|
+
const { ResilientQueue } = require("@mayurbhusare/resilient-queue");
|
|
52
|
+
const { createExpressAdapter } = require("@mayurbhusare/resilient-queue-ui");
|
|
53
|
+
|
|
54
|
+
const app = express();
|
|
55
|
+
|
|
56
|
+
const emailQueue = new ResilientQueue({ redisUrl: "redis://127.0.0.1:6379", keyPrefix: "rq:emails" });
|
|
57
|
+
const videoQueue = new ResilientQueue({ redisUrl: "redis://127.0.0.1:6379", keyPrefix: "rq:video" });
|
|
58
|
+
|
|
59
|
+
// Host application authentication (MANDATORY)
|
|
60
|
+
app.use("/admin/queues", authMiddleware);
|
|
61
|
+
|
|
62
|
+
// Mount dashboard UI & REST API
|
|
63
|
+
app.use(
|
|
64
|
+
"/admin/queues",
|
|
65
|
+
createExpressAdapter({
|
|
66
|
+
queues: {
|
|
67
|
+
"emails": emailQueue,
|
|
68
|
+
"video-transcoding": videoQueue
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
app.listen(3000, () => {
|
|
74
|
+
console.log("Admin dashboard active at http://localhost:3000/admin/queues");
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Fastify Integration
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
const fastify = require("fastify")();
|
|
82
|
+
const { ResilientQueue } = require("@mayurbhusare/resilient-queue");
|
|
83
|
+
const { fastifyDashboardPlugin } = require("@mayurbhusare/resilient-queue-ui");
|
|
84
|
+
|
|
85
|
+
const orderQueue = new ResilientQueue({ redisUrl: "redis://127.0.0.1:6379", keyPrefix: "rq:orders" });
|
|
86
|
+
|
|
87
|
+
// Register dashboard plugin under a prefix with auth hook
|
|
88
|
+
fastify.register(fastifyDashboardPlugin, {
|
|
89
|
+
prefix: "/admin/queues",
|
|
90
|
+
queues: {
|
|
91
|
+
"orders": orderQueue
|
|
92
|
+
},
|
|
93
|
+
preHandler: fastifyAuthHook
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
fastify.listen({ port: 3000 });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Dashboard UI & Frontend Architecture
|
|
102
|
+
|
|
103
|
+
The package includes a built-in, lightweight administration dashboard served directly by the Express and Fastify adapters:
|
|
104
|
+
|
|
105
|
+
- **Clean, Responsive Layout**: Includes a sidebar navigation area and responsive drawer for mobile devices.
|
|
106
|
+
- **Views**:
|
|
107
|
+
- **Queue Overview**: Consumes `GET /api/queues` with real-time aggregate telemetry cards, paused/active/closed/unavailable/error badges, client-side search and status filtering, multi-column sorting (with strict non-zero distinction for unavailable metrics), direct row-level navigation to DLQ Explorer and Job Inspector, and configurable background polling.
|
|
108
|
+
- **Dead Letter Queue (DLQ) Explorer**: Read-only browser for dead-letter jobs with pagination, sort order toggle, corrupted JSON resilience, failure diagnostics, payload inspection, and cross-linking to Job Inspector.
|
|
109
|
+
- **Job Inspector**: Comprehensive job inspection view supporting lookups by queue and job ID (`GET /api/queues/:queueName/jobs/:jobId`), displaying execution state badges, progress bar, lifecycle timestamps, failure diagnostics, input data payloads, and return values.
|
|
110
|
+
- **Operational Diagnostics**: Actionable, structured guidance for known API error codes (`QUEUE_CLOSED`, `QUEUE_NOT_FOUND`, `JOB_NOT_FOUND`, `NOT_SUPPORTED`, `VALIDATION_ERROR`, `INTERNAL_ERROR`), clearly distinguishing core library version compatibility disclosures from missing queues, closed queue instances, and server errors.
|
|
111
|
+
- **Robust UI States**: Handles loading spinners, empty states (with server configuration snippets), empty search filter states, and temporary sync failure banners with cached queue retention.
|
|
112
|
+
- **Accessibility & Keyboard Usability**:
|
|
113
|
+
- **Full Keyboard Operability**: Complete keyboard accessibility across search filters, sort headers, drawer navigation, and table actions with high-contrast `:focus-visible` focus rings.
|
|
114
|
+
- **Skip to Main Content**: Accessible skip link (`#content-container`) enables assistive technology users to bypass repetitive navigation.
|
|
115
|
+
- **Discrete Screen-Reader Announcements**: Dedicated polite ARIA live announcer (`#live-announcer`) provides non-intrusive status updates during navigation and metric refreshes without DOM chattering.
|
|
116
|
+
- **Color-Independent Status**: Badges pair semantic colors with distinct visual glyphs (`●`, `⏸`, `■`, `⚠️`, `—`) and explicit ARIA labels so status is never conveyed by color alone.
|
|
117
|
+
- **Mobile Drawer Usability**: Includes focus trapping inside the mobile navigation menu, backdrop tap-to-dismiss, and Escape key dismissal with focus restoration to the trigger button.
|
|
118
|
+
- **Focus & Cursor Preservation**: Preserves active focus, input text, and cursor selection ranges across background metric auto-refresh cycles.
|
|
119
|
+
- **Reduced Motion Support**: Honored via `@media (prefers-reduced-motion: reduce)` to freeze animated spinners and disable non-essential transitions.
|
|
120
|
+
- **Zero External Frontend Dependencies**: Pure semantic HTML5, modern CSS3 with CSS variables, and modular vanilla JavaScript.
|
|
121
|
+
|
|
122
|
+
### Reusable Frontend API Client
|
|
123
|
+
|
|
124
|
+
You can use the frontend API client directly in Node.js, bundlers, or the browser:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
const { ApiClient, ApiError } = require("@mayurbhusare/resilient-queue-ui");
|
|
128
|
+
|
|
129
|
+
const client = new ApiClient({
|
|
130
|
+
baseUrl: "http://localhost:3000/admin/queues"
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Fetch all registered queues and metrics
|
|
134
|
+
const { queues } = await client.getQueues();
|
|
135
|
+
|
|
136
|
+
// Fetch metrics for a specific queue
|
|
137
|
+
const metrics = await client.getQueueMetrics("emails");
|
|
138
|
+
|
|
139
|
+
// Fetch DLQ entries with pagination
|
|
140
|
+
const dlq = await client.getQueueDLQ("emails", { start: 0, limit: 25, order: "desc" });
|
|
141
|
+
|
|
142
|
+
// Fetch individual job descriptor
|
|
143
|
+
const { job } = await client.getJob("emails", "job-123");
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Core Compatibility & Version Support
|
|
147
|
+
|
|
148
|
+
> [!NOTE]
|
|
149
|
+
> - **Full Live Functionality (`>=2.2.0`)**: Real-time telemetry metrics, DLQ exploration, pause/resume detection, and deep job inspection via `queue.getJob(jobId)` are fully supported with [`@mayurbhusare/resilient-queue@>=2.2.0`](https://www.npmjs.com/package/@mayurbhusare/resilient-queue).
|
|
150
|
+
> - **Legacy Compatibility (`<2.2.0`)**: If paired with an earlier core release lacking `getJob()` (such as `v2.0.2`), the dashboard adapter gracefully returns `501 NOT_SUPPORTED` and the UI renders a compatibility notice without crashing, while all other dashboard features (Overview telemetry, DLQ Explorer, and HTTP adapters) remain fully operational.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Testing & Development
|
|
155
|
+
|
|
156
|
+
Run the complete test suite:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Run all tests (unit, integration, and frontend)
|
|
160
|
+
npm test
|
|
161
|
+
|
|
162
|
+
# Run frontend tests (ApiClient and UI views)
|
|
163
|
+
npm run test:frontend
|
|
164
|
+
|
|
165
|
+
# Run unit tests
|
|
166
|
+
npm run test:unit
|
|
167
|
+
|
|
168
|
+
# Run adapter integration tests
|
|
169
|
+
npm run test:integration
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Security Considerations
|
|
173
|
+
|
|
174
|
+
> [!CAUTION]
|
|
175
|
+
> **Host Authentication & Authorization Required**:
|
|
176
|
+
> Job payloads (`job.data`), execution results (`job.result`), and error diagnostics (`job.failureReason`) may contain sensitive user information, credentials, financial details, or internal system errors.
|
|
177
|
+
|
|
178
|
+
1. **No Built-in Authentication or Authorization**: The package provides zero built-in authentication, user models, or permission schemes. Host applications **must** guard both static UI routes and `/api/*` endpoints with authentication middleware.
|
|
179
|
+
2. **Apply Authentication Barriers**: Protect all dashboard routes behind your application's existing authentication stack (e.g., Express sessions/JWT, Fastify `preHandler` hooks, or reverse-proxy authentication such as OAuth2-Proxy / Cloudflare Access / AWS ALB).
|
|
180
|
+
3. **Queue-Level Authorization**: If different teams or tenants share the same service, apply queue-level authorization in host middleware to restrict specific queues by user role.
|
|
181
|
+
4. **CORS is NOT Authentication**: Permissive CORS headers (e.g. `Access-Control-Allow-Origin: *`) do **not** protect or authenticate endpoints. Never enable unauthenticated cross-origin access to dashboard routes.
|
|
182
|
+
5. **Caching Protection**: All API endpoints emit `Cache-Control: no-store` and `X-Content-Type-Options: nosniff` to prevent proxy or browser caching of sensitive job data.
|
|
183
|
+
6. **Credential Redaction**: Internal Redis and database connection URIs (e.g., `redis://user:pass@host:port`) and auth tokens in 500 internal error responses are automatically scrubbed and redacted to `[redacted]`.
|
|
184
|
+
7. **Never Expose Publicly**: Never mount `@mayurbhusare/resilient-queue-ui` routes on an unauthenticated, publicly accessible endpoint. Bind management processes to private VPC networks or loopback interfaces where possible.
|
|
185
|
+
|
|
186
|
+
For full architectural details, threat models, Express and Fastify integration patterns, and the production checklist, see the [Security Guide (docs/security.md)](./docs/security.md) and runnable [Examples (examples/)](./examples/README.md).
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## API Specification
|
|
191
|
+
|
|
192
|
+
The REST API contract powering the dashboard is detailed in [docs/api-contract.md](./docs/api-contract.md).
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Architecture & Portability
|
|
197
|
+
|
|
198
|
+
- **No Root Coupling**: This package maintains independent dependencies, configuration, and documentation, allowing seamless extraction into a dedicated repository.
|
|
199
|
+
- **Strict Public API Usage**: The package interacts with `@mayurbhusare/resilient-queue` exclusively through its published public interface (`import { ResilientQueue } from "@mayurbhusare/resilient-queue"`), avoiding any reliance on internal submodule paths.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
[MIT](./LICENSE) © 2026 Resilient Queue.
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mayurbhusare/resilient-queue-ui",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Standalone administration dashboard and API adapter for @mayurbhusare/resilient-queue",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js",
|
|
8
|
+
"./api-client": "./src/ui/api-client.js",
|
|
9
|
+
"./ui/*": "./src/ui/*",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18.0.0"
|
|
14
|
+
},
|
|
15
|
+
"type": "commonjs",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Mayur S. Bhusare",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"redis",
|
|
20
|
+
"queue",
|
|
21
|
+
"dashboard",
|
|
22
|
+
"admin",
|
|
23
|
+
"ui",
|
|
24
|
+
"monitoring",
|
|
25
|
+
"resilient-queue",
|
|
26
|
+
"job-queue"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/resilient-queue/resilient-queue-ui.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/resilient-queue/resilient-queue-ui/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/resilient-queue/resilient-queue-ui#readme",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"src"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "node examples/dev-server.js",
|
|
44
|
+
"test": "node --test",
|
|
45
|
+
"test:frontend": "node --test tests/unit/api-client.test.js tests/unit/ui-components.test.js",
|
|
46
|
+
"test:unit": "node --test tests/unit/*.test.js",
|
|
47
|
+
"test:integration": "node --test tests/integration/*.test.js",
|
|
48
|
+
"test:browser": "node --test tests/e2e/*.test.js",
|
|
49
|
+
"test:e2e": "node --test tests/e2e/*.test.js"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@mayurbhusare/resilient-queue": "^2.2.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@mayurbhusare/resilient-queue": "^2.2.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
createDashboardDispatcher,
|
|
5
|
+
createErrorResponse,
|
|
6
|
+
sanitizeErrorMessage,
|
|
7
|
+
sendJson
|
|
8
|
+
} = require("../dispatcher");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates an Express / Connect middleware adapter for the dashboard.
|
|
12
|
+
*
|
|
13
|
+
* @param {import("../dispatcher").DashboardAdapterOptions} options
|
|
14
|
+
* @returns {function(req: object, res: object, next: function): void}
|
|
15
|
+
*/
|
|
16
|
+
function createExpressAdapter(options) {
|
|
17
|
+
const dispatcher = createDashboardDispatcher(options);
|
|
18
|
+
|
|
19
|
+
return function expressAdapter(req, res, next) {
|
|
20
|
+
dispatcher.dispatch(req, res, next).catch((err) => {
|
|
21
|
+
if (typeof next === "function") {
|
|
22
|
+
next(err);
|
|
23
|
+
} else {
|
|
24
|
+
sendJson(
|
|
25
|
+
res,
|
|
26
|
+
500,
|
|
27
|
+
createErrorResponse(
|
|
28
|
+
"INTERNAL_ERROR",
|
|
29
|
+
sanitizeErrorMessage(err?.message)
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
createExpressAdapter
|
|
39
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
createDashboardDispatcher,
|
|
5
|
+
resolveUiAssetPath,
|
|
6
|
+
sendStaticFile,
|
|
7
|
+
sanitizeErrorMessage
|
|
8
|
+
} = require("../dispatcher");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Fastify plugin adapter for @mayurbhusare/resilient-queue-ui.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} fastify Fastify instance
|
|
14
|
+
* @param {object} options Plugin options
|
|
15
|
+
* @param {function} [done] Callback invoked when plugin registration completes
|
|
16
|
+
*/
|
|
17
|
+
function fastifyDashboardPlugin(fastify, options = {}, done) {
|
|
18
|
+
const dispatcher = createDashboardDispatcher(options);
|
|
19
|
+
|
|
20
|
+
const routeOptions = {};
|
|
21
|
+
if (
|
|
22
|
+
options &&
|
|
23
|
+
(typeof options.preHandler === "function" ||
|
|
24
|
+
Array.isArray(options.preHandler))
|
|
25
|
+
) {
|
|
26
|
+
routeOptions.preHandler = options.preHandler;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fastify.get("/api/queues", routeOptions, async (request, reply) => {
|
|
30
|
+
try {
|
|
31
|
+
const result = await dispatcher.executeQueuesRoute();
|
|
32
|
+
reply
|
|
33
|
+
.header("Cache-Control", "no-store")
|
|
34
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
35
|
+
.code(result.status)
|
|
36
|
+
.send(result.body);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
reply
|
|
39
|
+
.header("Cache-Control", "no-store")
|
|
40
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
41
|
+
.code(500)
|
|
42
|
+
.send({
|
|
43
|
+
error: {
|
|
44
|
+
code: "INTERNAL_ERROR",
|
|
45
|
+
message: sanitizeErrorMessage(err?.message || "Internal server error")
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
fastify.get(
|
|
52
|
+
"/api/queues/:queueName/metrics",
|
|
53
|
+
routeOptions,
|
|
54
|
+
async (request, reply) => {
|
|
55
|
+
try {
|
|
56
|
+
const rawQueueName = request?.params?.queueName;
|
|
57
|
+
const result = await dispatcher.executeMetricsRoute(rawQueueName);
|
|
58
|
+
reply
|
|
59
|
+
.header("Cache-Control", "no-store")
|
|
60
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
61
|
+
.code(result.status)
|
|
62
|
+
.send(result.body);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
reply
|
|
65
|
+
.header("Cache-Control", "no-store")
|
|
66
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
67
|
+
.code(500)
|
|
68
|
+
.send({
|
|
69
|
+
error: {
|
|
70
|
+
code: "INTERNAL_ERROR",
|
|
71
|
+
message: sanitizeErrorMessage(err?.message || "Internal server error")
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
fastify.get(
|
|
79
|
+
"/api/queues/:queueName/dlq",
|
|
80
|
+
routeOptions,
|
|
81
|
+
async (request, reply) => {
|
|
82
|
+
try {
|
|
83
|
+
const rawQueueName = request?.params?.queueName;
|
|
84
|
+
const query = request?.query || {};
|
|
85
|
+
const result = await dispatcher.executeDLQRoute(rawQueueName, query);
|
|
86
|
+
reply
|
|
87
|
+
.header("Cache-Control", "no-store")
|
|
88
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
89
|
+
.code(result.status)
|
|
90
|
+
.send(result.body);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
reply
|
|
93
|
+
.header("Cache-Control", "no-store")
|
|
94
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
95
|
+
.code(500)
|
|
96
|
+
.send({
|
|
97
|
+
error: {
|
|
98
|
+
code: "INTERNAL_ERROR",
|
|
99
|
+
message: sanitizeErrorMessage(err?.message || "Internal server error")
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
fastify.get(
|
|
107
|
+
"/api/queues/:queueName/jobs/:jobId",
|
|
108
|
+
routeOptions,
|
|
109
|
+
async (request, reply) => {
|
|
110
|
+
try {
|
|
111
|
+
const rawQueueName = request?.params?.queueName;
|
|
112
|
+
const rawJobId = request?.params?.jobId;
|
|
113
|
+
const result = await dispatcher.executeJobRoute(rawQueueName, rawJobId);
|
|
114
|
+
reply
|
|
115
|
+
.header("Cache-Control", "no-store")
|
|
116
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
117
|
+
.code(result.status)
|
|
118
|
+
.send(result.body);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
reply
|
|
121
|
+
.header("Cache-Control", "no-store")
|
|
122
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
123
|
+
.code(500)
|
|
124
|
+
.send({
|
|
125
|
+
error: {
|
|
126
|
+
code: "INTERNAL_ERROR",
|
|
127
|
+
message: sanitizeErrorMessage(err?.message || "Internal server error")
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const uiRoutes = [
|
|
135
|
+
"/",
|
|
136
|
+
"/index.html",
|
|
137
|
+
"/styles.css",
|
|
138
|
+
"/api-client.js",
|
|
139
|
+
"/app.js",
|
|
140
|
+
"/views/overview.js",
|
|
141
|
+
"/views/dlq.js",
|
|
142
|
+
"/views/job.js",
|
|
143
|
+
"/ui",
|
|
144
|
+
"/ui/",
|
|
145
|
+
"/ui/index.html",
|
|
146
|
+
"/ui/styles.css",
|
|
147
|
+
"/ui/api-client.js",
|
|
148
|
+
"/ui/app.js",
|
|
149
|
+
"/ui/views/overview.js",
|
|
150
|
+
"/ui/views/dlq.js",
|
|
151
|
+
"/ui/views/job.js"
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
for (const route of uiRoutes) {
|
|
155
|
+
fastify.get(route, routeOptions, async (request, reply) => {
|
|
156
|
+
try {
|
|
157
|
+
const asset = resolveUiAssetPath(route);
|
|
158
|
+
if (asset) {
|
|
159
|
+
sendStaticFile(reply, asset.fullPath, asset.contentType);
|
|
160
|
+
} else {
|
|
161
|
+
reply
|
|
162
|
+
.header("Cache-Control", "no-store")
|
|
163
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
164
|
+
.code(404)
|
|
165
|
+
.send({ error: { code: "ROUTE_NOT_FOUND", message: "Route not found" } });
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
reply
|
|
169
|
+
.header("Cache-Control", "no-store")
|
|
170
|
+
.header("X-Content-Type-Options", "nosniff")
|
|
171
|
+
.code(500)
|
|
172
|
+
.send({
|
|
173
|
+
error: {
|
|
174
|
+
code: "INTERNAL_ERROR",
|
|
175
|
+
message: sanitizeErrorMessage(err?.message || "Internal server error")
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (typeof done === "function") {
|
|
183
|
+
done();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return Promise.resolve();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
fastifyDashboardPlugin
|
|
192
|
+
};
|