worker-que 1.0.0 → 1.0.1
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/CONTRIBUTING.md +337 -0
- package/DASHBOARD.md +254 -252
- package/README.md +362 -261
- package/package.json +2 -3
- package/DASHBOARD-QUICKSTART.md +0 -278
- package/SSL-QUICK-REFERENCE.md +0 -225
package/DASHBOARD.md
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
# Dashboard
|
|
1
|
+
# Dashboard Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Complete guide for the worker-que web dashboard - a real-time monitoring and management interface for your job queue.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Table of Contents
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- ⚡ **Auto-refresh** - Configurable real-time updates
|
|
7
|
+
- [Quick Start](#quick-start)
|
|
8
|
+
- [Configuration](#configuration)
|
|
9
|
+
- [Authentication](#authentication)
|
|
10
|
+
- [API Reference](#api-reference)
|
|
11
|
+
- [Troubleshooting](#troubleshooting)
|
|
12
|
+
- [Production Deployment](#production-deployment)
|
|
14
13
|
|
|
15
14
|
## Quick Start
|
|
16
15
|
|
|
17
16
|
### Installation
|
|
18
17
|
|
|
19
|
-
The dashboard requires Express.js:
|
|
20
|
-
|
|
21
18
|
```bash
|
|
22
|
-
npm install express
|
|
23
|
-
npm install --save-dev @types/express # If using TypeScript
|
|
19
|
+
npm install worker-que express pg
|
|
24
20
|
```
|
|
25
21
|
|
|
26
22
|
### Basic Setup
|
|
@@ -28,7 +24,7 @@ npm install --save-dev @types/express # If using TypeScript
|
|
|
28
24
|
```typescript
|
|
29
25
|
import express from 'express';
|
|
30
26
|
import { Pool } from 'pg';
|
|
31
|
-
import { createDashboard } from 'que
|
|
27
|
+
import { createDashboard } from 'worker-que/dist/dashboard';
|
|
32
28
|
|
|
33
29
|
const app = express();
|
|
34
30
|
const pool = new Pool({
|
|
@@ -39,7 +35,7 @@ const pool = new Pool({
|
|
|
39
35
|
password: 'password',
|
|
40
36
|
});
|
|
41
37
|
|
|
42
|
-
// Mount dashboard
|
|
38
|
+
// Mount dashboard
|
|
43
39
|
app.use('/admin/queue', createDashboard(pool));
|
|
44
40
|
|
|
45
41
|
app.listen(3000, () => {
|
|
@@ -47,94 +43,122 @@ app.listen(3000, () => {
|
|
|
47
43
|
});
|
|
48
44
|
```
|
|
49
45
|
|
|
50
|
-
That's it! Visit http://localhost:3000/admin/queue to see your dashboard.
|
|
46
|
+
That's it! Visit `http://localhost:3000/admin/queue` to see your dashboard.
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
### Real-time Statistics
|
|
51
|
+
|
|
52
|
+
- **Total Jobs** - All jobs in the queue
|
|
53
|
+
- **Ready** - Jobs ready to process
|
|
54
|
+
- **Scheduled** - Jobs scheduled for future
|
|
55
|
+
- **Failed** - Jobs that have errored
|
|
56
|
+
|
|
57
|
+
### Visual Analytics
|
|
58
|
+
|
|
59
|
+
- **Jobs by Queue** - Bar chart showing distribution across queues
|
|
60
|
+
- **Jobs by Class** - Bar chart showing distribution by job type
|
|
61
|
+
|
|
62
|
+
### Job Management
|
|
63
|
+
|
|
64
|
+
- **Filter** - By status, queue, and job class
|
|
65
|
+
- **Search** - Find specific jobs
|
|
66
|
+
- **Pagination** - Browse through large job lists
|
|
67
|
+
- **Retry** - Restart failed jobs
|
|
68
|
+
- **Delete** - Remove jobs from queue
|
|
69
|
+
|
|
70
|
+
### Recent Failures
|
|
71
|
+
|
|
72
|
+
View the most recent failed jobs with:
|
|
73
|
+
- Error messages
|
|
74
|
+
- Error counts
|
|
75
|
+
- Quick retry/delete actions
|
|
51
76
|
|
|
52
|
-
## Configuration
|
|
77
|
+
## Configuration
|
|
53
78
|
|
|
54
|
-
###
|
|
79
|
+
### Dashboard Options
|
|
55
80
|
|
|
56
81
|
```typescript
|
|
57
82
|
interface DashboardOptions {
|
|
58
|
-
// Dashboard title (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Auto-refresh interval in milliseconds
|
|
65
|
-
refreshInterval?: number; // Default: 5000 (5 seconds)
|
|
66
|
-
|
|
67
|
-
// Maximum number of recent failures to show
|
|
68
|
-
maxRecentFailures?: number; // Default: 50
|
|
69
|
-
|
|
70
|
-
// Authentication function
|
|
71
|
-
auth?: (req, res, next) => boolean | Promise<boolean>;
|
|
83
|
+
title?: string; // Dashboard title (default: 'Que Dashboard')
|
|
84
|
+
basePath?: string; // Base path for API routes (default: '/que')
|
|
85
|
+
refreshInterval?: number; // Auto-refresh interval in ms (default: 5000)
|
|
86
|
+
maxRecentFailures?: number; // Max failures to show (default: 50)
|
|
87
|
+
auth?: (req, res, next) => boolean | Promise<boolean>; // Auth function
|
|
72
88
|
}
|
|
73
89
|
```
|
|
74
90
|
|
|
75
91
|
### Examples
|
|
76
92
|
|
|
77
|
-
#### Custom Title and Refresh
|
|
93
|
+
#### Custom Title and Refresh
|
|
78
94
|
|
|
79
95
|
```typescript
|
|
80
96
|
app.use('/queue', createDashboard(pool, {
|
|
81
97
|
title: 'Production Job Queue',
|
|
98
|
+
basePath: '/queue',
|
|
82
99
|
refreshInterval: 2000, // Refresh every 2 seconds
|
|
83
100
|
}));
|
|
84
101
|
```
|
|
85
102
|
|
|
86
|
-
####
|
|
103
|
+
#### Different Mount Path
|
|
87
104
|
|
|
88
105
|
```typescript
|
|
89
106
|
app.use('/jobs', createDashboard(pool, {
|
|
90
|
-
basePath: '/jobs', //
|
|
107
|
+
basePath: '/jobs', // Must match mount path
|
|
91
108
|
}));
|
|
109
|
+
// Visit http://localhost:3000/jobs
|
|
92
110
|
```
|
|
93
111
|
|
|
94
112
|
## Authentication
|
|
95
113
|
|
|
96
|
-
|
|
114
|
+
The dashboard supports flexible authentication through the `auth` function.
|
|
115
|
+
|
|
116
|
+
### API Key Authentication
|
|
97
117
|
|
|
98
118
|
```typescript
|
|
99
119
|
app.use('/admin/queue', createDashboard(pool, {
|
|
100
|
-
auth: (req
|
|
101
|
-
|
|
102
|
-
return apiKey === 'your-secret-api-key';
|
|
120
|
+
auth: (req) => {
|
|
121
|
+
return req.headers['x-api-key'] === process.env.DASHBOARD_API_KEY;
|
|
103
122
|
}
|
|
104
123
|
}));
|
|
105
124
|
```
|
|
106
125
|
|
|
107
|
-
|
|
126
|
+
**Usage:**
|
|
127
|
+
```bash
|
|
128
|
+
curl -H "X-API-Key: your-secret-key" http://localhost:3000/admin/queue/api/stats
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Session-Based Authentication
|
|
108
132
|
|
|
109
133
|
```typescript
|
|
110
134
|
import session from 'express-session';
|
|
111
135
|
|
|
112
136
|
app.use(session({
|
|
113
|
-
secret:
|
|
137
|
+
secret: process.env.SESSION_SECRET,
|
|
114
138
|
resave: false,
|
|
115
139
|
saveUninitialized: false,
|
|
116
140
|
}));
|
|
117
141
|
|
|
118
142
|
app.use('/admin/queue', createDashboard(pool, {
|
|
119
|
-
auth: (req
|
|
120
|
-
|
|
121
|
-
return req.session?.user?.role === 'admin';
|
|
143
|
+
auth: (req) => {
|
|
144
|
+
return req.session?.user?.isAdmin === true;
|
|
122
145
|
}
|
|
123
146
|
}));
|
|
124
147
|
```
|
|
125
148
|
|
|
126
|
-
###
|
|
149
|
+
### JWT Authentication
|
|
127
150
|
|
|
128
151
|
```typescript
|
|
152
|
+
import jwt from 'jsonwebtoken';
|
|
153
|
+
|
|
129
154
|
app.use('/admin/queue', createDashboard(pool, {
|
|
130
|
-
auth:
|
|
131
|
-
const token = req.headers['authorization']?.split(' ')[1];
|
|
132
|
-
|
|
133
|
-
if (!token) return false;
|
|
134
|
-
|
|
155
|
+
auth: (req) => {
|
|
135
156
|
try {
|
|
136
|
-
const
|
|
137
|
-
|
|
157
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
158
|
+
if (!token) return false;
|
|
159
|
+
|
|
160
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
161
|
+
return decoded.role === 'admin';
|
|
138
162
|
} catch {
|
|
139
163
|
return false;
|
|
140
164
|
}
|
|
@@ -142,27 +166,21 @@ app.use('/admin/queue', createDashboard(pool, {
|
|
|
142
166
|
}));
|
|
143
167
|
```
|
|
144
168
|
|
|
145
|
-
###
|
|
169
|
+
### Async Database Authentication
|
|
146
170
|
|
|
147
171
|
```typescript
|
|
148
|
-
import jwt from 'jsonwebtoken';
|
|
149
|
-
|
|
150
172
|
app.use('/admin/queue', createDashboard(pool, {
|
|
151
|
-
auth: (req
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return decoded.role === 'admin';
|
|
156
|
-
} catch {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
173
|
+
auth: async (req) => {
|
|
174
|
+
const token = req.headers.authorization;
|
|
175
|
+
const user = await verifyUserToken(token);
|
|
176
|
+
return user?.hasPermission('view-queue');
|
|
159
177
|
}
|
|
160
178
|
}));
|
|
161
179
|
```
|
|
162
180
|
|
|
163
|
-
## API
|
|
181
|
+
## API Reference
|
|
164
182
|
|
|
165
|
-
The dashboard exposes
|
|
183
|
+
The dashboard exposes REST API endpoints for programmatic access.
|
|
166
184
|
|
|
167
185
|
### GET /api/stats
|
|
168
186
|
|
|
@@ -193,18 +211,18 @@ Get queue statistics.
|
|
|
193
211
|
|
|
194
212
|
### GET /api/jobs
|
|
195
213
|
|
|
196
|
-
Get paginated list of jobs with
|
|
214
|
+
Get paginated list of jobs with filters.
|
|
197
215
|
|
|
198
216
|
**Query Parameters:**
|
|
199
|
-
- `status
|
|
200
|
-
- `queue
|
|
201
|
-
- `jobClass
|
|
202
|
-
- `limit
|
|
203
|
-
- `offset
|
|
217
|
+
- `status` - Filter by status (`all`, `ready`, `scheduled`, `failed`)
|
|
218
|
+
- `queue` - Filter by queue name
|
|
219
|
+
- `jobClass` - Filter by job class
|
|
220
|
+
- `limit` - Results per page (default: 50)
|
|
221
|
+
- `offset` - Pagination offset (default: 0)
|
|
204
222
|
|
|
205
223
|
**Example:**
|
|
206
224
|
```
|
|
207
|
-
GET /api/jobs?status=failed&queue=critical&limit=20
|
|
225
|
+
GET /api/jobs?status=failed&queue=critical&limit=20
|
|
208
226
|
```
|
|
209
227
|
|
|
210
228
|
**Response:**
|
|
@@ -217,7 +235,7 @@ GET /api/jobs?status=failed&queue=critical&limit=20&offset=0
|
|
|
217
235
|
"priority": 10,
|
|
218
236
|
"runAt": "2024-01-15T14:30:00Z",
|
|
219
237
|
"jobClass": "ProcessPayment",
|
|
220
|
-
"args": [{ "amount": 100
|
|
238
|
+
"args": [{ "amount": 100 }],
|
|
221
239
|
"errorCount": 3,
|
|
222
240
|
"lastError": "Payment gateway timeout"
|
|
223
241
|
}
|
|
@@ -282,16 +300,16 @@ Get list of all job class names.
|
|
|
282
300
|
|
|
283
301
|
**Response:**
|
|
284
302
|
```json
|
|
285
|
-
["SendEmail", "ProcessPayment", "GenerateReport"
|
|
303
|
+
["SendEmail", "ProcessPayment", "GenerateReport"]
|
|
286
304
|
```
|
|
287
305
|
|
|
288
306
|
## Programmatic Usage
|
|
289
307
|
|
|
290
|
-
You can
|
|
308
|
+
You can use the dashboard service directly in your code:
|
|
291
309
|
|
|
292
310
|
```typescript
|
|
293
311
|
import { Pool } from 'pg';
|
|
294
|
-
import { DashboardService } from 'que
|
|
312
|
+
import { DashboardService } from 'worker-que/dist/dashboard';
|
|
295
313
|
|
|
296
314
|
const pool = new Pool({ /* config */ });
|
|
297
315
|
const dashboard = new DashboardService(pool);
|
|
@@ -299,7 +317,6 @@ const dashboard = new DashboardService(pool);
|
|
|
299
317
|
// Get statistics
|
|
300
318
|
const stats = await dashboard.getStats();
|
|
301
319
|
console.log(`Total jobs: ${stats.total}`);
|
|
302
|
-
console.log(`Failed jobs: ${stats.failed}`);
|
|
303
320
|
|
|
304
321
|
// Get jobs with filters
|
|
305
322
|
const { jobs, total } = await dashboard.getJobs({
|
|
@@ -321,236 +338,221 @@ const queues = await dashboard.getQueues();
|
|
|
321
338
|
const jobClasses = await dashboard.getJobClasses();
|
|
322
339
|
```
|
|
323
340
|
|
|
324
|
-
##
|
|
341
|
+
## Troubleshooting
|
|
325
342
|
|
|
326
|
-
###
|
|
343
|
+
### Error: "Cannot GET /que/api/jobs"
|
|
327
344
|
|
|
328
|
-
|
|
329
|
-
import express from 'express';
|
|
330
|
-
import { Pool } from 'pg';
|
|
331
|
-
import { createDashboard } from 'que-ts/dashboard';
|
|
345
|
+
**Problem:** Dashboard routes not mounted.
|
|
332
346
|
|
|
333
|
-
|
|
334
|
-
const pool = new Pool({ /* config */ });
|
|
335
|
-
|
|
336
|
-
// Your existing routes
|
|
337
|
-
app.get('/', (req, res) => {
|
|
338
|
-
res.send('Home page');
|
|
339
|
-
});
|
|
347
|
+
**Solution:** Make sure you mount the dashboard with `app.use()`:
|
|
340
348
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
349
|
+
```typescript
|
|
350
|
+
import { createDashboard } from 'worker-que/dist/dashboard';
|
|
344
351
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
title: 'My App Queue',
|
|
348
|
-
auth: (req) => req.session?.isAdmin,
|
|
352
|
+
app.use('/que', createDashboard(pool, {
|
|
353
|
+
basePath: '/que', // Must match mount path
|
|
349
354
|
}));
|
|
350
|
-
|
|
351
|
-
app.listen(3000);
|
|
352
355
|
```
|
|
353
356
|
|
|
354
|
-
###
|
|
357
|
+
### Dashboard Shows No Data
|
|
355
358
|
|
|
356
|
-
|
|
357
|
-
import express from 'express';
|
|
358
|
-
import { Pool } from 'pg';
|
|
359
|
-
import { createDashboard } from 'que-ts/dashboard';
|
|
360
|
-
import { Worker } from 'que-ts';
|
|
359
|
+
**Problem:** Database table doesn't exist or is empty.
|
|
361
360
|
|
|
362
|
-
|
|
363
|
-
|
|
361
|
+
**Solution:**
|
|
362
|
+
1. Create the `que_jobs` table using the schema
|
|
363
|
+
2. Check database connection
|
|
364
|
+
3. Verify table has jobs: `SELECT COUNT(*) FROM que_jobs;`
|
|
364
365
|
|
|
365
|
-
|
|
366
|
-
app.use('/queue', createDashboard(pool));
|
|
366
|
+
### Authentication Not Working
|
|
367
367
|
|
|
368
|
-
|
|
369
|
-
const criticalWorker = new Worker(
|
|
370
|
-
{ /* db config */ },
|
|
371
|
-
{ queue: 'critical', interval: 100 }
|
|
372
|
-
);
|
|
368
|
+
**Problem:** Auth function not returning correct value.
|
|
373
369
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
370
|
+
**Solution:**
|
|
371
|
+
- Make sure auth function returns `true` or `false`
|
|
372
|
+
- For async auth, declare function as `async`
|
|
373
|
+
- Check browser console for 403 errors
|
|
378
374
|
|
|
379
|
-
|
|
380
|
-
criticalWorker.register('ProcessPayment', handlePayment);
|
|
381
|
-
backgroundWorker.register('GenerateReport', handleReport);
|
|
375
|
+
### Slow Dashboard Performance
|
|
382
376
|
|
|
383
|
-
|
|
384
|
-
backgroundWorker.work();
|
|
377
|
+
**Solutions:**
|
|
385
378
|
|
|
386
|
-
|
|
379
|
+
1. Add database indexes:
|
|
380
|
+
```sql
|
|
381
|
+
CREATE INDEX idx_que_jobs_run_at ON que_jobs(run_at);
|
|
382
|
+
CREATE INDEX idx_que_jobs_error_count ON que_jobs(error_count);
|
|
383
|
+
CREATE INDEX idx_que_jobs_queue ON que_jobs(queue);
|
|
384
|
+
CREATE INDEX idx_que_jobs_job_class ON que_jobs(job_class);
|
|
387
385
|
```
|
|
388
386
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
If running behind a proxy, make sure to set the correct `basePath`:
|
|
392
|
-
|
|
387
|
+
2. Increase refresh interval:
|
|
393
388
|
```typescript
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
// proxy_pass http://localhost:3000/queue;
|
|
397
|
-
// }
|
|
389
|
+
createDashboard(pool, { refreshInterval: 10000 })
|
|
390
|
+
```
|
|
398
391
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
})
|
|
392
|
+
3. Reduce max failures shown:
|
|
393
|
+
```typescript
|
|
394
|
+
createDashboard(pool, { maxRecentFailures: 25 })
|
|
402
395
|
```
|
|
403
396
|
|
|
404
|
-
## Deployment
|
|
397
|
+
## Production Deployment
|
|
405
398
|
|
|
406
|
-
###
|
|
399
|
+
### Environment Variables
|
|
407
400
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
-
|
|
414
|
-
|
|
401
|
+
```bash
|
|
402
|
+
# Dashboard
|
|
403
|
+
DASHBOARD_PATH=/admin/queue
|
|
404
|
+
DASHBOARD_TITLE=Production Queue
|
|
405
|
+
DASHBOARD_REFRESH_MS=5000
|
|
406
|
+
DASHBOARD_API_KEY=your-secret-key
|
|
407
|
+
|
|
408
|
+
# Database
|
|
409
|
+
DB_HOST=postgres
|
|
410
|
+
DB_PORT=5432
|
|
411
|
+
DB_NAME=myapp
|
|
412
|
+
DB_USER=postgres
|
|
413
|
+
DB_PASSWORD=secret
|
|
414
|
+
```
|
|
415
415
|
|
|
416
|
-
###
|
|
416
|
+
### Secure Configuration
|
|
417
417
|
|
|
418
418
|
```typescript
|
|
419
|
-
|
|
419
|
+
require('dotenv').config();
|
|
420
420
|
|
|
421
421
|
const pool = new Pool({
|
|
422
|
-
|
|
422
|
+
host: process.env.DB_HOST,
|
|
423
|
+
port: parseInt(process.env.DB_PORT),
|
|
424
|
+
database: process.env.DB_NAME,
|
|
425
|
+
user: process.env.DB_USER,
|
|
426
|
+
password: process.env.DB_PASSWORD,
|
|
423
427
|
ssl: process.env.NODE_ENV === 'production',
|
|
424
428
|
});
|
|
425
429
|
|
|
426
|
-
app.use(process.env.DASHBOARD_PATH
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
430
|
+
app.use(process.env.DASHBOARD_PATH, createDashboard(pool, {
|
|
431
|
+
title: process.env.DASHBOARD_TITLE,
|
|
432
|
+
basePath: process.env.DASHBOARD_PATH,
|
|
433
|
+
refreshInterval: parseInt(process.env.DASHBOARD_REFRESH_MS),
|
|
434
|
+
auth: (req) => {
|
|
435
|
+
return req.headers['x-api-key'] === process.env.DASHBOARD_API_KEY;
|
|
436
|
+
},
|
|
437
|
+
}));
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Behind a Reverse Proxy
|
|
441
|
+
|
|
442
|
+
If running behind nginx or similar:
|
|
443
|
+
|
|
444
|
+
```nginx
|
|
445
|
+
# nginx.conf
|
|
446
|
+
location /admin/queue {
|
|
447
|
+
proxy_pass http://localhost:3000/admin/queue;
|
|
448
|
+
proxy_http_version 1.1;
|
|
449
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
450
|
+
proxy_set_header Connection 'upgrade';
|
|
451
|
+
proxy_set_header Host $host;
|
|
452
|
+
proxy_cache_bypass $http_upgrade;
|
|
453
|
+
}
|
|
436
454
|
```
|
|
437
455
|
|
|
438
|
-
### Docker
|
|
456
|
+
### Docker Deployment
|
|
439
457
|
|
|
440
458
|
```dockerfile
|
|
441
459
|
FROM node:18-alpine
|
|
442
|
-
|
|
443
460
|
WORKDIR /app
|
|
444
461
|
COPY package*.json ./
|
|
445
462
|
RUN npm ci --only=production
|
|
446
|
-
|
|
447
463
|
COPY . .
|
|
448
|
-
|
|
449
464
|
ENV NODE_ENV=production
|
|
450
|
-
ENV
|
|
451
|
-
ENV DASHBOARD_REFRESH_MS=5000
|
|
452
|
-
|
|
465
|
+
ENV PORT=3000
|
|
453
466
|
EXPOSE 3000
|
|
454
|
-
|
|
455
467
|
CMD ["node", "dist/server.js"]
|
|
456
468
|
```
|
|
457
469
|
|
|
458
|
-
|
|
470
|
+
```bash
|
|
471
|
+
docker build -t queue-dashboard .
|
|
472
|
+
docker run -p 3000:3000 \
|
|
473
|
+
-e DB_HOST=postgres \
|
|
474
|
+
-e DB_NAME=myapp \
|
|
475
|
+
-e DASHBOARD_API_KEY=secret \
|
|
476
|
+
queue-dashboard
|
|
477
|
+
```
|
|
459
478
|
|
|
460
|
-
###
|
|
479
|
+
### Security Checklist
|
|
461
480
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
481
|
+
- [ ] Enable authentication
|
|
482
|
+
- [ ] Use HTTPS in production
|
|
483
|
+
- [ ] Restrict network access
|
|
484
|
+
- [ ] Use environment variables for secrets
|
|
485
|
+
- [ ] Enable rate limiting
|
|
486
|
+
- [ ] Use read-only database user for dashboard
|
|
487
|
+
- [ ] Regular security audits
|
|
488
|
+
- [ ] Keep dependencies updated
|
|
466
489
|
|
|
467
|
-
|
|
490
|
+
## Complete Example
|
|
468
491
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
492
|
+
```typescript
|
|
493
|
+
import express from 'express';
|
|
494
|
+
import session from 'express-session';
|
|
495
|
+
import { Pool } from 'pg';
|
|
496
|
+
import { createDashboard } from 'worker-que/dist/dashboard';
|
|
497
|
+
import { Client, Worker } from 'worker-que';
|
|
473
498
|
|
|
474
|
-
|
|
499
|
+
const app = express();
|
|
500
|
+
const dbConfig = {
|
|
501
|
+
host: process.env.DB_HOST,
|
|
502
|
+
port: parseInt(process.env.DB_PORT),
|
|
503
|
+
database: process.env.DB_NAME,
|
|
504
|
+
user: process.env.DB_USER,
|
|
505
|
+
password: process.env.DB_PASSWORD,
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const pool = new Pool(dbConfig);
|
|
509
|
+
const client = new Client(dbConfig);
|
|
510
|
+
const worker = new Worker(dbConfig);
|
|
511
|
+
|
|
512
|
+
// Setup session
|
|
513
|
+
app.use(session({
|
|
514
|
+
secret: process.env.SESSION_SECRET,
|
|
515
|
+
resave: false,
|
|
516
|
+
saveUninitialized: false,
|
|
517
|
+
}));
|
|
475
518
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
CREATE INDEX idx_que_jobs_run_at ON que_jobs(run_at);
|
|
481
|
-
CREATE INDEX idx_que_jobs_error_count ON que_jobs(error_count);
|
|
482
|
-
CREATE INDEX idx_que_jobs_queue ON que_jobs(queue);
|
|
483
|
-
CREATE INDEX idx_que_jobs_job_class ON que_jobs(job_class);
|
|
484
|
-
```
|
|
485
|
-
3. Reduce `maxRecentFailures` option
|
|
486
|
-
4. Use database connection pooling
|
|
487
|
-
|
|
488
|
-
### Dashboard not refreshing
|
|
489
|
-
|
|
490
|
-
**Check:**
|
|
491
|
-
1. JavaScript is enabled in browser
|
|
492
|
-
2. No console errors in browser dev tools
|
|
493
|
-
3. API endpoints are accessible (check network tab)
|
|
494
|
-
4. CORS is configured if dashboard is on different domain
|
|
495
|
-
|
|
496
|
-
## Screenshots
|
|
497
|
-
|
|
498
|
-
### Main Dashboard
|
|
499
|
-

|
|
500
|
-
|
|
501
|
-
Shows:
|
|
502
|
-
- Real-time job statistics
|
|
503
|
-
- Ready, scheduled, and failed job counts
|
|
504
|
-
- Visual charts for queue and class distribution
|
|
505
|
-
|
|
506
|
-
### Jobs List
|
|
507
|
-

|
|
508
|
-
|
|
509
|
-
Features:
|
|
510
|
-
- Filter by status, queue, and job class
|
|
511
|
-
- Pagination for large job lists
|
|
512
|
-
- Quick actions to retry or delete jobs
|
|
519
|
+
// Register job handlers
|
|
520
|
+
worker.register('SendEmail', async (job) => {
|
|
521
|
+
// Email logic
|
|
522
|
+
});
|
|
513
523
|
|
|
514
|
-
|
|
515
|
-
|
|
524
|
+
// Mount dashboard with auth
|
|
525
|
+
app.use('/admin/queue', createDashboard(pool, {
|
|
526
|
+
title: 'Production Queue',
|
|
527
|
+
basePath: '/admin/queue',
|
|
528
|
+
refreshInterval: 3000,
|
|
529
|
+
auth: (req) => req.session?.user?.isAdmin === true,
|
|
530
|
+
}));
|
|
516
531
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
.replace('background: #667eea', 'background: #your-color');
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
2. Or mount your own routes:
|
|
538
|
-
```typescript
|
|
539
|
-
import { DashboardService } from 'que-ts/dashboard';
|
|
540
|
-
|
|
541
|
-
const service = new DashboardService(pool);
|
|
542
|
-
|
|
543
|
-
app.get('/custom-dashboard', (req, res) => {
|
|
544
|
-
// Your custom HTML using service.getStats()
|
|
545
|
-
});
|
|
546
|
-
```
|
|
532
|
+
// Start worker
|
|
533
|
+
worker.work();
|
|
534
|
+
|
|
535
|
+
// Start server
|
|
536
|
+
const PORT = process.env.PORT || 3000;
|
|
537
|
+
app.listen(PORT, () => {
|
|
538
|
+
console.log(`Dashboard: http://localhost:${PORT}/admin/queue`);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// Graceful shutdown
|
|
542
|
+
process.on('SIGTERM', async () => {
|
|
543
|
+
await worker.shutdown();
|
|
544
|
+
await client.close();
|
|
545
|
+
await pool.end();
|
|
546
|
+
process.exit(0);
|
|
547
|
+
});
|
|
548
|
+
```
|
|
547
549
|
|
|
548
550
|
## Support
|
|
549
551
|
|
|
550
|
-
- 📖
|
|
551
|
-
-
|
|
552
|
-
-
|
|
552
|
+
- 📖 [Main Documentation](./README.md)
|
|
553
|
+
- 🔐 [SSL Configuration](./SSL.md)
|
|
554
|
+
- 🐛 [Issue Tracker](https://github.com/your-username/worker-que/issues)
|
|
553
555
|
|
|
554
|
-
|
|
556
|
+
---
|
|
555
557
|
|
|
556
|
-
|
|
558
|
+
**Need help?** Open an issue or check the examples directory for more code samples.
|